[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: \"Submit Xray-core bug\"\nbody:\n  - type: checkboxes\n    attributes:\n      label: Integrity requirements\n      description: |-\n        Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.\n      options:\n        - label: I have read all the comments in the issue template and ensured that this issue meet the requirements.\n          required: true\n        - label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.\n          required: true\n        - label: I provided the complete config and logs, rather than just providing the truncated parts based on my own judgment.\n          required: true\n        - label: I searched issues and did not find any similar issues.\n          required: true\n        - label: The problem can be successfully reproduced in the latest Release\n          required: true\n  - type: textarea\n    attributes:\n      label: Description\n      description: |-\n        Please provide a detailed description of the error. And the information you think valuable.\n        If the problem occurs after the update, please provide the **specific** version\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Reproduction Method\n      description: |-\n        Based on the configuration you provided below, provide the method to reproduce the bug.\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: |-\n        ## Configuration and Log Section\n        \n        ### For config\n        Please provide the configuration files that can reproduce the problem, including the server and client.\n        Don't just paste a big exported config file here. Eliminate useless inbound/outbound, rules, options, this can help determine the problem, if you really want to get help.\n        After removing parts that do not affect reproduction, provide the actual running **complete** file.\n        meaning of complete: This config can be directly used to start the core, **not a truncated part of the config**. For fields like keys, use newly generated valid parameters that have not been actually used to fill in.\n\n        ### For logs\n        Please set the log level to debug and dnsLog to true first.\n        Restart Xray-core, then operate according to the reproduction method, try to reduce the irrelevant part in the log.\n        Remember to delete parts with personal information (such as UUID and IP).\n        Provide the log of Xray-core, not the log output by the panel or other things.\n\n        ### Finally\n        The specific content to be filled in each of the following text boxes needs to be placed between ```<details><pre><code>``` and ```</code></pre></details>```, like this\n        ```\n        <details><pre><code>\n        (config)\n        </code></pre></details>\n        ```\n  - type: textarea\n    attributes:\n      label: Client config\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Server config\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Client log\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Server log\n    validations:\n      required: true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_zh.yml",
    "content": "name: bug反馈\ndescription: \"提交 Xray-core bug\"\nbody:\n  - type: checkboxes\n    attributes:\n      label: 完整性要求\n      description: |-\n        请勾选以下所有选项以证明您已经阅读并理解了以下要求，否则该 issue 将被关闭。\n      options:\n        - label: 我读完了 issue 模板中的所有注释，确保填写符合要求。\n          required: true\n        - label: 我保证阅读了文档，了解所有我编写的配置文件项的含义，而不是大量堆砌看似有用的选项或默认值。\n          required: true\n        - label: 我提供了完整的配置文件和日志，而不是出于自己的判断只给出截取的部分。\n          required: true\n        - label: 我搜索了 issues, 没有发现已提出的类似问题。\n          required: true\n        - label: 问题在 Release 最新的版本上可以成功复现\n          required: true\n  - type: textarea\n    attributes:\n      label: 描述\n      description: |-\n        请提供错误的详细描述。以及你认为有价值的信息。\n        如果问题在更新后出现，请提供**具体**出现问题的版本号。\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 重现方式\n      description: |-\n        基于你下面提供的配置，提供重现BUG方法。\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: |-\n        ## 配置与日志部分\n        \n        ### 对于配置文件\n        请提供可以重现问题的配置文件，包括服务端和客户端。\n        不要直接在这里黏贴一大段导出的 config 文件。去掉无用的出入站、规则、选项，这可以帮助确定问题，如果你真的想得到帮助。\n        在去掉不影响复现的部分后，提供实际运行的**完整**文件。\n        完整的含义：可以直接使用这个配置启动核心，**不是截取的部分配置**。对于密钥等参数使用重新生成未实际使用的有效参数填充。\n        \n        ### 对于日志\n        请先将日志等级设置为 debug, dnsLog 设置为true.\n        重启 Xray-core ，再按复现方式操作，尽量减少日志中的无关部分。\n        记得删除有关个人信息（如UUID与IP）的部分。\n        提供 Xray-core 的日志，而不是面板或者别的东西输出的日志。\n\n        ### 最后\n        把下面的每格具体内容需要放在 ```<details><pre><code>``` 和 ```</code></pre></details>``` 中间，如\n        ```\n        <details><pre><code>\n        (config)\n        </code></pre></details>\n        ```\n  - type: textarea\n    attributes:\n      label: 客户端配置\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 服务端配置\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 客户端日志\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 服务端日志\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "contact_links:\n  - name: Community Support and Questions\n    url: https://github.com/XTLS/Xray-core/discussions\n    about: Please ask and answer questions there. The issue tracker is for issues with core.\n"
  },
  {
    "path": ".github/build/friendly-filenames.json",
    "content": "{\n  \"android-arm64\": { \"friendlyName\": \"android-arm64-v8a\" },\n  \"darwin-amd64\": { \"friendlyName\": \"macos-64\" },\n  \"darwin-arm64\": { \"friendlyName\": \"macos-arm64-v8a\" },\n  \"freebsd-386\": { \"friendlyName\": \"freebsd-32\" },\n  \"freebsd-amd64\": { \"friendlyName\": \"freebsd-64\" },\n  \"freebsd-arm64\": { \"friendlyName\": \"freebsd-arm64-v8a\" },\n  \"freebsd-arm7\": { \"friendlyName\": \"freebsd-arm32-v7a\" },\n  \"linux-386\": { \"friendlyName\": \"linux-32\" },\n  \"linux-amd64\": { \"friendlyName\": \"linux-64\" },\n  \"linux-arm5\": { \"friendlyName\": \"linux-arm32-v5\" },\n  \"linux-arm64\": { \"friendlyName\": \"linux-arm64-v8a\" },\n  \"linux-arm6\": { \"friendlyName\": \"linux-arm32-v6\" },\n  \"linux-arm7\": { \"friendlyName\": \"linux-arm32-v7a\" },\n  \"linux-mips64le\": { \"friendlyName\": \"linux-mips64le\" },\n  \"linux-mips64\": { \"friendlyName\": \"linux-mips64\" },\n  \"linux-mipslesoftfloat\": { \"friendlyName\": \"linux-mips32le-softfloat\" },\n  \"linux-mipsle\": { \"friendlyName\": \"linux-mips32le\" },\n  \"linux-mipssoftfloat\": { \"friendlyName\": \"linux-mips32-softfloat\" },\n  \"linux-mips\": { \"friendlyName\": \"linux-mips32\" },\n  \"linux-ppc64le\": { \"friendlyName\": \"linux-ppc64le\" },\n  \"linux-ppc64\": { \"friendlyName\": \"linux-ppc64\" },\n  \"linux-riscv64\": { \"friendlyName\": \"linux-riscv64\" },\n  \"linux-loong64\": { \"friendlyName\": \"linux-loong64\" },\n  \"linux-s390x\": { \"friendlyName\": \"linux-s390x\" },\n  \"openbsd-386\": { \"friendlyName\": \"openbsd-32\" },\n  \"openbsd-amd64\": { \"friendlyName\": \"openbsd-64\" },\n  \"openbsd-arm64\": { \"friendlyName\": \"openbsd-arm64-v8a\" },\n  \"openbsd-arm7\": { \"friendlyName\": \"openbsd-arm32-v7a\" },\n  \"windows-386\": { \"friendlyName\": \"windows-32\" },\n  \"windows-amd64\": { \"friendlyName\": \"windows-64\" },\n  \"windows-arm64\": { \"friendlyName\": \"windows-arm64-v8a\" }\n}\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/docker/Dockerfile",
    "content": "# syntax=docker/dockerfile:latest\nFROM --platform=$BUILDPLATFORM golang:latest AS build\n\n# Build xray-core\nWORKDIR /src\nCOPY . .\nARG TARGETOS\nARG TARGETARCH\nRUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags \"-s -w -buildid=\" ./main\n\n# Download geodat into a staging directory\nADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat\nADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat\n\nRUN mkdir -p /tmp/empty\n\n# Create config files with empty JSON content\nRUN mkdir -p /tmp/usr/local/etc/xray\nRUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json\n{\n  \"log\": {\n    \"error\": \"/var/log/xray/error.log\",\n    \"loglevel\": \"warning\",\n    \"access\": \"none\",\n    \"dnsLog\": false\n  }\n}\nEOF\nRUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json\n\n# Create log files\nRUN mkdir -p /tmp/var/log/xray && touch \\\n  /tmp/var/log/xray/access.log \\\n  /tmp/var/log/xray/error.log\n\n# Build finally image\nFROM gcr.io/distroless/static:nonroot\n\nCOPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray\nCOPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray\nCOPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/\nCOPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray\nCOPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/\nCOPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray\nCOPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/\n\nVOLUME /usr/local/etc/xray\nVOLUME /var/log/xray\n\nARG TZ=Etc/UTC\nENV TZ=$TZ\n\nENTRYPOINT [ \"/usr/local/bin/xray\" ]\nCMD [ \"-confdir\", \"/usr/local/etc/xray/\" ]\n"
  },
  {
    "path": ".github/docker/Dockerfile.usa",
    "content": "# syntax=docker/dockerfile:latest\nFROM --platform=$BUILDPLATFORM golang:latest AS build\n\n# Build xray-core\nWORKDIR /src\nCOPY . .\nARG TARGETOS\nARG TARGETARCH\nRUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags \"-s -w -buildid=\" ./main\n\n# Download geodat into a staging directory\nADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat\nADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat\n\nRUN mkdir -p /tmp/empty\n\n# Create config files with empty JSON content\nRUN mkdir -p /tmp/usr/local/etc/xray\nRUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json\n{\n  \"log\": {\n    \"error\": \"/var/log/xray/error.log\",\n    \"loglevel\": \"warning\",\n    \"access\": \"none\",\n    \"dnsLog\": false\n  }\n}\nEOF\nRUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json\nRUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json\n\n# Create log files\nRUN mkdir -p /tmp/var/log/xray && touch \\\n  /tmp/var/log/xray/access.log \\\n  /tmp/var/log/xray/error.log\n\n# Build finally image\n# Note on Distroless Base Image and Architecture Support:\n# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:\n#   - linux/amd64\n#   - linux/arm/v7\n#   - linux/arm64/v8\n#   - linux/ppc64le\n#   - linux/s390x\n# - Upon inspection, the blob contents of the Distroless images across these architectures are nearly identical, with only minor differences in metadata (e.g., 'Architecture' field in the manifest).\n# - Due to this similarity in content, it is feasible to forcibly specify a single platform (e.g., '--platform=linux/amd64') for unsupported architectures, as the core image content remains compatible with statically compiled binaries like Go applications.\nFROM --platform=linux/amd64 gcr.io/distroless/static:nonroot\n\nCOPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray\nCOPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray\nCOPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/\nCOPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray\nCOPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/\nCOPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray\nCOPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/\n\nVOLUME /usr/local/etc/xray\nVOLUME /var/log/xray\n\nARG TZ=Etc/UTC\nENV TZ=$TZ\n\nENTRYPOINT [ \"/usr/local/bin/xray\" ]\nCMD [ \"-confdir\", \"/usr/local/etc/xray/\" ]\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Build and Push Docker Image\n\non:\n  release:\n    types:\n      - published\n      - released\n\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: \"Docker image tag:\"\n        required: true\n      latest:\n        description: \"Set to latest\"\n        type: boolean\n        default: false\n\njobs:\n  build-and-push:\n    if: (github.event.action != 'published') || (github.event.action == 'published' && github.event.release.prerelease == true)\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Set repository and image name to lowercase\n        env:\n          IMAGE_NAME: \"${{ github.repository }}\"\n        run: |\n          echo \"IMAGE_NAME=${IMAGE_NAME,,}\" >>${GITHUB_ENV}\n          echo \"FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}\" >>${GITHUB_ENV}\n\n      - name: Validate and extract tag\n        run: |\n          SOURCE_TAG=\"${{ github.event.inputs.tag }}\"\n          if [[ -z \"$SOURCE_TAG\" ]]; then\n            SOURCE_TAG=\"${{ github.ref_name }}\"\n          fi\n          if [[ -z \"$SOURCE_TAG\" ]]; then\n            SOURCE_TAG=\"${{ github.event.release.tag_name }}\"\n          fi\n\n          if [[ -z \"$SOURCE_TAG\" ]]; then\n            echo \"Error: Could not determine a valid tag source. Input tag and context tag (github.ref_name) are both empty.\"\n            exit 1\n          fi\n\n          if [[ \"$SOURCE_TAG\" =~ ^v[0-9]+\\.[0-9] ]]; then\n            IMAGE_TAG=\"${SOURCE_TAG#v}\"\n          else\n            IMAGE_TAG=\"$SOURCE_TAG\"\n          fi\n\n          echo \"Docker image tag: '$IMAGE_TAG'.\"\n          echo \"IMAGE_TAG=$IMAGE_TAG\" >>${GITHUB_ENV}\n\n          LATEST=false\n          if [[ \"${{ github.event_name }}\" == \"release\" && \"${{ github.event.release.prerelease }}\" == \"false\" ]] || [[ \"${{ github.event_name }}\" == \"workflow_dispatch\" && \"${{ github.event.inputs.latest }}\" == \"true\" ]]; then\n            LATEST=true\n          fi\n\n          echo \"Latest: '$LATEST'.\"\n          echo \"LATEST=$LATEST\" >>${GITHUB_ENV}\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v4\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v4\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v4\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build Docker image (main architectures)\n        id: build_main_arches\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          file: .github/docker/Dockerfile\n          platforms: |\n            linux/amd64\n            linux/arm/v7\n            linux/arm64/v8\n            linux/ppc64le\n            linux/s390x\n          provenance: false\n          outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true\n\n      - name: Build Docker image (additional architectures)\n        id: build_additional_arches\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          file: .github/docker/Dockerfile.usa\n          platforms: |\n            linux/386\n            linux/arm/v6\n            linux/riscv64\n            linux/loong64\n          provenance: false\n          outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true\n\n      - name: Create manifest list and push\n        run: |\n          echo \"Creating multi-arch manifest with tag: '${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}'.\"\n          docker buildx imagetools create \\\n            --tag ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }} \\\n            ${{ env.FULL_IMAGE_NAME }}@${{ steps.build_main_arches.outputs.digest }} \\\n            ${{ env.FULL_IMAGE_NAME }}@${{ steps.build_additional_arches.outputs.digest }}\n\n          if [[ \"${{ env.LATEST }}\" == \"true\" ]]; then\n            echo \"Adding 'latest' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:latest'.\"\n            docker buildx imagetools create \\\n            --tag ${{ env.FULL_IMAGE_NAME }}:latest \\\n            ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}\n          fi\n\n      - name: Inspect image\n        run: |\n          docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}\n\n          if [[ \"${{ env.LATEST }}\" == \"true\" ]]; then\n            docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest\n          fi\n"
  },
  {
    "path": ".github/workflows/release-win7.yml",
    "content": "name: Build and Release for Windows 7\n\non:\n  workflow_dispatch:\n  release:\n    types: [published]\n  push:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  check-assets:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n\n      - name: Restore Wintun Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-wintun-\n\n      - name: Check Assets Existence\n        id: check-assets\n        run: |\n          [ -d 'resources' ] || mkdir resources\n          LIST=('geoip.dat' 'geosite.dat')\n          for FILE_NAME in \"${LIST[@]}\"\n          do\n            echo -e \"Checking ${FILE_NAME}...\"\n            if [ -s \"./resources/${FILE_NAME}\" ]; then\n              echo -e \"${FILE_NAME} exists.\"\n            else\n              echo -e \"${FILE_NAME} does not exist.\"\n              echo \"missing=true\" >> $GITHUB_OUTPUT\n              break\n            fi\n          done\n          LIST=('amd64' 'x86')\n          for ARCHITECTURE in \"${LIST[@]}\"\n          do\n            echo -e \"Checking wintun.dll for ${ARCHITECTURE}...\"\n            if [ -s \"./resources/wintun/bin/${ARCHITECTURE}/wintun.dll\" ]; then\n              echo -e \"wintun.dll for ${ARCHITECTURE} exists.\"\n            else\n              echo -e \"wintun.dll for ${ARCHITECTURE} is missing.\"\n              echo \"missing=true\" >> $GITHUB_OUTPUT\n              break\n            fi\n          done\n\n      - name: Sleep for 90 seconds if Assets Missing\n        if: steps.check-assets.outputs.missing == 'true'\n        run: sleep 90\n\n  build:\n    needs: check-assets\n    permissions:\n      contents: write\n    strategy:\n      matrix:\n        include:\n          # BEGIN Windows 7\n          - goos: windows\n            goarch: amd64\n            assetname: win7-64\n          - goos: windows\n            goarch: 386\n            assetname: win7-32\n          # END Windows 7\n      fail-fast: false\n\n    runs-on: ubuntu-latest\n    env:\n      GOOS: ${{ matrix.goos}}\n      GOARCH: ${{ matrix.goarch }}\n      CGO_ENABLED: 0\n    steps:\n      - name: Checkout codebase\n        uses: actions/checkout@v6\n\n      - name: Show workflow information\n        run: |\n          _NAME=${{ matrix.assetname }}\n          echo \"GOOS: ${{ matrix.goos }}, GOARCH: ${{ matrix.goarch }}, RELEASE_NAME: $_NAME\"\n          echo \"ASSET_NAME=$_NAME\" >> $GITHUB_ENV\n\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n          check-latest: true\n\n      - name: Setup patched builder\n        run: |\n          GOSDK=$(go env GOROOT)\n          rm -r $GOSDK/*\n          cd $GOSDK\n          curl -O -L -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip\n          unzip ./go-for-win7-linux-amd64.zip -d $GOSDK\n          rm ./go-for-win7-linux-amd64.zip\n\n      - name: Get project dependencies\n        run: go mod download\n\n      - name: Build Xray\n        run: |\n          mkdir -p build_assets\n          COMMID=$(git describe --always --dirty)\n          echo 'Building Xray for Windows 7...'\n          go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n          # The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.\n          # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags=\"-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n\n      - name: Restore Wintun Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-wintun-\n\n      - name: Add additional assets into package\n        run: |\n          mv -f resources/geo* build_assets/\n          if [[ ${GOOS} == 'windows' ]]; then\n            echo 'CreateObject(\"Wscript.Shell\").Run \"xray.exe -config config.json\",0' > build_assets/xray_no_window.vbs\n            echo 'Start-Process -FilePath \".\\xray.exe\" -ArgumentList \"-config .\\config.json\" -WindowStyle Hidden' > build_assets/xray_no_window.ps1\n            if [[ ${GOARCH} == 'amd64' ]]; then\n              mv resources/wintun/bin/amd64/wintun.dll build_assets/\n            fi\n            if [[ ${GOARCH} == '386' ]]; then\n              mv resources/wintun/bin/x86/wintun.dll build_assets/\n            fi\n            mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt\n          fi\n\n      - name: Copy README.md & LICENSE\n        run: |\n          cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md\n          cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE\n\n      - name: Create ZIP archive\n        if: github.event_name == 'release'\n        shell: bash\n        run: |\n          pushd build_assets || exit 1\n          touch -mt $(date +%Y01010000) *\n          zip -9vr ../Xray-${{ env.ASSET_NAME }}.zip .\n          popd || exit 1\n          FILE=./Xray-${{ env.ASSET_NAME }}.zip\n          DGST=$FILE.dgst\n          for METHOD in {\"md5\",\"sha1\",\"sha256\",\"sha512\"}\n          do\n            openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST\n          done\n\n      - name: Upload binaries to release\n        uses: svenstaro/upload-release-action@v2\n        if: github.event_name == 'release'\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: ./Xray-${{ env.ASSET_NAME }}.zip*\n          tag: ${{ github.ref }}\n          file_glob: true\n\n      - name: Upload files to Artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: Xray-${{ env.ASSET_NAME }}\n          path: |\n            ./build_assets/*\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Build and Release\n\non:\n  workflow_dispatch:\n  release:\n    types: [published]\n  push:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  check-assets:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n\n      - name: Restore Wintun Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-wintun-\n\n      - name: Check Assets Existence\n        id: check-assets\n        run: |\n          [ -d 'resources' ] || mkdir resources\n          LIST=('geoip.dat' 'geosite.dat')\n          for FILE_NAME in \"${LIST[@]}\"\n          do\n            echo -e \"Checking ${FILE_NAME}...\"\n            if [ -s \"./resources/${FILE_NAME}\" ]; then\n              echo -e \"${FILE_NAME} exists.\"\n            else\n              echo -e \"${FILE_NAME} does not exist.\"\n              echo \"missing=true\" >> $GITHUB_OUTPUT\n              break\n            fi\n          done\n          LIST=('amd64' 'x86' 'arm64')\n          for ARCHITECTURE in \"${LIST[@]}\"\n          do\n            echo -e \"Checking wintun.dll for ${ARCHITECTURE}...\"\n            if [ -s \"./resources/wintun/bin/${ARCHITECTURE}/wintun.dll\" ]; then\n              echo -e \"wintun.dll for ${ARCHITECTURE} exists.\"\n            else\n              echo -e \"wintun.dll for ${ARCHITECTURE} is missing.\"\n              echo \"missing=true\" >> $GITHUB_OUTPUT\n              break\n            fi\n          done\n\n      - name: Trigger Asset Update Workflow if Assets Missing\n        if: steps.check-assets.outputs.missing == 'true'\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            const { owner, repo } = context.repo;\n            await github.rest.actions.createWorkflowDispatch({\n              owner,\n              repo,\n              workflow_id: 'scheduled-assets-update.yml',\n              ref: context.ref\n            });\n            console.log('Triggered scheduled-assets-update.yml due to missing assets on branch:', context.ref);\n\n      - name: Sleep for 90 seconds if Assets Missing\n        if: steps.check-assets.outputs.missing == 'true'\n        run: sleep 90\n\n  build:\n    needs: check-assets\n    permissions:\n      contents: write\n    strategy:\n      matrix:\n        # Include amd64 on all platforms.\n        goos: [windows, freebsd, openbsd, linux, darwin]\n        goarch: [amd64, 386]\n        patch-assetname: [\"\"]\n        exclude:\n          # Exclude i386 on darwin\n          - goarch: 386\n            goos: darwin\n        include:\n          # BEGIN MacOS ARM64\n          - goos: darwin\n            goarch: arm64\n          # END MacOS ARM64\n          # BEGIN Linux ARM 5 6 7\n          - goos: linux\n            goarch: arm\n            goarm: 7\n          - goos: linux\n            goarch: arm\n            goarm: 6\n          - goos: linux\n            goarch: arm\n            goarm: 5\n          # END Linux ARM 5 6 7\n          # BEGIN Android ARM 8\n          - goos: android\n            goarch: arm64\n          # END Android ARM 8\n          # BEGIN Android AMD64\n          - goos: android\n            goarch: amd64\n            patch-assetname: android-amd64\n          # END Android AMD64\n          # Windows ARM\n          - goos: windows\n            goarch: arm64\n          # BEGIN Other architectures\n          # BEGIN riscv64 & ARM64 & LOONG64\n          - goos: linux\n            goarch: arm64\n          - goos: linux\n            goarch: riscv64\n          - goos: linux\n            goarch: loong64\n          # END riscv64 & ARM64 & LOONG64\n          # BEGIN MIPS\n          - goos: linux\n            goarch: mips64\n          - goos: linux\n            goarch: mips64le\n          - goos: linux\n            goarch: mipsle\n          - goos: linux\n            goarch: mips\n          # END MIPS\n          # BEGIN PPC\n          - goos: linux\n            goarch: ppc64\n          - goos: linux\n            goarch: ppc64le\n          # END PPC\n          # BEGIN FreeBSD ARM\n          - goos: freebsd\n            goarch: arm64\n          - goos: freebsd\n            goarch: arm\n            goarm: 7\n          # END FreeBSD ARM\n          # BEGIN S390X\n          - goos: linux\n            goarch: s390x\n          # END S390X\n          # END Other architectures\n          # BEGIN OPENBSD ARM\n          - goos: openbsd\n            goarch: arm64\n          - goos: openbsd\n            goarch: arm\n            goarm: 7\n          # END OPENBSD ARM\n      fail-fast: false\n\n    runs-on: ubuntu-latest\n    env:\n      GOOS: ${{ matrix.goos }}\n      GOARCH: ${{ matrix.goarch }}\n      GOARM: ${{ matrix.goarm }}\n      CGO_ENABLED: 0\n    steps:\n      - name: Checkout codebase\n        uses: actions/checkout@v6\n\n      - name: Set up NDK\n        if: matrix.goos == 'android'\n        run: |\n          wget -qO android-ndk.zip https://dl.google.com/android/repository/android-ndk-r28b-linux.zip\n          unzip android-ndk.zip\n          rm android-ndk.zip\n          declare -A arches=(\n            [\"amd64\"]=\"x86_64-linux-android24-clang\"\n            [\"arm64\"]=\"aarch64-linux-android24-clang\"\n          )\n          echo CC=\"$(realpath android-ndk-*/toolchains/llvm/prebuilt/linux-x86_64/bin)/${arches[${{ matrix.goarch }}]}\" >> $GITHUB_ENV\n          echo CGO_ENABLED=1 >> $GITHUB_ENV\n\n      - name: Show workflow information\n        run: |\n          _NAME=${{ matrix.patch-assetname }}\n          [ -n \"$_NAME\" ] || _NAME=$(jq \".[\\\"$GOOS-$GOARCH$GOARM$GOMIPS\\\"].friendlyName\" -r < .github/build/friendly-filenames.json)\n          echo \"GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME\"\n          echo \"ASSET_NAME=$_NAME\" >> $GITHUB_ENV\n\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n          check-latest: true\n\n      - name: Get project dependencies\n        run: go mod download\n\n      - name: Build Xray\n        run: |\n          mkdir -p build_assets\n          COMMID=$(git describe --always --dirty)\n          if [[ ${GOOS} == 'windows' ]]; then\n            echo 'Building Xray for Windows...'\n            go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n            # The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.\n            # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags=\"-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n          else\n            echo 'Building Xray...'\n            if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then\n              go build -o build_assets/xray -trimpath -buildvcs=false -gcflags=\"-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n              echo 'Building soft-float Xray for MIPS/MIPSLE 32-bit...'\n              GOMIPS=softfloat go build -o build_assets/xray_softfloat -trimpath -buildvcs=false -gcflags=\"-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n            else\n              go build -o build_assets/xray -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=\" -v ./main\n            fi\n          fi\n\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n\n      - name: Restore Wintun Cache\n        if: matrix.goos == 'windows'\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-wintun-\n\n      - name: Add additional assets into package\n        run: |\n          mv -f resources/geo* build_assets/\n          if [[ ${GOOS} == 'windows' ]]; then\n            echo 'CreateObject(\"Wscript.Shell\").Run \"xray.exe -config config.json\",0' > build_assets/xray_no_window.vbs\n            echo 'Start-Process -FilePath \".\\xray.exe\" -ArgumentList \"-config .\\config.json\" -WindowStyle Hidden' > build_assets/xray_no_window.ps1\n            if [[ ${GOARCH} == 'amd64' ]]; then\n              mv resources/wintun/bin/amd64/wintun.dll build_assets/\n            fi\n            if [[ ${GOARCH} == '386' ]]; then\n              mv resources/wintun/bin/x86/wintun.dll build_assets/\n            fi\n            if [[ ${GOARCH} == 'arm64' ]]; then\n              mv resources/wintun/bin/arm64/wintun.dll build_assets/\n            fi\n            mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt\n          fi\n\n      - name: Copy README.md & LICENSE\n        run: |\n          cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md\n          cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE\n\n      - name: Create ZIP archive\n        if: github.event_name == 'release'\n        shell: bash\n        run: |\n          pushd build_assets || exit 1\n          touch -mt $(date +%Y01010000) *\n          zip -9vr ../Xray-${{ env.ASSET_NAME }}.zip .\n          popd || exit 1\n          FILE=./Xray-${{ env.ASSET_NAME }}.zip\n          DGST=$FILE.dgst\n          for METHOD in {\"md5\",\"sha1\",\"sha256\",\"sha512\"}\n          do\n            openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST\n          done\n\n      - name: Upload binaries to release\n        uses: svenstaro/upload-release-action@v2\n        if: github.event_name == 'release'\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: ./Xray-${{ env.ASSET_NAME }}.zip*\n          tag: ${{ github.ref }}\n          file_glob: true\n\n      - name: Upload files to Artifacts\n        uses: actions/upload-artifact@v7\n        with:\n          name: Xray-${{ env.ASSET_NAME }}\n          path: |\n            ./build_assets/*\n"
  },
  {
    "path": ".github/workflows/scheduled-assets-update.yml",
    "content": "name: Scheduled assets update\n\n# NOTE: This Github Actions is required by other actions, for preparing other packaging assets in a\n#       routine manner, for example: GeoIP/GeoSite.\n#       Currently updating:\n#       - Geodat (GeoIP/Geosite)\n#       - Wintun (wintun.dll)\n\non:\n  workflow_dispatch:\n  schedule:\n    # Update GeoData on every day (22:30 UTC)\n    - cron: \"30 22 * * *\"\n  push:\n    # Prevent triggering update request storm\n    paths:\n      - \".github/workflows/scheduled-assets-update.yml\"\n  pull_request:\n    # Prevent triggering update request storm\n    paths:\n      - \".github/workflows/scheduled-assets-update.yml\"\n\njobs:\n  geodat:\n    if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n\n      - name: Update Geodat\n        id: update\n        uses: nick-fields/retry@v3\n        with:\n          timeout_minutes: 60\n          retry_wait_seconds: 60\n          max_attempts: 60\n          command: |\n            [ -d 'resources' ] || mkdir resources\n            LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite')\n            for i in \"${LIST[@]}\"\n            do\n              INFO=($(echo $i | awk 'BEGIN{FS=\" \";OFS=\" \"} {print $1,$2,$3,$4}'))\n              FILE_NAME=\"${INFO[3]}.dat\"\n              echo -e \"Verifying HASH key...\"\n              HASH=\"$(curl -sL -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \"https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum\" | awk -F ' ' '{print $1}')\"\n              if [ -s \"./resources/${FILE_NAME}\" ] && [ \"$(sha256sum \"./resources/${FILE_NAME}\" | awk -F ' ' '{print $1}')\" == \"${HASH}\" ]; then\n                  continue\n              else\n                  echo -e \"Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat...\"\n                  curl -L -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \"https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat\" -o ./resources/${FILE_NAME}\n                  echo -e \"Verifying HASH key...\"\n                  [ \"$(sha256sum \"./resources/${FILE_NAME}\" | awk -F ' ' '{print $1}')\" == \"${HASH}\" ] || { echo -e \"The HASH key of ${FILE_NAME} does not match cloud one.\"; exit 1; }\n                  echo \"unhit=true\" >> $GITHUB_OUTPUT\n              fi\n            done\n\n      - name: Save Geodat Cache\n        uses: actions/cache/save@v5\n        if: ${{ steps.update.outputs.unhit }}\n        with:\n          path: resources\n          key: xray-geodat-${{ github.sha }}-${{ github.run_number }}\n\n  wintun:\n    if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Restore Wintun Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-wintun-\n\n      - name: Force downloading if run manually or on file update\n        if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'\n        run: |\n          echo \"FORCE_UPDATE=true\" >> $GITHUB_ENV\n\n      - name: Update Wintun\n        id: update\n        uses: nick-fields/retry@v3\n        with:\n          timeout_minutes: 60\n          retry_wait_seconds: 60\n          max_attempts: 60\n          command: |\n            [ -d 'resources' ] || mkdir resources\n            LIST=('amd64' 'x86' 'arm64')\n            for ARCHITECTURE in \"${LIST[@]}\"\n            do\n              FILE_PATH=\"resources/wintun/bin/${ARCHITECTURE}/wintun.dll\"\n              echo -e \"Checking if wintun.dll for ${ARCHITECTURE} exists...\"\n              if [ -s \"./resources/wintun/bin/${ARCHITECTURE}/wintun.dll\" ]; then\n                  echo -e \"wintun.dll for ${ARCHITECTURE} exists\"\n                  continue\n              else\n                  echo -e \"wintun.dll for ${ARCHITECTURE} is missing\"\n                  missing=true\n              fi\n            done\n            if [ -s \"./resources/wintun/LICENSE.txt\" ]; then\n              echo -e \"LICENSE for Wintun exists\"\n            else\n              echo -e \"LICENSE for Wintun is missing\"\n              missing=true\n            fi\n            if [[ -v FORCE_UPDATE ]]; then\n              missing=true\n            fi\n            if [[ \"$missing\" == true ]]; then\n              FILENAME=wintun.zip\n              DOWNLOAD_FILE=wintun-0.14.1.zip\n              echo -e \"Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}...\"\n              curl -L \"https://www.wintun.net/builds/${DOWNLOAD_FILE}\" -o \"${FILENAME}\"\n              echo -e \"Unpacking wintun...\"\n              unzip -u ${FILENAME} -d resources/\n              echo \"unhit=true\" >> $GITHUB_OUTPUT\n            fi\n\n      - name: Save Wintun Cache\n        uses: actions/cache/save@v5\n        if: ${{ steps.update.outputs.unhit }}\n        with:\n          path: resources\n          key: xray-wintun-${{ github.sha }}-${{ github.run_number }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  check-assets:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n      - name: Check Assets Existence\n        id: check-assets\n        run: |\n          [ -d 'resources' ] || mkdir resources\n          LIST=('geoip.dat' 'geosite.dat')\n          for FILE_NAME in \"${LIST[@]}\"\n          do\n            echo -e \"Checking ${FILE_NAME}...\"\n            if [ -s \"./resources/${FILE_NAME}\" ]; then\n              echo -e \"${FILE_NAME} exists.\"\n            else\n              echo -e \"${FILE_NAME} does not exist.\"\n              echo \"missing=true\" >> $GITHUB_OUTPUT\n              break\n            fi\n          done\n      - name: Sleep for 90 seconds if Assets Missing\n        if: steps.check-assets.outputs.missing == 'true'\n        run: sleep 90\n\n  check-proto:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout codebase\n        uses: actions/checkout@v6\n      - name: Check Proto Version Header\n        run: |\n          head -n 4 core/config.pb.go > ref.txt\n          find . -name \"*.pb.go\" ! -name \"*_grpc.pb.go\" -print0 | while IFS= read -r -d '' file; do\n            if ! cmp -s ref.txt <(head -n 4 \"$file\"); then\n              echo \"Error: Header mismatch in $file\"\n              head -n 4 \"$file\"\n              exit 1\n            fi\n          done\n\n  test:\n    needs: check-assets\n    permissions:\n      contents: read\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [windows-latest, ubuntu-latest, macos-latest]\n    steps:\n      - name: Checkout codebase\n        uses: actions/checkout@v6\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n          check-latest: true\n      - name: Restore Geodat Cache\n        uses: actions/cache/restore@v5\n        with:\n          path: resources\n          key: xray-geodat-\n          enableCrossOsArchive: true\n      - name: Test\n        run: go test -timeout 1h -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# macOS specific files\n.DS_Store\n\n# IDE/editor specific files\n.idea/\n.vscode/\n*.swp\n*.swo\n\n# Archives and compressed files\n*.zip\n*.tar.gz\n*.tar\n*.gz\n*.bz2\n\n# Go build binaries\nxray\nxray_softfloat\nmockgen\nvprotogen\n!infra/vprotogen/\nerrorgen\n!common/errors/errorgen/\n*.dat\n\n# Build assets\n/build_assets/\n\n# Output from dlv test\n**/debug.*\n\n# Certificates and keys\n*.crt\n*.key\n\n# Dependency directories (uncomment if needed)\n# vendor/\n\n# Logs\n*.log\n\n# Coverage reports\ncoverage.*\n\n# Node modules (in case of frontend assets)\nnode_modules/\n\n# System files\nThumbs.db\nehthumbs.db\n\n# Other common ignores\n*.bak\n*.tmp\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nhttps://t.me/projectXtls.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "# Project X\n\n[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).\n\n[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).\n\n## Sponsors\n\n[![Remnawave](https://github.com/user-attachments/assets/a22d34ae-01ee-441c-843a-85356748ed1e)](https://docs.rw)\n\n[![Happ](https://github.com/user-attachments/assets/14055dab-e8bb-48bd-89e8-962709e4098e)](https://happ.su)\n\n[**Sponsor Xray-core**](https://github.com/XTLS/Xray-core/issues/3668)\n\n## Donation & NFTs\n\n### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)\n\n[<img alt=\"Project X NFT\" width=\"150px\" src=\"https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg\" />](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)\n\n- **TRX(Tron)/USDT/USDC: `TNrDh5VSfwd4RPrwsohr6poyNTfFefNYan`**\n- **TON: `UQApeV-u2gm43aC1uP76xAC1m6vCylstaN1gpfBmre_5IyTH`**\n- **BTC: `1JpqcziZZuqv3QQJhZGNGBVdCBrGgkL6cT`**\n- **XMR: `4ABHQZ3yJZkBnLoqiKvb3f8eqUnX4iMPb6wdant5ZLGQELctcerceSGEfJnoCk6nnyRZm73wrwSgvZ2WmjYLng6R7sR67nq`**\n- **SOL/USDT/USDC: `3x5NuXHzB5APG6vRinPZcsUv5ukWUY1tBGRSJiEJWtZa`**\n- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**\n- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1**\n- **VLESS NFT: https://opensea.io/collection/vless**\n- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**\n- **Related links: [VLESS Post-Quantum Encryption](https://github.com/XTLS/Xray-core/pull/5067), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113), [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)**\n\n## License\n\n[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE)\n\n## Documentation\n\n[Project X Official Website](https://xtls.github.io)\n\n## Telegram\n\n[Project X](https://t.me/projectXray)\n\n[Project X Channel](https://t.me/projectXtls)\n\n[Project VLESS](https://t.me/projectVless) (Русский)\n\n[Project XHTTP](https://t.me/projectXhttp) (Persian)\n\n## Installation\n\n- Linux Script\n  - [XTLS/Xray-install](https://github.com/XTLS/Xray-install) (**Official**)\n  - [tempest](https://github.com/team-cloudchaser/tempest) (supports [`systemd`](https://systemd.io) and [OpenRC](https://github.com/OpenRC/openrc); Linux-only)\n- Docker\n  - [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)\n  - [teddysun/xray](https://hub.docker.com/r/teddysun/xray)\n  - [wulabing/xray_docker](https://github.com/wulabing/xray_docker)\n- Web Panel\n  - [Remnawave](https://github.com/remnawave/panel)\n  - [3X-UI](https://github.com/MHSanaei/3x-ui)\n  - [PasarGuard](https://github.com/PasarGuard/panel)\n  - [Xray-UI](https://github.com/qist/xray-ui)\n  - [X-Panel](https://github.com/xeefei/X-Panel)\n  - [Marzban](https://github.com/Gozargah/Marzban)\n  - [Hiddify](https://github.com/hiddify/Hiddify-Manager)\n  - [TX-UI](https://github.com/AghayeCoder/tx-ui)\n- One Click\n  - [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)\n  - [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)\n  - [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)\n- Magisk\n  - [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)\n  - [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)\n  - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)\n- Homebrew\n  - `brew install xray`\n\n## Usage\n\n- Example\n  - [VLESS-XTLS-uTLS-REALITY](https://github.com/XTLS/REALITY#readme)\n  - [VLESS-TCP-XTLS-Vision](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision)\n  - [All-in-One-fallbacks-Nginx](https://github.com/XTLS/Xray-examples/tree/main/All-in-One-fallbacks-Nginx)\n- Xray-examples\n  - [XTLS/Xray-examples](https://github.com/XTLS/Xray-examples)\n  - [chika0801/Xray-examples](https://github.com/chika0801/Xray-examples)\n  - [lxhao61/integrated-examples](https://github.com/lxhao61/integrated-examples)\n- Tutorial\n  - [XTLS Vision](https://github.com/chika0801/Xray-install)\n  - [REALITY (English)](https://cscot.pages.dev/2023/03/02/Xray-REALITY-tutorial/)\n  - [XTLS-Iran-Reality (English)](https://github.com/SasukeFreestyle/XTLS-Iran-Reality)\n  - [Xray REALITY with 'steal oneself' (English)](https://computerscot.github.io/vless-xtls-utls-reality-steal-oneself.html)\n  - [Xray with WireGuard inbound (English)](https://g800.pages.dev/wireguard)\n\n## GUI Clients\n\n- OpenWrt\n  - [PassWall](https://github.com/Openwrt-Passwall/openwrt-passwall), [PassWall 2](https://github.com/Openwrt-Passwall/openwrt-passwall2)\n  - [ShadowSocksR Plus+](https://github.com/fw876/helloworld)\n  - [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))\n- Asuswrt-Merlin\n  - [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)\n  - [fancyss](https://github.com/hq450/fancyss)\n- Windows\n  - [v2rayN](https://github.com/2dust/v2rayN)\n  - [Furious](https://github.com/LorenEteval/Furious)\n  - [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)\n  - [AnyPortal](https://github.com/AnyPortal/AnyPortal)\n  - [GenyConnect](https://github.com/genyleap/GenyConnect)\n- Android\n  - [v2rayNG](https://github.com/2dust/v2rayNG)\n  - [X-flutter](https://github.com/XTLS/X-flutter)\n  - [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)\n  - [SimpleXray](https://github.com/lhear/SimpleXray)\n  - [XrayFA](https://github.com/Q7DF1/XrayFA)\n  - [AnyPortal](https://github.com/AnyPortal/AnyPortal)\n  - [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)\n- iOS & macOS arm64 & tvOS\n  - [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)\n  - [Streisand](https://apps.apple.com/app/streisand/id6450534064)\n  - [OneXray](https://github.com/OneXray/OneXray)\n- macOS arm64 & x64\n  - [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)\n  - [V2rayU](https://github.com/yanue/V2rayU)\n  - [V2RayXS](https://github.com/tzmax/V2RayXS)\n  - [Furious](https://github.com/LorenEteval/Furious)\n  - [OneXray](https://github.com/OneXray/OneXray)\n  - [GoXRay](https://github.com/goxray/desktop)\n  - [AnyPortal](https://github.com/AnyPortal/AnyPortal)\n  - [v2rayN](https://github.com/2dust/v2rayN)\n  - [GenyConnect](https://github.com/genyleap/GenyConnect)\n- Linux\n  - [v2rayA](https://github.com/v2rayA/v2rayA)\n  - [Furious](https://github.com/LorenEteval/Furious)\n  - [GorzRay](https://github.com/ketetefid/GorzRay)\n  - [GoXRay](https://github.com/goxray/desktop)\n  - [AnyPortal](https://github.com/AnyPortal/AnyPortal)\n  - [v2rayN](https://github.com/2dust/v2rayN)\n  - [GenyConnect](https://github.com/genyleap/GenyConnect)\n\n## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...\n\n- iOS & macOS arm64 & tvOS\n  - [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)\n  - [Loon](https://apps.apple.com/us/app/loon/id1373567447)\n  - [Egern](https://apps.apple.com/us/app/egern/id1616105820)\n  - [Quantumult X](https://apps.apple.com/us/app/quantumult-x/id1443988620)\n- Xray Tools\n  - [xray-knife](https://github.com/lilendian0x00/xray-knife)\n  - [xray-checker](https://github.com/kutovoys/xray-checker)\n- Xray Wrapper\n  - [XTLS/libXray](https://github.com/XTLS/libXray)\n  - [xtls-sdk](https://github.com/remnawave/xtls-sdk)\n  - [xtlsapi](https://github.com/hiddify/xtlsapi)\n  - [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)\n  - [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)\n  - [xray-api](https://github.com/XVGuardian/xray-api)\n- [XrayR](https://github.com/XrayR-project/XrayR)\n  - [XrayR-release](https://github.com/XrayR-project/XrayR-release)\n  - [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)\n- Cores\n  - [Amnezia VPN](https://github.com/amnezia-vpn)\n  - [mihomo](https://github.com/MetaCubeX/mihomo)\n  - [sing-box](https://github.com/SagerNet/sing-box)\n\n## Contributing\n\n[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md)\n\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/XTLS/Xray-core)\n\n## Credits\n\n- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).\n- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).\n\n## One-line Compilation\n\n### Windows (PowerShell)\n\n```powershell\n$env:CGO_ENABLED=0\ngo build -o xray.exe -trimpath -buildvcs=false -ldflags=\"-s -w -buildid=\" -v ./main\n```\n\n### Linux / macOS\n\n```bash\nCGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags=\"-s -w -buildid=\" -v ./main\n```\n\n### Reproducible Releases\n\nMake sure that you are using the same Go version, and remember to set the git commit id (7 bytes):\n\n```bash\nCGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags=\"all=-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=\" -v ./main\n```\n\nIf you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:\n\n```bash\nCGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags=\"-l=4\" -ldflags=\"-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=\" -v ./main\n```\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/XTLS/Xray-core.svg)](https://starchart.cc/XTLS/Xray-core)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nIf you found an issue related to security vulnerability or protocol-identification problem, please report it to us via \"[Report a vulnerability](https://github.com/XTLS/Xray-core/security/advisories/new)\" privately, instead of publish it publicly before we release the fixed version.\n\nThanks for your contribution to the FREE Internet!\n"
  },
  {
    "path": "app/app.go",
    "content": "// Package app contains feature implementations of Xray. The features may be enabled during runtime.\npackage app\n"
  },
  {
    "path": "app/commander/commander.go",
    "content": "package commander\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"google.golang.org/grpc\"\n)\n\n// Commander is a Xray feature that provides gRPC methods to external clients.\ntype Commander struct {\n\tsync.Mutex\n\tserver   *grpc.Server\n\tservices []Service\n\tohm      outbound.Manager\n\ttag      string\n\tlisten   string\n}\n\n// NewCommander creates a new Commander based on the given config.\nfunc NewCommander(ctx context.Context, config *Config) (*Commander, error) {\n\tc := &Commander{\n\t\ttag:    config.Tag,\n\t\tlisten: config.Listen,\n\t}\n\n\tcommon.Must(core.RequireFeatures(ctx, func(om outbound.Manager) {\n\t\tc.ohm = om\n\t}))\n\n\tfor _, rawConfig := range config.Service {\n\t\tconfig, err := rawConfig.GetInstance()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trawService, err := common.CreateObject(ctx, config)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tservice, ok := rawService.(Service)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"not a Service.\")\n\t\t}\n\t\tc.services = append(c.services, service)\n\t}\n\n\treturn c, nil\n}\n\n// Type implements common.HasType.\nfunc (c *Commander) Type() interface{} {\n\treturn (*Commander)(nil)\n}\n\n// Start implements common.Runnable.\nfunc (c *Commander) Start() error {\n\tc.Lock()\n\tc.server = grpc.NewServer()\n\tfor _, service := range c.services {\n\t\tservice.Register(c.server)\n\t}\n\tc.Unlock()\n\n\tvar listen = func(listener net.Listener) {\n\t\tif err := c.server.Serve(listener); err != nil {\n\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to start grpc server\")\n\t\t}\n\t}\n\n\tif len(c.listen) > 0 {\n\t\tif l, err := net.Listen(\"tcp\", c.listen); err != nil {\n\t\t\terrors.LogErrorInner(context.Background(), err, \"API server failed to listen on \", c.listen)\n\t\t\treturn err\n\t\t} else {\n\t\t\terrors.LogInfo(context.Background(), \"API server listening on \", l.Addr())\n\t\t\tgo listen(l)\n\t\t}\n\t\treturn nil\n\t}\n\n\tlistener := &OutboundListener{\n\t\tbuffer: make(chan net.Conn, 4),\n\t\tdone:   done.New(),\n\t}\n\n\tgo listen(listener)\n\n\tif err := c.ohm.RemoveHandler(context.Background(), c.tag); err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to remove existing handler\")\n\t}\n\n\treturn c.ohm.AddHandler(context.Background(), &Outbound{\n\t\ttag:      c.tag,\n\t\tlistener: listener,\n\t})\n}\n\n// Close implements common.Closable.\nfunc (c *Commander) Close() error {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tif c.server != nil {\n\t\tc.server.Stop()\n\t\tc.server = nil\n\t}\n\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\treturn NewCommander(ctx, cfg.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/commander/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/commander/config.proto\n\npackage commander\n\nimport (\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Config is the settings for Commander.\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Tag of the outbound handler that handles grpc connections.\n\tTag string `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\t// Network address of commander grpc service.\n\tListen string `protobuf:\"bytes,3,opt,name=listen,proto3\" json:\"listen,omitempty\"`\n\t// Services that supported by this server. All services must implement Service\n\t// interface.\n\tService       []*serial.TypedMessage `protobuf:\"bytes,2,rep,name=service,proto3\" json:\"service,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_commander_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_commander_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_commander_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetListen() string {\n\tif x != nil {\n\t\treturn x.Listen\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetService() []*serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Service\n\t}\n\treturn nil\n}\n\n// ReflectionConfig is the placeholder config for ReflectionService.\ntype ReflectionConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReflectionConfig) Reset() {\n\t*x = ReflectionConfig{}\n\tmi := &file_app_commander_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReflectionConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReflectionConfig) ProtoMessage() {}\n\nfunc (x *ReflectionConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_commander_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReflectionConfig.ProtoReflect.Descriptor instead.\nfunc (*ReflectionConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_commander_config_proto_rawDescGZIP(), []int{1}\n}\n\nvar File_app_commander_config_proto protoreflect.FileDescriptor\n\nconst file_app_commander_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1aapp/commander/config.proto\\x12\\x12xray.app.commander\\x1a!common/serial/typed_message.proto\\\"n\\n\" +\n\t\"\\x06Config\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x16\\n\" +\n\t\"\\x06listen\\x18\\x03 \\x01(\\tR\\x06listen\\x12:\\n\" +\n\t\"\\aservice\\x18\\x02 \\x03(\\v2 .xray.common.serial.TypedMessageR\\aservice\\\"\\x12\\n\" +\n\t\"\\x10ReflectionConfigBX\\n\" +\n\t\"\\x16com.xray.app.commanderP\\x01Z'github.com/xtls/xray-core/app/commander\\xaa\\x02\\x12Xray.App.Commanderb\\x06proto3\"\n\nvar (\n\tfile_app_commander_config_proto_rawDescOnce sync.Once\n\tfile_app_commander_config_proto_rawDescData []byte\n)\n\nfunc file_app_commander_config_proto_rawDescGZIP() []byte {\n\tfile_app_commander_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_commander_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_commander_config_proto_rawDesc), len(file_app_commander_config_proto_rawDesc)))\n\t})\n\treturn file_app_commander_config_proto_rawDescData\n}\n\nvar file_app_commander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_app_commander_config_proto_goTypes = []any{\n\t(*Config)(nil),              // 0: xray.app.commander.Config\n\t(*ReflectionConfig)(nil),    // 1: xray.app.commander.ReflectionConfig\n\t(*serial.TypedMessage)(nil), // 2: xray.common.serial.TypedMessage\n}\nvar file_app_commander_config_proto_depIdxs = []int32{\n\t2, // 0: xray.app.commander.Config.service:type_name -> xray.common.serial.TypedMessage\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_app_commander_config_proto_init() }\nfunc file_app_commander_config_proto_init() {\n\tif File_app_commander_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_commander_config_proto_rawDesc), len(file_app_commander_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_commander_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_commander_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_commander_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_commander_config_proto = out.File\n\tfile_app_commander_config_proto_goTypes = nil\n\tfile_app_commander_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/commander/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.commander;\noption csharp_namespace = \"Xray.App.Commander\";\noption go_package = \"github.com/xtls/xray-core/app/commander\";\noption java_package = \"com.xray.app.commander\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\n\n// Config is the settings for Commander.\nmessage Config {\n  // Tag of the outbound handler that handles grpc connections.\n  string tag = 1;\n\n  // Network address of commander grpc service.\n  string listen = 3;\n\n  // Services that supported by this server. All services must implement Service\n  // interface.\n  repeated xray.common.serial.TypedMessage service = 2;\n}\n\n// ReflectionConfig is the placeholder config for ReflectionService.\nmessage ReflectionConfig {}\n"
  },
  {
    "path": "app/commander/outbound.go",
    "content": "package commander\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\n// OutboundListener is a net.Listener for listening gRPC connections.\ntype OutboundListener struct {\n\tbuffer chan net.Conn\n\tdone   *done.Instance\n}\n\nfunc (l *OutboundListener) add(conn net.Conn) {\n\tselect {\n\tcase l.buffer <- conn:\n\tcase <-l.done.Wait():\n\t\tconn.Close()\n\tdefault:\n\t\tconn.Close()\n\t}\n}\n\n// Accept implements net.Listener.\nfunc (l *OutboundListener) Accept() (net.Conn, error) {\n\tselect {\n\tcase <-l.done.Wait():\n\t\treturn nil, errors.New(\"listen closed\")\n\tcase c := <-l.buffer:\n\t\treturn c, nil\n\t}\n}\n\n// Close implements net.Listener.\nfunc (l *OutboundListener) Close() error {\n\tcommon.Must(l.done.Close())\nL:\n\tfor {\n\t\tselect {\n\t\tcase c := <-l.buffer:\n\t\t\tc.Close()\n\t\tdefault:\n\t\t\tbreak L\n\t\t}\n\t}\n\treturn nil\n}\n\n// Addr implements net.Listener.\nfunc (l *OutboundListener) Addr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP:   net.IP{0, 0, 0, 0},\n\t\tPort: 0,\n\t}\n}\n\n// Outbound is a outbound.Handler that handles gRPC connections.\ntype Outbound struct {\n\ttag      string\n\tlistener *OutboundListener\n\taccess   sync.RWMutex\n\tclosed   bool\n}\n\n// Dispatch implements outbound.Handler.\nfunc (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) {\n\tco.access.RLock()\n\n\tif co.closed {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\tco.access.RUnlock()\n\t\treturn\n\t}\n\n\tcloseSignal := done.New()\n\tc := cnc.NewConnection(cnc.ConnectionInputMulti(link.Writer), cnc.ConnectionOutputMulti(link.Reader), cnc.ConnectionOnClose(closeSignal))\n\tco.listener.add(c)\n\tco.access.RUnlock()\n\t<-closeSignal.Wait()\n}\n\n// Tag implements outbound.Handler.\nfunc (co *Outbound) Tag() string {\n\treturn co.tag\n}\n\n// Start implements common.Runnable.\nfunc (co *Outbound) Start() error {\n\tco.access.Lock()\n\tco.closed = false\n\tco.access.Unlock()\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (co *Outbound) Close() error {\n\tco.access.Lock()\n\tdefer co.access.Unlock()\n\n\tco.closed = true\n\treturn co.listener.Close()\n}\n\n// SenderSettings implements outbound.Handler.\nfunc (co *Outbound) SenderSettings() *serial.TypedMessage {\n\treturn nil\n}\n\n// ProxySettings implements outbound.Handler.\nfunc (co *Outbound) ProxySettings() *serial.TypedMessage {\n\treturn nil\n}\n"
  },
  {
    "path": "app/commander/service.go",
    "content": "package commander\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/reflection\"\n)\n\n// Service is a Commander service.\ntype Service interface {\n\t// Register registers the service itself to a gRPC server.\n\tRegister(*grpc.Server)\n}\n\ntype reflectionService struct{}\n\nfunc (r reflectionService) Register(s *grpc.Server) {\n\treflection.Register(s)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ReflectionConfig)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\treturn reflectionService{}, nil\n\t}))\n}\n"
  },
  {
    "path": "app/dispatcher/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/dispatcher/config.proto\n\npackage dispatcher\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype SessionConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SessionConfig) Reset() {\n\t*x = SessionConfig{}\n\tmi := &file_app_dispatcher_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SessionConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SessionConfig) ProtoMessage() {}\n\nfunc (x *SessionConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dispatcher_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.\nfunc (*SessionConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_dispatcher_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSettings      *SessionConfig         `protobuf:\"bytes,1,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_dispatcher_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dispatcher_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_dispatcher_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Config) GetSettings() *SessionConfig {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\nvar File_app_dispatcher_config_proto protoreflect.FileDescriptor\n\nconst file_app_dispatcher_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1bapp/dispatcher/config.proto\\x12\\x13xray.app.dispatcher\\\"\\x15\\n\" +\n\t\"\\rSessionConfigJ\\x04\\b\\x01\\x10\\x02\\\"H\\n\" +\n\t\"\\x06Config\\x12>\\n\" +\n\t\"\\bsettings\\x18\\x01 \\x01(\\v2\\\".xray.app.dispatcher.SessionConfigR\\bsettingsB[\\n\" +\n\t\"\\x17com.xray.app.dispatcherP\\x01Z(github.com/xtls/xray-core/app/dispatcher\\xaa\\x02\\x13Xray.App.Dispatcherb\\x06proto3\"\n\nvar (\n\tfile_app_dispatcher_config_proto_rawDescOnce sync.Once\n\tfile_app_dispatcher_config_proto_rawDescData []byte\n)\n\nfunc file_app_dispatcher_config_proto_rawDescGZIP() []byte {\n\tfile_app_dispatcher_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_dispatcher_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dispatcher_config_proto_rawDesc), len(file_app_dispatcher_config_proto_rawDesc)))\n\t})\n\treturn file_app_dispatcher_config_proto_rawDescData\n}\n\nvar file_app_dispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_app_dispatcher_config_proto_goTypes = []any{\n\t(*SessionConfig)(nil), // 0: xray.app.dispatcher.SessionConfig\n\t(*Config)(nil),        // 1: xray.app.dispatcher.Config\n}\nvar file_app_dispatcher_config_proto_depIdxs = []int32{\n\t0, // 0: xray.app.dispatcher.Config.settings:type_name -> xray.app.dispatcher.SessionConfig\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_app_dispatcher_config_proto_init() }\nfunc file_app_dispatcher_config_proto_init() {\n\tif File_app_dispatcher_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dispatcher_config_proto_rawDesc), len(file_app_dispatcher_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_dispatcher_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_dispatcher_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_dispatcher_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_dispatcher_config_proto = out.File\n\tfile_app_dispatcher_config_proto_goTypes = nil\n\tfile_app_dispatcher_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/dispatcher/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.dispatcher;\noption csharp_namespace = \"Xray.App.Dispatcher\";\noption go_package = \"github.com/xtls/xray-core/app/dispatcher\";\noption java_package = \"com.xray.app.dispatcher\";\noption java_multiple_files = true;\n\nmessage SessionConfig {\n  reserved 1;\n}\n\nmessage Config {\n  SessionConfig settings = 1;\n}\n"
  },
  {
    "path": "app/dispatcher/default.go",
    "content": "package dispatcher\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\trouting_session \"github.com/xtls/xray-core/features/routing/session\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nvar errSniffingTimeout = errors.New(\"timeout on sniffing\")\n\ntype cachedReader struct {\n\tsync.Mutex\n\treader buf.TimeoutReader // *pipe.Reader or *buf.TimeoutWrapperReader\n\tcache  buf.MultiBuffer\n}\n\nfunc (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {\n\tmb, err := r.reader.ReadMultiBufferTimeout(deadline)\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Lock()\n\tif !mb.IsEmpty() {\n\t\tr.cache, _ = buf.MergeMulti(r.cache, mb)\n\t}\n\tb.Clear()\n\trawBytes := b.Extend(min(r.cache.Len(), b.Cap()))\n\tn := r.cache.Copy(rawBytes)\n\tb.Resize(0, int32(n))\n\tr.Unlock()\n\treturn nil\n}\n\nfunc (r *cachedReader) readInternal() buf.MultiBuffer {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\tif r.cache != nil && !r.cache.IsEmpty() {\n\t\tmb := r.cache\n\t\tr.cache = nil\n\t\treturn mb\n\t}\n\n\treturn nil\n}\n\nfunc (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tmb := r.readInternal()\n\tif mb != nil {\n\t\treturn mb, nil\n\t}\n\n\treturn r.reader.ReadMultiBuffer()\n}\n\nfunc (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) {\n\tmb := r.readInternal()\n\tif mb != nil {\n\t\treturn mb, nil\n\t}\n\n\treturn r.reader.ReadMultiBufferTimeout(timeout)\n}\n\nfunc (r *cachedReader) Interrupt() {\n\tr.Lock()\n\tif r.cache != nil {\n\t\tr.cache = buf.ReleaseMulti(r.cache)\n\t}\n\tr.Unlock()\n\tif p, ok := r.reader.(*pipe.Reader); ok {\n\t\tp.Interrupt()\n\t}\n}\n\n// DefaultDispatcher is a default implementation of Dispatcher.\ntype DefaultDispatcher struct {\n\tohm    outbound.Manager\n\trouter routing.Router\n\tpolicy policy.Manager\n\tstats  stats.Manager\n\tfdns   dns.FakeDNSEngine\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\td := new(DefaultDispatcher)\n\t\tif err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {\n\t\t\tcore.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {\n\t\t\t\td.fdns = fdns\n\t\t\t})\n\t\t\treturn d.Init(config.(*Config), om, router, pm, sm)\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn d, nil\n\t}))\n}\n\n// Init initializes DefaultDispatcher.\nfunc (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {\n\td.ohm = om\n\td.router = router\n\td.policy = pm\n\td.stats = sm\n\treturn nil\n}\n\n// Type implements common.HasType.\nfunc (*DefaultDispatcher) Type() interface{} {\n\treturn routing.DispatcherType()\n}\n\n// Start implements common.Runnable.\nfunc (*DefaultDispatcher) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (*DefaultDispatcher) Close() error { return nil }\n\nfunc (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link) {\n\topt := pipe.OptionsFromContext(ctx)\n\tuplinkReader, uplinkWriter := pipe.New(opt...)\n\tdownlinkReader, downlinkWriter := pipe.New(opt...)\n\n\tinboundLink := &transport.Link{\n\t\tReader: downlinkReader,\n\t\tWriter: uplinkWriter,\n\t}\n\n\toutboundLink := &transport.Link{\n\t\tReader: uplinkReader,\n\t\tWriter: downlinkWriter,\n\t}\n\n\tsessionInbound := session.InboundFromContext(ctx)\n\tvar user *protocol.MemoryUser\n\tif sessionInbound != nil {\n\t\tuser = sessionInbound.User\n\t}\n\n\tif user != nil && len(user.Email) > 0 {\n\t\tp := d.policy.ForLevel(user.Level)\n\t\tif p.Stats.UserUplink {\n\t\t\tname := \"user>>>\" + user.Email + \">>>traffic>>>uplink\"\n\t\t\tif c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {\n\t\t\t\tinboundLink.Writer = &SizeStatWriter{\n\t\t\t\t\tCounter: c,\n\t\t\t\t\tWriter:  inboundLink.Writer,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif p.Stats.UserDownlink {\n\t\t\tname := \"user>>>\" + user.Email + \">>>traffic>>>downlink\"\n\t\t\tif c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {\n\t\t\t\toutboundLink.Writer = &SizeStatWriter{\n\t\t\t\t\tCounter: c,\n\t\t\t\t\tWriter:  outboundLink.Writer,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif p.Stats.UserOnline {\n\t\t\tname := \"user>>>\" + user.Email + \">>>online\"\n\t\t\tif om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {\n\t\t\t\tuserIP := sessionInbound.Source.Address.String()\n\t\t\t\tom.AddIP(userIP)\n\t\t\t\tcontext.AfterFunc(ctx, func() { om.RemoveIP(userIP) })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn inboundLink, outboundLink\n}\n\nfunc WrapLink(ctx context.Context, policyManager policy.Manager, statsManager stats.Manager, link *transport.Link) *transport.Link {\n\tsessionInbound := session.InboundFromContext(ctx)\n\tvar user *protocol.MemoryUser\n\tif sessionInbound != nil {\n\t\tuser = sessionInbound.User\n\t}\n\n\tlink.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader}\n\n\tif user != nil && len(user.Email) > 0 {\n\t\tp := policyManager.ForLevel(user.Level)\n\t\tif p.Stats.UserUplink {\n\t\t\tname := \"user>>>\" + user.Email + \">>>traffic>>>uplink\"\n\t\t\tif c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {\n\t\t\t\tlink.Reader.(*buf.TimeoutWrapperReader).Counter = c\n\t\t\t}\n\t\t}\n\t\tif p.Stats.UserDownlink {\n\t\t\tname := \"user>>>\" + user.Email + \">>>traffic>>>downlink\"\n\t\t\tif c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {\n\t\t\t\tlink.Writer = &SizeStatWriter{\n\t\t\t\t\tCounter: c,\n\t\t\t\t\tWriter:  link.Writer,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif p.Stats.UserOnline {\n\t\t\tname := \"user>>>\" + user.Email + \">>>online\"\n\t\t\tif om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil {\n\t\t\t\tuserIP := sessionInbound.Source.Address.String()\n\t\t\t\tom.AddIP(userIP)\n\t\t\t\tcontext.AfterFunc(ctx, func() { om.RemoveIP(userIP) })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn link\n}\n\nfunc (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {\n\tdomain := result.Domain()\n\tif domain == \"\" {\n\t\treturn false\n\t}\n\tfor _, d := range request.ExcludeForDomain {\n\t\tif strings.HasPrefix(d, \"regexp:\") {\n\t\t\tpattern := d[7:]\n\t\t\tre, err := regexp.Compile(pattern)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfo(ctx, \"Unable to compile regex\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif re.MatchString(domain) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t} else {\n\t\t\tif strings.ToLower(domain) == d {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\tprotocolString := result.Protocol()\n\tif resComp, ok := result.(SnifferResultComposite); ok {\n\t\tprotocolString = resComp.ProtocolForDomainResult()\n\t}\n\tfor _, p := range request.OverrideDestinationForProtocol {\n\t\tif strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) {\n\t\t\treturn true\n\t\t}\n\t\tif fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != \"bittorrent\" && p == \"fakedns\" &&\n\t\t\tfkr0.IsIPInIPPool(destination.Address) {\n\t\t\terrors.LogInfo(ctx, \"Using sniffer \", protocolString, \" since the fake DNS missed\")\n\t\t\treturn true\n\t\t}\n\t\tif resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {\n\t\t\tif resultSubset.IsProtoSubsetOf(p) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Dispatch implements routing.Dispatcher.\nfunc (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {\n\tif !destination.IsValid() {\n\t\tpanic(\"Dispatcher: Invalid destination.\")\n\t}\n\toutbounds := session.OutboundsFromContext(ctx)\n\tif len(outbounds) == 0 {\n\t\toutbounds = []*session.Outbound{{}}\n\t\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\t}\n\tob := outbounds[len(outbounds)-1]\n\tob.OriginalTarget = destination\n\tob.Target = destination\n\tcontent := session.ContentFromContext(ctx)\n\tif content == nil {\n\t\tcontent = new(session.Content)\n\t\tctx = session.ContextWithContent(ctx, content)\n\t}\n\n\tsniffingRequest := content.SniffingRequest\n\tinbound, outbound := d.getLink(ctx)\n\tif !sniffingRequest.Enabled {\n\t\tgo d.routedDispatch(ctx, outbound, destination)\n\t} else {\n\t\tgo func() {\n\t\t\tcReader := &cachedReader{\n\t\t\t\treader: outbound.Reader.(*pipe.Reader),\n\t\t\t}\n\t\t\toutbound.Reader = cReader\n\t\t\tresult, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)\n\t\t\tif err == nil {\n\t\t\t\tcontent.Protocol = result.Protocol()\n\t\t\t}\n\t\t\tif err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {\n\t\t\t\tdomain := result.Domain()\n\t\t\t\terrors.LogInfo(ctx, \"sniffed domain: \", domain)\n\t\t\t\tdestination.Address = net.ParseAddress(domain)\n\t\t\t\tprotocol := result.Protocol()\n\t\t\t\tif resComp, ok := result.(SnifferResultComposite); ok {\n\t\t\t\t\tprotocol = resComp.ProtocolForDomainResult()\n\t\t\t\t}\n\t\t\t\tisFakeIP := false\n\t\t\t\tif fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(ob.Target.Address) {\n\t\t\t\t\tisFakeIP = true\n\t\t\t\t}\n\t\t\t\tif sniffingRequest.RouteOnly && protocol != \"fakedns\" && protocol != \"fakedns+others\" && !isFakeIP {\n\t\t\t\t\tob.RouteTarget = destination\n\t\t\t\t} else {\n\t\t\t\t\tob.Target = destination\n\t\t\t\t}\n\t\t\t}\n\t\t\td.routedDispatch(ctx, outbound, destination)\n\t\t}()\n\t}\n\treturn inbound, nil\n}\n\n// DispatchLink implements routing.Dispatcher.\nfunc (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {\n\tif !destination.IsValid() {\n\t\treturn errors.New(\"Dispatcher: Invalid destination.\")\n\t}\n\toutbounds := session.OutboundsFromContext(ctx)\n\tif len(outbounds) == 0 {\n\t\toutbounds = []*session.Outbound{{}}\n\t\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\t}\n\tob := outbounds[len(outbounds)-1]\n\tob.OriginalTarget = destination\n\tob.Target = destination\n\tcontent := session.ContentFromContext(ctx)\n\tif content == nil {\n\t\tcontent = new(session.Content)\n\t\tctx = session.ContextWithContent(ctx, content)\n\t}\n\toutbound = WrapLink(ctx, d.policy, d.stats, outbound)\n\tsniffingRequest := content.SniffingRequest\n\tif !sniffingRequest.Enabled {\n\t\td.routedDispatch(ctx, outbound, destination)\n\t} else {\n\t\tcReader := &cachedReader{\n\t\t\treader: outbound.Reader.(buf.TimeoutReader),\n\t\t}\n\t\toutbound.Reader = cReader\n\t\tresult, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)\n\t\tif err == nil {\n\t\t\tcontent.Protocol = result.Protocol()\n\t\t}\n\t\tif err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {\n\t\t\tdomain := result.Domain()\n\t\t\terrors.LogInfo(ctx, \"sniffed domain: \", domain)\n\t\t\tdestination.Address = net.ParseAddress(domain)\n\t\t\tprotocol := result.Protocol()\n\t\t\tif resComp, ok := result.(SnifferResultComposite); ok {\n\t\t\t\tprotocol = resComp.ProtocolForDomainResult()\n\t\t\t}\n\t\t\tisFakeIP := false\n\t\t\tif fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(ob.Target.Address) {\n\t\t\t\tisFakeIP = true\n\t\t\t}\n\t\t\tif sniffingRequest.RouteOnly && protocol != \"fakedns\" && protocol != \"fakedns+others\" && !isFakeIP {\n\t\t\t\tob.RouteTarget = destination\n\t\t\t} else {\n\t\t\t\tob.Target = destination\n\t\t\t}\n\t\t}\n\t\td.routedDispatch(ctx, outbound, destination)\n\t}\n\n\treturn nil\n}\n\nfunc sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {\n\tpayload := buf.NewWithSize(32767)\n\tdefer payload.Release()\n\n\tsniffer := NewSniffer(ctx)\n\n\tmetaresult, metadataErr := sniffer.SniffMetadata(ctx)\n\n\tif metadataOnly {\n\t\treturn metaresult, metadataErr\n\t}\n\n\tcontentResult, contentErr := func() (SniffResult, error) {\n\t\tcacheDeadline := 200 * time.Millisecond\n\t\ttotalAttempt := 0\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil, ctx.Err()\n\t\t\tdefault:\n\t\t\t\tcachingStartingTimeStamp := time.Now()\n\t\t\t\terr := cReader.Cache(payload, cacheDeadline)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tcachingTimeElapsed := time.Since(cachingStartingTimeStamp)\n\t\t\t\tcacheDeadline -= cachingTimeElapsed\n\n\t\t\t\tif !payload.IsEmpty() {\n\t\t\t\t\tresult, err := sniffer.Sniff(ctx, payload.Bytes(), network)\n\t\t\t\t\tswitch err {\n\t\t\t\t\tcase common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not\n\t\t\t\t\t\ttotalAttempt++\n\t\t\t\t\tcase protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing\n\t\t\t\t\t\t// in this case, do not add totalAttempt(allow to read until timeout)\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn result, err\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttotalAttempt++\n\t\t\t\t}\n\t\t\t\tif totalAttempt >= 2 || cacheDeadline <= 0 {\n\t\t\t\t\treturn nil, errSniffingTimeout\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\tif contentErr != nil && metadataErr == nil {\n\t\treturn metaresult, nil\n\t}\n\tif contentErr == nil && metadataErr == nil {\n\t\treturn CompositeResult(metaresult, contentResult), nil\n\t}\n\treturn contentResult, contentErr\n}\n\nfunc (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\n\tvar handler outbound.Handler\n\n\troutingLink := routing_session.AsRoutingContext(ctx)\n\tinTag := routingLink.GetInboundTag()\n\tisPickRoute := 0\n\tif forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != \"\" {\n\t\tctx = session.SetForcedOutboundTagToContext(ctx, \"\")\n\t\tif h := d.ohm.GetHandler(forcedOutboundTag); h != nil {\n\t\t\tisPickRoute = 1\n\t\t\terrors.LogInfo(ctx, \"taking platform initialized detour [\", forcedOutboundTag, \"] for [\", destination, \"]\")\n\t\t\thandler = h\n\t\t} else {\n\t\t\terrors.LogError(ctx, \"non existing tag for platform initialized detour: \", forcedOutboundTag)\n\t\t\tcommon.Close(link.Writer)\n\t\t\tcommon.Interrupt(link.Reader)\n\t\t\treturn\n\t\t}\n\t} else if d.router != nil {\n\t\tif route, err := d.router.PickRoute(routingLink); err == nil {\n\t\t\toutTag := route.GetOutboundTag()\n\t\t\tif h := d.ohm.GetHandler(outTag); h != nil {\n\t\t\t\tisPickRoute = 2\n\t\t\t\tif route.GetRuleTag() == \"\" {\n\t\t\t\t\terrors.LogInfo(ctx, \"taking detour [\", outTag, \"] for [\", destination, \"]\")\n\t\t\t\t} else {\n\t\t\t\t\terrors.LogInfo(ctx, \"Hit route rule: [\", route.GetRuleTag(), \"] so taking detour [\", outTag, \"] for [\", destination, \"]\")\n\t\t\t\t}\n\t\t\t\thandler = h\n\t\t\t} else {\n\t\t\t\terrors.LogWarning(ctx, \"non existing outTag: \", outTag)\n\t\t\t\tcommon.Close(link.Writer)\n\t\t\t\tcommon.Interrupt(link.Reader)\n\t\t\t\treturn // DO NOT CHANGE: the traffic shouldn't be processed by default outbound if the specified outbound tag doesn't exist (yet), e.g., VLESS Reverse Proxy\n\t\t\t}\n\t\t} else {\n\t\t\terrors.LogInfo(ctx, \"default route for \", destination)\n\t\t}\n\t}\n\n\tif handler == nil {\n\t\thandler = d.ohm.GetDefaultHandler()\n\t}\n\n\tif handler == nil {\n\t\terrors.LogInfo(ctx, \"default outbound handler not exist\")\n\t\tcommon.Close(link.Writer)\n\t\tcommon.Interrupt(link.Reader)\n\t\treturn\n\t}\n\n\tob.Tag = handler.Tag()\n\tif accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {\n\t\tif tag := handler.Tag(); tag != \"\" {\n\t\t\tif inTag == \"\" {\n\t\t\t\taccessMessage.Detour = tag\n\t\t\t} else if isPickRoute == 1 {\n\t\t\t\taccessMessage.Detour = inTag + \" ==> \" + tag\n\t\t\t} else if isPickRoute == 2 {\n\t\t\t\taccessMessage.Detour = inTag + \" -> \" + tag\n\t\t\t} else {\n\t\t\t\taccessMessage.Detour = inTag + \" >> \" + tag\n\t\t\t}\n\t\t}\n\t\tlog.Record(accessMessage)\n\t}\n\n\thandler.Dispatch(ctx, link)\n}\n"
  },
  {
    "path": "app/dispatcher/dispatcher.go",
    "content": "package dispatcher\n"
  },
  {
    "path": "app/dispatcher/fakednssniffer.go",
    "content": "package dispatcher\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\n// newFakeDNSSniffer Creates a Fake DNS metadata sniffer\nfunc newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {\n\tvar fakeDNSEngine dns.FakeDNSEngine\n\t{\n\t\tfakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil))\n\t\tif fakeDNSEngineFeat != nil {\n\t\t\tfakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine)\n\t\t}\n\t}\n\n\tif fakeDNSEngine == nil {\n\t\terrNotInit := errors.New(\"FakeDNSEngine is not initialized, but such a sniffer is used\").AtError()\n\t\treturn protocolSnifferWithMetadata{}, errNotInit\n\t}\n\treturn protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {\n\t\toutbounds := session.OutboundsFromContext(ctx)\n\t\tob := outbounds[len(outbounds)-1]\n\t\tif ob.Target.Network == net.Network_TCP || ob.Target.Network == net.Network_UDP {\n\t\t\tdomainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(ob.Target.Address)\n\t\t\tif domainFromFakeDNS != \"\" {\n\t\t\t\terrors.LogInfo(ctx, \"fake dns got domain: \", domainFromFakeDNS, \" for ip: \", ob.Target.Address.String())\n\t\t\t\treturn &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil\n\t\t\t}\n\t\t}\n\n\t\tif ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {\n\t\t\tipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)\n\t\t\tif fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {\n\t\t\t\tinPool := fkr0.IsIPInIPPool(ob.Target.Address)\n\t\t\t\tipAddressInRangeValue.addressInRange = &inPool\n\t\t\t}\n\t\t}\n\n\t\treturn nil, common.ErrNoClue\n\t}, metadataSniffer: true}, nil\n}\n\ntype fakeDNSSniffResult struct {\n\tdomainName string\n}\n\nfunc (fakeDNSSniffResult) Protocol() string {\n\treturn \"fakedns\"\n}\n\nfunc (f fakeDNSSniffResult) Domain() string {\n\treturn f.domainName\n}\n\ntype fakeDNSExtraOpts int\n\nconst ipAddressInRange fakeDNSExtraOpts = 1\n\ntype ipAddressInRangeOpt struct {\n\taddressInRange *bool\n}\n\ntype DNSThenOthersSniffResult struct {\n\tdomainName           string\n\tprotocolOriginalName string\n}\n\nfunc (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {\n\treturn strings.HasPrefix(protocolName, f.protocolOriginalName)\n}\n\nfunc (DNSThenOthersSniffResult) Protocol() string {\n\treturn \"fakedns+others\"\n}\n\nfunc (f DNSThenOthersSniffResult) Domain() string {\n\treturn f.domainName\n}\n\nfunc newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (\n\tprotocolSnifferWithMetadata, error,\n) { // nolint: unparam\n\t// ctx may be used in the future\n\t_ = ctx\n\treturn protocolSnifferWithMetadata{\n\t\tprotocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {\n\t\t\tipAddressInRangeValue := &ipAddressInRangeOpt{}\n\t\t\tctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)\n\t\t\tresult, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)\n\t\t\tif err == nil {\n\t\t\t\treturn result, nil\n\t\t\t}\n\t\t\tif ipAddressInRangeValue.addressInRange != nil {\n\t\t\t\tif *ipAddressInRangeValue.addressInRange {\n\t\t\t\t\tfor _, v := range others {\n\t\t\t\t\t\tif v.metadataSniffer || bytes != nil {\n\t\t\t\t\t\t\tif result, err := v.protocolSniffer(ctx, bytes); err == nil {\n\t\t\t\t\t\t\t\treturn DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn nil, common.ErrNoClue\n\t\t\t\t}\n\t\t\t\terrors.LogDebug(ctx, \"ip address not in fake dns range, return as is\")\n\t\t\t\treturn nil, common.ErrNoClue\n\t\t\t}\n\t\t\terrors.LogWarning(ctx, \"fake dns sniffer did not set address in range option, assume false.\")\n\t\t\treturn nil, common.ErrNoClue\n\t\t},\n\t\tmetadataSniffer: false,\n\t}, nil\n}\n"
  },
  {
    "path": "app/dispatcher/sniffer.go",
    "content": "package dispatcher\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/protocol/bittorrent\"\n\t\"github.com/xtls/xray-core/common/protocol/http\"\n\t\"github.com/xtls/xray-core/common/protocol/quic\"\n\t\"github.com/xtls/xray-core/common/protocol/tls\"\n)\n\ntype SniffResult interface {\n\tProtocol() string\n\tDomain() string\n}\n\ntype protocolSniffer func(context.Context, []byte) (SniffResult, error)\n\ntype protocolSnifferWithMetadata struct {\n\tprotocolSniffer protocolSniffer\n\t// A Metadata sniffer will be invoked on connection establishment only, with nil body,\n\t// for both TCP and UDP connections\n\t// It will not be shown as a traffic type for routing unless there is no other successful sniffing.\n\tmetadataSniffer bool\n\tnetwork         net.Network\n}\n\ntype Sniffer struct {\n\tsniffer []protocolSnifferWithMetadata\n}\n\nfunc NewSniffer(ctx context.Context) *Sniffer {\n\tret := &Sniffer{\n\t\tsniffer: []protocolSnifferWithMetadata{\n\t\t\t{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b, c) }, false, net.Network_TCP},\n\t\t\t{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},\n\t\t\t{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},\n\t\t\t{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},\n\t\t\t{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},\n\t\t},\n\t}\n\tif sniffer, err := newFakeDNSSniffer(ctx); err == nil {\n\t\tothers := ret.sniffer\n\t\tret.sniffer = append(ret.sniffer, sniffer)\n\t\tfakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)\n\t\tif err == nil {\n\t\t\tret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)\n\t\t}\n\t}\n\treturn ret\n}\n\nvar errUnknownContent = errors.New(\"unknown content\")\n\nfunc (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {\n\tvar pendingSniffer []protocolSnifferWithMetadata\n\tfor _, si := range s.sniffer {\n\t\tprotocolSniffer := si.protocolSniffer\n\t\tif si.metadataSniffer || si.network != network {\n\t\t\tcontinue\n\t\t}\n\t\tresult, err := protocolSniffer(c, payload)\n\t\tif err == common.ErrNoClue {\n\t\t\tpendingSniffer = append(pendingSniffer, si)\n\t\t\tcontinue\n\t\t} else if err == protocol.ErrProtoNeedMoreData { // Sniffer protocol matched, but need more data to complete sniffing\n\t\t\ts.sniffer = []protocolSnifferWithMetadata{si}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err == nil && result != nil {\n\t\t\treturn result, nil\n\t\t}\n\t}\n\n\tif len(pendingSniffer) > 0 {\n\t\ts.sniffer = pendingSniffer\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\treturn nil, errUnknownContent\n}\n\nfunc (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) {\n\tvar pendingSniffer []protocolSnifferWithMetadata\n\tfor _, si := range s.sniffer {\n\t\ts := si.protocolSniffer\n\t\tif !si.metadataSniffer {\n\t\t\tpendingSniffer = append(pendingSniffer, si)\n\t\t\tcontinue\n\t\t}\n\t\tresult, err := s(c, nil)\n\t\tif err == common.ErrNoClue {\n\t\t\tpendingSniffer = append(pendingSniffer, si)\n\t\t\tcontinue\n\t\t}\n\n\t\tif err == nil && result != nil {\n\t\t\treturn result, nil\n\t\t}\n\t}\n\n\tif len(pendingSniffer) > 0 {\n\t\ts.sniffer = pendingSniffer\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\treturn nil, errUnknownContent\n}\n\nfunc CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult {\n\treturn &compositeResult{domainResult: domainResult, protocolResult: protocolResult}\n}\n\ntype compositeResult struct {\n\tdomainResult   SniffResult\n\tprotocolResult SniffResult\n}\n\nfunc (c compositeResult) Protocol() string {\n\treturn c.protocolResult.Protocol()\n}\n\nfunc (c compositeResult) Domain() string {\n\treturn c.domainResult.Domain()\n}\n\nfunc (c compositeResult) ProtocolForDomainResult() string {\n\treturn c.domainResult.Protocol()\n}\n\ntype SnifferResultComposite interface {\n\tProtocolForDomainResult() string\n}\n\ntype SnifferIsProtoSubsetOf interface {\n\tIsProtoSubsetOf(protocolName string) bool\n}\n"
  },
  {
    "path": "app/dispatcher/stats.go",
    "content": "package dispatcher\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\ntype SizeStatWriter struct {\n\tCounter stats.Counter\n\tWriter  buf.Writer\n}\n\nfunc (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tw.Counter.Add(int64(mb.Len()))\n\treturn w.Writer.WriteMultiBuffer(mb)\n}\n\nfunc (w *SizeStatWriter) Close() error {\n\treturn common.Close(w.Writer)\n}\n\nfunc (w *SizeStatWriter) Interrupt() {\n\tcommon.Interrupt(w.Writer)\n}\n"
  },
  {
    "path": "app/dispatcher/stats_test.go",
    "content": "package dispatcher_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\ntype TestCounter int64\n\nfunc (c *TestCounter) Value() int64 {\n\treturn int64(*c)\n}\n\nfunc (c *TestCounter) Add(v int64) int64 {\n\tx := int64(*c) + v\n\t*c = TestCounter(x)\n\treturn x\n}\n\nfunc (c *TestCounter) Set(v int64) int64 {\n\t*c = TestCounter(v)\n\treturn v\n}\n\nfunc TestStatsWriter(t *testing.T) {\n\tvar c TestCounter\n\twriter := &SizeStatWriter{\n\t\tCounter: &c,\n\t\tWriter:  buf.Discard,\n\t}\n\n\tmb := buf.MergeBytes(nil, []byte(\"abcd\"))\n\tcommon.Must(writer.WriteMultiBuffer(mb))\n\n\tmb = buf.MergeBytes(nil, []byte(\"efg\"))\n\tcommon.Must(writer.WriteMultiBuffer(mb))\n\n\tif c.Value() != 7 {\n\t\tt.Fatal(\"unexpected counter value. want 7, but got \", c.Value())\n\t}\n}\n"
  },
  {
    "path": "app/dns/cache_controller.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\tgo_errors \"errors\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/signal/pubsub\"\n\t\"github.com/xtls/xray-core/common/task\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\n\t\"golang.org/x/net/dns/dnsmessage\"\n\t\"golang.org/x/sync/singleflight\"\n)\n\nconst (\n\tminSizeForEmptyRebuild  = 512\n\tshrinkAbsoluteThreshold = 10240\n\tshrinkRatioThreshold    = 0.65\n\tmigrationBatchSize      = 4096\n)\n\ntype CacheController struct {\n\tname            string\n\tdisableCache    bool\n\tserveStale      bool\n\tserveExpiredTTL int32\n\n\tips      map[string]*record\n\tdirtyips map[string]*record\n\n\tsync.RWMutex\n\tpub           *pubsub.Service\n\tcacheCleanup  *task.Periodic\n\thighWatermark int\n\trequestGroup  singleflight.Group\n}\n\nfunc NewCacheController(name string, disableCache bool, serveStale bool, serveExpiredTTL uint32) *CacheController {\n\tc := &CacheController{\n\t\tname:            name,\n\t\tdisableCache:    disableCache,\n\t\tserveStale:      serveStale,\n\t\tserveExpiredTTL: -int32(serveExpiredTTL),\n\t\tips:             make(map[string]*record),\n\t\tpub:             pubsub.NewService(),\n\t}\n\n\tc.cacheCleanup = &task.Periodic{\n\t\tInterval: 300 * time.Second,\n\t\tExecute:  c.CacheCleanup,\n\t}\n\treturn c\n}\n\n// CacheCleanup clears expired items from cache\nfunc (c *CacheController) CacheCleanup() error {\n\texpiredKeys, err := c.collectExpiredKeys()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(expiredKeys) == 0 {\n\t\treturn nil\n\t}\n\tc.writeAndShrink(expiredKeys)\n\treturn nil\n}\n\nfunc (c *CacheController) collectExpiredKeys() ([]string, error) {\n\tc.RLock()\n\tdefer c.RUnlock()\n\n\tif len(c.ips) == 0 {\n\t\treturn nil, errors.New(\"nothing to do. stopping...\")\n\t}\n\n\t// skip collection if a migration is in progress\n\tif c.dirtyips != nil {\n\t\treturn nil, nil\n\t}\n\n\tnow := time.Now()\n\tif c.serveStale && c.serveExpiredTTL != 0 {\n\t\tnow = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)\n\t}\n\n\texpiredKeys := make([]string, 0, len(c.ips)/4) // pre-allocate\n\n\tfor domain, rec := range c.ips {\n\t\tif (rec.A != nil && rec.A.Expire.Before(now)) ||\n\t\t\t(rec.AAAA != nil && rec.AAAA.Expire.Before(now)) {\n\t\t\texpiredKeys = append(expiredKeys, domain)\n\t\t}\n\t}\n\n\treturn expiredKeys, nil\n}\n\nfunc (c *CacheController) writeAndShrink(expiredKeys []string) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\t// double check to prevent upper call multiple cleanup tasks\n\tif c.dirtyips != nil {\n\t\treturn\n\t}\n\n\tlenBefore := len(c.ips)\n\tif lenBefore > c.highWatermark {\n\t\tc.highWatermark = lenBefore\n\t}\n\n\tnow := time.Now()\n\tif c.serveStale && c.serveExpiredTTL != 0 {\n\t\tnow = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)\n\t}\n\n\tfor _, domain := range expiredKeys {\n\t\trec := c.ips[domain]\n\t\tif rec == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif rec.A != nil && rec.A.Expire.Before(now) {\n\t\t\trec.A = nil\n\t\t}\n\t\tif rec.AAAA != nil && rec.AAAA.Expire.Before(now) {\n\t\t\trec.AAAA = nil\n\t\t}\n\t\tif rec.A == nil && rec.AAAA == nil {\n\t\t\tdelete(c.ips, domain)\n\t\t}\n\t}\n\n\tlenAfter := len(c.ips)\n\n\tif lenAfter == 0 {\n\t\tif c.highWatermark >= minSizeForEmptyRebuild {\n\t\t\terrors.LogDebug(context.Background(), c.name,\n\t\t\t\t\" rebuilding empty cache map to reclaim memory.\",\n\t\t\t\t\" size_before_cleanup=\", lenBefore,\n\t\t\t\t\" peak_size_before_rebuild=\", c.highWatermark,\n\t\t\t)\n\n\t\t\tc.ips = make(map[string]*record)\n\t\t\tc.highWatermark = 0\n\t\t}\n\t\treturn\n\t}\n\n\tif reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&\n\t\tfloat64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {\n\t\terrors.LogDebug(context.Background(), c.name,\n\t\t\t\" shrinking cache map to reclaim memory.\",\n\t\t\t\" new_size=\", lenAfter,\n\t\t\t\" peak_size_before_shrink=\", c.highWatermark,\n\t\t\t\" reduction_since_peak=\", reductionFromPeak,\n\t\t)\n\n\t\tc.dirtyips = c.ips\n\t\tc.ips = make(map[string]*record, int(float64(lenAfter)*1.1))\n\t\tc.highWatermark = lenAfter\n\t\tgo c.migrate()\n\t}\n\n}\n\ntype migrationEntry struct {\n\tkey   string\n\tvalue *record\n}\n\nfunc (c *CacheController) migrate() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terrors.LogError(context.Background(), c.name, \" panic during cache migration: \", r)\n\t\t\tc.Lock()\n\t\t\tc.dirtyips = nil\n\t\t\t// c.ips = make(map[string]*record)\n\t\t\t// c.highWatermark = 0\n\t\t\tc.Unlock()\n\t\t}\n\t}()\n\n\tc.RLock()\n\tdirtyips := c.dirtyips\n\tc.RUnlock()\n\n\t// double check to prevent upper call multiple cleanup tasks\n\tif dirtyips == nil {\n\t\treturn\n\t}\n\n\terrors.LogDebug(context.Background(), c.name, \" starting background cache migration for \", len(dirtyips), \" items\")\n\n\tbatch := make([]migrationEntry, 0, migrationBatchSize)\n\tfor domain, recD := range dirtyips {\n\t\tbatch = append(batch, migrationEntry{domain, recD})\n\n\t\tif len(batch) >= migrationBatchSize {\n\t\t\tc.flush(batch)\n\t\t\tbatch = batch[:0]\n\t\t\truntime.Gosched()\n\t\t}\n\t}\n\tif len(batch) > 0 {\n\t\tc.flush(batch)\n\t}\n\n\tc.Lock()\n\tc.dirtyips = nil\n\tc.Unlock()\n\n\terrors.LogDebug(context.Background(), c.name, \" cache migration completed\")\n}\n\nfunc (c *CacheController) flush(batch []migrationEntry) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tfor _, dirty := range batch {\n\t\tif cur := c.ips[dirty.key]; cur != nil {\n\t\t\tmerge := &record{}\n\t\t\tif cur.A == nil {\n\t\t\t\tmerge.A = dirty.value.A\n\t\t\t} else {\n\t\t\t\tmerge.A = cur.A\n\t\t\t}\n\t\t\tif cur.AAAA == nil {\n\t\t\t\tmerge.AAAA = dirty.value.AAAA\n\t\t\t} else {\n\t\t\t\tmerge.AAAA = cur.AAAA\n\t\t\t}\n\t\t\tc.ips[dirty.key] = merge\n\t\t} else {\n\t\t\tc.ips[dirty.key] = dirty.value\n\t\t}\n\t}\n}\n\nfunc (c *CacheController) updateRecord(req *dnsRequest, rep *IPRecord) {\n\trtt := time.Since(req.start)\n\n\tswitch req.reqType {\n\tcase dnsmessage.TypeA:\n\t\tc.pub.Publish(req.domain+\"4\", rep)\n\tcase dnsmessage.TypeAAAA:\n\t\tc.pub.Publish(req.domain+\"6\", rep)\n\t}\n\n\tif c.disableCache {\n\t\terrors.LogInfo(context.Background(), c.name, \" got answer: \", req.domain, \" \", req.reqType, \" -> \", rep.IP, \", rtt: \", rtt)\n\t\treturn\n\t}\n\n\tc.Lock()\n\tlockWait := time.Since(req.start) - rtt\n\n\tnewRec := &record{}\n\toldRec := c.ips[req.domain]\n\tvar dirtyRec *record\n\tif c.dirtyips != nil {\n\t\tdirtyRec = c.dirtyips[req.domain]\n\t}\n\n\tvar pubRecord *IPRecord\n\tvar pubSuffix string\n\n\tswitch req.reqType {\n\tcase dnsmessage.TypeA:\n\t\tnewRec.A = rep\n\t\tif oldRec != nil && oldRec.AAAA != nil {\n\t\t\tnewRec.AAAA = oldRec.AAAA\n\t\t\tpubRecord = oldRec.AAAA\n\t\t} else if dirtyRec != nil && dirtyRec.AAAA != nil {\n\t\t\tpubRecord = dirtyRec.AAAA\n\t\t}\n\t\tpubSuffix = \"6\"\n\tcase dnsmessage.TypeAAAA:\n\t\tnewRec.AAAA = rep\n\t\tif oldRec != nil && oldRec.A != nil {\n\t\t\tnewRec.A = oldRec.A\n\t\t\tpubRecord = oldRec.A\n\t\t} else if dirtyRec != nil && dirtyRec.A != nil {\n\t\t\tpubRecord = dirtyRec.A\n\t\t}\n\t\tpubSuffix = \"4\"\n\t}\n\n\tc.ips[req.domain] = newRec\n\tc.Unlock()\n\n\tif pubRecord != nil {\n\t\t_, ttl, err := pubRecord.getIPs()\n\t\tif ttl > 0 && !go_errors.Is(err, errRecordNotFound) {\n\t\t\tc.pub.Publish(req.domain+pubSuffix, pubRecord)\n\t\t}\n\t}\n\n\terrors.LogInfo(context.Background(), c.name, \" got answer: \", req.domain, \" \", req.reqType, \" -> \", rep.IP, \", rtt: \", rtt, \", lock: \", lockWait)\n\n\tif !c.serveStale || c.serveExpiredTTL != 0 {\n\t\tcommon.Must(c.cacheCleanup.Start())\n\t}\n}\n\nfunc (c *CacheController) findRecords(domain string) *record {\n\tc.RLock()\n\tdefer c.RUnlock()\n\n\trec := c.ips[domain]\n\tif rec == nil && c.dirtyips != nil {\n\t\trec = c.dirtyips[domain]\n\t}\n\treturn rec\n}\n\nfunc (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {\n\t// ipv4 and ipv6 belong to different subscription groups\n\tif option.IPv4Enable {\n\t\tsub4 = c.pub.Subscribe(domain + \"4\")\n\t}\n\tif option.IPv6Enable {\n\t\tsub6 = c.pub.Subscribe(domain + \"6\")\n\t}\n\treturn\n}\n\nfunc closeSubscribers(sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {\n\tif sub4 != nil {\n\t\tsub4.Close()\n\t}\n\tif sub6 != nil {\n\t\tsub6.Close()\n\t}\n}\n"
  },
  {
    "path": "app/dns/config.go",
    "content": "package dns\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\nvar typeMap = map[DomainMatchingType]strmatcher.Type{\n\tDomainMatchingType_Full:      strmatcher.Full,\n\tDomainMatchingType_Subdomain: strmatcher.Domain,\n\tDomainMatchingType_Keyword:   strmatcher.Substr,\n\tDomainMatchingType_Regex:     strmatcher.Regex,\n}\n\n// References:\n// https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml\n// https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan\nvar localTLDsAndDotlessDomains = []*NameServer_PriorityDomain{\n\t{Type: DomainMatchingType_Regex, Domain: \"^[^.]+$\"}, // This will only match domains without any dot\n\t{Type: DomainMatchingType_Subdomain, Domain: \"local\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"localdomain\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"localhost\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"lan\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"home.arpa\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"example\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"invalid\"},\n\t{Type: DomainMatchingType_Subdomain, Domain: \"test\"},\n}\n\nvar localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{\n\tRule: \"geosite:private\",\n\tSize: uint32(len(localTLDsAndDotlessDomains)),\n}\n\nfunc toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {\n\tstrMType, f := typeMap[t]\n\tif !f {\n\t\treturn nil, errors.New(\"unknown mapping type\", t).AtWarning()\n\t}\n\tmatcher, err := strMType.New(domain)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to create str matcher\").Base(err)\n\t}\n\treturn matcher, nil\n}\n\nfunc toNetIP(addrs []net.Address) ([]net.IP, error) {\n\tips := make([]net.IP, 0, len(addrs))\n\tfor _, addr := range addrs {\n\t\tif addr.Family().IsIP() {\n\t\t\tips = append(ips, addr.IP())\n\t\t} else {\n\t\t\treturn nil, errors.New(\"Failed to convert address\", addr, \"to Net IP.\").AtWarning()\n\t\t}\n\t}\n\treturn ips, nil\n}\n\nfunc generateRandomTag() string {\n\tid := uuid.New()\n\treturn \"xray.system.\" + id.String()\n}\n"
  },
  {
    "path": "app/dns/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/dns/config.proto\n\npackage dns\n\nimport (\n\trouter \"github.com/xtls/xray-core/app/router\"\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DomainMatchingType int32\n\nconst (\n\tDomainMatchingType_Full      DomainMatchingType = 0\n\tDomainMatchingType_Subdomain DomainMatchingType = 1\n\tDomainMatchingType_Keyword   DomainMatchingType = 2\n\tDomainMatchingType_Regex     DomainMatchingType = 3\n)\n\n// Enum value maps for DomainMatchingType.\nvar (\n\tDomainMatchingType_name = map[int32]string{\n\t\t0: \"Full\",\n\t\t1: \"Subdomain\",\n\t\t2: \"Keyword\",\n\t\t3: \"Regex\",\n\t}\n\tDomainMatchingType_value = map[string]int32{\n\t\t\"Full\":      0,\n\t\t\"Subdomain\": 1,\n\t\t\"Keyword\":   2,\n\t\t\"Regex\":     3,\n\t}\n)\n\nfunc (x DomainMatchingType) Enum() *DomainMatchingType {\n\tp := new(DomainMatchingType)\n\t*p = x\n\treturn p\n}\n\nfunc (x DomainMatchingType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_app_dns_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DomainMatchingType) Type() protoreflect.EnumType {\n\treturn &file_app_dns_config_proto_enumTypes[0]\n}\n\nfunc (x DomainMatchingType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DomainMatchingType.Descriptor instead.\nfunc (DomainMatchingType) EnumDescriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype QueryStrategy int32\n\nconst (\n\tQueryStrategy_USE_IP  QueryStrategy = 0\n\tQueryStrategy_USE_IP4 QueryStrategy = 1\n\tQueryStrategy_USE_IP6 QueryStrategy = 2\n\tQueryStrategy_USE_SYS QueryStrategy = 3\n)\n\n// Enum value maps for QueryStrategy.\nvar (\n\tQueryStrategy_name = map[int32]string{\n\t\t0: \"USE_IP\",\n\t\t1: \"USE_IP4\",\n\t\t2: \"USE_IP6\",\n\t\t3: \"USE_SYS\",\n\t}\n\tQueryStrategy_value = map[string]int32{\n\t\t\"USE_IP\":  0,\n\t\t\"USE_IP4\": 1,\n\t\t\"USE_IP6\": 2,\n\t\t\"USE_SYS\": 3,\n\t}\n)\n\nfunc (x QueryStrategy) Enum() *QueryStrategy {\n\tp := new(QueryStrategy)\n\t*p = x\n\treturn p\n}\n\nfunc (x QueryStrategy) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_app_dns_config_proto_enumTypes[1].Descriptor()\n}\n\nfunc (QueryStrategy) Type() protoreflect.EnumType {\n\treturn &file_app_dns_config_proto_enumTypes[1]\n}\n\nfunc (x QueryStrategy) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use QueryStrategy.Descriptor instead.\nfunc (QueryStrategy) EnumDescriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{1}\n}\n\ntype NameServer struct {\n\tstate             protoimpl.MessageState       `protogen:\"open.v1\"`\n\tAddress           *net.Endpoint                `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tClientIp          []byte                       `protobuf:\"bytes,5,opt,name=client_ip,json=clientIp,proto3\" json:\"client_ip,omitempty\"`\n\tSkipFallback      bool                         `protobuf:\"varint,6,opt,name=skipFallback,proto3\" json:\"skipFallback,omitempty\"`\n\tPrioritizedDomain []*NameServer_PriorityDomain `protobuf:\"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3\" json:\"prioritized_domain,omitempty\"`\n\tExpectedGeoip     []*router.GeoIP              `protobuf:\"bytes,3,rep,name=expected_geoip,json=expectedGeoip,proto3\" json:\"expected_geoip,omitempty\"`\n\tOriginalRules     []*NameServer_OriginalRule   `protobuf:\"bytes,4,rep,name=original_rules,json=originalRules,proto3\" json:\"original_rules,omitempty\"`\n\tQueryStrategy     QueryStrategy                `protobuf:\"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy\" json:\"query_strategy,omitempty\"`\n\tActPrior          bool                         `protobuf:\"varint,8,opt,name=actPrior,proto3\" json:\"actPrior,omitempty\"`\n\tTag               string                       `protobuf:\"bytes,9,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tTimeoutMs         uint64                       `protobuf:\"varint,10,opt,name=timeoutMs,proto3\" json:\"timeoutMs,omitempty\"`\n\tDisableCache      *bool                        `protobuf:\"varint,11,opt,name=disableCache,proto3,oneof\" json:\"disableCache,omitempty\"`\n\tServeStale        *bool                        `protobuf:\"varint,15,opt,name=serveStale,proto3,oneof\" json:\"serveStale,omitempty\"`\n\tServeExpiredTTL   *uint32                      `protobuf:\"varint,16,opt,name=serveExpiredTTL,proto3,oneof\" json:\"serveExpiredTTL,omitempty\"`\n\tFinalQuery        bool                         `protobuf:\"varint,12,opt,name=finalQuery,proto3\" json:\"finalQuery,omitempty\"`\n\tUnexpectedGeoip   []*router.GeoIP              `protobuf:\"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3\" json:\"unexpected_geoip,omitempty\"`\n\tActUnprior        bool                         `protobuf:\"varint,14,opt,name=actUnprior,proto3\" json:\"actUnprior,omitempty\"`\n\tPolicyID          uint32                       `protobuf:\"varint,17,opt,name=policyID,proto3\" json:\"policyID,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *NameServer) Reset() {\n\t*x = NameServer{}\n\tmi := &file_app_dns_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NameServer) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NameServer) ProtoMessage() {}\n\nfunc (x *NameServer) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NameServer.ProtoReflect.Descriptor instead.\nfunc (*NameServer) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *NameServer) GetAddress() *net.Endpoint {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *NameServer) GetClientIp() []byte {\n\tif x != nil {\n\t\treturn x.ClientIp\n\t}\n\treturn nil\n}\n\nfunc (x *NameServer) GetSkipFallback() bool {\n\tif x != nil {\n\t\treturn x.SkipFallback\n\t}\n\treturn false\n}\n\nfunc (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {\n\tif x != nil {\n\t\treturn x.PrioritizedDomain\n\t}\n\treturn nil\n}\n\nfunc (x *NameServer) GetExpectedGeoip() []*router.GeoIP {\n\tif x != nil {\n\t\treturn x.ExpectedGeoip\n\t}\n\treturn nil\n}\n\nfunc (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule {\n\tif x != nil {\n\t\treturn x.OriginalRules\n\t}\n\treturn nil\n}\n\nfunc (x *NameServer) GetQueryStrategy() QueryStrategy {\n\tif x != nil {\n\t\treturn x.QueryStrategy\n\t}\n\treturn QueryStrategy_USE_IP\n}\n\nfunc (x *NameServer) GetActPrior() bool {\n\tif x != nil {\n\t\treturn x.ActPrior\n\t}\n\treturn false\n}\n\nfunc (x *NameServer) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *NameServer) GetTimeoutMs() uint64 {\n\tif x != nil {\n\t\treturn x.TimeoutMs\n\t}\n\treturn 0\n}\n\nfunc (x *NameServer) GetDisableCache() bool {\n\tif x != nil && x.DisableCache != nil {\n\t\treturn *x.DisableCache\n\t}\n\treturn false\n}\n\nfunc (x *NameServer) GetServeStale() bool {\n\tif x != nil && x.ServeStale != nil {\n\t\treturn *x.ServeStale\n\t}\n\treturn false\n}\n\nfunc (x *NameServer) GetServeExpiredTTL() uint32 {\n\tif x != nil && x.ServeExpiredTTL != nil {\n\t\treturn *x.ServeExpiredTTL\n\t}\n\treturn 0\n}\n\nfunc (x *NameServer) GetFinalQuery() bool {\n\tif x != nil {\n\t\treturn x.FinalQuery\n\t}\n\treturn false\n}\n\nfunc (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {\n\tif x != nil {\n\t\treturn x.UnexpectedGeoip\n\t}\n\treturn nil\n}\n\nfunc (x *NameServer) GetActUnprior() bool {\n\tif x != nil {\n\t\treturn x.ActUnprior\n\t}\n\treturn false\n}\n\nfunc (x *NameServer) GetPolicyID() uint32 {\n\tif x != nil {\n\t\treturn x.PolicyID\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// NameServer list used by this DNS client.\n\t// A special value 'localhost' as a domain address can be set to use DNS on local system.\n\tNameServer []*NameServer `protobuf:\"bytes,5,rep,name=name_server,json=nameServer,proto3\" json:\"name_server,omitempty\"`\n\t// Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes\n\t// (IPv6).\n\tClientIp    []byte                `protobuf:\"bytes,3,opt,name=client_ip,json=clientIp,proto3\" json:\"client_ip,omitempty\"`\n\tStaticHosts []*Config_HostMapping `protobuf:\"bytes,4,rep,name=static_hosts,json=staticHosts,proto3\" json:\"static_hosts,omitempty\"`\n\t// Tag is the inbound tag of DNS client.\n\tTag string `protobuf:\"bytes,6,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\t// DisableCache disables DNS cache\n\tDisableCache           bool          `protobuf:\"varint,8,opt,name=disableCache,proto3\" json:\"disableCache,omitempty\"`\n\tServeStale             bool          `protobuf:\"varint,12,opt,name=serveStale,proto3\" json:\"serveStale,omitempty\"`\n\tServeExpiredTTL        uint32        `protobuf:\"varint,13,opt,name=serveExpiredTTL,proto3\" json:\"serveExpiredTTL,omitempty\"`\n\tQueryStrategy          QueryStrategy `protobuf:\"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy\" json:\"query_strategy,omitempty\"`\n\tDisableFallback        bool          `protobuf:\"varint,10,opt,name=disableFallback,proto3\" json:\"disableFallback,omitempty\"`\n\tDisableFallbackIfMatch bool          `protobuf:\"varint,11,opt,name=disableFallbackIfMatch,proto3\" json:\"disableFallbackIfMatch,omitempty\"`\n\tEnableParallelQuery    bool          `protobuf:\"varint,14,opt,name=enableParallelQuery,proto3\" json:\"enableParallelQuery,omitempty\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_dns_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Config) GetNameServer() []*NameServer {\n\tif x != nil {\n\t\treturn x.NameServer\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetClientIp() []byte {\n\tif x != nil {\n\t\treturn x.ClientIp\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetStaticHosts() []*Config_HostMapping {\n\tif x != nil {\n\t\treturn x.StaticHosts\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetDisableCache() bool {\n\tif x != nil {\n\t\treturn x.DisableCache\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetServeStale() bool {\n\tif x != nil {\n\t\treturn x.ServeStale\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetServeExpiredTTL() uint32 {\n\tif x != nil {\n\t\treturn x.ServeExpiredTTL\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetQueryStrategy() QueryStrategy {\n\tif x != nil {\n\t\treturn x.QueryStrategy\n\t}\n\treturn QueryStrategy_USE_IP\n}\n\nfunc (x *Config) GetDisableFallback() bool {\n\tif x != nil {\n\t\treturn x.DisableFallback\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetDisableFallbackIfMatch() bool {\n\tif x != nil {\n\t\treturn x.DisableFallbackIfMatch\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetEnableParallelQuery() bool {\n\tif x != nil {\n\t\treturn x.EnableParallelQuery\n\t}\n\treturn false\n}\n\ntype NameServer_PriorityDomain struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          DomainMatchingType     `protobuf:\"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType\" json:\"type,omitempty\"`\n\tDomain        string                 `protobuf:\"bytes,2,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NameServer_PriorityDomain) Reset() {\n\t*x = NameServer_PriorityDomain{}\n\tmi := &file_app_dns_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NameServer_PriorityDomain) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NameServer_PriorityDomain) ProtoMessage() {}\n\nfunc (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead.\nfunc (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *NameServer_PriorityDomain) GetType() DomainMatchingType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn DomainMatchingType_Full\n}\n\nfunc (x *NameServer_PriorityDomain) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\ntype NameServer_OriginalRule struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRule          string                 `protobuf:\"bytes,1,opt,name=rule,proto3\" json:\"rule,omitempty\"`\n\tSize          uint32                 `protobuf:\"varint,2,opt,name=size,proto3\" json:\"size,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NameServer_OriginalRule) Reset() {\n\t*x = NameServer_OriginalRule{}\n\tmi := &file_app_dns_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NameServer_OriginalRule) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NameServer_OriginalRule) ProtoMessage() {}\n\nfunc (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead.\nfunc (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{0, 1}\n}\n\nfunc (x *NameServer_OriginalRule) GetRule() string {\n\tif x != nil {\n\t\treturn x.Rule\n\t}\n\treturn \"\"\n}\n\nfunc (x *NameServer_OriginalRule) GetSize() uint32 {\n\tif x != nil {\n\t\treturn x.Size\n\t}\n\treturn 0\n}\n\ntype Config_HostMapping struct {\n\tstate  protoimpl.MessageState `protogen:\"open.v1\"`\n\tType   DomainMatchingType     `protobuf:\"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType\" json:\"type,omitempty\"`\n\tDomain string                 `protobuf:\"bytes,2,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tIp     [][]byte               `protobuf:\"bytes,3,rep,name=ip,proto3\" json:\"ip,omitempty\"`\n\t// ProxiedDomain indicates the mapped domain has the same IP address on this\n\t// domain. Xray will use this domain for IP queries.\n\tProxiedDomain string `protobuf:\"bytes,4,opt,name=proxied_domain,json=proxiedDomain,proto3\" json:\"proxied_domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config_HostMapping) Reset() {\n\t*x = Config_HostMapping{}\n\tmi := &file_app_dns_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config_HostMapping) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config_HostMapping) ProtoMessage() {}\n\nfunc (x *Config_HostMapping) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config_HostMapping.ProtoReflect.Descriptor instead.\nfunc (*Config_HostMapping) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_config_proto_rawDescGZIP(), []int{1, 0}\n}\n\nfunc (x *Config_HostMapping) GetType() DomainMatchingType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn DomainMatchingType_Full\n}\n\nfunc (x *Config_HostMapping) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config_HostMapping) GetIp() [][]byte {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *Config_HostMapping) GetProxiedDomain() string {\n\tif x != nil {\n\t\treturn x.ProxiedDomain\n\t}\n\treturn \"\"\n}\n\nvar File_app_dns_config_proto protoreflect.FileDescriptor\n\nconst file_app_dns_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x14app/dns/config.proto\\x12\\fxray.app.dns\\x1a\\x1ccommon/net/destination.proto\\x1a\\x17app/router/config.proto\\\"\\xdf\\a\\n\" +\n\t\"\\n\" +\n\t\"NameServer\\x123\\n\" +\n\t\"\\aaddress\\x18\\x01 \\x01(\\v2\\x19.xray.common.net.EndpointR\\aaddress\\x12\\x1b\\n\" +\n\t\"\\tclient_ip\\x18\\x05 \\x01(\\fR\\bclientIp\\x12\\\"\\n\" +\n\t\"\\fskipFallback\\x18\\x06 \\x01(\\bR\\fskipFallback\\x12V\\n\" +\n\t\"\\x12prioritized_domain\\x18\\x02 \\x03(\\v2'.xray.app.dns.NameServer.PriorityDomainR\\x11prioritizedDomain\\x12=\\n\" +\n\t\"\\x0eexpected_geoip\\x18\\x03 \\x03(\\v2\\x16.xray.app.router.GeoIPR\\rexpectedGeoip\\x12L\\n\" +\n\t\"\\x0eoriginal_rules\\x18\\x04 \\x03(\\v2%.xray.app.dns.NameServer.OriginalRuleR\\roriginalRules\\x12B\\n\" +\n\t\"\\x0equery_strategy\\x18\\a \\x01(\\x0e2\\x1b.xray.app.dns.QueryStrategyR\\rqueryStrategy\\x12\\x1a\\n\" +\n\t\"\\bactPrior\\x18\\b \\x01(\\bR\\bactPrior\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\t \\x01(\\tR\\x03tag\\x12\\x1c\\n\" +\n\t\"\\ttimeoutMs\\x18\\n\" +\n\t\" \\x01(\\x04R\\ttimeoutMs\\x12'\\n\" +\n\t\"\\fdisableCache\\x18\\v \\x01(\\bH\\x00R\\fdisableCache\\x88\\x01\\x01\\x12#\\n\" +\n\t\"\\n\" +\n\t\"serveStale\\x18\\x0f \\x01(\\bH\\x01R\\n\" +\n\t\"serveStale\\x88\\x01\\x01\\x12-\\n\" +\n\t\"\\x0fserveExpiredTTL\\x18\\x10 \\x01(\\rH\\x02R\\x0fserveExpiredTTL\\x88\\x01\\x01\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"finalQuery\\x18\\f \\x01(\\bR\\n\" +\n\t\"finalQuery\\x12A\\n\" +\n\t\"\\x10unexpected_geoip\\x18\\r \\x03(\\v2\\x16.xray.app.router.GeoIPR\\x0funexpectedGeoip\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"actUnprior\\x18\\x0e \\x01(\\bR\\n\" +\n\t\"actUnprior\\x12\\x1a\\n\" +\n\t\"\\bpolicyID\\x18\\x11 \\x01(\\rR\\bpolicyID\\x1a^\\n\" +\n\t\"\\x0ePriorityDomain\\x124\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2 .xray.app.dns.DomainMatchingTypeR\\x04type\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x01(\\tR\\x06domain\\x1a6\\n\" +\n\t\"\\fOriginalRule\\x12\\x12\\n\" +\n\t\"\\x04rule\\x18\\x01 \\x01(\\tR\\x04rule\\x12\\x12\\n\" +\n\t\"\\x04size\\x18\\x02 \\x01(\\rR\\x04sizeB\\x0f\\n\" +\n\t\"\\r_disableCacheB\\r\\n\" +\n\t\"\\v_serveStaleB\\x12\\n\" +\n\t\"\\x10_serveExpiredTTL\\\"\\x98\\x05\\n\" +\n\t\"\\x06Config\\x129\\n\" +\n\t\"\\vname_server\\x18\\x05 \\x03(\\v2\\x18.xray.app.dns.NameServerR\\n\" +\n\t\"nameServer\\x12\\x1b\\n\" +\n\t\"\\tclient_ip\\x18\\x03 \\x01(\\fR\\bclientIp\\x12C\\n\" +\n\t\"\\fstatic_hosts\\x18\\x04 \\x03(\\v2 .xray.app.dns.Config.HostMappingR\\vstaticHosts\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x06 \\x01(\\tR\\x03tag\\x12\\\"\\n\" +\n\t\"\\fdisableCache\\x18\\b \\x01(\\bR\\fdisableCache\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"serveStale\\x18\\f \\x01(\\bR\\n\" +\n\t\"serveStale\\x12(\\n\" +\n\t\"\\x0fserveExpiredTTL\\x18\\r \\x01(\\rR\\x0fserveExpiredTTL\\x12B\\n\" +\n\t\"\\x0equery_strategy\\x18\\t \\x01(\\x0e2\\x1b.xray.app.dns.QueryStrategyR\\rqueryStrategy\\x12(\\n\" +\n\t\"\\x0fdisableFallback\\x18\\n\" +\n\t\" \\x01(\\bR\\x0fdisableFallback\\x126\\n\" +\n\t\"\\x16disableFallbackIfMatch\\x18\\v \\x01(\\bR\\x16disableFallbackIfMatch\\x120\\n\" +\n\t\"\\x13enableParallelQuery\\x18\\x0e \\x01(\\bR\\x13enableParallelQuery\\x1a\\x92\\x01\\n\" +\n\t\"\\vHostMapping\\x124\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2 .xray.app.dns.DomainMatchingTypeR\\x04type\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x01(\\tR\\x06domain\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x03 \\x03(\\fR\\x02ip\\x12%\\n\" +\n\t\"\\x0eproxied_domain\\x18\\x04 \\x01(\\tR\\rproxiedDomainJ\\x04\\b\\a\\x10\\b*E\\n\" +\n\t\"\\x12DomainMatchingType\\x12\\b\\n\" +\n\t\"\\x04Full\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tSubdomain\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aKeyword\\x10\\x02\\x12\\t\\n\" +\n\t\"\\x05Regex\\x10\\x03*B\\n\" +\n\t\"\\rQueryStrategy\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06USE_IP\\x10\\x00\\x12\\v\\n\" +\n\t\"\\aUSE_IP4\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aUSE_IP6\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aUSE_SYS\\x10\\x03BF\\n\" +\n\t\"\\x10com.xray.app.dnsP\\x01Z!github.com/xtls/xray-core/app/dns\\xaa\\x02\\fXray.App.Dnsb\\x06proto3\"\n\nvar (\n\tfile_app_dns_config_proto_rawDescOnce sync.Once\n\tfile_app_dns_config_proto_rawDescData []byte\n)\n\nfunc file_app_dns_config_proto_rawDescGZIP() []byte {\n\tfile_app_dns_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dns_config_proto_rawDesc), len(file_app_dns_config_proto_rawDesc)))\n\t})\n\treturn file_app_dns_config_proto_rawDescData\n}\n\nvar file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_app_dns_config_proto_goTypes = []any{\n\t(DomainMatchingType)(0),           // 0: xray.app.dns.DomainMatchingType\n\t(QueryStrategy)(0),                // 1: xray.app.dns.QueryStrategy\n\t(*NameServer)(nil),                // 2: xray.app.dns.NameServer\n\t(*Config)(nil),                    // 3: xray.app.dns.Config\n\t(*NameServer_PriorityDomain)(nil), // 4: xray.app.dns.NameServer.PriorityDomain\n\t(*NameServer_OriginalRule)(nil),   // 5: xray.app.dns.NameServer.OriginalRule\n\t(*Config_HostMapping)(nil),        // 6: xray.app.dns.Config.HostMapping\n\t(*net.Endpoint)(nil),              // 7: xray.common.net.Endpoint\n\t(*router.GeoIP)(nil),              // 8: xray.app.router.GeoIP\n}\nvar file_app_dns_config_proto_depIdxs = []int32{\n\t7,  // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint\n\t4,  // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain\n\t8,  // 2: xray.app.dns.NameServer.expected_geoip:type_name -> xray.app.router.GeoIP\n\t5,  // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule\n\t1,  // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy\n\t8,  // 5: xray.app.dns.NameServer.unexpected_geoip:type_name -> xray.app.router.GeoIP\n\t2,  // 6: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer\n\t6,  // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping\n\t1,  // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy\n\t0,  // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType\n\t0,  // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType\n\t11, // [11:11] is the sub-list for method output_type\n\t11, // [11:11] is the sub-list for method input_type\n\t11, // [11:11] is the sub-list for extension type_name\n\t11, // [11:11] is the sub-list for extension extendee\n\t0,  // [0:11] is the sub-list for field type_name\n}\n\nfunc init() { file_app_dns_config_proto_init() }\nfunc file_app_dns_config_proto_init() {\n\tif File_app_dns_config_proto != nil {\n\t\treturn\n\t}\n\tfile_app_dns_config_proto_msgTypes[0].OneofWrappers = []any{}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dns_config_proto_rawDesc), len(file_app_dns_config_proto_rawDesc)),\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_dns_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_dns_config_proto_depIdxs,\n\t\tEnumInfos:         file_app_dns_config_proto_enumTypes,\n\t\tMessageInfos:      file_app_dns_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_dns_config_proto = out.File\n\tfile_app_dns_config_proto_goTypes = nil\n\tfile_app_dns_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/dns/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.dns;\noption csharp_namespace = \"Xray.App.Dns\";\noption go_package = \"github.com/xtls/xray-core/app/dns\";\noption java_package = \"com.xray.app.dns\";\noption java_multiple_files = true;\n\nimport \"common/net/destination.proto\";\nimport \"app/router/config.proto\";\n\nmessage NameServer {\n  xray.common.net.Endpoint address = 1;\n  bytes client_ip = 5;\n  bool skipFallback = 6;\n\n  message PriorityDomain {\n    DomainMatchingType type = 1;\n    string domain = 2;\n  }\n\n  message OriginalRule {\n    string rule = 1;\n    uint32 size = 2;\n  }\n\n  repeated PriorityDomain prioritized_domain = 2;\n  repeated xray.app.router.GeoIP expected_geoip = 3;\n  repeated OriginalRule original_rules = 4;\n  QueryStrategy query_strategy = 7;\n  bool actPrior = 8;\n  string tag = 9;\n  uint64 timeoutMs = 10;\n  optional bool disableCache = 11;\n  optional bool serveStale = 15;\n  optional uint32 serveExpiredTTL = 16;\n  bool finalQuery = 12;\n  repeated xray.app.router.GeoIP unexpected_geoip = 13;\n  bool actUnprior = 14;\n  uint32 policyID = 17;\n}\n\nenum DomainMatchingType {\n  Full = 0;\n  Subdomain = 1;\n  Keyword = 2;\n  Regex = 3;\n}\n\nenum QueryStrategy {\n  USE_IP = 0;\n  USE_IP4 = 1;\n  USE_IP6 = 2;\n  USE_SYS = 3;\n}\n\nmessage Config {\n  // NameServer list used by this DNS client.\n  // A special value 'localhost' as a domain address can be set to use DNS on local system.\n  repeated NameServer name_server = 5;\n\n  // Client IP for EDNS client subnet. Must be 4 bytes (IPv4) or 16 bytes\n  // (IPv6).\n  bytes client_ip = 3;\n\n  message HostMapping {\n    DomainMatchingType type = 1;\n    string domain = 2;\n\n    repeated bytes ip = 3;\n\n    // ProxiedDomain indicates the mapped domain has the same IP address on this\n    // domain. Xray will use this domain for IP queries.\n    string proxied_domain = 4;\n  }\n\n  repeated HostMapping static_hosts = 4;\n\n  // Tag is the inbound tag of DNS client.\n  string tag = 6;\n\n  reserved 7;\n\n  // DisableCache disables DNS cache\n  bool disableCache = 8;\n  bool serveStale = 12;\n  uint32 serveExpiredTTL = 13;\n\n  QueryStrategy query_strategy = 9;\n\n  bool disableFallback = 10;\n  bool disableFallbackIfMatch = 11;\n\n  bool enableParallelQuery = 14;\n}\n"
  },
  {
    "path": "app/dns/dns.go",
    "content": "// Package dns is an implementation of core.DNS feature.\npackage dns\n\nimport (\n\t\"context\"\n\tgo_errors \"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\n// DNS is a DNS rely server.\ntype DNS struct {\n\tsync.Mutex\n\tdisableFallback        bool\n\tdisableFallbackIfMatch bool\n\tenableParallelQuery    bool\n\tipOption               *dns.IPOption\n\thosts                  *StaticHosts\n\tclients                []*Client\n\tctx                    context.Context\n\tdomainMatcher          strmatcher.IndexMatcher\n\tmatcherInfos           []*DomainMatcherInfo\n\tcheckSystem            bool\n}\n\n// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher\ntype DomainMatcherInfo struct {\n\tclientIdx     uint16\n\tdomainRuleIdx uint16\n}\n\n// New creates a new DNS server with given configuration.\nfunc New(ctx context.Context, config *Config) (*DNS, error) {\n\tvar clientIP net.IP\n\tswitch len(config.ClientIp) {\n\tcase 0, net.IPv4len, net.IPv6len:\n\t\tclientIP = net.IP(config.ClientIp)\n\tdefault:\n\t\treturn nil, errors.New(\"unexpected client IP length \", len(config.ClientIp))\n\t}\n\n\tvar ipOption dns.IPOption\n\tcheckSystem := false\n\tswitch config.QueryStrategy {\n\tcase QueryStrategy_USE_IP:\n\t\tipOption = dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t}\n\tcase QueryStrategy_USE_SYS:\n\t\tipOption = dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t}\n\t\tcheckSystem = true\n\tcase QueryStrategy_USE_IP4:\n\t\tipOption = dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: false,\n\t\t\tFakeEnable: false,\n\t\t}\n\tcase QueryStrategy_USE_IP6:\n\t\tipOption = dns.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t}\n\tdefault:\n\t\treturn nil, errors.New(\"unexpected query strategy \", config.QueryStrategy)\n\t}\n\n\tvar hosts *StaticHosts\n\tmphLoaded := false\n\tdomainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return \"\" })\n\tif domainMatcherPath != \"\" {\n\t\tif f, err := os.Open(domainMatcherPath); err == nil {\n\t\t\tdefer f.Close()\n\t\t\tif m, err := router.LoadGeoSiteMatcher(f, \"HOSTS\"); err == nil {\n\t\t\t\tf.Seek(0, 0)\n\t\t\t\tif hostIPs, err := router.LoadGeoSiteHosts(f); err == nil {\n\t\t\t\t\tif sh, err := NewStaticHostsFromCache(m, hostIPs); err == nil {\n\t\t\t\t\t\thosts = sh\n\t\t\t\t\t\tmphLoaded = true\n\t\t\t\t\t\terrors.LogDebug(ctx, \"MphDomainMatcher loaded from cache for DNS hosts, size: \", sh.matchers.Size())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif !mphLoaded {\n\t\tsh, err := NewStaticHosts(config.StaticHosts)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to create hosts\").Base(err)\n\t\t}\n\t\thosts = sh\n\t}\n\n\tvar clients []*Client\n\tdomainRuleCount := 0\n\n\tvar defaultTag = config.Tag\n\tif len(config.Tag) == 0 {\n\t\tdefaultTag = generateRandomTag()\n\t}\n\n\tfor _, ns := range config.NameServer {\n\t\tdomainRuleCount += len(ns.PrioritizedDomain)\n\t}\n\n\t// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1\n\tmatcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)\n\tdomainMatcher := &strmatcher.MatcherGroup{}\n\n\tfor _, ns := range config.NameServer {\n\t\tclientIdx := len(clients)\n\t\tupdateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {\n\t\t\tmidx := domainMatcher.Add(domainRule)\n\t\t\tmatcherInfos[midx] = &DomainMatcherInfo{\n\t\t\t\tclientIdx:     uint16(clientIdx),\n\t\t\t\tdomainRuleIdx: uint16(originalRuleIdx),\n\t\t\t}\n\t\t}\n\n\t\tmyClientIP := clientIP\n\t\tswitch len(ns.ClientIp) {\n\t\tcase net.IPv4len, net.IPv6len:\n\t\t\tmyClientIP = net.IP(ns.ClientIp)\n\t\t}\n\n\t\tdisableCache := config.DisableCache\n\t\tif ns.DisableCache != nil {\n\t\t\tdisableCache = *ns.DisableCache\n\t\t}\n\n\t\tserveStale := config.ServeStale\n\t\tif ns.ServeStale != nil {\n\t\t\tserveStale = *ns.ServeStale\n\t\t}\n\n\t\tserveExpiredTTL := config.ServeExpiredTTL\n\t\tif ns.ServeExpiredTTL != nil {\n\t\t\tserveExpiredTTL = *ns.ServeExpiredTTL\n\t\t}\n\n\t\tvar tag = defaultTag\n\t\tif len(ns.Tag) > 0 {\n\t\t\ttag = ns.Tag\n\t\t}\n\t\tclientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)\n\t\tif !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {\n\t\t\treturn nil, errors.New(\"no QueryStrategy available for \", ns.Address)\n\t\t}\n\n\t\tclient, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, &matcherInfos, updateDomain)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to create client\").Base(err)\n\t\t}\n\t\tclients = append(clients, client)\n\t}\n\n\t// If there is no DNS client in config, add a `localhost` DNS client\n\tif len(clients) == 0 {\n\t\tclients = append(clients, NewLocalDNSClient(ipOption))\n\t}\n\n\treturn &DNS{\n\t\thosts:                  hosts,\n\t\tipOption:               &ipOption,\n\t\tclients:                clients,\n\t\tctx:                    ctx,\n\t\tdomainMatcher:          domainMatcher,\n\t\tmatcherInfos:           matcherInfos,\n\t\tdisableFallback:        config.DisableFallback,\n\t\tdisableFallbackIfMatch: config.DisableFallbackIfMatch,\n\t\tenableParallelQuery:    config.EnableParallelQuery,\n\t\tcheckSystem:            checkSystem,\n\t}, nil\n}\n\n// Type implements common.HasType.\nfunc (*DNS) Type() interface{} {\n\treturn dns.ClientType()\n}\n\n// Start implements common.Runnable.\nfunc (s *DNS) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (s *DNS) Close() error {\n\treturn nil\n}\n\n// IsOwnLink implements proxy.dns.ownLinkVerifier\nfunc (s *DNS) IsOwnLink(ctx context.Context) bool {\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound == nil {\n\t\treturn false\n\t}\n\tfor _, client := range s.clients {\n\t\tif client.tag == inbound.Tag {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// LookupIP implements dns.Client.\nfunc (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {\n\t// Normalize the FQDN form query\n\tdomain = strings.TrimSuffix(domain, \".\")\n\tif domain == \"\" {\n\t\treturn nil, 0, errors.New(\"empty domain name\")\n\t}\n\n\tif s.checkSystem {\n\t\tsupportIPv4, supportIPv6 := checkRoutes()\n\t\toption.IPv4Enable = option.IPv4Enable && supportIPv4\n\t\toption.IPv6Enable = option.IPv6Enable && supportIPv6\n\t} else {\n\t\toption.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable\n\t\toption.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable\n\t}\n\n\tif !option.IPv4Enable && !option.IPv6Enable {\n\t\treturn nil, 0, dns.ErrEmptyResponse\n\t}\n\n\t// Static host lookup\n\tswitch addrs, err := s.hosts.Lookup(domain, option); {\n\tcase err != nil:\n\t\tif go_errors.Is(err, dns.ErrEmptyResponse) {\n\t\t\treturn nil, 0, dns.ErrEmptyResponse\n\t\t}\n\t\treturn nil, 0, errors.New(\"returning nil for domain \", domain).Base(err)\n\tcase addrs == nil: // Domain not recorded in static host\n\t\tbreak\n\tcase len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)\n\t\treturn nil, 0, dns.ErrEmptyResponse\n\tcase len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement\n\t\terrors.LogInfo(s.ctx, \"domain replaced: \", domain, \" -> \", addrs[0].Domain())\n\t\tdomain = addrs[0].Domain()\n\tdefault: // Successfully found ip records in static host\n\t\terrors.LogInfo(s.ctx, \"returning \", len(addrs), \" IP(s) for domain \", domain, \" -> \", addrs)\n\t\tips, err := toNetIP(addrs)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\treturn ips, 10, nil // Hosts ttl is 10\n\t}\n\n\t// Name servers lookup\n\tif s.enableParallelQuery {\n\t\treturn s.parallelQuery(domain, option)\n\t} else {\n\t\treturn s.serialQuery(domain, option)\n\t}\n}\n\nfunc (s *DNS) sortClients(domain string) []*Client {\n\tclients := make([]*Client, 0, len(s.clients))\n\tclientUsed := make([]bool, len(s.clients))\n\tclientNames := make([]string, 0, len(s.clients))\n\tdomainRules := []string{}\n\n\t// Priority domain matching\n\thasMatch := false\n\tMatchSlice := s.domainMatcher.Match(domain)\n\tsort.Slice(MatchSlice, func(i, j int) bool {\n\t\treturn MatchSlice[i] < MatchSlice[j]\n\t})\n\tfor _, match := range MatchSlice {\n\t\tinfo := s.matcherInfos[match]\n\t\tclient := s.clients[info.clientIdx]\n\t\tdomainRule := client.domains[info.domainRuleIdx]\n\t\tdomainRules = append(domainRules, fmt.Sprintf(\"%s(DNS idx:%d)\", domainRule, info.clientIdx))\n\t\tif clientUsed[info.clientIdx] {\n\t\t\tcontinue\n\t\t}\n\t\tclientUsed[info.clientIdx] = true\n\t\tclients = append(clients, client)\n\t\tclientNames = append(clientNames, client.Name())\n\t\thasMatch = true\n\t\tif client.finalQuery {\n\t\t\treturn clients\n\t\t}\n\t}\n\n\tif !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) {\n\t\t// Default round-robin query\n\t\tfor idx, client := range s.clients {\n\t\t\tif clientUsed[idx] || client.skipFallback {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tclientUsed[idx] = true\n\t\t\tclients = append(clients, client)\n\t\t\tclientNames = append(clientNames, client.Name())\n\t\t\tif client.finalQuery {\n\t\t\t\treturn clients\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(domainRules) > 0 {\n\t\terrors.LogDebug(s.ctx, \"domain \", domain, \" matches following rules: \", domainRules)\n\t}\n\tif len(clientNames) > 0 {\n\t\terrors.LogDebug(s.ctx, \"domain \", domain, \" will use DNS in order: \", clientNames)\n\t}\n\n\tif len(clients) == 0 {\n\t\tif len(s.clients) > 0 {\n\t\t\tclients = append(clients, s.clients[0])\n\t\t\tclientNames = append(clientNames, s.clients[0].Name())\n\t\t\terrors.LogWarning(s.ctx, \"domain \", domain, \" will use the first DNS: \", clientNames)\n\t\t} else {\n\t\t\terrors.LogError(s.ctx, \"no DNS clients available for domain \", domain, \" and no default clients configured\")\n\t\t}\n\t}\n\n\treturn clients\n}\n\nfunc mergeQueryErrors(domain string, errs []error) error {\n\tif len(errs) == 0 {\n\t\treturn dns.ErrEmptyResponse\n\t}\n\n\tvar noRNF error\n\tfor _, err := range errs {\n\t\tif go_errors.Is(err, errRecordNotFound) {\n\t\t\tcontinue // server no response, ignore\n\t\t} else if noRNF == nil {\n\t\t\tnoRNF = err\n\t\t} else if !go_errors.Is(err, noRNF) {\n\t\t\treturn errors.New(\"returning nil for domain \", domain).Base(errors.Combine(errs...))\n\t\t}\n\t}\n\tif go_errors.Is(noRNF, dns.ErrEmptyResponse) {\n\t\treturn dns.ErrEmptyResponse\n\t}\n\tif noRNF == nil {\n\t\tnoRNF = errRecordNotFound\n\t}\n\treturn errors.New(\"returning nil for domain \", domain).Base(noRNF)\n}\n\nfunc (s *DNS) serialQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {\n\tvar errs []error\n\tfor _, client := range s.sortClients(domain) {\n\t\tif !option.FakeEnable && strings.EqualFold(client.Name(), \"FakeDNS\") {\n\t\t\terrors.LogDebug(s.ctx, \"skip DNS resolution for domain \", domain, \" at server \", client.Name())\n\t\t\tcontinue\n\t\t}\n\n\t\tips, ttl, err := client.QueryIP(s.ctx, domain, option)\n\n\t\tif len(ips) > 0 {\n\t\t\treturn ips, ttl, nil\n\t\t}\n\n\t\terrors.LogInfoInner(s.ctx, err, \"failed to lookup ip for domain \", domain, \" at server \", client.Name(), \" in serial query mode\")\n\t\tif err == nil {\n\t\t\terr = dns.ErrEmptyResponse\n\t\t}\n\t\terrs = append(errs, err)\n\t}\n\treturn nil, 0, mergeQueryErrors(domain, errs)\n}\n\nfunc (s *DNS) parallelQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {\n\tvar errs []error\n\tclients := s.sortClients(domain)\n\n\tresultsChan := asyncQueryAll(domain, option, clients, s.ctx)\n\n\tgroups, groupOf := makeGroups( /*s.ctx,*/ clients)\n\tresults := make([]*queryResult, len(clients))\n\tpending := make([]int, len(groups))\n\tfor gi, g := range groups {\n\t\tpending[gi] = g.end - g.start + 1\n\t}\n\n\tnextGroup := 0\n\tfor range clients {\n\t\tresult := <-resultsChan\n\t\tresults[result.index] = &result\n\n\t\tgi := groupOf[result.index]\n\t\tpending[gi]--\n\n\t\tfor nextGroup < len(groups) {\n\t\t\tg := groups[nextGroup]\n\n\t\t\t// group race, minimum rtt -> return\n\t\t\tfor j := g.start; j <= g.end; j++ {\n\t\t\t\tr := results[j]\n\t\t\t\tif r != nil && r.err == nil && len(r.ips) > 0 {\n\t\t\t\t\treturn r.ips, r.ttl, nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// current group is incomplete and no one success -> continue pending\n\t\t\tif pending[nextGroup] > 0 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// all failed -> log and continue next group\n\t\t\tfor j := g.start; j <= g.end; j++ {\n\t\t\t\tr := results[j]\n\t\t\t\te := r.err\n\t\t\t\tif e == nil {\n\t\t\t\t\te = dns.ErrEmptyResponse\n\t\t\t\t}\n\t\t\t\terrors.LogInfoInner(s.ctx, e, \"failed to lookup ip for domain \", domain, \" at server \", clients[j].Name(), \" in parallel query mode\")\n\t\t\t\terrs = append(errs, e)\n\t\t\t}\n\t\t\tnextGroup++\n\t\t}\n\t}\n\n\treturn nil, 0, mergeQueryErrors(domain, errs)\n}\n\ntype queryResult struct {\n\tips   []net.IP\n\tttl   uint32\n\terr   error\n\tindex int\n}\n\nfunc asyncQueryAll(domain string, option dns.IPOption, clients []*Client, ctx context.Context) chan queryResult {\n\tif len(clients) == 0 {\n\t\tch := make(chan queryResult)\n\t\tclose(ch)\n\t\treturn ch\n\t}\n\n\tch := make(chan queryResult, len(clients))\n\tfor i, client := range clients {\n\t\tif !option.FakeEnable && strings.EqualFold(client.Name(), \"FakeDNS\") {\n\t\t\terrors.LogDebug(ctx, \"skip DNS resolution for domain \", domain, \" at server \", client.Name())\n\t\t\tch <- queryResult{err: dns.ErrEmptyResponse, index: i}\n\t\t\tcontinue\n\t\t}\n\n\t\tgo func(i int, c *Client) {\n\t\t\tqctx := ctx\n\t\t\tif !c.server.IsDisableCache() {\n\t\t\t\tnctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), c.timeoutMs*2)\n\t\t\t\tqctx = nctx\n\t\t\t\tdefer cancel()\n\t\t\t}\n\t\t\tips, ttl, err := c.QueryIP(qctx, domain, option)\n\t\t\tch <- queryResult{ips: ips, ttl: ttl, err: err, index: i}\n\t\t}(i, client)\n\t}\n\treturn ch\n}\n\ntype group struct{ start, end int }\n\n// merge only adjacent and rule-equivalent Client into a single group\nfunc makeGroups( /*ctx context.Context,*/ clients []*Client) ([]group, []int) {\n\tn := len(clients)\n\tif n == 0 {\n\t\treturn nil, nil\n\t}\n\tgroups := make([]group, 0, n)\n\tgroupOf := make([]int, n)\n\n\ts, e := 0, 0\n\tfor i := 1; i < n; i++ {\n\t\tif clients[i-1].policyID == clients[i].policyID {\n\t\t\te = i\n\t\t} else {\n\t\t\tfor k := s; k <= e; k++ {\n\t\t\t\tgroupOf[k] = len(groups)\n\t\t\t}\n\t\t\tgroups = append(groups, group{start: s, end: e})\n\t\t\ts, e = i, i\n\t\t}\n\t}\n\tfor k := s; k <= e; k++ {\n\t\tgroupOf[k] = len(groups)\n\t}\n\tgroups = append(groups, group{start: s, end: e})\n\n\t// var b strings.Builder\n\t// b.WriteString(\"dns grouping: total clients=\")\n\t// b.WriteString(strconv.Itoa(n))\n\t// b.WriteString(\", groups=\")\n\t// b.WriteString(strconv.Itoa(len(groups)))\n\n\t// for gi, g := range groups {\n\t// \tb.WriteString(\"\\n  [\")\n\t// \tb.WriteString(strconv.Itoa(g.start))\n\t// \tb.WriteString(\"..\")\n\t// \tb.WriteString(strconv.Itoa(g.end))\n\t// \tb.WriteString(\"] gid=\")\n\t// \tb.WriteString(strconv.Itoa(gi))\n\t// \tb.WriteString(\" pid=\")\n\t// \tb.WriteString(strconv.FormatUint(uint64(clients[g.start].policyID), 10))\n\t// \tb.WriteString(\" members: \")\n\n\t// \tfor i := g.start; i <= g.end; i++ {\n\t// \t\tif i > g.start {\n\t// \t\t\tb.WriteString(\", \")\n\t// \t\t}\n\t// \t\tb.WriteString(strconv.Itoa(i))\n\t// \t\tb.WriteByte(':')\n\t// \t\tb.WriteString(clients[i].Name())\n\t// \t}\n\t// }\n\t// errors.LogDebug(ctx, b.String())\n\n\treturn groups, groupOf\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n\nfunc probeRoutes() (ipv4 bool, ipv6 bool) {\n\tif conn, err := net.Dial(\"udp4\", \"192.33.4.12:53\"); err == nil {\n\t\tipv4 = true\n\t\tconn.Close()\n\t}\n\tif conn, err := net.Dial(\"udp6\", \"[2001:500:2::c]:53\"); err == nil {\n\t\tipv6 = true\n\t\tconn.Close()\n\t}\n\treturn\n}\n\nvar routeCache struct {\n\tsync.Once\n\tsync.RWMutex\n\texpire     time.Time\n\tipv4, ipv6 bool\n}\n\nfunc checkRoutes() (bool, bool) {\n\tif !isGUIPlatform {\n\t\trouteCache.Once.Do(func() {\n\t\t\trouteCache.ipv4, routeCache.ipv6 = probeRoutes()\n\t\t})\n\t\treturn routeCache.ipv4, routeCache.ipv6\n\t}\n\n\trouteCache.RWMutex.RLock()\n\tnow := time.Now()\n\tif routeCache.expire.After(now) {\n\t\trouteCache.RWMutex.RUnlock()\n\t\treturn routeCache.ipv4, routeCache.ipv6\n\t}\n\trouteCache.RWMutex.RUnlock()\n\n\trouteCache.RWMutex.Lock()\n\tdefer routeCache.RWMutex.Unlock()\n\n\tnow = time.Now()\n\tif routeCache.expire.After(now) { // double-check\n\t\treturn routeCache.ipv4, routeCache.ipv6\n\t}\n\trouteCache.ipv4, routeCache.ipv6 = probeRoutes()    // ~2ms\n\trouteCache.expire = now.Add(100 * time.Millisecond) // ttl\n\treturn routeCache.ipv4, routeCache.ipv6\n}\n\nvar isGUIPlatform = detectGUIPlatform()\n\nfunc detectGUIPlatform() bool {\n\tswitch runtime.GOOS {\n\tcase \"android\", \"ios\", \"windows\", \"darwin\":\n\t\treturn true\n\tcase \"linux\", \"freebsd\", \"openbsd\":\n\t\tif t := os.Getenv(\"XDG_SESSION_TYPE\"); t == \"wayland\" || t == \"x11\" {\n\t\t\treturn true\n\t\t}\n\t\tif os.Getenv(\"DISPLAY\") != \"\" || os.Getenv(\"WAYLAND_DISPLAY\") != \"\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "app/dns/dns_test.go",
    "content": "package dns_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t. \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\tfeature_dns \"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n)\n\ntype staticHandler struct{}\n\nfunc (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {\n\tans := new(dns.Msg)\n\tans.Id = r.Id\n\n\tvar clientIP net.IP\n\n\topt := r.IsEdns0()\n\tif opt != nil {\n\t\tfor _, o := range opt.Option {\n\t\t\tif o.Option() == dns.EDNS0SUBNET {\n\t\t\t\tsubnet := o.(*dns.EDNS0_SUBNET)\n\t\t\t\tclientIP = subnet.Address\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, q := range r.Question {\n\t\tswitch {\n\t\tcase q.Name == \"google.com.\" && q.Qtype == dns.TypeA:\n\t\t\tif clientIP == nil {\n\t\t\t\trr, _ := dns.NewRR(\"google.com. IN A 8.8.8.8\")\n\t\t\t\tans.Answer = append(ans.Answer, rr)\n\t\t\t} else {\n\t\t\t\trr, _ := dns.NewRR(\"google.com. IN A 8.8.4.4\")\n\t\t\t\tans.Answer = append(ans.Answer, rr)\n\t\t\t}\n\n\t\tcase q.Name == \"api.google.com.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"api.google.com. IN A 8.8.7.7\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"v2.api.google.com.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"v2.api.google.com. IN A 8.8.7.8\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"facebook.com.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"facebook.com. IN A 9.9.9.9\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"ipv6.google.com.\" && q.Qtype == dns.TypeA:\n\t\t\trr, err := dns.NewRR(\"ipv6.google.com. IN A 8.8.8.7\")\n\t\t\tcommon.Must(err)\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"ipv6.google.com.\" && q.Qtype == dns.TypeAAAA:\n\t\t\trr, err := dns.NewRR(\"ipv6.google.com. IN AAAA 2001:4860:4860::8888\")\n\t\t\tcommon.Must(err)\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"notexist.google.com.\" && q.Qtype == dns.TypeAAAA:\n\t\t\tans.MsgHdr.Rcode = dns.RcodeNameError\n\n\t\tcase q.Name == \"notexist.google.com.\" && q.Qtype == dns.TypeA:\n\t\t\tans.MsgHdr.Rcode = dns.RcodeNameError\n\n\t\tcase q.Name == \"hostname.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"hostname. IN A 127.0.0.1\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"hostname.local.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"hostname.local. IN A 127.0.0.1\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"hostname.localdomain.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"hostname.localdomain. IN A 127.0.0.1\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"localhost.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"localhost. IN A 127.0.0.2\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"localhost-a.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"localhost-a. IN A 127.0.0.3\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"localhost-b.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"localhost-b. IN A 127.0.0.4\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"Mijia\\\\ Cloud.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"Mijia\\\\ Cloud. IN A 127.0.0.1\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\t\t}\n\t}\n\tw.WriteMsg(ans)\n}\n\nfunc TestUDPServerSubnet(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tClientIp: []byte{7, 8, 9, 10},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\tips, _, err := client.LookupIP(\"google.com\", feature_dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t\tFakeEnable: false,\n\t})\n\tif err != nil {\n\t\tt.Fatal(\"unexpected error: \", err)\n\t}\n\n\tif r := cmp.Diff(ips, []net.IP{{8, 8, 4, 4}}); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n\nfunc TestUDPServer(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\t{\n\t\tips, _, err := client.LookupIP(\"google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{\n\t\tips, _, err := client.LookupIP(\"facebook.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{9, 9, 9, 9}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{\n\t\t_, _, err := client.LookupIP(\"notexist.google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err == nil {\n\t\t\tt.Fatal(\"nil error\")\n\t\t}\n\t\tif r := feature_dns.RCodeFromError(err); r != uint16(dns.RcodeNameError) {\n\t\t\tt.Fatal(\"expected NameError, but got \", r)\n\t\t}\n\t}\n\n\t{\n\t\tips, _, err := client.LookupIP(\"ipv4only.google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif !errors.AllEqual(feature_dns.ErrEmptyResponse, errors.Cause(err)) {\n\t\t\tt.Fatal(\"error: \", err)\n\t\t}\n\t\tif len(ips) != 0 {\n\t\t\tt.Fatal(\"ips: \", ips)\n\t\t}\n\t}\n\n\tdnsServer.Shutdown()\n\n\t{\n\t\tips, _, err := client.LookupIP(\"google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n}\n\nfunc TestPrioritizedDomain(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: 9999, /* unreachable */\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   DomainMatchingType_Full,\n\t\t\t\t\t\t\t\tDomain: \"google.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\tstartTime := time.Now()\n\n\t{\n\t\tips, _, err := client.LookupIP(\"google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\tendTime := time.Now()\n\tif startTime.After(endTime.Add(time.Second * 2)) {\n\t\tt.Error(\"DNS query doesn't finish in 2 seconds.\")\n\t}\n}\n\nfunc TestUDPServerIPv6(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\t{\n\t\tips, _, err := client.LookupIP(\"ipv6.google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{32, 1, 72, 96, 72, 96, 0, 0, 0, 0, 0, 0, 0, 0, 136, 136}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n}\n\nfunc TestStaticHostDomain(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tStaticHosts: []*Config_HostMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:          DomainMatchingType_Full,\n\t\t\t\t\t\tDomain:        \"example.com\",\n\t\t\t\t\t\tProxiedDomain: \"google.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\t{\n\t\tips, _, err := client.LookupIP(\"example.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\tdnsServer.Shutdown()\n}\n\nfunc TestIPMatch(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t// private dns, not match\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCountryCode: \"local\",\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t// inner ip, will not match\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{192, 168, 11, 1},\n\t\t\t\t\t\t\t\t\t\tPrefix: 32,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t// second dns, match ip\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCountryCode: \"test\",\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{8, 8, 8, 8},\n\t\t\t\t\t\t\t\t\t\tPrefix: 32,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCountryCode: \"test\",\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{8, 8, 8, 4},\n\t\t\t\t\t\t\t\t\t\tPrefix: 32,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\tstartTime := time.Now()\n\n\t{\n\t\tips, _, err := client.LookupIP(\"google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\tendTime := time.Now()\n\tif startTime.After(endTime.Add(time.Second * 2)) {\n\t\tt.Error(\"DNS query doesn't finish in 2 seconds.\")\n\t}\n}\n\nfunc TestLocalDomain(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: 9999, /* unreachable */\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t// Equivalent of dotless:localhost\n\t\t\t\t\t\t\t{Type: DomainMatchingType_Regex, Domain: \"^[^.]*localhost[^.]*$\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{ // Will match localhost, localhost-a and localhost-b,\n\t\t\t\t\t\t\t\tCountryCode: \"local\",\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{Ip: []byte{127, 0, 0, 2}, Prefix: 32},\n\t\t\t\t\t\t\t\t\t{Ip: []byte{127, 0, 0, 3}, Prefix: 32},\n\t\t\t\t\t\t\t\t\t{Ip: []byte{127, 0, 0, 4}, Prefix: 32},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t// Equivalent of dotless: and domain:local\n\t\t\t\t\t\t\t{Type: DomainMatchingType_Regex, Domain: \"^[^.]*$\"},\n\t\t\t\t\t\t\t{Type: DomainMatchingType_Subdomain, Domain: \"local\"},\n\t\t\t\t\t\t\t{Type: DomainMatchingType_Subdomain, Domain: \"localdomain\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tStaticHosts: []*Config_HostMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   DomainMatchingType_Full,\n\t\t\t\t\t\tDomain: \"hostnamestatic\",\n\t\t\t\t\t\tIp:     [][]byte{{127, 0, 0, 53}},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:          DomainMatchingType_Full,\n\t\t\t\t\t\tDomain:        \"hostnamealias\",\n\t\t\t\t\t\tProxiedDomain: \"hostname.localdomain\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\tstartTime := time.Now()\n\n\t{ // Will match dotless:\n\t\tips, _, err := client.LookupIP(\"hostname\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match domain:local\n\t\tips, _, err := client.LookupIP(\"hostname.local\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match static ip\n\t\tips, _, err := client.LookupIP(\"hostnamestatic\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 53}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match domain replacing\n\t\tips, _, err := client.LookupIP(\"hostnamealias\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match dotless:localhost, but not expectedIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:\n\t\tips, _, err := client.LookupIP(\"localhost\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 2}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3\n\t\tips, _, err := client.LookupIP(\"localhost-a\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 3}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3\n\t\tips, _, err := client.LookupIP(\"localhost-b\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 4}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match dotless:\n\t\tips, _, err := client.LookupIP(\"Mijia Cloud\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\tendTime := time.Now()\n\tif startTime.After(endTime.Add(time.Second * 2)) {\n\t\tt.Error(\"DNS query doesn't finish in 2 seconds.\")\n\t}\n}\n\nfunc TestMultiMatchPrioritizedDomain(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&Config{\n\t\t\t\tNameServer: []*NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: 9999, /* unreachable */\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   DomainMatchingType_Subdomain,\n\t\t\t\t\t\t\t\tDomain: \"google.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{ // Will only match 8.8.8.8 and 8.8.4.4\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{Ip: []byte{8, 8, 8, 8}, Prefix: 32},\n\t\t\t\t\t\t\t\t\t{Ip: []byte{8, 8, 4, 4}, Prefix: 32},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   DomainMatchingType_Subdomain,\n\t\t\t\t\t\t\t\tDomain: \"google.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{ // Will match 8.8.8.8 and 8.8.8.7, etc\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{Ip: []byte{8, 8, 8, 7}, Prefix: 24},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   DomainMatchingType_Subdomain,\n\t\t\t\t\t\t\t\tDomain: \"api.google.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{ // Will only match 8.8.7.7 (api.google.com)\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{Ip: []byte{8, 8, 7, 7}, Prefix: 32},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPrioritizedDomain: []*NameServer_PriorityDomain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   DomainMatchingType_Full,\n\t\t\t\t\t\t\t\tDomain: \"v2.api.google.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpectedGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{ // Will only match 8.8.7.8 (v2.api.google.com)\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{Ip: []byte{8, 8, 7, 8}, Prefix: 32},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\n\tclient := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)\n\n\tstartTime := time.Now()\n\n\t{ // Will match server 1,2 and server 1 returns expected ip\n\t\tips, _, err := client.LookupIP(\"google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one\n\t\tips, _, err := client.LookupIP(\"ipv6.google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: false,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 8, 7}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match server 3,1,2 and server 3 returns expected one\n\t\tips, _, err := client.LookupIP(\"api.google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 7, 7}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\t{ // Will match server 4,3,1,2 and server 4 returns expected one\n\t\tips, _, err := client.LookupIP(\"v2.api.google.com\", feature_dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\n\t\tif r := cmp.Diff(ips, []net.IP{{8, 8, 7, 8}}); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n\n\tendTime := time.Now()\n\tif startTime.After(endTime.Add(time.Second * 2)) {\n\t\tt.Error(\"DNS query doesn't finish in 2 seconds.\")\n\t}\n}\n"
  },
  {
    "path": "app/dns/dnscommon.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"math\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\n\t\"golang.org/x/net/dns/dnsmessage\"\n)\n\n// Fqdn normalizes domain make sure it ends with '.'\n// case-sensitive\nfunc Fqdn(domain string) string {\n\tif len(domain) > 0 && strings.HasSuffix(domain, \".\") {\n\t\treturn domain\n\t}\n\treturn domain + \".\"\n}\n\ntype record struct {\n\tA    *IPRecord\n\tAAAA *IPRecord\n}\n\n// IPRecord is a cacheable item for a resolved domain\ntype IPRecord struct {\n\tReqID     uint16\n\tIP        []net.IP\n\tExpire    time.Time\n\tRCode     dnsmessage.RCode\n\tRawHeader *dnsmessage.Header\n}\n\nfunc (r *IPRecord) getIPs() ([]net.IP, int32, error) {\n\tif r == nil {\n\t\treturn nil, 0, errRecordNotFound\n\t}\n\n\tuntilExpire := time.Until(r.Expire).Seconds()\n\tttl := int32(math.Ceil(untilExpire))\n\n\tif r.RCode != dnsmessage.RCodeSuccess {\n\t\treturn nil, ttl, dns_feature.RCodeError(r.RCode)\n\t}\n\tif len(r.IP) == 0 {\n\t\treturn nil, ttl, dns_feature.ErrEmptyResponse\n\t}\n\n\treturn r.IP, ttl, nil\n}\n\nvar errRecordNotFound = errors.New(\"record not found\")\n\ntype dnsRequest struct {\n\treqType dnsmessage.Type\n\tdomain  string\n\tstart   time.Time\n\texpire  time.Time\n\tmsg     *dnsmessage.Message\n}\n\nfunc genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {\n\tif len(clientIP) == 0 && padding == 0 {\n\t\treturn nil\n\t}\n\n\tconst EDNS0SUBNET = 0x8\n\tconst EDNS0PADDING = 0xc\n\n\topt := new(dnsmessage.Resource)\n\tcommon.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))\n\tbody := dnsmessage.OPTResource{}\n\topt.Body = &body\n\n\tif len(clientIP) != 0 {\n\t\tvar netmask int\n\t\tvar family uint16\n\n\t\tif len(clientIP) == 4 {\n\t\t\tfamily = 1\n\t\t\tnetmask = 24 // 24 for IPV4, 96 for IPv6\n\t\t} else {\n\t\t\tfamily = 2\n\t\t\tnetmask = 96\n\t\t}\n\n\t\tb := make([]byte, 4)\n\t\tbinary.BigEndian.PutUint16(b[0:], family)\n\t\tb[2] = byte(netmask)\n\t\tb[3] = 0\n\t\tswitch family {\n\t\tcase 1:\n\t\t\tip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8))\n\t\t\tneedLength := (netmask + 8 - 1) / 8 // division rounding up\n\t\t\tb = append(b, ip[:needLength]...)\n\t\tcase 2:\n\t\t\tip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8))\n\t\t\tneedLength := (netmask + 8 - 1) / 8 // division rounding up\n\t\t\tb = append(b, ip[:needLength]...)\n\t\t}\n\n\t\tbody.Options = append(body.Options,\n\t\t\tdnsmessage.Option{\n\t\t\t\tCode: EDNS0SUBNET,\n\t\t\t\tData: b,\n\t\t\t})\n\t}\n\n\tif padding != 0 {\n\t\tbody.Options = append(body.Options,\n\t\t\tdnsmessage.Option{\n\t\t\t\tCode: EDNS0PADDING,\n\t\t\t\tData: make([]byte, padding),\n\t\t\t})\n\t}\n\n\treturn opt\n}\n\nfunc buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {\n\tqA := dnsmessage.Question{\n\t\tName:  dnsmessage.MustNewName(domain),\n\t\tType:  dnsmessage.TypeA,\n\t\tClass: dnsmessage.ClassINET,\n\t}\n\n\tqAAAA := dnsmessage.Question{\n\t\tName:  dnsmessage.MustNewName(domain),\n\t\tType:  dnsmessage.TypeAAAA,\n\t\tClass: dnsmessage.ClassINET,\n\t}\n\n\tvar reqs []*dnsRequest\n\tnow := time.Now()\n\n\tif option.IPv4Enable {\n\t\tmsg := new(dnsmessage.Message)\n\t\tmsg.Header.ID = reqIDGen()\n\t\tmsg.Header.RecursionDesired = true\n\t\tmsg.Questions = []dnsmessage.Question{qA}\n\t\tif reqOpts != nil {\n\t\t\tmsg.Additionals = append(msg.Additionals, *reqOpts)\n\t\t}\n\t\treqs = append(reqs, &dnsRequest{\n\t\t\treqType: dnsmessage.TypeA,\n\t\t\tdomain:  domain,\n\t\t\tstart:   now,\n\t\t\tmsg:     msg,\n\t\t})\n\t}\n\n\tif option.IPv6Enable {\n\t\tmsg := new(dnsmessage.Message)\n\t\tmsg.Header.ID = reqIDGen()\n\t\tmsg.Header.RecursionDesired = true\n\t\tmsg.Questions = []dnsmessage.Question{qAAAA}\n\t\tif reqOpts != nil {\n\t\t\tmsg.Additionals = append(msg.Additionals, *reqOpts)\n\t\t}\n\t\treqs = append(reqs, &dnsRequest{\n\t\t\treqType: dnsmessage.TypeAAAA,\n\t\t\tdomain:  domain,\n\t\t\tstart:   now,\n\t\t\tmsg:     msg,\n\t\t})\n\t}\n\n\treturn reqs\n}\n\n// parseResponse parses DNS answers from the returned payload\nfunc parseResponse(payload []byte) (*IPRecord, error) {\n\tvar parser dnsmessage.Parser\n\th, err := parser.Start(payload)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse DNS response\").Base(err).AtWarning()\n\t}\n\tif err := parser.SkipAllQuestions(); err != nil {\n\t\treturn nil, errors.New(\"failed to skip questions in DNS response\").Base(err).AtWarning()\n\t}\n\n\tnow := time.Now()\n\tipRecord := &IPRecord{\n\t\tReqID:     h.ID,\n\t\tRCode:     h.RCode,\n\t\tExpire:    now.Add(time.Second * dns_feature.DefaultTTL),\n\t\tRawHeader: &h,\n\t}\n\nL:\n\tfor {\n\t\tah, err := parser.AnswerHeader()\n\t\tif err != nil {\n\t\t\tif err != dnsmessage.ErrSectionDone {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to parse answer section for domain: \", ah.Name.String())\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tttl := ah.TTL\n\t\tif ttl == 0 {\n\t\t\tttl = 1\n\t\t}\n\t\texpire := now.Add(time.Duration(ttl) * time.Second)\n\t\tif ipRecord.Expire.After(expire) {\n\t\t\tipRecord.Expire = expire\n\t\t}\n\n\t\tswitch ah.Type {\n\t\tcase dnsmessage.TypeA:\n\t\t\tans, err := parser.AResource()\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to parse A record for domain: \", ah.Name)\n\t\t\t\tbreak L\n\t\t\t}\n\t\t\tipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]).IP())\n\t\tcase dnsmessage.TypeAAAA:\n\t\t\tans, err := parser.AAAAResource()\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to parse AAAA record for domain: \", ah.Name)\n\t\t\t\tbreak L\n\t\t\t}\n\t\t\tnewIP := net.IPAddress(ans.AAAA[:]).IP()\n\t\t\tif len(newIP) == net.IPv6len {\n\t\t\t\tipRecord.IP = append(ipRecord.IP, newIP)\n\t\t\t}\n\t\tdefault:\n\t\t\tif err := parser.SkipAnswer(); err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to skip answer\")\n\t\t\t\tbreak L\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn ipRecord, nil\n}\n\n// toDnsContext create a new background context with parent inbound, session and dns log\nfunc toDnsContext(ctx context.Context, addr string) context.Context {\n\tdnsCtx := core.ToBackgroundDetachedContext(ctx)\n\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\tdnsCtx = session.ContextWithInbound(dnsCtx, inbound)\n\t}\n\tdnsCtx = session.ContextWithContent(dnsCtx, session.ContentFromContext(ctx))\n\tdnsCtx = log.ContextWithAccessMessage(dnsCtx, &log.AccessMessage{\n\t\tFrom:   \"DNS\",\n\t\tTo:     addr,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t})\n\treturn dnsCtx\n}\n"
  },
  {
    "path": "app/dns/dnscommon_test.go",
    "content": "package dns\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\t\"golang.org/x/net/dns/dnsmessage\"\n)\n\nfunc Test_parseResponse(t *testing.T) {\n\tvar p [][]byte\n\n\tans := new(dns.Msg)\n\tans.Id = 0\n\tp = append(p, common.Must2(ans.Pack()))\n\n\tp = append(p, []byte{})\n\n\tans = new(dns.Msg)\n\tans.Id = 1\n\tans.Answer = append(ans.Answer,\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN CNAME m.test.google.com\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN CNAME fake.google.com\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN A 8.8.8.8\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN A 8.8.4.4\")),\n\t)\n\tp = append(p, common.Must2(ans.Pack()))\n\n\tans = new(dns.Msg)\n\tans.Id = 2\n\tans.Answer = append(ans.Answer,\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN CNAME m.test.google.com\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN CNAME fake.google.com\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN CNAME m.test.google.com\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN CNAME test.google.com\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN AAAA 2001:4860:4860::8888\")),\n\t\tcommon.Must2(dns.NewRR(\"google.com. IN AAAA 2001:4860:4860::8844\")),\n\t)\n\tp = append(p, common.Must2(ans.Pack()))\n\n\ttests := []struct {\n\t\tname    string\n\t\twant    *IPRecord\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\t&IPRecord{0, []net.IP(nil), time.Time{}, dnsmessage.RCodeSuccess, nil},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"error\",\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"a record\",\n\t\t\t&IPRecord{\n\t\t\t\t1,\n\t\t\t\t[]net.IP{net.ParseIP(\"8.8.8.8\"), net.ParseIP(\"8.8.4.4\")},\n\t\t\t\ttime.Time{},\n\t\t\t\tdnsmessage.RCodeSuccess,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"aaaa record\",\n\t\t\t&IPRecord{2, []net.IP{net.ParseIP(\"2001:4860:4860::8888\"), net.ParseIP(\"2001:4860:4860::8844\")}, time.Time{}, dnsmessage.RCodeSuccess, nil},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor i, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := parseResponse(p[i])\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"handleResponse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got != nil {\n\t\t\t\t// reset the time and RawHeader\n\t\t\t\tgot.Expire = time.Time{}\n\t\t\t\tgot.RawHeader = nil\n\t\t\t}\n\t\t\tif cmp.Diff(got, tt.want) != \"\" {\n\t\t\t\tt.Error(cmp.Diff(got, tt.want))\n\t\t\t\t// t.Errorf(\"handleResponse() = %#v, want %#v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_buildReqMsgs(t *testing.T) {\n\tstubID := func() uint16 {\n\t\treturn uint16(rand.Uint32())\n\t}\n\ttype args struct {\n\t\tdomain  string\n\t\toption  dns_feature.IPOption\n\t\treqOpts *dnsmessage.Resource\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant int\n\t}{\n\t\t{\"dual stack\", args{\"test.com\", dns_feature.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t}, nil}, 2},\n\t\t{\"ipv4 only\", args{\"test.com\", dns_feature.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: false,\n\t\t\tFakeEnable: false,\n\t\t}, nil}, 1},\n\t\t{\"ipv6 only\", args{\"test.com\", dns_feature.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t}, nil}, 1},\n\t\t{\"none/error\", args{\"test.com\", dns_feature.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: false,\n\t\t\tFakeEnable: false,\n\t\t}, nil}, 0},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {\n\t\t\t\tt.Errorf(\"buildReqMsgs() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_genEDNS0Options(t *testing.T) {\n\ttype args struct {\n\t\tclientIP net.IP\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant *dnsmessage.Resource\n\t}{\n\t\t// TODO: Add test cases.\n\t\t{\"ipv4\", args{net.ParseIP(\"4.3.2.1\")}, nil},\n\t\t{\"ipv6\", args{net.ParseIP(\"2001::4321\")}, nil},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := genEDNS0Options(tt.args.clientIP, 0); got == nil {\n\t\t\t\tt.Errorf(\"genEDNS0Options() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFqdn(t *testing.T) {\n\ttype args struct {\n\t\tdomain string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant string\n\t}{\n\t\t{\"with fqdn\", args{\"www.example.com.\"}, \"www.example.com.\"},\n\t\t{\"without fqdn\", args{\"www.example.com\"}, \"www.example.com.\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := Fqdn(tt.args.domain); got != tt.want {\n\t\t\t\tt.Errorf(\"Fqdn() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "app/dns/fakedns/fake.go",
    "content": "package fakedns\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"math/big\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/cache\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\ntype Holder struct {\n\tdomainToIP cache.Lru\n\tipRange    *net.IPNet\n\tmu         *sync.Mutex\n\n\tconfig *FakeDnsPool\n}\n\nfunc (fkdns *Holder) IsIPInIPPool(ip net.Address) bool {\n\tif ip.Family().IsDomain() {\n\t\treturn false\n\t}\n\treturn fkdns.ipRange.Contains(ip.IP())\n}\n\nfunc (fkdns *Holder) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {\n\tisIPv6 := fkdns.ipRange.IP.To4() == nil\n\tif (isIPv6 && ipv6) || (!isIPv6 && ipv4) {\n\t\treturn fkdns.GetFakeIPForDomain(domain)\n\t}\n\treturn []net.Address{}\n}\n\nfunc (*Holder) Type() interface{} {\n\treturn (*dns.FakeDNSEngine)(nil)\n}\n\nfunc (fkdns *Holder) Start() error {\n\tif fkdns.config != nil && fkdns.config.IpPool != \"\" && fkdns.config.LruSize != 0 {\n\t\treturn fkdns.initializeFromConfig()\n\t}\n\treturn errors.New(\"invalid fakeDNS setting\")\n}\n\nfunc (fkdns *Holder) Close() error {\n\tfkdns.domainToIP = nil\n\tfkdns.ipRange = nil\n\tfkdns.mu = nil\n\treturn nil\n}\n\nfunc NewFakeDNSHolder() (*Holder, error) {\n\tvar fkdns *Holder\n\tvar err error\n\n\tif fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {\n\t\treturn nil, errors.New(\"Unable to create Fake Dns Engine\").Base(err).AtError()\n\t}\n\terr = fkdns.initialize(dns.FakeIPv4Pool, 65535)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fkdns, nil\n}\n\nfunc NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {\n\treturn &Holder{nil, nil, nil, conf}, nil\n}\n\nfunc (fkdns *Holder) initializeFromConfig() error {\n\treturn fkdns.initialize(fkdns.config.IpPool, int(fkdns.config.LruSize))\n}\n\nfunc (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {\n\tvar ipRange *net.IPNet\n\tvar err error\n\n\tif _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {\n\t\treturn errors.New(\"Unable to parse CIDR for Fake DNS IP assignment\").Base(err).AtError()\n\t}\n\n\tones, bits := ipRange.Mask.Size()\n\trooms := bits - ones\n\tif math.Log2(float64(lruSize)) >= float64(rooms) {\n\t\treturn errors.New(\"LRU size is bigger than subnet size\").AtError()\n\t}\n\tfkdns.domainToIP = cache.NewLru(lruSize)\n\tfkdns.ipRange = ipRange\n\tfkdns.mu = new(sync.Mutex)\n\treturn nil\n}\n\n// GetFakeIPForDomain checks and generates a fake IP for a domain name\nfunc (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {\n\tfkdns.mu.Lock()\n\tdefer fkdns.mu.Unlock()\n\tif v, ok := fkdns.domainToIP.Get(domain); ok {\n\t\treturn []net.Address{v.(net.Address)}\n\t}\n\tcurrentTimeMillis := uint64(time.Now().UnixNano() / 1e6)\n\tones, bits := fkdns.ipRange.Mask.Size()\n\trooms := bits - ones\n\tif rooms < 64 {\n\t\tcurrentTimeMillis %= (uint64(1) << rooms)\n\t}\n\tbigIntIP := big.NewInt(0).SetBytes(fkdns.ipRange.IP)\n\tbigIntIP = bigIntIP.Add(bigIntIP, new(big.Int).SetUint64(currentTimeMillis))\n\tvar ip net.Address\n\tfor {\n\t\tip = net.IPAddress(bigIntIP.Bytes())\n\n\t\t// if we run for a long time, we may go back to beginning and start seeing the IP in use\n\t\tif _, ok := fkdns.domainToIP.PeekKeyFromValue(ip); !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tbigIntIP = bigIntIP.Add(bigIntIP, big.NewInt(1))\n\t\tif !fkdns.ipRange.Contains(bigIntIP.Bytes()) {\n\t\t\tbigIntIP = big.NewInt(0).SetBytes(fkdns.ipRange.IP)\n\t\t}\n\t}\n\tfkdns.domainToIP.Put(domain, ip)\n\treturn []net.Address{ip}\n}\n\n// GetDomainFromFakeDNS checks if an IP is a fake IP and have corresponding domain name\nfunc (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string {\n\tif !ip.Family().IsIP() || !fkdns.ipRange.Contains(ip.IP()) {\n\t\treturn \"\"\n\t}\n\tif k, ok := fkdns.domainToIP.GetKeyFromValue(ip); ok {\n\t\treturn k.(string)\n\t}\n\terrors.LogInfo(context.Background(), \"A fake ip request to \", ip, \", however there is no matching domain name in fake DNS\")\n\treturn \"\"\n}\n\ntype HolderMulti struct {\n\tholders []*Holder\n\n\tconfig *FakeDnsPoolMulti\n}\n\nfunc (h *HolderMulti) IsIPInIPPool(ip net.Address) bool {\n\tif ip.Family().IsDomain() {\n\t\treturn false\n\t}\n\tfor _, v := range h.holders {\n\t\tif v.IsIPInIPPool(ip) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (h *HolderMulti) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {\n\tvar ret []net.Address\n\tfor _, v := range h.holders {\n\t\tret = append(ret, v.GetFakeIPForDomain3(domain, ipv4, ipv6)...)\n\t}\n\treturn ret\n}\n\nfunc (h *HolderMulti) GetFakeIPForDomain(domain string) []net.Address {\n\tvar ret []net.Address\n\tfor _, v := range h.holders {\n\t\tret = append(ret, v.GetFakeIPForDomain(domain)...)\n\t}\n\treturn ret\n}\n\nfunc (h *HolderMulti) GetDomainFromFakeDNS(ip net.Address) string {\n\tfor _, v := range h.holders {\n\t\tif domain := v.GetDomainFromFakeDNS(ip); domain != \"\" {\n\t\t\treturn domain\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (h *HolderMulti) Type() interface{} {\n\treturn (*dns.FakeDNSEngine)(nil)\n}\n\nfunc (h *HolderMulti) Start() error {\n\tfor _, v := range h.holders {\n\t\tif v.config != nil && v.config.IpPool != \"\" && v.config.LruSize != 0 {\n\t\t\tif err := v.Start(); err != nil {\n\t\t\t\treturn errors.New(\"Cannot start all fake dns pools\").Base(err)\n\t\t\t}\n\t\t} else {\n\t\t\treturn errors.New(\"invalid fakeDNS setting\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *HolderMulti) Close() error {\n\tfor _, v := range h.holders {\n\t\tif err := v.Close(); err != nil {\n\t\t\treturn errors.New(\"Cannot close all fake dns pools\").Base(err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *HolderMulti) createHolderGroups() error {\n\tfor _, v := range h.config.Pools {\n\t\tholder, err := NewFakeDNSHolderConfigOnly(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.holders = append(h.holders, holder)\n\t}\n\treturn nil\n}\n\nfunc NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {\n\tholderMulti := &HolderMulti{nil, conf}\n\tif err := holderMulti.createHolderGroups(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn holderMulti, nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*FakeDnsPool)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tvar f *Holder\n\t\tvar err error\n\t\tif f, err = NewFakeDNSHolderConfigOnly(config.(*FakeDnsPool)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn f, nil\n\t}))\n\n\tcommon.Must(common.RegisterConfig((*FakeDnsPoolMulti)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tvar f *HolderMulti\n\t\tvar err error\n\t\tif f, err = NewFakeDNSHolderMulti(config.(*FakeDnsPoolMulti)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn f, nil\n\t}))\n}\n"
  },
  {
    "path": "app/dns/fakedns/fakedns.go",
    "content": "package fakedns\n"
  },
  {
    "path": "app/dns/fakedns/fakedns.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/dns/fakedns/fakedns.proto\n\npackage fakedns\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype FakeDnsPool struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIpPool        string                 `protobuf:\"bytes,1,opt,name=ip_pool,json=ipPool,proto3\" json:\"ip_pool,omitempty\"` //CIDR of IP pool used as fake DNS IP\n\tLruSize       int64                  `protobuf:\"varint,2,opt,name=lruSize,proto3\" json:\"lruSize,omitempty\"`            //Size of Pool for remembering relationship between domain name and IP address\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *FakeDnsPool) Reset() {\n\t*x = FakeDnsPool{}\n\tmi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *FakeDnsPool) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FakeDnsPool) ProtoMessage() {}\n\nfunc (x *FakeDnsPool) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_fakedns_fakedns_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FakeDnsPool.ProtoReflect.Descriptor instead.\nfunc (*FakeDnsPool) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *FakeDnsPool) GetIpPool() string {\n\tif x != nil {\n\t\treturn x.IpPool\n\t}\n\treturn \"\"\n}\n\nfunc (x *FakeDnsPool) GetLruSize() int64 {\n\tif x != nil {\n\t\treturn x.LruSize\n\t}\n\treturn 0\n}\n\ntype FakeDnsPoolMulti struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPools         []*FakeDnsPool         `protobuf:\"bytes,1,rep,name=pools,proto3\" json:\"pools,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *FakeDnsPoolMulti) Reset() {\n\t*x = FakeDnsPoolMulti{}\n\tmi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *FakeDnsPoolMulti) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FakeDnsPoolMulti) ProtoMessage() {}\n\nfunc (x *FakeDnsPoolMulti) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FakeDnsPoolMulti.ProtoReflect.Descriptor instead.\nfunc (*FakeDnsPoolMulti) Descriptor() ([]byte, []int) {\n\treturn file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *FakeDnsPoolMulti) GetPools() []*FakeDnsPool {\n\tif x != nil {\n\t\treturn x.Pools\n\t}\n\treturn nil\n}\n\nvar File_app_dns_fakedns_fakedns_proto protoreflect.FileDescriptor\n\nconst file_app_dns_fakedns_fakedns_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1dapp/dns/fakedns/fakedns.proto\\x12\\x14xray.app.dns.fakedns\\\"@\\n\" +\n\t\"\\vFakeDnsPool\\x12\\x17\\n\" +\n\t\"\\aip_pool\\x18\\x01 \\x01(\\tR\\x06ipPool\\x12\\x18\\n\" +\n\t\"\\alruSize\\x18\\x02 \\x01(\\x03R\\alruSize\\\"K\\n\" +\n\t\"\\x10FakeDnsPoolMulti\\x127\\n\" +\n\t\"\\x05pools\\x18\\x01 \\x03(\\v2!.xray.app.dns.fakedns.FakeDnsPoolR\\x05poolsB^\\n\" +\n\t\"\\x18com.xray.app.dns.fakednsP\\x01Z)github.com/xtls/xray-core/app/dns/fakedns\\xaa\\x02\\x14Xray.App.Dns.Fakednsb\\x06proto3\"\n\nvar (\n\tfile_app_dns_fakedns_fakedns_proto_rawDescOnce sync.Once\n\tfile_app_dns_fakedns_fakedns_proto_rawDescData []byte\n)\n\nfunc file_app_dns_fakedns_fakedns_proto_rawDescGZIP() []byte {\n\tfile_app_dns_fakedns_fakedns_proto_rawDescOnce.Do(func() {\n\t\tfile_app_dns_fakedns_fakedns_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_dns_fakedns_fakedns_proto_rawDesc), len(file_app_dns_fakedns_fakedns_proto_rawDesc)))\n\t})\n\treturn file_app_dns_fakedns_fakedns_proto_rawDescData\n}\n\nvar file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_app_dns_fakedns_fakedns_proto_goTypes = []any{\n\t(*FakeDnsPool)(nil),      // 0: xray.app.dns.fakedns.FakeDnsPool\n\t(*FakeDnsPoolMulti)(nil), // 1: xray.app.dns.fakedns.FakeDnsPoolMulti\n}\nvar file_app_dns_fakedns_fakedns_proto_depIdxs = []int32{\n\t0, // 0: xray.app.dns.fakedns.FakeDnsPoolMulti.pools:type_name -> xray.app.dns.fakedns.FakeDnsPool\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_app_dns_fakedns_fakedns_proto_init() }\nfunc file_app_dns_fakedns_fakedns_proto_init() {\n\tif File_app_dns_fakedns_fakedns_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dns_fakedns_fakedns_proto_rawDesc), len(file_app_dns_fakedns_fakedns_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_dns_fakedns_fakedns_proto_goTypes,\n\t\tDependencyIndexes: file_app_dns_fakedns_fakedns_proto_depIdxs,\n\t\tMessageInfos:      file_app_dns_fakedns_fakedns_proto_msgTypes,\n\t}.Build()\n\tFile_app_dns_fakedns_fakedns_proto = out.File\n\tfile_app_dns_fakedns_fakedns_proto_goTypes = nil\n\tfile_app_dns_fakedns_fakedns_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/dns/fakedns/fakedns.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.dns.fakedns;\noption csharp_namespace = \"Xray.App.Dns.Fakedns\";\noption go_package = \"github.com/xtls/xray-core/app/dns/fakedns\";\noption java_package = \"com.xray.app.dns.fakedns\";\noption java_multiple_files = true;\n\nmessage FakeDnsPool{\n  string ip_pool = 1; //CIDR of IP pool used as fake DNS IP\n  int64  lruSize = 2; //Size of Pool for remembering relationship between domain name and IP address\n}\n\nmessage FakeDnsPoolMulti{\n  repeated FakeDnsPool pools = 1;\n}"
  },
  {
    "path": "app/dns/fakedns/fakedns_test.go",
    "content": "package fakedns\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nvar ipPrefix = \"198.1\"\n\nfunc TestNewFakeDnsHolder(_ *testing.T) {\n\t_, err := NewFakeDNSHolder()\n\tcommon.Must(err)\n}\n\nfunc TestFakeDnsHolderCreateMapping(t *testing.T) {\n\tfkdns, err := NewFakeDNSHolder()\n\tcommon.Must(err)\n\n\taddr := fkdns.GetFakeIPForDomain(\"fakednstest.example.com\")\n\tassert.Equal(t, ipPrefix, addr[0].IP().String()[0:len(ipPrefix)])\n}\n\nfunc TestFakeDnsHolderCreateMappingMany(t *testing.T) {\n\tfkdns, err := NewFakeDNSHolder()\n\tcommon.Must(err)\n\n\taddr := fkdns.GetFakeIPForDomain(\"fakednstest.example.com\")\n\tassert.Equal(t, ipPrefix, addr[0].IP().String()[0:len(ipPrefix)])\n\n\taddr2 := fkdns.GetFakeIPForDomain(\"fakednstest2.example.com\")\n\tassert.Equal(t, ipPrefix, addr2[0].IP().String()[0:len(ipPrefix)])\n\tassert.NotEqual(t, addr[0].IP().String(), addr2[0].IP().String())\n}\n\nfunc TestFakeDnsHolderCreateMappingManyAndResolve(t *testing.T) {\n\tfkdns, err := NewFakeDNSHolder()\n\tcommon.Must(err)\n\n\taddr := fkdns.GetFakeIPForDomain(\"fakednstest.example.com\")\n\taddr2 := fkdns.GetFakeIPForDomain(\"fakednstest2.example.com\")\n\n\t{\n\t\tresult := fkdns.GetDomainFromFakeDNS(addr[0])\n\t\tassert.Equal(t, \"fakednstest.example.com\", result)\n\t}\n\n\t{\n\t\tresult := fkdns.GetDomainFromFakeDNS(addr2[0])\n\t\tassert.Equal(t, \"fakednstest2.example.com\", result)\n\t}\n}\n\nfunc TestFakeDnsHolderCreateMappingManySingleDomain(t *testing.T) {\n\tfkdns, err := NewFakeDNSHolder()\n\tcommon.Must(err)\n\n\taddr := fkdns.GetFakeIPForDomain(\"fakednstest.example.com\")\n\taddr2 := fkdns.GetFakeIPForDomain(\"fakednstest.example.com\")\n\tassert.Equal(t, addr[0].IP().String(), addr2[0].IP().String())\n}\n\nfunc TestGetFakeIPForDomainConcurrently(t *testing.T) {\n\tfkdns, err := NewFakeDNSHolder()\n\tcommon.Must(err)\n\n\ttotal := 200\n\taddr := make([][]net.Address, total)\n\tvar errg errgroup.Group\n\tfor i := 0; i < total; i++ {\n\t\terrg.Go(testGetFakeIP(i, addr, fkdns))\n\t}\n\terrg.Wait()\n\tfor i := 0; i < total; i++ {\n\t\tfor j := i + 1; j < total; j++ {\n\t\t\tassert.NotEqual(t, addr[i][0].IP().String(), addr[j][0].IP().String())\n\t\t}\n\t}\n}\n\nfunc testGetFakeIP(index int, addr [][]net.Address, fkdns *Holder) func() error {\n\treturn func() error {\n\t\taddr[index] = fkdns.GetFakeIPForDomain(\"fakednstest\" + strconv.Itoa(index) + \".example.com\")\n\t\treturn nil\n\t}\n}\n\nfunc TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {\n\tfkdns, err := NewFakeDNSHolderConfigOnly(&FakeDnsPool{\n\t\tIpPool:  dns.FakeIPv4Pool,\n\t\tLruSize: 256,\n\t})\n\tcommon.Must(err)\n\n\terr = fkdns.Start()\n\n\tcommon.Must(err)\n\n\taddr := fkdns.GetFakeIPForDomain(\"fakednstest.example.com\")\n\taddr2 := fkdns.GetFakeIPForDomain(\"fakednstest2.example.com\")\n\n\tfor i := 0; i <= 8192; i++ {\n\t\t{\n\t\t\tresult := fkdns.GetDomainFromFakeDNS(addr[0])\n\t\t\tassert.Equal(t, \"fakednstest.example.com\", result)\n\t\t}\n\n\t\t{\n\t\t\tresult := fkdns.GetDomainFromFakeDNS(addr2[0])\n\t\t\tassert.Equal(t, \"fakednstest2.example.com\", result)\n\t\t}\n\n\t\t{\n\t\t\tuuid := uuid.New()\n\t\t\tdomain := uuid.String() + \".fakednstest.example.com\"\n\t\t\ttempAddr := fkdns.GetFakeIPForDomain(domain)\n\t\t\trsaddr := tempAddr[0].IP().String()\n\n\t\t\tresult := fkdns.GetDomainFromFakeDNS(net.ParseAddress(rsaddr))\n\t\t\tassert.Equal(t, domain, result)\n\t\t}\n\t}\n}\n\nfunc TestFakeDNSMulti(t *testing.T) {\n\tfakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{\n\t\tPools: []*FakeDnsPool{{\n\t\t\tIpPool:  \"240.0.0.0/12\",\n\t\t\tLruSize: 256,\n\t\t}, {\n\t\t\tIpPool:  \"fddd:c5b4:ff5f:f4f0::/64\",\n\t\t\tLruSize: 256,\n\t\t}},\n\t},\n\t)\n\tcommon.Must(err)\n\n\terr = fakeMulti.Start()\n\n\tcommon.Must(err)\n\n\tassert.Nil(t, err, \"Should not throw error\")\n\t_ = fakeMulti\n\n\tt.Run(\"checkInRange\", func(t *testing.T) {\n\t\tt.Run(\"ipv4\", func(t *testing.T) {\n\t\t\tinPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{240, 0, 0, 5}))\n\t\t\tassert.True(t, inPool)\n\t\t})\n\t\tt.Run(\"ipv6\", func(t *testing.T) {\n\t\t\tip, err := net.ResolveIPAddr(\"ip\", \"fddd:c5b4:ff5f:f4f0::5\")\n\t\t\tassert.Nil(t, err)\n\t\t\tinPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))\n\t\t\tassert.True(t, inPool)\n\t\t})\n\t\tt.Run(\"ipv4_inverse\", func(t *testing.T) {\n\t\t\tinPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{241, 0, 0, 5}))\n\t\t\tassert.False(t, inPool)\n\t\t})\n\t\tt.Run(\"ipv6_inverse\", func(t *testing.T) {\n\t\t\tip, err := net.ResolveIPAddr(\"ip\", \"fcdd:c5b4:ff5f:f4f0::5\")\n\t\t\tassert.Nil(t, err)\n\t\t\tinPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))\n\t\t\tassert.False(t, inPool)\n\t\t})\n\t})\n\n\tt.Run(\"allocateTwoAddressForTwoPool\", func(t *testing.T) {\n\t\taddress := fakeMulti.GetFakeIPForDomain(\"fakednstest.example.com\")\n\t\tassert.Len(t, address, 2, \"should be 2 address one for each pool\")\n\t\tt.Run(\"eachOfThemShouldResolve:0\", func(t *testing.T) {\n\t\t\tdomain := fakeMulti.GetDomainFromFakeDNS(address[0])\n\t\t\tassert.Equal(t, \"fakednstest.example.com\", domain)\n\t\t})\n\t\tt.Run(\"eachOfThemShouldResolve:1\", func(t *testing.T) {\n\t\t\tdomain := fakeMulti.GetDomainFromFakeDNS(address[1])\n\t\t\tassert.Equal(t, \"fakednstest.example.com\", domain)\n\t\t})\n\t})\n\n\tt.Run(\"understandIPTypeSelector\", func(t *testing.T) {\n\t\tt.Run(\"ipv4\", func(t *testing.T) {\n\t\t\taddress := fakeMulti.GetFakeIPForDomain3(\"fakednstestipv4.example.com\", true, false)\n\t\t\tassert.Len(t, address, 1, \"should be 1 address\")\n\t\t\tassert.True(t, address[0].Family().IsIPv4())\n\t\t})\n\t\tt.Run(\"ipv6\", func(t *testing.T) {\n\t\t\taddress := fakeMulti.GetFakeIPForDomain3(\"fakednstestipv6.example.com\", false, true)\n\t\t\tassert.Len(t, address, 1, \"should be 1 address\")\n\t\t\tassert.True(t, address[0].Family().IsIPv6())\n\t\t})\n\t\tt.Run(\"ipv46\", func(t *testing.T) {\n\t\t\taddress := fakeMulti.GetFakeIPForDomain3(\"fakednstestipv46.example.com\", true, true)\n\t\t\tassert.Len(t, address, 2, \"should be 2 address\")\n\t\t\tassert.True(t, address[0].Family().IsIPv4())\n\t\t\tassert.True(t, address[1].Family().IsIPv6())\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "app/dns/hosts.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"strconv\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\n// StaticHosts represents static domain-ip mapping in DNS server.\ntype StaticHosts struct {\n\tips      [][]net.Address\n\tmatchers strmatcher.IndexMatcher\n}\n\n// NewStaticHosts creates a new StaticHosts instance.\nfunc NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {\n\tg := new(strmatcher.MatcherGroup)\n\tsh := &StaticHosts{\n\t\tips:      make([][]net.Address, len(hosts)+16),\n\t\tmatchers: g,\n\t}\n\n\tdefer runtime.GC()\n\tfor i, mapping := range hosts {\n\t\thosts[i] = nil\n\t\tmatcher, err := toStrMatcher(mapping.Type, mapping.Domain)\n\t\tif err != nil {\n\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to create domain matcher, ignore domain rule [type: \", mapping.Type, \", domain: \", mapping.Domain, \"]\")\n\t\t\tcontinue\n\t\t}\n\t\tid := g.Add(matcher)\n\t\tips := make([]net.Address, 0, len(mapping.Ip)+1)\n\t\tswitch {\n\t\tcase len(mapping.ProxiedDomain) > 0:\n\t\t\tif mapping.ProxiedDomain[0] == '#' {\n\t\t\t\trcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tips = append(ips, dns.RCodeError(rcode))\n\t\t\t} else {\n\t\t\t\tips = append(ips, net.DomainAddress(mapping.ProxiedDomain))\n\t\t\t}\n\t\tcase len(mapping.Ip) > 0:\n\t\t\tfor _, ip := range mapping.Ip {\n\t\t\t\taddr := net.IPAddress(ip)\n\t\t\t\tif addr == nil {\n\t\t\t\t\terrors.LogError(context.Background(), \"invalid IP address in static hosts: \", ip, \", ignore this ip for rule [type: \", mapping.Type, \", domain: \", mapping.Domain, \"]\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tips = append(ips, addr)\n\t\t\t}\n\t\t\tif len(ips) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tsh.ips[id] = ips\n\t}\n\n\treturn sh, nil\n}\n\nfunc filterIP(ips []net.Address, option dns.IPOption) []net.Address {\n\tfiltered := make([]net.Address, 0, len(ips))\n\tfor _, ip := range ips {\n\t\tif (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {\n\t\t\tfiltered = append(filtered, ip)\n\t\t}\n\t}\n\treturn filtered\n}\n\nfunc (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {\n\tips := make([]net.Address, 0)\n\tfound := false\n\tfor _, id := range h.matchers.Match(domain) {\n\t\tfor _, v := range h.ips[id] {\n\t\t\tif err, ok := v.(dns.RCodeError); ok {\n\t\t\t\tif uint16(err) == 0 {\n\t\t\t\t\treturn nil, dns.ErrEmptyResponse\n\t\t\t\t}\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tips = append(ips, h.ips[id]...)\n\t\tfound = true\n\t}\n\tif !found {\n\t\treturn nil, nil\n\t}\n\treturn ips, nil\n}\n\nfunc (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {\n\tswitch addrs, err := h.lookupInternal(domain); {\n\tcase err != nil:\n\t\treturn nil, err\n\tcase len(addrs) == 0: // Not recorded in static hosts, return nil\n\t\treturn addrs, nil\n\tcase len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain\n\t\terrors.LogDebug(context.Background(), \"found replaced domain: \", domain, \" -> \", addrs[0].Domain(), \". Try to unwrap it\")\n\t\tif maxDepth > 0 {\n\t\t\tunwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif unwrapped != nil {\n\t\t\t\treturn unwrapped, nil\n\t\t\t}\n\t\t}\n\t\treturn addrs, nil\n\tdefault: // IP record found, return a non-nil IP array\n\t\treturn filterIP(addrs, option), nil\n\t}\n}\n\n// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.\nfunc (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {\n\treturn h.lookup(domain, option, 5)\n}\nfunc NewStaticHostsFromCache(matcher strmatcher.IndexMatcher, hostIPs map[string][]string) (*StaticHosts, error) {\n\tsh := &StaticHosts{\n\t\tips:      make([][]net.Address, matcher.Size()+1),\n\t\tmatchers: matcher,\n\t}\n\n\torder := hostIPs[\"_ORDER\"]\n\tvar offset uint32\n\n\timg, ok := matcher.(*strmatcher.IndexMatcherGroup)\n\tif !ok {\n\t\t// Single matcher (e.g. only manual or only one geosite)\n\t\tif len(order) > 0 {\n\t\t\tpattern := order[0]\n\t\t\tips := parseIPs(hostIPs[pattern])\n\t\t\tfor i := uint32(1); i <= matcher.Size(); i++ {\n\t\t\t\tsh.ips[i] = ips\n\t\t\t}\n\t\t}\n\t\treturn sh, nil\n\t}\n\n\tfor i, m := range img.Matchers {\n\t\tif i < len(order) {\n\t\t\tpattern := order[i]\n\t\t\tips := parseIPs(hostIPs[pattern])\n\t\t\tfor j := uint32(1); j <= m.Size(); j++ {\n\t\t\t\tsh.ips[offset+j] = ips\n\t\t\t}\n\t\t\toffset += m.Size()\n\t\t}\n\t}\n\treturn sh, nil\n}\n\nfunc parseIPs(raw []string) []net.Address {\n\taddrs := make([]net.Address, 0, len(raw))\n\tfor _, s := range raw {\n\t\tif len(s) > 1 && s[0] == '#' {\n\t\t\trcode, _ := strconv.Atoi(s[1:])\n\t\t\taddrs = append(addrs, dns.RCodeError(rcode))\n\t\t} else {\n\t\t\taddrs = append(addrs, net.ParseAddress(s))\n\t\t}\n\t}\n\treturn addrs\n}\n"
  },
  {
    "path": "app/dns/hosts_test.go",
    "content": "package dns_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\nfunc TestStaticHosts(t *testing.T) {\n\tpb := []*Config_HostMapping{\n\t\t{\n\t\t\tType:          DomainMatchingType_Subdomain,\n\t\t\tDomain:        \"lan\",\n\t\t\tProxiedDomain: \"#3\",\n\t\t},\n\t\t{\n\t\t\tType:   DomainMatchingType_Full,\n\t\t\tDomain: \"example.com\",\n\t\t\tIp: [][]byte{\n\t\t\t\t{1, 1, 1, 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tType:   DomainMatchingType_Full,\n\t\t\tDomain: \"proxy.xray.com\",\n\t\t\tIp: [][]byte{\n\t\t\t\t{1, 2, 3, 4},\n\t\t\t\t{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\t},\n\t\t\tProxiedDomain: \"another-proxy.xray.com\",\n\t\t},\n\t\t{\n\t\t\tType:          DomainMatchingType_Full,\n\t\t\tDomain:        \"proxy2.xray.com\",\n\t\t\tProxiedDomain: \"proxy.xray.com\",\n\t\t},\n\t\t{\n\t\t\tType:   DomainMatchingType_Subdomain,\n\t\t\tDomain: \"example.cn\",\n\t\t\tIp: [][]byte{\n\t\t\t\t{2, 2, 2, 2},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tType:   DomainMatchingType_Subdomain,\n\t\t\tDomain: \"baidu.com\",\n\t\t\tIp: [][]byte{\n\t\t\t\t{127, 0, 0, 1},\n\t\t\t\t{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\t},\n\t\t},\n\t}\n\n\thosts, err := NewStaticHosts(pb)\n\tcommon.Must(err)\n\n\t{\n\t\t_, err := hosts.Lookup(\"example.com.lan\", dns.IPOption{})\n\t\tif dns.RCodeFromError(err) != 3 {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\t{\n\t\tips, _ := hosts.Lookup(\"example.com\", dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t})\n\t\tif len(ips) != 1 {\n\t\t\tt.Error(\"expect 1 IP, but got \", len(ips))\n\t\t}\n\t\tif diff := cmp.Diff([]byte(ips[0].IP()), []byte{1, 1, 1, 1}); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n\n\t{\n\t\tdomain, _ := hosts.Lookup(\"proxy.xray.com\", dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: false,\n\t\t})\n\t\tif len(domain) != 1 {\n\t\t\tt.Error(\"expect 1 domain, but got \", len(domain))\n\t\t}\n\t\tif diff := cmp.Diff(domain[0].Domain(), \"another-proxy.xray.com\"); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n\n\t{\n\t\tdomain, _ := hosts.Lookup(\"proxy2.xray.com\", dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: false,\n\t\t})\n\t\tif len(domain) != 1 {\n\t\t\tt.Error(\"expect 1 domain, but got \", len(domain))\n\t\t}\n\t\tif diff := cmp.Diff(domain[0].Domain(), \"another-proxy.xray.com\"); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n\n\t{\n\t\tips, _ := hosts.Lookup(\"www.example.cn\", dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t})\n\t\tif len(ips) != 1 {\n\t\t\tt.Error(\"expect 1 IP, but got \", len(ips))\n\t\t}\n\t\tif diff := cmp.Diff([]byte(ips[0].IP()), []byte{2, 2, 2, 2}); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n\n\t{\n\t\tips, _ := hosts.Lookup(\"baidu.com\", dns.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: true,\n\t\t})\n\t\tif len(ips) != 1 {\n\t\t\tt.Error(\"expect 1 IP, but got \", len(ips))\n\t\t}\n\t\tif diff := cmp.Diff([]byte(ips[0].IP()), []byte(net.LocalHostIPv6.IP())); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n}\nfunc TestStaticHostsFromCache(t *testing.T) {\n\tsites := []*router.GeoSite{\n\t\t{\n\t\t\tCountryCode: \"cloudflare-dns.com\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Full, Value: \"example.com\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tCountryCode: \"geosite:cn\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Domain, Value: \"baidu.cn\"},\n\t\t\t},\n\t\t},\n\t}\n\tdeps := map[string][]string{\n\t\t\"HOSTS\": {\"cloudflare-dns.com\", \"geosite:cn\"},\n\t}\n\thostIPs := map[string][]string{\n\t\t\"cloudflare-dns.com\": {\"1.1.1.1\"},\n\t\t\"geosite:cn\":         {\"2.2.2.2\"},\n\t\t\"_ORDER\":             {\"cloudflare-dns.com\", \"geosite:cn\"},\n\t}\n\n\tvar buf bytes.Buffer\n\terr := router.SerializeGeoSiteList(sites, deps, hostIPs, &buf)\n\tcommon.Must(err)\n\n\t// Load matcher\n\tm, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), \"HOSTS\")\n\tcommon.Must(err)\n\n\t// Load hostIPs\n\tf := bytes.NewReader(buf.Bytes())\n\thips, err := router.LoadGeoSiteHosts(f)\n\tcommon.Must(err)\n\n\thosts, err := NewStaticHostsFromCache(m, hips)\n\tcommon.Must(err)\n\n\t{\n\t\tips, _ := hosts.Lookup(\"example.com\", dns.IPOption{IPv4Enable: true})\n\t\tif len(ips) != 1 || ips[0].String() != \"1.1.1.1\" {\n\t\t\tt.Error(\"failed to lookup example.com from cache\")\n\t\t}\n\t}\n\n\t{\n\t\tips, _ := hosts.Lookup(\"baidu.cn\", dns.IPOption{IPv4Enable: true})\n\t\tif len(ips) != 1 || ips[0].String() != \"2.2.2.2\" {\n\t\t\tt.Error(\"failed to lookup baidu.cn from cache deps\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/dns/nameserver.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\ntype mphMatcherWrapper struct {\n\tm strmatcher.IndexMatcher\n}\n\nfunc (w *mphMatcherWrapper) Match(s string) bool {\n\treturn w.m.Match(s) != nil\n}\n\nfunc (w *mphMatcherWrapper) String() string {\n\treturn \"mph-matcher\"\n}\n\n// Server is the interface for Name Server.\ntype Server interface {\n\t// Name of the Client.\n\tName() string\n\n\tIsDisableCache() bool\n\n\t// QueryIP sends IP queries to its configured server.\n\tQueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error)\n}\n\n// Client is the interface for DNS client.\ntype Client struct {\n\tserver        Server\n\tskipFallback  bool\n\tdomains       []string\n\texpectedIPs   router.GeoIPMatcher\n\tunexpectedIPs router.GeoIPMatcher\n\tactPrior      bool\n\tactUnprior    bool\n\ttag           string\n\ttimeoutMs     time.Duration\n\tfinalQuery    bool\n\tipOption      *dns.IPOption\n\tcheckSystem   bool\n\tpolicyID      uint32\n}\n\n// NewServer creates a name server object according to the network destination url.\nfunc NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (Server, error) {\n\tif address := dest.Address; address.Family().IsDomain() {\n\t\tu, err := url.Parse(address.Domain())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch {\n\t\tcase strings.EqualFold(u.String(), \"localhost\"):\n\t\t\treturn NewLocalNameServer(), nil\n\t\tcase strings.EqualFold(u.Scheme, \"https\"): // DNS-over-HTTPS Remote mode\n\t\t\treturn NewDoHNameServer(u, dispatcher, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil\n\t\tcase strings.EqualFold(u.Scheme, \"h2c\"): // DNS-over-HTTPS h2c Remote mode\n\t\t\treturn NewDoHNameServer(u, dispatcher, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil\n\t\tcase strings.EqualFold(u.Scheme, \"https+local\"): // DNS-over-HTTPS Local mode\n\t\t\treturn NewDoHNameServer(u, nil, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil\n\t\tcase strings.EqualFold(u.Scheme, \"h2c+local\"): // DNS-over-HTTPS h2c Local mode\n\t\t\treturn NewDoHNameServer(u, nil, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil\n\t\tcase strings.EqualFold(u.Scheme, \"quic+local\"): // DNS-over-QUIC Local mode\n\t\t\treturn NewQUICNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)\n\t\tcase strings.EqualFold(u.Scheme, \"tcp\"): // DNS-over-TCP Remote mode\n\t\t\treturn NewTCPNameServer(u, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)\n\t\tcase strings.EqualFold(u.Scheme, \"tcp+local\"): // DNS-over-TCP Local mode\n\t\t\treturn NewTCPLocalNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)\n\t\tcase strings.EqualFold(u.String(), \"fakedns\"):\n\t\t\tvar fd dns.FakeDNSEngine\n\t\t\terr = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {\n\t\t\t\tfd = fdns\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn NewFakeDNSServer(fd), nil\n\t\t}\n\t}\n\tif dest.Network == net.Network_Unknown {\n\t\tdest.Network = net.Network_UDP\n\t}\n\tif dest.Network == net.Network_UDP { // UDP classic DNS mode\n\t\treturn NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil\n\t}\n\treturn nil, errors.New(\"No available name server could be created from \", dest).AtWarning()\n}\n\n// NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.\nfunc NewClient(\n\tctx context.Context,\n\tns *NameServer,\n\tclientIP net.IP,\n\tdisableCache bool, serveStale bool, serveExpiredTTL uint32,\n\ttag string,\n\tipOption dns.IPOption,\n\tmatcherInfos *[]*DomainMatcherInfo,\n\tupdateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),\n) (*Client, error) {\n\tclient := &Client{}\n\n\terr := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {\n\t\t// Create a new server for each client for now\n\t\tserver, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create nameserver\").Base(err).AtWarning()\n\t\t}\n\n\t\t// Prioritize local domains with specific TLDs or those without any dot for the local DNS\n\t\tif _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {\n\t\t\tns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)\n\t\t\tns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)\n\t\t\t// The following lines is a solution to avoid core panics（rule index out of range） when setting `localhost` DNS client in config.\n\t\t\t// Because the `localhost` DNS client will append len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.\n\t\t\t// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).\n\t\t\t// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.\n\t\t\t// Related issues:\n\t\t\t// https://github.com/v2fly/v2ray-core/issues/529\n\t\t\t// https://github.com/v2fly/v2ray-core/issues/719\n\t\t\tfor i := 0; i < len(localTLDsAndDotlessDomains); i++ {\n\t\t\t\t*matcherInfos = append(*matcherInfos, &DomainMatcherInfo{\n\t\t\t\t\tclientIdx:     uint16(0),\n\t\t\t\t\tdomainRuleIdx: uint16(0),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Establish domain rules\n\t\tvar rules []string\n\t\truleCurr := 0\n\t\truleIter := 0\n\n\t\t// Check if domain matcher cache is provided via environment\n\t\tdomainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return \"\" })\n\t\tvar mphLoaded bool\n\n\t\tif domainMatcherPath != \"\" && ns.Tag != \"\" {\n\t\t\tf, err := filesystem.NewFileReader(domainMatcherPath)\n\t\t\tif err == nil {\n\t\t\t\tdefer f.Close()\n\t\t\t\tg, err := router.LoadGeoSiteMatcher(f, ns.Tag)\n\t\t\t\tif err == nil {\n\t\t\t\t\terrors.LogDebug(ctx, \"MphDomainMatcher loaded from cache for \", ns.Tag, \" dns tag)\")\n\t\t\t\t\tupdateDomainRule(&mphMatcherWrapper{m: g}, 0, *matcherInfos)\n\t\t\t\t\trules = append(rules, \"[MPH Cache]\")\n\t\t\t\t\tmphLoaded = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !mphLoaded {\n\t\t\tfor i, domain := range ns.PrioritizedDomain {\n\t\t\t\tns.PrioritizedDomain[i] = nil\n\t\t\t\tdomainRule, err := toStrMatcher(domain.Type, domain.Domain)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to create domain matcher, ignore domain rule [type: \", domain.Type, \", domain: \", domain.Domain, \"]\")\n\t\t\t\t\tdomainRule, _ = toStrMatcher(DomainMatchingType_Full, \"hack.fix.index.for.illegal.domain.rule\")\n\t\t\t\t}\n\t\t\t\toriginalRuleIdx := ruleCurr\n\t\t\t\tif ruleCurr < len(ns.OriginalRules) {\n\t\t\t\t\trule := ns.OriginalRules[ruleCurr]\n\t\t\t\t\tif ruleCurr >= len(rules) {\n\t\t\t\t\t\trules = append(rules, rule.Rule)\n\t\t\t\t\t}\n\t\t\t\t\truleIter++\n\t\t\t\t\tif ruleIter >= int(rule.Size) {\n\t\t\t\t\t\truleIter = 0\n\t\t\t\t\t\truleCurr++\n\t\t\t\t\t}\n\t\t\t\t} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)\n\t\t\t\t\trules = append(rules, domainRule.String())\n\t\t\t\t\truleCurr++\n\t\t\t\t}\n\t\t\t\tupdateDomainRule(domainRule, originalRuleIdx, *matcherInfos)\n\t\t\t}\n\t\t}\n\t\tns.PrioritizedDomain = nil\n\t\truntime.GC()\n\n\t\t// Establish expected IPs\n\t\tvar expectedMatcher router.GeoIPMatcher\n\t\tif len(ns.ExpectedGeoip) > 0 {\n\t\t\texpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"failed to create expected ip matcher\").Base(err).AtWarning()\n\t\t\t}\n\t\t\tns.ExpectedGeoip = nil\n\t\t\truntime.GC()\n\t\t}\n\n\t\t// Establish unexpected IPs\n\t\tvar unexpectedMatcher router.GeoIPMatcher\n\t\tif len(ns.UnexpectedGeoip) > 0 {\n\t\t\tunexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"failed to create unexpected ip matcher\").Base(err).AtWarning()\n\t\t\t}\n\t\t\tns.UnexpectedGeoip = nil\n\t\t\truntime.GC()\n\t\t}\n\n\t\tif len(clientIP) > 0 {\n\t\t\tswitch ns.Address.Address.GetAddress().(type) {\n\t\t\tcase *net.IPOrDomain_Domain:\n\t\t\t\terrors.LogInfo(ctx, \"DNS: client \", ns.Address.Address.GetDomain(), \" uses clientIP \", clientIP.String())\n\t\t\tcase *net.IPOrDomain_Ip:\n\t\t\t\terrors.LogInfo(ctx, \"DNS: client \", net.IP(ns.Address.Address.GetIp()), \" uses clientIP \", clientIP.String())\n\t\t\t}\n\t\t}\n\n\t\tvar timeoutMs = 4000 * time.Millisecond\n\t\tif ns.TimeoutMs > 0 {\n\t\t\ttimeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond\n\t\t}\n\n\t\tcheckSystem := ns.QueryStrategy == QueryStrategy_USE_SYS\n\n\t\tclient.server = server\n\t\tclient.skipFallback = ns.SkipFallback\n\t\tclient.domains = rules\n\t\tclient.expectedIPs = expectedMatcher\n\t\tclient.unexpectedIPs = unexpectedMatcher\n\t\tclient.actPrior = ns.ActPrior\n\t\tclient.actUnprior = ns.ActUnprior\n\t\tclient.tag = tag\n\t\tclient.timeoutMs = timeoutMs\n\t\tclient.finalQuery = ns.FinalQuery\n\t\tclient.ipOption = &ipOption\n\t\tclient.checkSystem = checkSystem\n\t\tclient.policyID = ns.PolicyID\n\t\treturn nil\n\t})\n\treturn client, err\n}\n\n// Name returns the server name the client manages.\nfunc (c *Client) Name() string {\n\treturn c.server.Name()\n}\n\n// QueryIP sends DNS query to the name server with the client's IP.\nfunc (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {\n\tif c.checkSystem {\n\t\tsupportIPv4, supportIPv6 := checkRoutes()\n\t\toption.IPv4Enable = option.IPv4Enable && supportIPv4\n\t\toption.IPv6Enable = option.IPv6Enable && supportIPv6\n\t} else {\n\t\toption.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable\n\t\toption.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable\n\t}\n\n\tif !option.IPv4Enable && !option.IPv6Enable {\n\t\treturn nil, 0, dns.ErrEmptyResponse\n\t}\n\n\tctx, cancel := context.WithTimeout(ctx, c.timeoutMs)\n\tctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})\n\tips, ttl, err := c.server.QueryIP(ctx, domain, option)\n\tcancel()\n\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif len(ips) == 0 {\n\t\treturn nil, 0, dns.ErrEmptyResponse\n\t}\n\n\tif c.expectedIPs != nil && !c.actPrior {\n\t\tips, _ = c.expectedIPs.FilterIPs(ips)\n\t\terrors.LogDebug(context.Background(), \"domain \", domain, \" expectedIPs \", ips, \" matched at server \", c.Name())\n\t\tif len(ips) == 0 {\n\t\t\treturn nil, 0, dns.ErrEmptyResponse\n\t\t}\n\t}\n\n\tif c.unexpectedIPs != nil && !c.actUnprior {\n\t\t_, ips = c.unexpectedIPs.FilterIPs(ips)\n\t\terrors.LogDebug(context.Background(), \"domain \", domain, \" unexpectedIPs \", ips, \" matched at server \", c.Name())\n\t\tif len(ips) == 0 {\n\t\t\treturn nil, 0, dns.ErrEmptyResponse\n\t\t}\n\t}\n\n\tif c.expectedIPs != nil && c.actPrior {\n\t\tipsNew, _ := c.expectedIPs.FilterIPs(ips)\n\t\tif len(ipsNew) > 0 {\n\t\t\tips = ipsNew\n\t\t\terrors.LogDebug(context.Background(), \"domain \", domain, \" priorIPs \", ips, \" matched at server \", c.Name())\n\t\t}\n\t}\n\n\tif c.unexpectedIPs != nil && c.actUnprior {\n\t\t_, ipsNew := c.unexpectedIPs.FilterIPs(ips)\n\t\tif len(ipsNew) > 0 {\n\t\t\tips = ipsNew\n\t\t\terrors.LogDebug(context.Background(), \"domain \", domain, \" unpriorIPs \", ips, \" matched at server \", c.Name())\n\t\t}\n\t}\n\n\treturn ips, ttl, nil\n}\n\nfunc ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {\n\tswitch queryStrategy {\n\tcase QueryStrategy_USE_IP:\n\t\treturn ipOption\n\tcase QueryStrategy_USE_SYS:\n\t\treturn ipOption\n\tcase QueryStrategy_USE_IP4:\n\t\treturn dns.IPOption{\n\t\t\tIPv4Enable: ipOption.IPv4Enable,\n\t\t\tIPv6Enable: false,\n\t\t\tFakeEnable: false,\n\t\t}\n\tcase QueryStrategy_USE_IP6:\n\t\treturn dns.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: ipOption.IPv6Enable,\n\t\t\tFakeEnable: false,\n\t\t}\n\tdefault:\n\t\treturn ipOption\n\t}\n}\n"
  },
  {
    "path": "app/dns/nameserver_cached.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\tgo_errors \"errors\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/signal/pubsub\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\ntype CachedNameserver interface {\n\tgetCacheController() *CacheController\n\n\tsendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns.IPOption)\n}\n\n// queryIP is called from dns.Server->queryIPTimeout\nfunc queryIP(ctx context.Context, s CachedNameserver, domain string, option dns.IPOption) ([]net.IP, uint32, error) {\n\tfqdn := Fqdn(domain)\n\n\tcache := s.getCacheController()\n\tif !cache.disableCache {\n\t\tif rec := cache.findRecords(fqdn); rec != nil {\n\t\t\tips, ttl, err := merge(option, rec.A, rec.AAAA)\n\t\t\tif !go_errors.Is(err, errRecordNotFound) {\n\t\t\t\tif ttl > 0 {\n\t\t\t\t\terrors.LogDebugInner(ctx, err, cache.name, \" cache HIT \", fqdn, \" -> \", ips)\n\t\t\t\t\tlog.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})\n\t\t\t\t\treturn ips, uint32(ttl), err\n\t\t\t\t}\n\t\t\t\tif cache.serveStale && (cache.serveExpiredTTL == 0 || cache.serveExpiredTTL < ttl) {\n\t\t\t\t\terrors.LogDebugInner(ctx, err, cache.name, \" cache OPTIMISTE \", fqdn, \" -> \", ips)\n\t\t\t\t\tlog.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheOptimiste, Elapsed: 0, Error: err})\n\t\t\t\t\tgo pull(ctx, s, fqdn, option)\n\t\t\t\t\treturn ips, 1, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\terrors.LogDebug(ctx, \"DNS cache is disabled. Querying IP for \", fqdn, \" at \", cache.name)\n\t}\n\n\treturn fetch(ctx, s, fqdn, option)\n}\n\nfunc pull(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) {\n\tnctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 8*time.Second)\n\tdefer cancel()\n\n\tfetch(nctx, s, fqdn, option)\n}\n\nfunc fetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) ([]net.IP, uint32, error) {\n\tkey := fqdn\n\tswitch {\n\tcase option.IPv4Enable && option.IPv6Enable:\n\t\tkey = key + \"46\"\n\tcase option.IPv4Enable:\n\t\tkey = key + \"4\"\n\tcase option.IPv6Enable:\n\t\tkey = key + \"6\"\n\t}\n\n\tv, _, _ := s.getCacheController().requestGroup.Do(key, func() (any, error) {\n\t\treturn doFetch(ctx, s, fqdn, option), nil\n\t})\n\tret := v.(result)\n\n\treturn ret.ips, ret.ttl, ret.error\n}\n\ntype result struct {\n\tips []net.IP\n\tttl uint32\n\terror\n}\n\nfunc doFetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) result {\n\tsub4, sub6 := s.getCacheController().registerSubscribers(fqdn, option)\n\tdefer closeSubscribers(sub4, sub6)\n\n\tnoResponseErrCh := make(chan error, 2)\n\tonEvent := func(sub *pubsub.Subscriber) (*IPRecord, error) {\n\t\tif sub == nil {\n\t\t\treturn nil, nil\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, ctx.Err()\n\t\tcase err := <-noResponseErrCh:\n\t\t\treturn nil, err\n\t\tcase msg := <-sub.Wait():\n\t\t\tsub.Close()\n\t\t\treturn msg.(*IPRecord), nil // should panic\n\t\t}\n\t}\n\n\tstart := time.Now()\n\ts.sendQuery(ctx, noResponseErrCh, fqdn, option)\n\n\trec4, err4 := onEvent(sub4)\n\trec6, err6 := onEvent(sub6)\n\n\tvar errs []error\n\tif err4 != nil {\n\t\terrs = append(errs, err4)\n\t}\n\tif err6 != nil {\n\t\terrs = append(errs, err6)\n\t}\n\n\tips, ttl, err := merge(option, rec4, rec6, errs...)\n\tvar rTTL uint32\n\tif ttl > 0 {\n\t\trTTL = uint32(ttl)\n\t} else if ttl == 0 && go_errors.Is(err, errRecordNotFound) {\n\t\trTTL = 0\n\t} else { // edge case: where a fast rep's ttl expires during the rtt of a slower, parallel query\n\t\trTTL = 1\n\t}\n\n\tlog.Record(&log.DNSLog{Server: s.getCacheController().name, Domain: fqdn, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})\n\treturn result{ips, rTTL, err}\n}\n\nfunc merge(option dns.IPOption, rec4 *IPRecord, rec6 *IPRecord, errs ...error) ([]net.IP, int32, error) {\n\tvar allIPs []net.IP\n\tvar rTTL int32 = dns.DefaultTTL\n\n\tmergeReq := option.IPv4Enable && option.IPv6Enable\n\n\tif option.IPv4Enable {\n\t\tips, ttl, err := rec4.getIPs() // it's safe\n\t\tif !mergeReq || go_errors.Is(err, errRecordNotFound) {\n\t\t\treturn ips, ttl, err\n\t\t}\n\t\tif ttl < rTTL {\n\t\t\trTTL = ttl\n\t\t}\n\t\tif len(ips) > 0 {\n\t\t\tallIPs = append(allIPs, ips...)\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif option.IPv6Enable {\n\t\tips, ttl, err := rec6.getIPs() // it's safe\n\t\tif !mergeReq || go_errors.Is(err, errRecordNotFound) {\n\t\t\treturn ips, ttl, err\n\t\t}\n\t\tif ttl < rTTL {\n\t\t\trTTL = ttl\n\t\t}\n\t\tif len(ips) > 0 {\n\t\t\tallIPs = append(allIPs, ips...)\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif len(allIPs) > 0 {\n\t\treturn allIPs, rTTL, nil\n\t}\n\tif len(errs) == 2 && go_errors.Is(errs[0], errs[1]) {\n\t\treturn nil, rTTL, errs[0]\n\t}\n\treturn nil, rTTL, errors.Combine(errs...)\n}\n"
  },
  {
    "path": "app/dns/nameserver_doh.go",
    "content": "package dns\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/protocol/dns\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"golang.org/x/net/http2\"\n)\n\n// DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,\n// which is compatible with traditional dns over udp(RFC1035),\n// thus most of the DOH implementation is copied from udpns.go\ntype DoHNameServer struct {\n\tcacheController *CacheController\n\thttpClient      *http.Client\n\tdohURL          string\n\tclientIP        net.IP\n}\n\n// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.\nfunc NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *DoHNameServer {\n\turl.Scheme = \"https\"\n\tmode := \"DOH\"\n\tif dispatcher == nil {\n\t\tmode = \"DOHL\"\n\t}\n\terrors.LogInfo(context.Background(), \"DNS: created \", mode, \" client for \", url.String(), \", with h2c \", h2c)\n\ts := &DoHNameServer{\n\t\tcacheController: NewCacheController(mode+\"//\"+url.Host, disableCache, serveStale, serveExpiredTTL),\n\t\tdohURL:          url.String(),\n\t\tclientIP:        clientIP,\n\t}\n\ts.httpClient = &http.Client{\n\t\tTransport: &http2.Transport{\n\t\t\tIdleConnTimeout: net.ConnIdleTimeout,\n\t\t\tReadIdleTimeout: net.ChromeH2KeepAlivePeriod,\n\t\t\tDialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {\n\t\t\t\tdest, err := net.ParseDestination(network + \":\" + addr)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tvar conn net.Conn\n\t\t\t\tif dispatcher != nil {\n\t\t\t\t\tdnsCtx := toDnsContext(ctx, s.dohURL)\n\t\t\t\t\tif h2c {\n\t\t\t\t\t\tdnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance\n\t\t\t\t\t\tdnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())\n\t\t\t\t\t}\n\t\t\t\t\tlink, err := dispatcher.Dispatch(dnsCtx, dest)\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\treturn nil, ctx.Err()\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tcc := common.ChainedClosable{}\n\t\t\t\t\tif cw, ok := link.Writer.(common.Closable); ok {\n\t\t\t\t\t\tcc = append(cc, cw)\n\t\t\t\t\t}\n\t\t\t\t\tif cr, ok := link.Reader.(common.Closable); ok {\n\t\t\t\t\t\tcc = append(cc, cr)\n\t\t\t\t\t}\n\t\t\t\t\tconn = cnc.NewConnection(\n\t\t\t\t\t\tcnc.ConnectionInputMulti(link.Writer),\n\t\t\t\t\t\tcnc.ConnectionOutputMulti(link.Reader),\n\t\t\t\t\t\tcnc.ConnectionOnClose(cc),\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\t\t\tFrom:   \"DNS\",\n\t\t\t\t\t\tTo:     s.dohURL,\n\t\t\t\t\t\tStatus: log.AccessAccepted,\n\t\t\t\t\t\tDetour: \"local\",\n\t\t\t\t\t})\n\t\t\t\t\tconn, err = internet.DialSystem(ctx, dest, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !h2c {\n\t\t\t\t\tconn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)\n\t\t\t\t\tif err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn conn, nil\n\t\t\t},\n\t\t},\n\t}\n\treturn s\n}\n\n// Name implements Server.\nfunc (s *DoHNameServer) Name() string {\n\treturn s.cacheController.name\n}\n\n// IsDisableCache implements Server.\nfunc (s *DoHNameServer) IsDisableCache() bool {\n\treturn s.cacheController.disableCache\n}\n\nfunc (s *DoHNameServer) newReqID() uint16 {\n\treturn 0\n}\n\n// getCacheController implements CachedNameserver.\nfunc (s *DoHNameServer) getCacheController() *CacheController {\n\treturn s.cacheController\n}\n\n// sendQuery implements CachedNameserver.\nfunc (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {\n\terrors.LogInfo(ctx, s.Name(), \" querying: \", fqdn)\n\n\tif s.Name()+\".\" == \"DOH//\"+fqdn {\n\t\terrors.LogError(ctx, s.Name(), \" tries to resolve itself! Use IP or set \\\"hosts\\\" instead\")\n\t\tif noResponseErrCh != nil {\n\t\t\tnoResponseErrCh <- errors.New(\"tries to resolve itself!\", s.Name())\n\t\t}\n\t\treturn\n\t}\n\n\t// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467\n\t// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all\n\treqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))\n\n\tvar deadline time.Time\n\tif d, ok := ctx.Deadline(); ok {\n\t\tdeadline = d\n\t} else {\n\t\tdeadline = time.Now().Add(time.Second * 5)\n\t}\n\n\tfor _, req := range reqs {\n\t\tgo func(r *dnsRequest) {\n\t\t\t// generate new context for each req, using same context\n\t\t\t// may cause reqs all aborted if any one encounter an error\n\t\t\tdnsCtx := ctx\n\n\t\t\t// reserve internal dns server requested Inbound\n\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\t\t\tdnsCtx = session.ContextWithInbound(dnsCtx, inbound)\n\t\t\t}\n\n\t\t\tdnsCtx = session.ContextWithContent(dnsCtx, &session.Content{\n\t\t\t\tProtocol:       \"https\",\n\t\t\t\tSkipDNSResolve: true,\n\t\t\t})\n\n\t\t\t// forced to use mux for DOH\n\t\t\t// dnsCtx = session.ContextWithMuxPreferred(dnsCtx, true)\n\n\t\t\tvar cancel context.CancelFunc\n\t\t\tdnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)\n\t\t\tdefer cancel()\n\n\t\t\tb, err := dns.PackMessage(r.msg)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to pack dns query for \", fqdn)\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to retrieve response for \", fqdn)\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\trec, err := parseResponse(resp)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to handle DOH response for \", fqdn)\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.cacheController.updateRecord(r, rec)\n\t\t}(req)\n\t}\n}\n\nfunc (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {\n\tbody := bytes.NewBuffer(b)\n\treq, err := http.NewRequest(\"POST\", s.dohURL, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/dns-message\")\n\treq.Header.Add(\"Content-Type\", \"application/dns-message\")\n\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\treq.Header.Set(\"X-Padding\", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))\n\n\thc := s.httpClient\n\n\tresp, err := hc.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tio.Copy(io.Discard, resp.Body) // flush resp.Body so that the conn is reusable\n\t\treturn nil, fmt.Errorf(\"DOH server returned code %d\", resp.StatusCode)\n\t}\n\n\treturn io.ReadAll(resp.Body)\n}\n\n// QueryIP implements Server.\nfunc (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {\n\treturn queryIP(ctx, s, domain, option)\n}\n"
  },
  {
    "path": "app/dns/nameserver_doh_test.go",
    "content": "package dns_test\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n)\n\nfunc TestDOHNameServer(t *testing.T) {\n\turl, err := url.Parse(\"https+local://1.1.1.1/dns-query\")\n\tcommon.Must(err)\n\n\ts := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n}\n\nfunc TestDOHNameServerWithCache(t *testing.T) {\n\turl, err := url.Parse(\"https+local://1.1.1.1/dns-query\")\n\tcommon.Must(err)\n\n\ts := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips2, _, err := s.QueryIP(ctx2, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif r := cmp.Diff(ips2, ips); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n\nfunc TestDOHNameServerWithIPv4Override(t *testing.T) {\n\turl, err := url.Parse(\"https+local://1.1.1.1/dns-query\")\n\tcommon.Must(err)\n\n\ts := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: false,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tif len(ip) != net.IPv4len {\n\t\t\tt.Error(\"expect only IPv4 response from DNS query\")\n\t\t}\n\t}\n}\n\nfunc TestDOHNameServerWithIPv6Override(t *testing.T) {\n\turl, err := url.Parse(\"https+local://1.1.1.1/dns-query\")\n\tcommon.Must(err)\n\n\ts := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: false,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tif len(ip) != net.IPv6len {\n\t\t\tt.Error(\"expect only IPv6 response from DNS query\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/dns/nameserver_fakedns.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\ntype FakeDNSServer struct {\n\tfakeDNSEngine dns.FakeDNSEngine\n}\n\nfunc NewFakeDNSServer(fd dns.FakeDNSEngine) *FakeDNSServer {\n\treturn &FakeDNSServer{fakeDNSEngine: fd}\n}\n\nfunc (FakeDNSServer) Name() string {\n\treturn \"FakeDNS\"\n}\n\n// IsDisableCache implements Server.\nfunc (s *FakeDNSServer) IsDisableCache() bool {\n\treturn true\n}\n\nfunc (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {\n\tif f.fakeDNSEngine == nil {\n\t\treturn nil, 0, errors.New(\"Unable to locate a fake DNS Engine\").AtError()\n\t}\n\n\tvar ips []net.Address\n\tif fkr0, ok := f.fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {\n\t\tips = fkr0.GetFakeIPForDomain3(domain, opt.IPv4Enable, opt.IPv6Enable)\n\t} else {\n\t\tips = f.fakeDNSEngine.GetFakeIPForDomain(domain)\n\t}\n\n\tnetIP, err := toNetIP(ips)\n\tif err != nil {\n\t\treturn nil, 0, errors.New(\"Unable to convert IP to net ip\").Base(err).AtError()\n\t}\n\n\terrors.LogInfo(ctx, f.Name(), \" got answer: \", domain, \" -> \", ips)\n\n\tif len(netIP) > 0 {\n\t\treturn netIP, 1, nil // fakeIP ttl is 1\n\t}\n\treturn nil, 0, dns.ErrEmptyResponse\n}\n"
  },
  {
    "path": "app/dns/nameserver_local.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/dns/localdns\"\n)\n\n// LocalNameServer is an wrapper over local DNS feature.\ntype LocalNameServer struct {\n\tclient *localdns.Client\n}\n\n// QueryIP implements Server.\nfunc (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {\n\n\tstart := time.Now()\n\tips, ttl, err = s.client.LookupIP(domain, option)\n\n\tif len(ips) > 0 {\n\t\terrors.LogInfo(ctx, \"Localhost got answer: \", domain, \" -> \", ips)\n\t\tlog.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})\n\t}\n\n\treturn\n}\n\n// Name implements Server.\nfunc (s *LocalNameServer) Name() string {\n\treturn \"localhost\"\n}\n\n// IsDisableCache implements Server.\nfunc (s *LocalNameServer) IsDisableCache() bool {\n\treturn true\n}\n\n// NewLocalNameServer creates localdns server object for directly lookup in system DNS.\nfunc NewLocalNameServer() *LocalNameServer {\n\terrors.LogInfo(context.Background(), \"DNS: created localhost client\")\n\treturn &LocalNameServer{\n\t\tclient: localdns.New(),\n\t}\n}\n\n// NewLocalDNSClient creates localdns client object for directly lookup in system DNS.\nfunc NewLocalDNSClient(ipOption dns.IPOption) *Client {\n\treturn &Client{server: NewLocalNameServer(), ipOption: &ipOption}\n}\n"
  },
  {
    "path": "app/dns/nameserver_local_test.go",
    "content": "package dns_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\nfunc TestLocalNameServer(t *testing.T) {\n\ts := NewLocalNameServer()\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*2)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t\tFakeEnable: false,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n}\n"
  },
  {
    "path": "app/dns/nameserver_quic.go",
    "content": "package dns\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/dns\"\n\t\"github.com/xtls/xray-core/common/session\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"golang.org/x/net/http2\"\n)\n\n// NextProtoDQ - During connection establishment, DNS/QUIC support is indicated\n// by selecting the ALPN token \"dq\" in the crypto handshake.\nconst NextProtoDQ = \"doq\"\n\nconst handshakeTimeout = time.Second * 8\n\n// QUICNameServer implemented DNS over QUIC\ntype QUICNameServer struct {\n\tsync.RWMutex\n\tcacheController *CacheController\n\tdestination     *net.Destination\n\tconnection      *quic.Conn\n\tclientIP        net.IP\n}\n\n// NewQUICNameServer creates DNS-over-QUIC client object for local resolving\nfunc NewQUICNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*QUICNameServer, error) {\n\tvar err error\n\tport := net.Port(853)\n\tif url.Port() != \"\" {\n\t\tport, err = net.PortFromString(url.Port())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tdest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)\n\n\ts := &QUICNameServer{\n\t\tcacheController: NewCacheController(url.String(), disableCache, serveStale, serveExpiredTTL),\n\t\tdestination:     &dest,\n\t\tclientIP:        clientIP,\n\t}\n\n\terrors.LogInfo(context.Background(), \"DNS: created Local DNS-over-QUIC client for \", url.String())\n\treturn s, nil\n}\n\n// Name implements Server.\nfunc (s *QUICNameServer) Name() string {\n\treturn s.cacheController.name\n}\n\n// IsDisableCache implements Server.\nfunc (s *QUICNameServer) IsDisableCache() bool {\n\treturn s.cacheController.disableCache\n}\n\nfunc (s *QUICNameServer) newReqID() uint16 {\n\treturn 0\n}\n\n// getCacheController implements CachedNameServer.\nfunc (s *QUICNameServer) getCacheController() *CacheController { return s.cacheController }\n\n// sendQuery implements CachedNameServer.\nfunc (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {\n\terrors.LogInfo(ctx, s.Name(), \" querying: \", fqdn)\n\n\treqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))\n\n\tvar deadline time.Time\n\tif d, ok := ctx.Deadline(); ok {\n\t\tdeadline = d\n\t} else {\n\t\tdeadline = time.Now().Add(time.Second * 5)\n\t}\n\n\tfor _, req := range reqs {\n\t\tgo func(r *dnsRequest) {\n\t\t\t// generate new context for each req, using same context\n\t\t\t// may cause reqs all aborted if any one encounter an error\n\t\t\tdnsCtx := ctx\n\n\t\t\t// reserve internal dns server requested Inbound\n\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\t\t\tdnsCtx = session.ContextWithInbound(dnsCtx, inbound)\n\t\t\t}\n\n\t\t\tdnsCtx = session.ContextWithContent(dnsCtx, &session.Content{\n\t\t\t\tProtocol:       \"quic\",\n\t\t\t\tSkipDNSResolve: true,\n\t\t\t})\n\n\t\t\tvar cancel context.CancelFunc\n\t\t\tdnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)\n\t\t\tdefer cancel()\n\n\t\t\tb, err := dns.PackMessage(r.msg)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to pack dns query\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdnsReqBuf := buf.New()\n\t\t\terr = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"binary write failed\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = dnsReqBuf.Write(b.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"buffer write failed\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tb.Release()\n\n\t\t\tconn, err := s.openStream(dnsCtx)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to open quic connection\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = conn.Write(dnsReqBuf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to send query\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_ = conn.Close()\n\n\t\t\trespBuf := buf.New()\n\t\t\tdefer respBuf.Release()\n\t\t\tn, err := respBuf.ReadFullFrom(conn, 2)\n\t\t\tif err != nil && n == 0 {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to read response length\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar length uint16\n\t\t\terr = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to parse response length\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\trespBuf.Clear()\n\t\t\tn, err = respBuf.ReadFullFrom(conn, int32(length))\n\t\t\tif err != nil && n == 0 {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to read response length\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trec, err := parseResponse(respBuf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to handle response\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.cacheController.updateRecord(r, rec)\n\t\t}(req)\n\t}\n}\n\n// QueryIP implements Server.\nfunc (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {\n\treturn queryIP(ctx, s, domain, option)\n}\n\nfunc isActive(s *quic.Conn) bool {\n\tselect {\n\tcase <-s.Context().Done():\n\t\treturn false\n\tdefault:\n\t\treturn true\n\t}\n}\n\nfunc (s *QUICNameServer) getConnection() (*quic.Conn, error) {\n\tvar conn *quic.Conn\n\ts.RLock()\n\tconn = s.connection\n\tif conn != nil && isActive(conn) {\n\t\ts.RUnlock()\n\t\treturn conn, nil\n\t}\n\tif conn != nil {\n\t\t// we're recreating the connection, let's create a new one\n\t\t_ = conn.CloseWithError(0, \"\")\n\t}\n\ts.RUnlock()\n\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tvar err error\n\tconn, err = s.openConnection()\n\tif err != nil {\n\t\t// This does not look too nice, but QUIC (or maybe quic-go)\n\t\t// doesn't seem stable enough.\n\t\t// Maybe retransmissions aren't fully implemented in quic-go?\n\t\t// Anyways, the simple solution is to make a second try when\n\t\t// it fails to open the QUIC connection.\n\t\tconn, err = s.openConnection()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\ts.connection = conn\n\treturn conn, nil\n}\n\nfunc (s *QUICNameServer) openConnection() (*quic.Conn, error) {\n\ttlsConfig := tls.Config{}\n\tquicConfig := &quic.Config{\n\t\tHandshakeIdleTimeout: handshakeTimeout,\n\t}\n\ttlsConfig.ServerName = s.destination.Address.String()\n\tconn, err := quic.DialAddr(context.Background(), s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto(\"http/1.1\", http2.NextProtoTLS, NextProtoDQ)), quicConfig)\n\tlog.Record(&log.AccessMessage{\n\t\tFrom:   \"DNS\",\n\t\tTo:     s.destination,\n\t\tStatus: log.AccessAccepted,\n\t\tDetour: \"local\",\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn conn, nil\n}\n\nfunc (s *QUICNameServer) openStream(ctx context.Context) (*quic.Stream, error) {\n\tconn, err := s.getConnection()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// open a new stream\n\treturn conn.OpenStreamSync(ctx)\n}\n"
  },
  {
    "path": "app/dns/nameserver_quic_test.go",
    "content": "package dns_test\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\nfunc TestQUICNameServer(t *testing.T) {\n\turl, err := url.Parse(\"quic://dns.adguard-dns.com\")\n\tcommon.Must(err)\n\ts, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*2)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\tctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips2, _, err := s.QueryIP(ctx2, \"google.com\", dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif r := cmp.Diff(ips2, ips); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n\nfunc TestQUICNameServerWithIPv4Override(t *testing.T) {\n\turl, err := url.Parse(\"quic://dns.adguard-dns.com\")\n\tcommon.Must(err)\n\ts, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*2)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: false,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tif len(ip) != net.IPv4len {\n\t\t\tt.Error(\"expect only IPv4 response from DNS query\")\n\t\t}\n\t}\n}\n\nfunc TestQUICNameServerWithIPv6Override(t *testing.T) {\n\turl, err := url.Parse(\"quic://dns.adguard-dns.com\")\n\tcommon.Must(err)\n\ts, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*2)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns.IPOption{\n\t\tIPv4Enable: false,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tif len(ip) != net.IPv6len {\n\t\t\tt.Error(\"expect only IPv6 response from DNS query\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/dns/nameserver_tcp.go",
    "content": "package dns\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net/url\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/protocol/dns\"\n\t\"github.com/xtls/xray-core/common/session\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// TCPNameServer implemented DNS over TCP (RFC7766).\ntype TCPNameServer struct {\n\tcacheController *CacheController\n\tdestination     *net.Destination\n\treqID           uint32\n\tdial            func(context.Context) (net.Conn, error)\n\tclientIP        net.IP\n}\n\n// NewTCPNameServer creates DNS over TCP server object for remote resolving.\nfunc NewTCPNameServer(\n\turl *url.URL,\n\tdispatcher routing.Dispatcher,\n\tdisableCache bool, serveStale bool, serveExpiredTTL uint32,\n\tclientIP net.IP,\n) (*TCPNameServer, error) {\n\ts, err := baseTCPNameServer(url, \"TCP\", disableCache, serveStale, serveExpiredTTL, clientIP)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.dial = func(ctx context.Context) (net.Conn, error) {\n\t\tlink, err := dispatcher.Dispatch(toDnsContext(ctx, s.destination.String()), *s.destination)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn cnc.NewConnection(\n\t\t\tcnc.ConnectionInputMulti(link.Writer),\n\t\t\tcnc.ConnectionOutputMulti(link.Reader),\n\t\t), nil\n\t}\n\n\terrors.LogInfo(context.Background(), \"DNS: created TCP client initialized for \", url.String())\n\treturn s, nil\n}\n\n// NewTCPLocalNameServer creates DNS over TCP client object for local resolving\nfunc NewTCPLocalNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {\n\ts, err := baseTCPNameServer(url, \"TCPL\", disableCache, serveStale, serveExpiredTTL, clientIP)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.dial = func(ctx context.Context) (net.Conn, error) {\n\t\treturn internet.DialSystem(ctx, *s.destination, nil)\n\t}\n\n\terrors.LogInfo(context.Background(), \"DNS: created Local TCP client initialized for \", url.String())\n\treturn s, nil\n}\n\nfunc baseTCPNameServer(url *url.URL, prefix string, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {\n\tport := net.Port(53)\n\tif url.Port() != \"\" {\n\t\tvar err error\n\t\tif port, err = net.PortFromString(url.Port()); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tdest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)\n\n\ts := &TCPNameServer{\n\t\tcacheController: NewCacheController(prefix+\"//\"+dest.NetAddr(), disableCache, serveStale, serveExpiredTTL),\n\t\tdestination:     &dest,\n\t\tclientIP:        clientIP,\n\t}\n\n\treturn s, nil\n}\n\n// Name implements Server.\nfunc (s *TCPNameServer) Name() string {\n\treturn s.cacheController.name\n}\n\n// IsDisableCache implements Server.\nfunc (s *TCPNameServer) IsDisableCache() bool {\n\treturn s.cacheController.disableCache\n}\n\nfunc (s *TCPNameServer) newReqID() uint16 {\n\treturn uint16(atomic.AddUint32(&s.reqID, 1))\n}\n\n// getCacheController implements CachedNameserver.\nfunc (s *TCPNameServer) getCacheController() *CacheController {\n\treturn s.cacheController\n}\n\n// sendQuery implements CachedNameserver.\nfunc (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {\n\terrors.LogInfo(ctx, s.Name(), \" querying DNS for: \", fqdn)\n\n\treqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))\n\n\tvar deadline time.Time\n\tif d, ok := ctx.Deadline(); ok {\n\t\tdeadline = d\n\t} else {\n\t\tdeadline = time.Now().Add(time.Second * 5)\n\t}\n\n\tfor _, req := range reqs {\n\t\tgo func(r *dnsRequest) {\n\t\t\tdnsCtx := ctx\n\n\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\t\t\tdnsCtx = session.ContextWithInbound(dnsCtx, inbound)\n\t\t\t}\n\n\t\t\tdnsCtx = session.ContextWithContent(dnsCtx, &session.Content{\n\t\t\t\tProtocol:       \"dns\",\n\t\t\t\tSkipDNSResolve: true,\n\t\t\t})\n\n\t\t\tvar cancel context.CancelFunc\n\t\t\tdnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)\n\t\t\tdefer cancel()\n\n\t\t\tb, err := dns.PackMessage(r.msg)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to pack dns query\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconn, err := s.dial(dnsCtx)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to dial namesever\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t\tdnsReqBuf := buf.New()\n\t\t\terr = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"binary write failed\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = dnsReqBuf.Write(b.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"buffer write failed\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tb.Release()\n\n\t\t\t_, err = conn.Write(dnsReqBuf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to send query\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdnsReqBuf.Release()\n\n\t\t\trespBuf := buf.New()\n\t\t\tdefer respBuf.Release()\n\t\t\tn, err := respBuf.ReadFullFrom(conn, 2)\n\t\t\tif err != nil && n == 0 {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to read response length\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar length uint16\n\t\t\terr = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to parse response length\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\trespBuf.Clear()\n\t\t\tn, err = respBuf.ReadFullFrom(conn, int32(length))\n\t\t\tif err != nil && n == 0 {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to read response length\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trec, err := parseResponse(respBuf.Bytes())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to parse DNS over TCP response\")\n\t\t\t\tif noResponseErrCh != nil {\n\t\t\t\t\tnoResponseErrCh <- err\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ts.cacheController.updateRecord(r, rec)\n\t\t}(req)\n\t}\n}\n\n// QueryIP implements Server.\nfunc (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {\n\treturn queryIP(ctx, s, domain, option)\n}\n"
  },
  {
    "path": "app/dns/nameserver_tcp_test.go",
    "content": "package dns_test\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n)\n\nfunc TestTCPLocalNameServer(t *testing.T) {\n\turl, err := url.Parse(\"tcp+local://8.8.8.8\")\n\tcommon.Must(err)\n\ts, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n}\n\nfunc TestTCPLocalNameServerWithCache(t *testing.T) {\n\turl, err := url.Parse(\"tcp+local://8.8.8.8\")\n\tcommon.Must(err)\n\ts, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips2, _, err := s.QueryIP(ctx2, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\tif r := cmp.Diff(ips2, ips); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n\nfunc TestTCPLocalNameServerWithIPv4Override(t *testing.T) {\n\turl, err := url.Parse(\"tcp+local://8.8.8.8\")\n\tcommon.Must(err)\n\ts, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: false,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tif len(ip) != net.IPv4len {\n\t\t\tt.Error(\"expect only IPv4 response from DNS query\")\n\t\t}\n\t}\n}\n\nfunc TestTCPLocalNameServerWithIPv6Override(t *testing.T) {\n\turl, err := url.Parse(\"tcp+local://8.8.8.8\")\n\tcommon.Must(err)\n\ts, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))\n\tcommon.Must(err)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tips, _, err := s.QueryIP(ctx, \"google.com\", dns_feature.IPOption{\n\t\tIPv4Enable: false,\n\t\tIPv6Enable: true,\n\t})\n\tcancel()\n\tcommon.Must(err)\n\n\tif len(ips) == 0 {\n\t\tt.Error(\"expect some ips, but got 0\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tif len(ip) != net.IPv6len {\n\t\t\tt.Error(\"expect only IPv6 response from DNS query\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/dns/nameserver_udp.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/dns\"\n\tudp_proto \"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/common/task\"\n\tdns_feature \"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n\t\"golang.org/x/net/dns/dnsmessage\"\n)\n\n// ClassicNameServer implemented traditional UDP DNS.\ntype ClassicNameServer struct {\n\tsync.RWMutex\n\tcacheController *CacheController\n\taddress         *net.Destination\n\trequests        map[uint16]*udpDnsRequest\n\tudpServer       *udp.Dispatcher\n\trequestsCleanup *task.Periodic\n\treqID           uint32\n\tclientIP        net.IP\n}\n\ntype udpDnsRequest struct {\n\tdnsRequest\n\tctx context.Context\n}\n\n// NewClassicNameServer creates udp server object for remote resolving.\nfunc NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *ClassicNameServer {\n\t// default to 53 if unspecific\n\tif address.Port == 0 {\n\t\taddress.Port = net.Port(53)\n\t}\n\n\ts := &ClassicNameServer{\n\t\tcacheController: NewCacheController(strings.ToUpper(address.String()), disableCache, serveStale, serveExpiredTTL),\n\t\taddress:         &address,\n\t\trequests:        make(map[uint16]*udpDnsRequest),\n\t\tclientIP:        clientIP,\n\t}\n\ts.requestsCleanup = &task.Periodic{\n\t\tInterval: time.Minute,\n\t\tExecute:  s.RequestsCleanup,\n\t}\n\ts.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)\n\n\terrors.LogInfo(context.Background(), \"DNS: created UDP client initialized for \", address.NetAddr())\n\treturn s\n}\n\n// Name implements Server.\nfunc (s *ClassicNameServer) Name() string {\n\treturn s.cacheController.name\n}\n\n// IsDisableCache implements Server.\nfunc (s *ClassicNameServer) IsDisableCache() bool {\n\treturn s.cacheController.disableCache\n}\n\n// RequestsCleanup clears expired items from cache\nfunc (s *ClassicNameServer) RequestsCleanup() error {\n\tnow := time.Now()\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif len(s.requests) == 0 {\n\t\treturn errors.New(s.Name(), \" nothing to do. stopping...\")\n\t}\n\n\tfor id, req := range s.requests {\n\t\tif req.expire.Before(now) {\n\t\t\tdelete(s.requests, id)\n\t\t}\n\t}\n\n\tif len(s.requests) == 0 {\n\t\ts.requests = make(map[uint16]*udpDnsRequest)\n\t}\n\n\treturn nil\n}\n\n// HandleResponse handles udp response packet from remote DNS server.\nfunc (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {\n\tpayload := packet.Payload\n\tipRec, err := parseResponse(payload.Bytes())\n\tpayload.Release()\n\tif err != nil {\n\t\terrors.LogErrorInner(ctx, err, s.Name(), \" fail to parse responded DNS udp\")\n\t\treturn\n\t}\n\n\ts.Lock()\n\tid := ipRec.ReqID\n\treq, ok := s.requests[id]\n\tif ok {\n\t\t// remove the pending request\n\t\tdelete(s.requests, id)\n\t}\n\ts.Unlock()\n\tif !ok {\n\t\terrors.LogErrorInner(ctx, err, s.Name(), \" cannot find the pending request\")\n\t\treturn\n\t}\n\n\t// if truncated, retry with EDNS0 option(udp payload size: 1350)\n\tif ipRec.RawHeader.Truncated {\n\t\t// if already has EDNS0 option, no need to retry\n\t\tif len(req.msg.Additionals) == 0 {\n\t\t\t// copy necessary meta data from original request\n\t\t\t// and add EDNS0 option\n\t\t\topt := new(dnsmessage.Resource)\n\t\t\tcommon.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))\n\t\t\topt.Body = &dnsmessage.OPTResource{}\n\t\t\tnewMsg := *req.msg\n\t\t\tnewReq := *req\n\t\t\tnewMsg.Additionals = append(newMsg.Additionals, *opt)\n\t\t\tnewMsg.ID = s.newReqID()\n\t\t\tnewReq.msg = &newMsg\n\t\t\ts.addPendingRequest(&newReq)\n\t\t\tb, _ := dns.PackMessage(newReq.msg)\n\t\t\tcopyDest := net.UDPDestination(s.address.Address, s.address.Port)\n\t\t\tb.UDP = &copyDest\n\t\t\ts.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)\n\t\t\treturn\n\t\t}\n\t}\n\n\ts.cacheController.updateRecord(&req.dnsRequest, ipRec)\n}\n\nfunc (s *ClassicNameServer) newReqID() uint16 {\n\treturn uint16(atomic.AddUint32(&s.reqID, 1))\n}\n\nfunc (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {\n\ts.Lock()\n\tid := req.msg.ID\n\treq.expire = time.Now().Add(time.Second * 8)\n\ts.requests[id] = req\n\ts.Unlock()\n\tcommon.Must(s.requestsCleanup.Start())\n}\n\n// getCacheController implements CachedNameserver.\nfunc (s *ClassicNameServer) getCacheController() *CacheController {\n\treturn s.cacheController\n}\n\n// sendQuery implements CachedNameserver.\nfunc (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {\n\terrors.LogInfo(ctx, s.Name(), \" querying DNS for: \", fqdn)\n\n\treqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))\n\n\tfor _, req := range reqs {\n\t\tudpReq := &udpDnsRequest{\n\t\t\tdnsRequest: *req,\n\t\t\tctx:        ctx,\n\t\t}\n\t\ts.addPendingRequest(udpReq)\n\t\tb, err := dns.PackMessage(req.msg)\n\t\tif err != nil {\n\t\t\terrors.LogErrorInner(ctx, err, \"failed to pack dns query\")\n\t\t\tif noResponseErrCh != nil {\n\t\t\t\tnoResponseErrCh <- err\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tcopyDest := net.UDPDestination(s.address.Address, s.address.Port)\n\t\tb.UDP = &copyDest\n\t\ts.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)\n\t}\n}\n\n// QueryIP implements Server.\nfunc (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {\n\treturn queryIP(ctx, s, domain, option)\n}\n"
  },
  {
    "path": "app/log/command/command.go",
    "content": "package command\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\tgrpc \"google.golang.org/grpc\"\n)\n\ntype LoggerServer struct {\n\tV *core.Instance\n}\n\n// RestartLogger implements LoggerService.\nfunc (s *LoggerServer) RestartLogger(ctx context.Context, request *RestartLoggerRequest) (*RestartLoggerResponse, error) {\n\tlogger := s.V.GetFeature((*log.Instance)(nil))\n\tif logger == nil {\n\t\treturn nil, errors.New(\"unable to get logger instance\")\n\t}\n\tif err := logger.Close(); err != nil {\n\t\treturn nil, errors.New(\"failed to close logger\").Base(err)\n\t}\n\tif err := logger.Start(); err != nil {\n\t\treturn nil, errors.New(\"failed to start logger\").Base(err)\n\t}\n\treturn &RestartLoggerResponse{}, nil\n}\n\nfunc (s *LoggerServer) mustEmbedUnimplementedLoggerServiceServer() {}\n\ntype service struct {\n\tv *core.Instance\n}\n\nfunc (s *service) Register(server *grpc.Server) {\n\tls := &LoggerServer{\n\t\tV: s.v,\n\t}\n\tRegisterLoggerServiceServer(server, ls)\n\n\t// For compatibility purposes\n\tvCoreDesc := LoggerService_ServiceDesc\n\tvCoreDesc.ServiceName = \"v2ray.core.app.log.command.LoggerService\"\n\tserver.RegisterService(&vCoreDesc, ls)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\ts := core.MustFromContext(ctx)\n\t\treturn &service{v: s}, nil\n\t}))\n}\n"
  },
  {
    "path": "app/log/command/command_test.go",
    "content": "package command_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/log\"\n\t. \"github.com/xtls/xray-core/app/log/command\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/inbound\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n)\n\nfunc TestLoggerRestart(t *testing.T) {\n\tv, err := core.New(&core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t})\n\tcommon.Must(err)\n\tcommon.Must(v.Start())\n\n\tserver := &LoggerServer{\n\t\tV: v,\n\t}\n\tcommon.Must2(server.RestartLogger(context.Background(), &RestartLoggerRequest{}))\n}\n"
  },
  {
    "path": "app/log/command/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/log/command/config.proto\n\npackage command\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_log_command_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_log_command_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_log_command_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype RestartLoggerRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RestartLoggerRequest) Reset() {\n\t*x = RestartLoggerRequest{}\n\tmi := &file_app_log_command_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RestartLoggerRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RestartLoggerRequest) ProtoMessage() {}\n\nfunc (x *RestartLoggerRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_log_command_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RestartLoggerRequest.ProtoReflect.Descriptor instead.\nfunc (*RestartLoggerRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_log_command_config_proto_rawDescGZIP(), []int{1}\n}\n\ntype RestartLoggerResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RestartLoggerResponse) Reset() {\n\t*x = RestartLoggerResponse{}\n\tmi := &file_app_log_command_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RestartLoggerResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RestartLoggerResponse) ProtoMessage() {}\n\nfunc (x *RestartLoggerResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_log_command_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RestartLoggerResponse.ProtoReflect.Descriptor instead.\nfunc (*RestartLoggerResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_log_command_config_proto_rawDescGZIP(), []int{2}\n}\n\nvar File_app_log_command_config_proto protoreflect.FileDescriptor\n\nconst file_app_log_command_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1capp/log/command/config.proto\\x12\\x14xray.app.log.command\\\"\\b\\n\" +\n\t\"\\x06Config\\\"\\x16\\n\" +\n\t\"\\x14RestartLoggerRequest\\\"\\x17\\n\" +\n\t\"\\x15RestartLoggerResponse2{\\n\" +\n\t\"\\rLoggerService\\x12j\\n\" +\n\t\"\\rRestartLogger\\x12*.xray.app.log.command.RestartLoggerRequest\\x1a+.xray.app.log.command.RestartLoggerResponse\\\"\\x00B^\\n\" +\n\t\"\\x18com.xray.app.log.commandP\\x01Z)github.com/xtls/xray-core/app/log/command\\xaa\\x02\\x14Xray.App.Log.Commandb\\x06proto3\"\n\nvar (\n\tfile_app_log_command_config_proto_rawDescOnce sync.Once\n\tfile_app_log_command_config_proto_rawDescData []byte\n)\n\nfunc file_app_log_command_config_proto_rawDescGZIP() []byte {\n\tfile_app_log_command_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_log_command_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_log_command_config_proto_rawDesc), len(file_app_log_command_config_proto_rawDesc)))\n\t})\n\treturn file_app_log_command_config_proto_rawDescData\n}\n\nvar file_app_log_command_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_app_log_command_config_proto_goTypes = []any{\n\t(*Config)(nil),                // 0: xray.app.log.command.Config\n\t(*RestartLoggerRequest)(nil),  // 1: xray.app.log.command.RestartLoggerRequest\n\t(*RestartLoggerResponse)(nil), // 2: xray.app.log.command.RestartLoggerResponse\n}\nvar file_app_log_command_config_proto_depIdxs = []int32{\n\t1, // 0: xray.app.log.command.LoggerService.RestartLogger:input_type -> xray.app.log.command.RestartLoggerRequest\n\t2, // 1: xray.app.log.command.LoggerService.RestartLogger:output_type -> xray.app.log.command.RestartLoggerResponse\n\t1, // [1:2] is the sub-list for method output_type\n\t0, // [0:1] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_app_log_command_config_proto_init() }\nfunc file_app_log_command_config_proto_init() {\n\tif File_app_log_command_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_log_command_config_proto_rawDesc), len(file_app_log_command_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_app_log_command_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_log_command_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_log_command_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_log_command_config_proto = out.File\n\tfile_app_log_command_config_proto_goTypes = nil\n\tfile_app_log_command_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/log/command/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.log.command;\noption csharp_namespace = \"Xray.App.Log.Command\";\noption go_package = \"github.com/xtls/xray-core/app/log/command\";\noption java_package = \"com.xray.app.log.command\";\noption java_multiple_files = true;\n\nmessage Config {}\n\nmessage RestartLoggerRequest {}\n\nmessage RestartLoggerResponse {}\n\nservice LoggerService {\n  rpc RestartLogger(RestartLoggerRequest) returns (RestartLoggerResponse) {}\n}\n"
  },
  {
    "path": "app/log/command/config_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.0\n// - protoc             v6.33.5\n// source: app/log/command/config.proto\n\npackage command\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tLoggerService_RestartLogger_FullMethodName = \"/xray.app.log.command.LoggerService/RestartLogger\"\n)\n\n// LoggerServiceClient is the client API for LoggerService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype LoggerServiceClient interface {\n\tRestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error)\n}\n\ntype loggerServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewLoggerServiceClient(cc grpc.ClientConnInterface) LoggerServiceClient {\n\treturn &loggerServiceClient{cc}\n}\n\nfunc (c *loggerServiceClient) RestartLogger(ctx context.Context, in *RestartLoggerRequest, opts ...grpc.CallOption) (*RestartLoggerResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RestartLoggerResponse)\n\terr := c.cc.Invoke(ctx, LoggerService_RestartLogger_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// LoggerServiceServer is the server API for LoggerService service.\n// All implementations must embed UnimplementedLoggerServiceServer\n// for forward compatibility.\ntype LoggerServiceServer interface {\n\tRestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error)\n\tmustEmbedUnimplementedLoggerServiceServer()\n}\n\n// UnimplementedLoggerServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedLoggerServiceServer struct{}\n\nfunc (UnimplementedLoggerServiceServer) RestartLogger(context.Context, *RestartLoggerRequest) (*RestartLoggerResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RestartLogger not implemented\")\n}\nfunc (UnimplementedLoggerServiceServer) mustEmbedUnimplementedLoggerServiceServer() {}\nfunc (UnimplementedLoggerServiceServer) testEmbeddedByValue()                       {}\n\n// UnsafeLoggerServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to LoggerServiceServer will\n// result in compilation errors.\ntype UnsafeLoggerServiceServer interface {\n\tmustEmbedUnimplementedLoggerServiceServer()\n}\n\nfunc RegisterLoggerServiceServer(s grpc.ServiceRegistrar, srv LoggerServiceServer) {\n\t// If the following call panics, it indicates UnimplementedLoggerServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&LoggerService_ServiceDesc, srv)\n}\n\nfunc _LoggerService_RestartLogger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RestartLoggerRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LoggerServiceServer).RestartLogger(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: LoggerService_RestartLogger_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LoggerServiceServer).RestartLogger(ctx, req.(*RestartLoggerRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// LoggerService_ServiceDesc is the grpc.ServiceDesc for LoggerService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar LoggerService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"xray.app.log.command.LoggerService\",\n\tHandlerType: (*LoggerServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"RestartLogger\",\n\t\t\tHandler:    _LoggerService_RestartLogger_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"app/log/command/config.proto\",\n}\n"
  },
  {
    "path": "app/log/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/log/config.proto\n\npackage log\n\nimport (\n\tlog \"github.com/xtls/xray-core/common/log\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype LogType int32\n\nconst (\n\tLogType_None    LogType = 0\n\tLogType_Console LogType = 1\n\tLogType_File    LogType = 2\n\tLogType_Event   LogType = 3\n)\n\n// Enum value maps for LogType.\nvar (\n\tLogType_name = map[int32]string{\n\t\t0: \"None\",\n\t\t1: \"Console\",\n\t\t2: \"File\",\n\t\t3: \"Event\",\n\t}\n\tLogType_value = map[string]int32{\n\t\t\"None\":    0,\n\t\t\"Console\": 1,\n\t\t\"File\":    2,\n\t\t\"Event\":   3,\n\t}\n)\n\nfunc (x LogType) Enum() *LogType {\n\tp := new(LogType)\n\t*p = x\n\treturn p\n}\n\nfunc (x LogType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (LogType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_app_log_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (LogType) Type() protoreflect.EnumType {\n\treturn &file_app_log_config_proto_enumTypes[0]\n}\n\nfunc (x LogType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use LogType.Descriptor instead.\nfunc (LogType) EnumDescriptor() ([]byte, []int) {\n\treturn file_app_log_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tErrorLogType  LogType                `protobuf:\"varint,1,opt,name=error_log_type,json=errorLogType,proto3,enum=xray.app.log.LogType\" json:\"error_log_type,omitempty\"`\n\tErrorLogLevel log.Severity           `protobuf:\"varint,2,opt,name=error_log_level,json=errorLogLevel,proto3,enum=xray.common.log.Severity\" json:\"error_log_level,omitempty\"`\n\tErrorLogPath  string                 `protobuf:\"bytes,3,opt,name=error_log_path,json=errorLogPath,proto3\" json:\"error_log_path,omitempty\"`\n\tAccessLogType LogType                `protobuf:\"varint,4,opt,name=access_log_type,json=accessLogType,proto3,enum=xray.app.log.LogType\" json:\"access_log_type,omitempty\"`\n\tAccessLogPath string                 `protobuf:\"bytes,5,opt,name=access_log_path,json=accessLogPath,proto3\" json:\"access_log_path,omitempty\"`\n\tEnableDnsLog  bool                   `protobuf:\"varint,6,opt,name=enable_dns_log,json=enableDnsLog,proto3\" json:\"enable_dns_log,omitempty\"`\n\tMaskAddress   string                 `protobuf:\"bytes,7,opt,name=mask_address,json=maskAddress,proto3\" json:\"mask_address,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_log_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_log_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_log_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetErrorLogType() LogType {\n\tif x != nil {\n\t\treturn x.ErrorLogType\n\t}\n\treturn LogType_None\n}\n\nfunc (x *Config) GetErrorLogLevel() log.Severity {\n\tif x != nil {\n\t\treturn x.ErrorLogLevel\n\t}\n\treturn log.Severity(0)\n}\n\nfunc (x *Config) GetErrorLogPath() string {\n\tif x != nil {\n\t\treturn x.ErrorLogPath\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetAccessLogType() LogType {\n\tif x != nil {\n\t\treturn x.AccessLogType\n\t}\n\treturn LogType_None\n}\n\nfunc (x *Config) GetAccessLogPath() string {\n\tif x != nil {\n\t\treturn x.AccessLogPath\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetEnableDnsLog() bool {\n\tif x != nil {\n\t\treturn x.EnableDnsLog\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetMaskAddress() string {\n\tif x != nil {\n\t\treturn x.MaskAddress\n\t}\n\treturn \"\"\n}\n\nvar File_app_log_config_proto protoreflect.FileDescriptor\n\nconst file_app_log_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x14app/log/config.proto\\x12\\fxray.app.log\\x1a\\x14common/log/log.proto\\\"\\xde\\x02\\n\" +\n\t\"\\x06Config\\x12;\\n\" +\n\t\"\\x0eerror_log_type\\x18\\x01 \\x01(\\x0e2\\x15.xray.app.log.LogTypeR\\ferrorLogType\\x12A\\n\" +\n\t\"\\x0ferror_log_level\\x18\\x02 \\x01(\\x0e2\\x19.xray.common.log.SeverityR\\rerrorLogLevel\\x12$\\n\" +\n\t\"\\x0eerror_log_path\\x18\\x03 \\x01(\\tR\\ferrorLogPath\\x12=\\n\" +\n\t\"\\x0faccess_log_type\\x18\\x04 \\x01(\\x0e2\\x15.xray.app.log.LogTypeR\\raccessLogType\\x12&\\n\" +\n\t\"\\x0faccess_log_path\\x18\\x05 \\x01(\\tR\\raccessLogPath\\x12$\\n\" +\n\t\"\\x0eenable_dns_log\\x18\\x06 \\x01(\\bR\\fenableDnsLog\\x12!\\n\" +\n\t\"\\fmask_address\\x18\\a \\x01(\\tR\\vmaskAddress*5\\n\" +\n\t\"\\aLogType\\x12\\b\\n\" +\n\t\"\\x04None\\x10\\x00\\x12\\v\\n\" +\n\t\"\\aConsole\\x10\\x01\\x12\\b\\n\" +\n\t\"\\x04File\\x10\\x02\\x12\\t\\n\" +\n\t\"\\x05Event\\x10\\x03BF\\n\" +\n\t\"\\x10com.xray.app.logP\\x01Z!github.com/xtls/xray-core/app/log\\xaa\\x02\\fXray.App.Logb\\x06proto3\"\n\nvar (\n\tfile_app_log_config_proto_rawDescOnce sync.Once\n\tfile_app_log_config_proto_rawDescData []byte\n)\n\nfunc file_app_log_config_proto_rawDescGZIP() []byte {\n\tfile_app_log_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_log_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_log_config_proto_rawDesc), len(file_app_log_config_proto_rawDesc)))\n\t})\n\treturn file_app_log_config_proto_rawDescData\n}\n\nvar file_app_log_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_app_log_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_app_log_config_proto_goTypes = []any{\n\t(LogType)(0),      // 0: xray.app.log.LogType\n\t(*Config)(nil),    // 1: xray.app.log.Config\n\t(log.Severity)(0), // 2: xray.common.log.Severity\n}\nvar file_app_log_config_proto_depIdxs = []int32{\n\t0, // 0: xray.app.log.Config.error_log_type:type_name -> xray.app.log.LogType\n\t2, // 1: xray.app.log.Config.error_log_level:type_name -> xray.common.log.Severity\n\t0, // 2: xray.app.log.Config.access_log_type:type_name -> xray.app.log.LogType\n\t3, // [3:3] is the sub-list for method output_type\n\t3, // [3:3] is the sub-list for method input_type\n\t3, // [3:3] is the sub-list for extension type_name\n\t3, // [3:3] is the sub-list for extension extendee\n\t0, // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_app_log_config_proto_init() }\nfunc file_app_log_config_proto_init() {\n\tif File_app_log_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_log_config_proto_rawDesc), len(file_app_log_config_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_log_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_log_config_proto_depIdxs,\n\t\tEnumInfos:         file_app_log_config_proto_enumTypes,\n\t\tMessageInfos:      file_app_log_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_log_config_proto = out.File\n\tfile_app_log_config_proto_goTypes = nil\n\tfile_app_log_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/log/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.log;\noption csharp_namespace = \"Xray.App.Log\";\noption go_package = \"github.com/xtls/xray-core/app/log\";\noption java_package = \"com.xray.app.log\";\noption java_multiple_files = true;\n\nimport \"common/log/log.proto\";\n\nenum LogType {\n  None = 0;\n  Console = 1;\n  File = 2;\n  Event = 3;\n}\n\nmessage Config {\n  LogType error_log_type = 1;\n  xray.common.log.Severity error_log_level = 2;\n  string error_log_path = 3;\n\n  LogType access_log_type = 4;\n  string access_log_path = 5;\n  bool enable_dns_log = 6;\n  string mask_address= 7;\n}\n"
  },
  {
    "path": "app/log/log.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n)\n\n// Instance is a log.Handler that handles logs.\ntype Instance struct {\n\tsync.RWMutex\n\tconfig       *Config\n\taccessLogger log.Handler\n\terrorLogger  log.Handler\n\tactive       bool\n\tdns          bool\n\tmask4        int\n\tmask6        int\n}\n\n// New creates a new log.Instance based on the given config.\nfunc New(ctx context.Context, config *Config) (*Instance, error) {\n\tm4, m6, err := ParseMaskAddress(config.MaskAddress)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tg := &Instance{\n\t\tconfig: config,\n\t\tactive: false,\n\t\tdns:    config.EnableDnsLog,\n\t\tmask4:  m4,\n\t\tmask6:  m6,\n\t}\n\tlog.RegisterHandler(g)\n\n\t// start logger now,\n\t// then other modules will be able to log during initialization\n\tif err := g.startInternal(); err != nil {\n\t\treturn nil, err\n\t}\n\n\terrors.LogDebug(ctx, \"Logger started\")\n\treturn g, nil\n}\n\nfunc (g *Instance) initAccessLogger() error {\n\thandler, err := createHandler(g.config.AccessLogType, HandlerCreatorOptions{\n\t\tPath: g.config.AccessLogPath,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tg.accessLogger = handler\n\treturn nil\n}\n\nfunc (g *Instance) initErrorLogger() error {\n\thandler, err := createHandler(g.config.ErrorLogType, HandlerCreatorOptions{\n\t\tPath: g.config.ErrorLogPath,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tg.errorLogger = handler\n\treturn nil\n}\n\n// Type implements common.HasType.\nfunc (*Instance) Type() interface{} {\n\treturn (*Instance)(nil)\n}\n\nfunc (g *Instance) startInternal() error {\n\tg.Lock()\n\tdefer g.Unlock()\n\n\tif g.active {\n\t\treturn nil\n\t}\n\n\tg.active = true\n\n\tif err := g.initAccessLogger(); err != nil {\n\t\treturn errors.New(\"failed to initialize access logger\").Base(err).AtWarning()\n\t}\n\tif err := g.initErrorLogger(); err != nil {\n\t\treturn errors.New(\"failed to initialize error logger\").Base(err).AtWarning()\n\t}\n\n\treturn nil\n}\n\n// Start implements common.Runnable.Start().\nfunc (g *Instance) Start() error {\n\treturn g.startInternal()\n}\n\n// Handle implements log.Handler.\nfunc (g *Instance) Handle(msg log.Message) {\n\tg.RLock()\n\tdefer g.RUnlock()\n\n\tif !g.active {\n\t\treturn\n\t}\n\n\tvar Msg log.Message\n\tif g.config.MaskAddress != \"\" {\n\t\tMsg = &MaskedMsgWrapper{\n\t\t\tMessage: msg,\n\t\t\tMask4:   g.mask4,\n\t\t\tMask6:   g.mask6,\n\t\t}\n\t} else {\n\t\tMsg = msg\n\t}\n\n\tswitch msg := msg.(type) {\n\tcase *log.AccessMessage:\n\t\tif g.accessLogger != nil {\n\t\t\tg.accessLogger.Handle(Msg)\n\t\t}\n\tcase *log.DNSLog:\n\t\tif g.dns && g.accessLogger != nil {\n\t\t\tg.accessLogger.Handle(Msg)\n\t\t}\n\tcase *log.GeneralMessage:\n\t\tif g.errorLogger != nil && msg.Severity <= g.config.ErrorLogLevel {\n\t\t\tg.errorLogger.Handle(Msg)\n\t\t}\n\tdefault:\n\t\t// Swallow\n\t}\n}\n\n// Close implements common.Closable.Close().\nfunc (g *Instance) Close() error {\n\terrors.LogDebug(context.Background(), \"Logger closing\")\n\n\tg.Lock()\n\tdefer g.Unlock()\n\n\tif !g.active {\n\t\treturn nil\n\t}\n\n\tg.active = false\n\n\tcommon.Close(g.accessLogger)\n\tg.accessLogger = nil\n\n\tcommon.Close(g.errorLogger)\n\tg.errorLogger = nil\n\n\treturn nil\n}\n\nfunc ParseMaskAddress(c string) (int, int, error) {\n\tvar m4, m6 int\n\tswitch c {\n\tcase \"half\":\n\t\tm4, m6 = 16, 32\n\tcase \"quarter\":\n\t\tm4, m6 = 8, 16\n\tcase \"full\":\n\t\tm4, m6 = 0, 0\n\tcase \"\":\n\t\t// do nothing\n\tdefault:\n\t\tif parts := strings.Split(c, \"+\"); len(parts) > 0 {\n\t\t\tif len(parts) >= 1 && parts[0] != \"\" {\n\t\t\t\ti, err := strconv.Atoi(strings.TrimPrefix(parts[0], \"/\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 32, 128, err\n\t\t\t\t}\n\t\t\t\tm4 = i\n\t\t\t}\n\t\t\tif len(parts) >= 2 && parts[1] != \"\" {\n\t\t\t\ti, err := strconv.Atoi(strings.TrimPrefix(parts[1], \"/\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 32, 128, err\n\t\t\t\t}\n\t\t\t\tm6 = i\n\t\t\t}\n\t\t}\n\t}\n\n\tif m4%8 != 0 || m4 > 32 || m4 < 0 {\n\t\treturn 32, 128, errors.New(\"Log Mask: ipv4 mask must be divisible by 8 and between 0-32\")\n\t}\n\n\treturn m4, m6, nil\n}\n\n// MaskedMsgWrapper is to wrap the string() method to mask IP addresses in the log.\ntype MaskedMsgWrapper struct {\n\tlog.Message\n\tMask4 int\n\tMask6 int\n}\n\nvar (\n\tipv4Regex = regexp.MustCompile(`(\\d{1,3}\\.){3}\\d{1,3}`)\n\tipv6Regex = regexp.MustCompile(`(?:[\\da-fA-F]{0,4}:[\\da-fA-F]{0,4}){2,7}`)\n)\n\nfunc (m *MaskedMsgWrapper) String() string {\n\tstr := m.Message.String()\n\n\t// Process ipv4\n\tmaskedMsg := ipv4Regex.ReplaceAllStringFunc(str, func(s string) string {\n\t\tif m.Mask4 == 32 {\n\t\t\treturn s\n\t\t}\n\t\tif m.Mask4 == 0 {\n\t\t\treturn \"[Masked IPv4]\"\n\t\t}\n\n\t\tparts := strings.Split(s, \".\")\n\t\tfor i := m.Mask4 / 8; i < 4; i++ {\n\t\t\tparts[i] = \"*\"\n\t\t}\n\t\treturn strings.Join(parts, \".\")\n\t})\n\n\t// process ipv6\n\tmaskedMsg = ipv6Regex.ReplaceAllStringFunc(maskedMsg, func(s string) string {\n\t\tif m.Mask6 == 128 {\n\t\t\treturn s\n\t\t}\n\t\tif m.Mask6 == 0 {\n\t\t\treturn \"Masked IPv6\"\n\t\t}\n\t\tip := net.ParseIP(s)\n\t\tif ip == nil {\n\t\t\treturn s\n\t\t}\n\t\treturn ip.Mask(net.CIDRMask(m.Mask6, 128)).String() + \"/\" + strconv.Itoa(m.Mask6)\n\t})\n\n\treturn maskedMsg\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/log/log_creator.go",
    "content": "package log\n\nimport (\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n)\n\ntype HandlerCreatorOptions struct {\n\tPath string\n}\n\ntype HandlerCreator func(LogType, HandlerCreatorOptions) (log.Handler, error)\n\nvar handlerCreatorMap = make(map[LogType]HandlerCreator)\n\nvar handlerCreatorMapLock = &sync.RWMutex{}\n\nfunc RegisterHandlerCreator(logType LogType, f HandlerCreator) error {\n\tif f == nil {\n\t\treturn errors.New(\"nil HandlerCreator\")\n\t}\n\n\thandlerCreatorMapLock.Lock()\n\tdefer handlerCreatorMapLock.Unlock()\n\n\thandlerCreatorMap[logType] = f\n\treturn nil\n}\n\nfunc createHandler(logType LogType, options HandlerCreatorOptions) (log.Handler, error) {\n\thandlerCreatorMapLock.RLock()\n\tdefer handlerCreatorMapLock.RUnlock()\n\n\tcreator, found := handlerCreatorMap[logType]\n\tif !found {\n\t\treturn nil, errors.New(\"unable to create log handler for \", logType)\n\t}\n\treturn creator(logType, options)\n}\n\nfunc init() {\n\tcommon.Must(RegisterHandlerCreator(LogType_Console, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) {\n\t\treturn log.NewLogger(log.CreateStdoutLogWriter()), nil\n\t}))\n\n\tcommon.Must(RegisterHandlerCreator(LogType_File, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) {\n\t\tcreator, err := log.CreateFileLogWriter(options.Path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn log.NewLogger(creator), nil\n\t}))\n\n\tcommon.Must(RegisterHandlerCreator(LogType_None, func(lt LogType, options HandlerCreatorOptions) (log.Handler, error) {\n\t\treturn nil, nil\n\t}))\n}\n"
  },
  {
    "path": "app/log/log_test.go",
    "content": "package log_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/testing/mocks\"\n)\n\nfunc TestCustomLogHandler(t *testing.T) {\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tvar loggedValue []string\n\n\tmockHandler := mocks.NewLogHandler(mockCtl)\n\tmockHandler.EXPECT().Handle(gomock.Any()).AnyTimes().DoAndReturn(func(msg clog.Message) {\n\t\tloggedValue = append(loggedValue, msg.String())\n\t})\n\n\tlog.RegisterHandlerCreator(log.LogType_Console, func(lt log.LogType, options log.HandlerCreatorOptions) (clog.Handler, error) {\n\t\treturn mockHandler, nil\n\t})\n\n\tlogger, err := log.New(context.Background(), &log.Config{\n\t\tErrorLogLevel: clog.Severity_Debug,\n\t\tErrorLogType:  log.LogType_Console,\n\t\tAccessLogType: log.LogType_None,\n\t})\n\tcommon.Must(err)\n\n\tcommon.Must(logger.Start())\n\n\tclog.Record(&clog.GeneralMessage{\n\t\tSeverity: clog.Severity_Debug,\n\t\tContent:  \"test\",\n\t})\n\n\tif len(loggedValue) < 2 {\n\t\tt.Fatal(\"expected 2 log messages, but actually \", loggedValue)\n\t}\n\n\tif loggedValue[1] != \"[Debug] test\" {\n\t\tt.Fatal(\"expected '[Debug] test', but actually \", loggedValue[1])\n\t}\n\n\tcommon.Must(logger.Close())\n}\n\nfunc TestMaskAddress(t *testing.T) {\n\tm4, m6, err := log.ParseMaskAddress(\"half\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmaskedAddr := log.MaskedMsgWrapper{\n\t\tMask4: m4,\n\t\tMask6: m6,\n\t}\n\tmaskedAddr.Message = net.ParseIP(\"11.45.1.4\")\n\tif maskedAddr.String() != \"11.45.*.*\" {\n\t\tt.Fatal(\"expected '11.45.*.*', but actually \", maskedAddr.String())\n\t}\n\tmaskedAddr.Message = net.ParseIP(\"11:45:14:19:19:81:0::\")\n\tif maskedAddr.String() != \"11:45::/32\" {\n\t\tt.Fatal(\"expected '11:45::/32', but actually\", maskedAddr.String())\n\t}\n\n\tm4, m6, err = log.ParseMaskAddress(\"/16+/64\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmaskedAddr = log.MaskedMsgWrapper{\n\t\tMask4: m4,\n\t\tMask6: m6,\n\t}\n\tmaskedAddr.Message = net.ParseIP(\"11.45.1.4\")\n\tif maskedAddr.String() != \"11.45.*.*\" {\n\t\tt.Fatal(\"expected '11.45.*.*', but actually \", maskedAddr.String())\n\t}\n\tmaskedAddr.Message = net.ParseIP(\"11:45:14:19:19:81:0::\")\n\tif maskedAddr.String() != \"11:45:14:19::/64\" {\n\t\tt.Fatal(\"expected '11:45:14:19::/64', but actually\", maskedAddr.String())\n\t}\n}\n"
  },
  {
    "path": "app/metrics/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/metrics/config.proto\n\npackage metrics\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Config is the settings for metrics.\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Tag of the outbound handler that handles metrics http connections.\n\tTag           string `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tListen        string `protobuf:\"bytes,2,opt,name=listen,proto3\" json:\"listen,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_metrics_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_metrics_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_metrics_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetListen() string {\n\tif x != nil {\n\t\treturn x.Listen\n\t}\n\treturn \"\"\n}\n\nvar File_app_metrics_config_proto protoreflect.FileDescriptor\n\nconst file_app_metrics_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x18app/metrics/config.proto\\x12\\x10xray.app.metrics\\\"2\\n\" +\n\t\"\\x06Config\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x16\\n\" +\n\t\"\\x06listen\\x18\\x02 \\x01(\\tR\\x06listenBR\\n\" +\n\t\"\\x14com.xray.app.metricsP\\x01Z%github.com/xtls/xray-core/app/metrics\\xaa\\x02\\x10Xray.App.Metricsb\\x06proto3\"\n\nvar (\n\tfile_app_metrics_config_proto_rawDescOnce sync.Once\n\tfile_app_metrics_config_proto_rawDescData []byte\n)\n\nfunc file_app_metrics_config_proto_rawDescGZIP() []byte {\n\tfile_app_metrics_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_metrics_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_metrics_config_proto_rawDesc), len(file_app_metrics_config_proto_rawDesc)))\n\t})\n\treturn file_app_metrics_config_proto_rawDescData\n}\n\nvar file_app_metrics_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_app_metrics_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.app.metrics.Config\n}\nvar file_app_metrics_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_app_metrics_config_proto_init() }\nfunc file_app_metrics_config_proto_init() {\n\tif File_app_metrics_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_metrics_config_proto_rawDesc), len(file_app_metrics_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_metrics_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_metrics_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_metrics_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_metrics_config_proto = out.File\n\tfile_app_metrics_config_proto_goTypes = nil\n\tfile_app_metrics_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/metrics/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.metrics;\noption csharp_namespace = \"Xray.App.Metrics\";\noption go_package = \"github.com/xtls/xray-core/app/metrics\";\noption java_package = \"com.xray.app.metrics\";\noption java_multiple_files = true;\n\n// Config is the settings for metrics.\nmessage Config {\n  // Tag of the outbound handler that handles metrics http connections.\n  string tag = 1;\n  string listen = 2;\n}\n"
  },
  {
    "path": "app/metrics/metrics.go",
    "content": "package metrics\n\nimport (\n\t\"context\"\n\t\"expvar\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\tfeature_stats \"github.com/xtls/xray-core/features/stats\"\n)\n\ntype MetricsHandler struct {\n\tohm          outbound.Manager\n\tstatsManager feature_stats.Manager\n\tobservatory  extension.Observatory\n\ttag          string\n\tlisten       string\n\ttcpListener  net.Listener\n}\n\n// NewMetricsHandler creates a new MetricsHandler based on the given config.\nfunc NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {\n\tc := &MetricsHandler{\n\t\ttag:    config.Tag,\n\t\tlisten: config.Listen,\n\t}\n\tcommon.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) {\n\t\tc.statsManager = sm\n\t\tc.ohm = om\n\t}))\n\texpvar.Publish(\"stats\", expvar.Func(func() interface{} {\n\t\tmanager, ok := c.statsManager.(*stats.Manager)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\tresp := map[string]map[string]map[string]int64{\n\t\t\t\"inbound\":  {},\n\t\t\t\"outbound\": {},\n\t\t\t\"user\":     {},\n\t\t}\n\t\tmanager.VisitCounters(func(name string, counter feature_stats.Counter) bool {\n\t\t\tnameSplit := strings.Split(name, \">>>\")\n\t\t\ttypeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]\n\t\t\tif item, found := resp[typeName][tagOrUser]; found {\n\t\t\t\titem[direction] = counter.Value()\n\t\t\t} else {\n\t\t\t\tresp[typeName][tagOrUser] = map[string]int64{\n\t\t\t\t\tdirection: counter.Value(),\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\treturn resp\n\t}))\n\texpvar.Publish(\"observatory\", expvar.Func(func() interface{} {\n\t\tif c.observatory == nil {\n\t\t\tcommon.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {\n\t\t\t\tc.observatory = observatory\n\t\t\t\treturn nil\n\t\t\t}))\n\t\t\tif c.observatory == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tresp := map[string]*observatory.OutboundStatus{}\n\t\tif o, err := c.observatory.GetObservation(context.Background()); err != nil {\n\t\t\treturn err\n\t\t} else {\n\t\t\tfor _, x := range o.(*observatory.ObservationResult).GetStatus() {\n\t\t\t\tresp[x.OutboundTag] = x\n\t\t\t}\n\t\t}\n\t\treturn resp\n\t}))\n\treturn c, nil\n}\n\nfunc (p *MetricsHandler) Type() interface{} {\n\treturn (*MetricsHandler)(nil)\n}\n\nfunc (p *MetricsHandler) Start() error {\n\n\t// direct listen a port if listen is set\n\tif p.listen != \"\" {\n\t\tTCPlistener, err := net.Listen(\"tcp\", p.listen)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tp.tcpListener = TCPlistener\n\t\terrors.LogInfo(context.Background(), \"Metrics server listening on \", p.listen)\n\n\t\tgo func() {\n\t\t\tif err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {\n\t\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to start metrics server\")\n\t\t\t}\n\t\t}()\n\t}\n\n\tlistener := &OutboundListener{\n\t\tbuffer: make(chan net.Conn, 4),\n\t\tdone:   done.New(),\n\t}\n\n\tgo func() {\n\t\tif err := http.Serve(listener, http.DefaultServeMux); err != nil {\n\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to start metrics server\")\n\t\t}\n\t}()\n\n\tif err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {\n\t\terrors.LogInfo(context.Background(), \"failed to remove existing handler\")\n\t}\n\n\treturn p.ohm.AddHandler(context.Background(), &Outbound{\n\t\ttag:      p.tag,\n\t\tlistener: listener,\n\t})\n}\n\nfunc (p *MetricsHandler) Close() error {\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\treturn NewMetricsHandler(ctx, cfg.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/metrics/outbound.go",
    "content": "package metrics\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\n// OutboundListener is a net.Listener for listening metrics http connections.\ntype OutboundListener struct {\n\tbuffer chan net.Conn\n\tdone   *done.Instance\n}\n\nfunc (l *OutboundListener) add(conn net.Conn) {\n\tselect {\n\tcase l.buffer <- conn:\n\tcase <-l.done.Wait():\n\t\tconn.Close()\n\tdefault:\n\t\tconn.Close()\n\t}\n}\n\n// Accept implements net.Listener.\nfunc (l *OutboundListener) Accept() (net.Conn, error) {\n\tselect {\n\tcase <-l.done.Wait():\n\t\treturn nil, errors.New(\"listen closed\")\n\tcase c := <-l.buffer:\n\t\treturn c, nil\n\t}\n}\n\n// Close implement net.Listener.\nfunc (l *OutboundListener) Close() error {\n\tcommon.Must(l.done.Close())\nL:\n\tfor {\n\t\tselect {\n\t\tcase c := <-l.buffer:\n\t\t\tc.Close()\n\t\tdefault:\n\t\t\tbreak L\n\t\t}\n\t}\n\treturn nil\n}\n\n// Addr implements net.Listener.\nfunc (l *OutboundListener) Addr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP:   net.IP{0, 0, 0, 0},\n\t\tPort: 0,\n\t}\n}\n\n// Outbound is an outbound.Handler that handles metrics http connections.\ntype Outbound struct {\n\ttag      string\n\tlistener *OutboundListener\n\taccess   sync.RWMutex\n\tclosed   bool\n}\n\n// Dispatch implements outbound.Handler.\nfunc (co *Outbound) Dispatch(ctx context.Context, link *transport.Link) {\n\tco.access.RLock()\n\n\tif co.closed {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\tco.access.RUnlock()\n\t\treturn\n\t}\n\n\tcloseSignal := done.New()\n\tc := cnc.NewConnection(cnc.ConnectionInputMulti(link.Writer), cnc.ConnectionOutputMulti(link.Reader), cnc.ConnectionOnClose(closeSignal))\n\tco.listener.add(c)\n\tco.access.RUnlock()\n\t<-closeSignal.Wait()\n}\n\n// Tag implements outbound.Handler.\nfunc (co *Outbound) Tag() string {\n\treturn co.tag\n}\n\n// Start implements common.Runnable.\nfunc (co *Outbound) Start() error {\n\tco.access.Lock()\n\tco.closed = false\n\tco.access.Unlock()\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (co *Outbound) Close() error {\n\tco.access.Lock()\n\tdefer co.access.Unlock()\n\n\tco.closed = true\n\treturn co.listener.Close()\n}\n\n// SenderSettings implements outbound.Handler.\nfunc (co *Outbound) SenderSettings() *serial.TypedMessage {\n\treturn nil\n}\n\n// ProxySettings implements outbound.Handler.\nfunc (co *Outbound) ProxySettings() *serial.TypedMessage {\n\treturn nil\n}\n"
  },
  {
    "path": "app/observatory/burst/burst.go",
    "content": "package burst\n\nimport (\n\t\"math\"\n\t\"time\"\n)\n\nconst (\n\trttFailed = time.Duration(math.MaxInt64 - iota)\n\trttUntested\n\trttUnqualified\n)\n"
  },
  {
    "path": "app/observatory/burst/burstobserver.go",
    "content": "package burst\n\nimport (\n\t\"context\"\n\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype Observer struct {\n\tconfig *Config\n\tctx    context.Context\n\n\tstatusLock sync.Mutex\n\thp         *HealthPing\n\n\tfinished *done.Instance\n\n\tohm outbound.Manager\n}\n\nfunc (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) {\n\treturn &observatory.ObservationResult{Status: o.createResult()}, nil\n}\n\nfunc (o *Observer) createResult() []*observatory.OutboundStatus {\n\tvar result []*observatory.OutboundStatus\n\to.hp.access.Lock()\n\tdefer o.hp.access.Unlock()\n\tfor name, value := range o.hp.Results {\n\t\tstatus := observatory.OutboundStatus{\n\t\t\tAlive:           value.getStatistics().All != value.getStatistics().Fail,\n\t\t\tDelay:           value.getStatistics().Average.Milliseconds(),\n\t\t\tLastErrorReason: \"\",\n\t\t\tOutboundTag:     name,\n\t\t\tLastSeenTime:    0,\n\t\t\tLastTryTime:     0,\n\t\t\tHealthPing: &observatory.HealthPingMeasurementResult{\n\t\t\t\tAll:       int64(value.getStatistics().All),\n\t\t\t\tFail:      int64(value.getStatistics().Fail),\n\t\t\t\tDeviation: int64(value.getStatistics().Deviation),\n\t\t\t\tAverage:   int64(value.getStatistics().Average),\n\t\t\t\tMax:       int64(value.getStatistics().Max),\n\t\t\t\tMin:       int64(value.getStatistics().Min),\n\t\t\t},\n\t\t}\n\t\tresult = append(result, &status)\n\t}\n\treturn result\n}\n\nfunc (o *Observer) Type() interface{} {\n\treturn extension.ObservatoryType()\n}\n\nfunc (o *Observer) Start() error {\n\tif o.config != nil && len(o.config.SubjectSelector) != 0 {\n\t\to.finished = done.New()\n\t\to.hp.StartScheduler(func() ([]string, error) {\n\t\t\ths, ok := o.ohm.(outbound.HandlerSelector)\n\t\t\tif !ok {\n\n\t\t\t\treturn nil, errors.New(\"outbound.Manager is not a HandlerSelector\")\n\t\t\t}\n\n\t\t\toutbounds := hs.Select(o.config.SubjectSelector)\n\t\t\treturn outbounds, nil\n\t\t})\n\t}\n\treturn nil\n}\n\nfunc (o *Observer) Close() error {\n\tif o.finished != nil {\n\t\to.hp.StopScheduler()\n\t\treturn o.finished.Close()\n\t}\n\treturn nil\n}\n\nfunc New(ctx context.Context, config *Config) (*Observer, error) {\n\tvar outboundManager outbound.Manager\n\tvar dispatcher routing.Dispatcher\n\terr := core.RequireFeatures(ctx, func(om outbound.Manager, rd routing.Dispatcher) {\n\t\toutboundManager = om\n\t\tdispatcher = rd\n\t})\n\tif err != nil {\n\t\treturn nil, errors.New(\"Cannot get depended features\").Base(err)\n\t}\n\thp := NewHealthPing(ctx, dispatcher, config.PingConfig)\n\treturn &Observer{\n\t\tconfig: config,\n\t\tctx:    ctx,\n\t\tohm:    outboundManager,\n\t\thp:     hp,\n\t}, nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/observatory/burst/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/observatory/burst/config.proto\n\npackage burst\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// @Document The selectors for outbound under observation\n\tSubjectSelector []string          `protobuf:\"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3\" json:\"subject_selector,omitempty\"`\n\tPingConfig      *HealthPingConfig `protobuf:\"bytes,3,opt,name=ping_config,json=pingConfig,proto3\" json:\"ping_config,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_observatory_burst_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_burst_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_burst_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetSubjectSelector() []string {\n\tif x != nil {\n\t\treturn x.SubjectSelector\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetPingConfig() *HealthPingConfig {\n\tif x != nil {\n\t\treturn x.PingConfig\n\t}\n\treturn nil\n}\n\ntype HealthPingConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// destination url, need 204 for success return\n\t// default https://connectivitycheck.gstatic.com/generate_204\n\tDestination string `protobuf:\"bytes,1,opt,name=destination,proto3\" json:\"destination,omitempty\"`\n\t// connectivity check url\n\tConnectivity string `protobuf:\"bytes,2,opt,name=connectivity,proto3\" json:\"connectivity,omitempty\"`\n\t// health check interval, int64 values of time.Duration\n\tInterval int64 `protobuf:\"varint,3,opt,name=interval,proto3\" json:\"interval,omitempty\"`\n\t// sampling count is the amount of recent ping results which are kept for calculation\n\tSamplingCount int32 `protobuf:\"varint,4,opt,name=samplingCount,proto3\" json:\"samplingCount,omitempty\"`\n\t// ping timeout, int64 values of time.Duration\n\tTimeout int64 `protobuf:\"varint,5,opt,name=timeout,proto3\" json:\"timeout,omitempty\"`\n\t// http method to make request\n\tHttpMethod    string `protobuf:\"bytes,6,opt,name=httpMethod,proto3\" json:\"httpMethod,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *HealthPingConfig) Reset() {\n\t*x = HealthPingConfig{}\n\tmi := &file_app_observatory_burst_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HealthPingConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HealthPingConfig) ProtoMessage() {}\n\nfunc (x *HealthPingConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_burst_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HealthPingConfig.ProtoReflect.Descriptor instead.\nfunc (*HealthPingConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_burst_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *HealthPingConfig) GetDestination() string {\n\tif x != nil {\n\t\treturn x.Destination\n\t}\n\treturn \"\"\n}\n\nfunc (x *HealthPingConfig) GetConnectivity() string {\n\tif x != nil {\n\t\treturn x.Connectivity\n\t}\n\treturn \"\"\n}\n\nfunc (x *HealthPingConfig) GetInterval() int64 {\n\tif x != nil {\n\t\treturn x.Interval\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingConfig) GetSamplingCount() int32 {\n\tif x != nil {\n\t\treturn x.SamplingCount\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingConfig) GetTimeout() int64 {\n\tif x != nil {\n\t\treturn x.Timeout\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingConfig) GetHttpMethod() string {\n\tif x != nil {\n\t\treturn x.HttpMethod\n\t}\n\treturn \"\"\n}\n\nvar File_app_observatory_burst_config_proto protoreflect.FileDescriptor\n\nconst file_app_observatory_burst_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\\"app/observatory/burst/config.proto\\x12\\x1fxray.core.app.observatory.burst\\\"\\x87\\x01\\n\" +\n\t\"\\x06Config\\x12)\\n\" +\n\t\"\\x10subject_selector\\x18\\x02 \\x03(\\tR\\x0fsubjectSelector\\x12R\\n\" +\n\t\"\\vping_config\\x18\\x03 \\x01(\\v21.xray.core.app.observatory.burst.HealthPingConfigR\\n\" +\n\t\"pingConfig\\\"\\xd4\\x01\\n\" +\n\t\"\\x10HealthPingConfig\\x12 \\n\" +\n\t\"\\vdestination\\x18\\x01 \\x01(\\tR\\vdestination\\x12\\\"\\n\" +\n\t\"\\fconnectivity\\x18\\x02 \\x01(\\tR\\fconnectivity\\x12\\x1a\\n\" +\n\t\"\\binterval\\x18\\x03 \\x01(\\x03R\\binterval\\x12$\\n\" +\n\t\"\\rsamplingCount\\x18\\x04 \\x01(\\x05R\\rsamplingCount\\x12\\x18\\n\" +\n\t\"\\atimeout\\x18\\x05 \\x01(\\x03R\\atimeout\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"httpMethod\\x18\\x06 \\x01(\\tR\\n\" +\n\t\"httpMethodBp\\n\" +\n\t\"\\x1ecom.xray.app.observatory.burstP\\x01Z/github.com/xtls/xray-core/app/observatory/burst\\xaa\\x02\\x1aXray.App.Observatory.Burstb\\x06proto3\"\n\nvar (\n\tfile_app_observatory_burst_config_proto_rawDescOnce sync.Once\n\tfile_app_observatory_burst_config_proto_rawDescData []byte\n)\n\nfunc file_app_observatory_burst_config_proto_rawDescGZIP() []byte {\n\tfile_app_observatory_burst_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_observatory_burst_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_observatory_burst_config_proto_rawDesc), len(file_app_observatory_burst_config_proto_rawDesc)))\n\t})\n\treturn file_app_observatory_burst_config_proto_rawDescData\n}\n\nvar file_app_observatory_burst_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_app_observatory_burst_config_proto_goTypes = []any{\n\t(*Config)(nil),           // 0: xray.core.app.observatory.burst.Config\n\t(*HealthPingConfig)(nil), // 1: xray.core.app.observatory.burst.HealthPingConfig\n}\nvar file_app_observatory_burst_config_proto_depIdxs = []int32{\n\t1, // 0: xray.core.app.observatory.burst.Config.ping_config:type_name -> xray.core.app.observatory.burst.HealthPingConfig\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_app_observatory_burst_config_proto_init() }\nfunc file_app_observatory_burst_config_proto_init() {\n\tif File_app_observatory_burst_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_observatory_burst_config_proto_rawDesc), len(file_app_observatory_burst_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_observatory_burst_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_observatory_burst_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_observatory_burst_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_observatory_burst_config_proto = out.File\n\tfile_app_observatory_burst_config_proto_goTypes = nil\n\tfile_app_observatory_burst_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/observatory/burst/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.core.app.observatory.burst;\noption csharp_namespace = \"Xray.App.Observatory.Burst\";\noption go_package = \"github.com/xtls/xray-core/app/observatory/burst\";\noption java_package = \"com.xray.app.observatory.burst\";\noption java_multiple_files = true;\n\nmessage Config {\n  /* @Document The selectors for outbound under observation\n  */\n  repeated string subject_selector = 2;\n\n  HealthPingConfig ping_config = 3;\n}\n\nmessage HealthPingConfig {\n  // destination url, need 204 for success return\n  // default https://connectivitycheck.gstatic.com/generate_204\n  string destination = 1;\n  // connectivity check url\n  string connectivity = 2;\n  // health check interval, int64 values of time.Duration\n  int64 interval = 3;\n  // sampling count is the amount of recent ping results which are kept for calculation\n  int32 samplingCount = 4;\n  // ping timeout, int64 values of time.Duration\n  int64 timeout = 5;\n  // http method to make request\n  string httpMethod = 6;\n\n}\n"
  },
  {
    "path": "app/observatory/burst/healthping.go",
    "content": "package burst\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\n// HealthPingSettings holds settings for health Checker\ntype HealthPingSettings struct {\n\tDestination   string        `json:\"destination\"`\n\tConnectivity  string        `json:\"connectivity\"`\n\tInterval      time.Duration `json:\"interval\"`\n\tSamplingCount int           `json:\"sampling\"`\n\tTimeout       time.Duration `json:\"timeout\"`\n\tHttpMethod    string        `json:\"httpMethod\"`\n}\n\n// HealthPing is the health checker for balancers\ntype HealthPing struct {\n\tctx         context.Context\n\tdispatcher  routing.Dispatcher\n\taccess      sync.Mutex\n\tticker      *time.Ticker\n\ttickerClose chan struct{}\n\n\tSettings *HealthPingSettings\n\tResults  map[string]*HealthPingRTTS\n}\n\n// NewHealthPing creates a new HealthPing with settings\nfunc NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *HealthPingConfig) *HealthPing {\n\tsettings := &HealthPingSettings{}\n\tif config != nil {\n\n\t\tvar httpMethod string\n\t\tif config.HttpMethod == \"\" {\n\t\t\thttpMethod = \"HEAD\"\n\t\t} else {\n\t\t\thttpMethod = strings.TrimSpace(config.HttpMethod)\n\t\t}\n\n\t\tsettings = &HealthPingSettings{\n\t\t\tConnectivity:  strings.TrimSpace(config.Connectivity),\n\t\t\tDestination:   strings.TrimSpace(config.Destination),\n\t\t\tInterval:      time.Duration(config.Interval),\n\t\t\tSamplingCount: int(config.SamplingCount),\n\t\t\tTimeout:       time.Duration(config.Timeout),\n\t\t\tHttpMethod:    httpMethod,\n\t\t}\n\t}\n\tif settings.Destination == \"\" {\n\t\t// Destination URL, need 204 for success return default to chromium\n\t\t// https://github.com/chromium/chromium/blob/main/components/safety_check/url_constants.cc#L10\n\t\t// https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/safety_check/url_constants.cc#10\n\t\tsettings.Destination = \"https://connectivitycheck.gstatic.com/generate_204\"\n\t}\n\tif settings.Interval == 0 {\n\t\tsettings.Interval = time.Duration(1) * time.Minute\n\t} else if settings.Interval < 10 {\n\t\terrors.LogWarning(ctx, \"health check interval is too small, 10s is applied\")\n\t\tsettings.Interval = time.Duration(10) * time.Second\n\t}\n\tif settings.SamplingCount <= 0 {\n\t\tsettings.SamplingCount = 10\n\t}\n\tif settings.Timeout <= 0 {\n\t\t// results are saved after all health pings finish,\n\t\t// a larger timeout could possibly makes checks run longer\n\t\tsettings.Timeout = time.Duration(5) * time.Second\n\t}\n\treturn &HealthPing{\n\t\tctx:        ctx,\n\t\tdispatcher: dispatcher,\n\t\tSettings:   settings,\n\t\tResults:    nil,\n\t}\n}\n\n// StartScheduler implements the HealthChecker\nfunc (h *HealthPing) StartScheduler(selector func() ([]string, error)) {\n\tif h.ticker != nil {\n\t\treturn\n\t}\n\tinterval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)\n\tticker := time.NewTicker(interval)\n\ttickerClose := make(chan struct{})\n\th.ticker = ticker\n\th.tickerClose = tickerClose\n\tgo func() {\n\t\ttags, err := selector()\n\t\tif err != nil {\n\t\t\terrors.LogWarning(h.ctx, \"error select outbounds for initial health check: \", err)\n\t\t\treturn\n\t\t}\n\t\th.Check(tags)\n\t}()\n\n\tgo func() {\n\t\tfor {\n\t\t\tgo func() {\n\t\t\t\ttags, err := selector()\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogWarning(h.ctx, \"error select outbounds for scheduled health check: \", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\th.doCheck(tags, interval, h.Settings.SamplingCount)\n\t\t\t\th.Cleanup(tags)\n\t\t\t}()\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\tcontinue\n\t\t\tcase <-tickerClose:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// StopScheduler implements the HealthChecker\nfunc (h *HealthPing) StopScheduler() {\n\tif h.ticker == nil {\n\t\treturn\n\t}\n\th.ticker.Stop()\n\th.ticker = nil\n\tclose(h.tickerClose)\n\th.tickerClose = nil\n}\n\n// Check implements the HealthChecker\nfunc (h *HealthPing) Check(tags []string) error {\n\tif len(tags) == 0 {\n\t\treturn nil\n\t}\n\terrors.LogInfo(h.ctx, \"perform one-time health check for tags \", tags)\n\th.doCheck(tags, 0, 1)\n\treturn nil\n}\n\ntype rtt struct {\n\thandler string\n\tvalue   time.Duration\n}\n\n// doCheck performs the 'rounds' amount checks in given 'duration'. You should make\n// sure all tags are valid for current balancer\nfunc (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {\n\tcount := len(tags) * rounds\n\tif count == 0 {\n\t\treturn\n\t}\n\tch := make(chan *rtt, count)\n\n\tfor _, tag := range tags {\n\t\thandler := tag\n\t\tclient := newPingClient(\n\t\t\th.ctx,\n\t\t\th.dispatcher,\n\t\t\th.Settings.Destination,\n\t\t\th.Settings.Timeout,\n\t\t\thandler,\n\t\t)\n\t\tfor i := 0; i < rounds; i++ {\n\t\t\tdelay := time.Duration(0)\n\t\t\tif duration > 0 {\n\t\t\t\tdelay = time.Duration(dice.RollInt63n(int64(duration)))\n\t\t\t}\n\t\t\ttime.AfterFunc(delay, func() {\n\t\t\t\terrors.LogDebug(h.ctx, \"checking \", handler)\n\t\t\t\tdelay, err := client.MeasureDelay(h.Settings.HttpMethod)\n\t\t\t\tif err == nil {\n\t\t\t\t\tch <- &rtt{\n\t\t\t\t\t\thandler: handler,\n\t\t\t\t\t\tvalue:   delay,\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif !h.checkConnectivity() {\n\t\t\t\t\terrors.LogWarning(h.ctx, \"network is down\")\n\t\t\t\t\tch <- &rtt{\n\t\t\t\t\t\thandler: handler,\n\t\t\t\t\t\tvalue:   0,\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\terrors.LogWarning(h.ctx, fmt.Sprintf(\n\t\t\t\t\t\"error ping %s with %s: %s\",\n\t\t\t\t\th.Settings.Destination,\n\t\t\t\t\thandler,\n\t\t\t\t\terr,\n\t\t\t\t))\n\t\t\t\tch <- &rtt{\n\t\t\t\t\thandler: handler,\n\t\t\t\t\tvalue:   rttFailed,\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\tfor i := 0; i < count; i++ {\n\t\trtt := <-ch\n\t\tif rtt.value > 0 {\n\t\t\t// should not put results when network is down\n\t\t\th.PutResult(rtt.handler, rtt.value)\n\t\t}\n\t}\n}\n\n// PutResult put a ping rtt to results\nfunc (h *HealthPing) PutResult(tag string, rtt time.Duration) {\n\th.access.Lock()\n\tdefer h.access.Unlock()\n\tif h.Results == nil {\n\t\th.Results = make(map[string]*HealthPingRTTS)\n\t}\n\tr, ok := h.Results[tag]\n\tif !ok {\n\t\t// validity is 2 times to sampling period, since the check are\n\t\t// distributed in the time line randomly, in extreme cases,\n\t\t// Previous checks are distributed on the left, and later ones\n\t\t// on the right\n\t\tvalidity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2\n\t\tr = NewHealthPingResult(h.Settings.SamplingCount, validity)\n\t\th.Results[tag] = r\n\t}\n\tr.Put(rtt)\n}\n\n// Cleanup removes results of removed handlers,\n// tags should be all valid tags of the Balancer now\nfunc (h *HealthPing) Cleanup(tags []string) {\n\th.access.Lock()\n\tdefer h.access.Unlock()\n\tfor tag := range h.Results {\n\t\tfound := false\n\t\tfor _, v := range tags {\n\t\t\tif tag == v {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tdelete(h.Results, tag)\n\t\t}\n\t}\n}\n\n// checkConnectivity checks the network connectivity, it returns\n// true if network is good or \"connectivity check url\" not set\nfunc (h *HealthPing) checkConnectivity() bool {\n\tif h.Settings.Connectivity == \"\" {\n\t\treturn true\n\t}\n\ttester := newDirectPingClient(\n\t\th.Settings.Connectivity,\n\t\th.Settings.Timeout,\n\t)\n\tif _, err := tester.MeasureDelay(h.Settings.HttpMethod); err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "app/observatory/burst/healthping_result.go",
    "content": "package burst\n\nimport (\n\t\"math\"\n\t\"time\"\n)\n\n// HealthPingStats is the statistics of HealthPingRTTS\ntype HealthPingStats struct {\n\tAll       int\n\tFail      int\n\tDeviation time.Duration\n\tAverage   time.Duration\n\tMax       time.Duration\n\tMin       time.Duration\n}\n\n// HealthPingRTTS holds ping rtts for health Checker\ntype HealthPingRTTS struct {\n\tidx      int\n\tcap      int\n\tvalidity time.Duration\n\trtts     []*pingRTT\n\n\tlastUpdateAt time.Time\n\tstats        *HealthPingStats\n}\n\ntype pingRTT struct {\n\ttime  time.Time\n\tvalue time.Duration\n}\n\n// NewHealthPingResult returns a *HealthPingResult with specified capacity\nfunc NewHealthPingResult(cap int, validity time.Duration) *HealthPingRTTS {\n\treturn &HealthPingRTTS{cap: cap, validity: validity}\n}\n\n// Get gets statistics of the HealthPingRTTS\nfunc (h *HealthPingRTTS) Get() *HealthPingStats {\n\treturn h.getStatistics()\n}\n\n// GetWithCache get statistics and write cache for next call\n// Make sure use Mutex.Lock() before calling it, RWMutex.RLock()\n// is not an option since it writes cache\nfunc (h *HealthPingRTTS) GetWithCache() *HealthPingStats {\n\tlastPutAt := h.rtts[h.idx].time\n\tnow := time.Now()\n\tif h.stats == nil || h.lastUpdateAt.Before(lastPutAt) || h.findOutdated(now) >= 0 {\n\t\th.stats = h.getStatistics()\n\t\th.lastUpdateAt = now\n\t}\n\treturn h.stats\n}\n\n// Put puts a new rtt to the HealthPingResult\nfunc (h *HealthPingRTTS) Put(d time.Duration) {\n\tif h.rtts == nil {\n\t\th.rtts = make([]*pingRTT, h.cap)\n\t\tfor i := 0; i < h.cap; i++ {\n\t\t\th.rtts[i] = &pingRTT{}\n\t\t}\n\t\th.idx = -1\n\t}\n\th.idx = h.calcIndex(1)\n\tnow := time.Now()\n\th.rtts[h.idx].time = now\n\th.rtts[h.idx].value = d\n}\n\nfunc (h *HealthPingRTTS) calcIndex(step int) int {\n\tidx := h.idx\n\tidx += step\n\tif idx >= h.cap {\n\t\tidx %= h.cap\n\t}\n\treturn idx\n}\n\nfunc (h *HealthPingRTTS) getStatistics() *HealthPingStats {\n\tstats := &HealthPingStats{}\n\tstats.Fail = 0\n\tstats.Max = 0\n\tstats.Min = rttFailed\n\tsum := time.Duration(0)\n\tcnt := 0\n\tvalidRTTs := make([]time.Duration, 0)\n\tfor _, rtt := range h.rtts {\n\t\tswitch {\n\t\tcase rtt.value == 0 || time.Since(rtt.time) > h.validity:\n\t\t\tcontinue\n\t\tcase rtt.value == rttFailed:\n\t\t\tstats.Fail++\n\t\t\tcontinue\n\t\t}\n\t\tcnt++\n\t\tsum += rtt.value\n\t\tvalidRTTs = append(validRTTs, rtt.value)\n\t\tif stats.Max < rtt.value {\n\t\t\tstats.Max = rtt.value\n\t\t}\n\t\tif stats.Min > rtt.value {\n\t\t\tstats.Min = rtt.value\n\t\t}\n\t}\n\tstats.All = cnt + stats.Fail\n\tif cnt == 0 {\n\t\tstats.Min = 0\n\t\treturn stats\n\t}\n\tstats.Average = time.Duration(int(sum) / cnt)\n\tvar std float64\n\tif cnt < 2 {\n\t\t// no enough data for standard deviation, we assume it's half of the average rtt\n\t\t// if we don't do this, standard deviation of 1 round tested nodes is 0, will always\n\t\t// selected before 2 or more rounds tested nodes\n\t\tstd = float64(stats.Average / 2)\n\t} else {\n\t\tvariance := float64(0)\n\t\tfor _, rtt := range validRTTs {\n\t\t\tvariance += math.Pow(float64(rtt-stats.Average), 2)\n\t\t}\n\t\tstd = math.Sqrt(variance / float64(cnt))\n\t}\n\tstats.Deviation = time.Duration(std)\n\treturn stats\n}\n\nfunc (h *HealthPingRTTS) findOutdated(now time.Time) int {\n\tfor i := h.cap - 1; i < 2*h.cap; i++ {\n\t\t// from oldest to latest\n\t\tidx := h.calcIndex(i)\n\t\tvalidity := h.rtts[idx].time.Add(h.validity)\n\t\tif h.lastUpdateAt.After(validity) {\n\t\t\treturn idx\n\t\t}\n\t\tif validity.Before(now) {\n\t\t\treturn idx\n\t\t}\n\t}\n\treturn -1\n}\n"
  },
  {
    "path": "app/observatory/burst/healthping_result_test.go",
    "content": "package burst_test\n\nimport (\n\t\"math\"\n\treflect \"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/observatory/burst\"\n)\n\nfunc TestHealthPingResults(t *testing.T) {\n\trtts := []int64{60, 140, 60, 140, 60, 60, 140, 60, 140}\n\thr := burst.NewHealthPingResult(4, time.Hour)\n\tfor _, rtt := range rtts {\n\t\thr.Put(time.Duration(rtt))\n\t}\n\trttFailed := time.Duration(math.MaxInt64)\n\texpected := &burst.HealthPingStats{\n\t\tAll:       4,\n\t\tFail:      0,\n\t\tDeviation: 40,\n\t\tAverage:   100,\n\t\tMax:       140,\n\t\tMin:       60,\n\t}\n\tactual := hr.Get()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, actual)\n\t}\n\thr.Put(rttFailed)\n\thr.Put(rttFailed)\n\texpected.Fail = 2\n\tactual = hr.Get()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"failed half-failures test, expected: %v, actual: %v\", expected, actual)\n\t}\n\thr.Put(rttFailed)\n\thr.Put(rttFailed)\n\texpected = &burst.HealthPingStats{\n\t\tAll:       4,\n\t\tFail:      4,\n\t\tDeviation: 0,\n\t\tAverage:   0,\n\t\tMax:       0,\n\t\tMin:       0,\n\t}\n\tactual = hr.Get()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"failed all-failures test, expected: %v, actual: %v\", expected, actual)\n\t}\n}\n\nfunc TestHealthPingResultsIgnoreOutdated(t *testing.T) {\n\trtts := []int64{60, 140, 60, 140}\n\thr := burst.NewHealthPingResult(4, time.Duration(10)*time.Millisecond)\n\tfor i, rtt := range rtts {\n\t\tif i == 2 {\n\t\t\t// wait for previous 2 outdated\n\t\t\ttime.Sleep(time.Duration(10) * time.Millisecond)\n\t\t}\n\t\thr.Put(time.Duration(rtt))\n\t}\n\thr.Get()\n\texpected := &burst.HealthPingStats{\n\t\tAll:       2,\n\t\tFail:      0,\n\t\tDeviation: 40,\n\t\tAverage:   100,\n\t\tMax:       140,\n\t\tMin:       60,\n\t}\n\tactual := hr.Get()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"failed 'half-outdated' test, expected: %v, actual: %v\", expected, actual)\n\t}\n\t// wait for all outdated\n\ttime.Sleep(time.Duration(10) * time.Millisecond)\n\texpected = &burst.HealthPingStats{\n\t\tAll:       0,\n\t\tFail:      0,\n\t\tDeviation: 0,\n\t\tAverage:   0,\n\t\tMax:       0,\n\t\tMin:       0,\n\t}\n\tactual = hr.Get()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"failed 'outdated / not-tested' test, expected: %v, actual: %v\", expected, actual)\n\t}\n\n\thr.Put(time.Duration(60))\n\texpected = &burst.HealthPingStats{\n\t\tAll:  1,\n\t\tFail: 0,\n\t\t// 1 sample, std=0.5rtt\n\t\tDeviation: 30,\n\t\tAverage:   60,\n\t\tMax:       60,\n\t\tMin:       60,\n\t}\n\tactual = hr.Get()\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, actual)\n\t}\n}\n"
  },
  {
    "path": "app/observatory/burst/ping.go",
    "content": "package burst\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/tagged\"\n)\n\ntype pingClient struct {\n\tdestination string\n\thttpClient  *http.Client\n}\n\nfunc newPingClient(ctx context.Context, dispatcher routing.Dispatcher, destination string, timeout time.Duration, handler string) *pingClient {\n\treturn &pingClient{\n\t\tdestination: destination,\n\t\thttpClient:  newHTTPClient(ctx, dispatcher, handler, timeout),\n\t}\n}\n\nfunc newDirectPingClient(destination string, timeout time.Duration) *pingClient {\n\treturn &pingClient{\n\t\tdestination: destination,\n\t\thttpClient:  &http.Client{Timeout: timeout},\n\t}\n}\n\nfunc newHTTPClient(ctxv context.Context, dispatcher routing.Dispatcher, handler string, timeout time.Duration) *http.Client {\n\ttr := &http.Transport{\n\t\tDisableKeepAlives: true,\n\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\tdest, err := net.ParseDestination(network + \":\" + addr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn tagged.Dialer(ctxv, dispatcher, dest, handler)\n\t\t},\n\t}\n\treturn &http.Client{\n\t\tTransport: tr,\n\t\tTimeout:   timeout,\n\t\t// don't follow redirect\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t}\n}\n\n// MeasureDelay returns the delay time of the request to dest\nfunc (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {\n\tif s.httpClient == nil {\n\t\tpanic(\"pingClient not initialized\")\n\t}\n\n\treq, err := http.NewRequest(httpMethod, s.destination, nil)\n\tif err != nil {\n\t\treturn rttFailed, err\n\t}\n\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\n\tstart := time.Now()\n\tresp, err := s.httpClient.Do(req)\n\tif err != nil {\n\t\treturn rttFailed, err\n\t}\n\tif httpMethod == http.MethodGet {\n\t\t_, err = io.Copy(io.Discard, resp.Body)\n\t\tif err != nil {\n\t\t\treturn rttFailed, err\n\t\t}\n\t}\n\tresp.Body.Close()\n\n\treturn time.Since(start), nil\n}\n"
  },
  {
    "path": "app/observatory/command/command.go",
    "content": "package command\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/common\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n\t\"google.golang.org/grpc\"\n)\n\ntype service struct {\n\tUnimplementedObservatoryServiceServer\n\tv *core.Instance\n\n\tobservatory extension.Observatory\n}\n\nfunc (s *service) GetOutboundStatus(ctx context.Context, request *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) {\n\tresp, err := s.observatory.GetObservation(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tretdata := resp.(*observatory.ObservationResult)\n\treturn &GetOutboundStatusResponse{\n\t\tStatus: retdata,\n\t}, nil\n}\n\nfunc (s *service) Register(server *grpc.Server) {\n\tRegisterObservatoryServiceServer(server, s)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\ts := core.MustFromContext(ctx)\n\t\tsv := &service{v: s}\n\t\terr := s.RequireFeatures(func(Observatory extension.Observatory) {\n\t\t\tsv.observatory = Observatory\n\t\t}, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn sv, nil\n\t}))\n}\n"
  },
  {
    "path": "app/observatory/command/command.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/observatory/command/command.proto\n\npackage command\n\nimport (\n\tobservatory \"github.com/xtls/xray-core/app/observatory\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype GetOutboundStatusRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetOutboundStatusRequest) Reset() {\n\t*x = GetOutboundStatusRequest{}\n\tmi := &file_app_observatory_command_command_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetOutboundStatusRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetOutboundStatusRequest) ProtoMessage() {}\n\nfunc (x *GetOutboundStatusRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_command_command_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetOutboundStatusRequest.ProtoReflect.Descriptor instead.\nfunc (*GetOutboundStatusRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_command_command_proto_rawDescGZIP(), []int{0}\n}\n\ntype GetOutboundStatusResponse struct {\n\tstate         protoimpl.MessageState         `protogen:\"open.v1\"`\n\tStatus        *observatory.ObservationResult `protobuf:\"bytes,1,opt,name=status,proto3\" json:\"status,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetOutboundStatusResponse) Reset() {\n\t*x = GetOutboundStatusResponse{}\n\tmi := &file_app_observatory_command_command_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetOutboundStatusResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetOutboundStatusResponse) ProtoMessage() {}\n\nfunc (x *GetOutboundStatusResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_command_command_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetOutboundStatusResponse.ProtoReflect.Descriptor instead.\nfunc (*GetOutboundStatusResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_command_command_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *GetOutboundStatusResponse) GetStatus() *observatory.ObservationResult {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn nil\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_observatory_command_command_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_command_command_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_command_command_proto_rawDescGZIP(), []int{2}\n}\n\nvar File_app_observatory_command_command_proto protoreflect.FileDescriptor\n\nconst file_app_observatory_command_command_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"%app/observatory/command/command.proto\\x12!xray.core.app.observatory.command\\x1a\\x1capp/observatory/config.proto\\\"\\x1a\\n\" +\n\t\"\\x18GetOutboundStatusRequest\\\"a\\n\" +\n\t\"\\x19GetOutboundStatusResponse\\x12D\\n\" +\n\t\"\\x06status\\x18\\x01 \\x01(\\v2,.xray.core.app.observatory.ObservationResultR\\x06status\\\"\\b\\n\" +\n\t\"\\x06Config2\\xa7\\x01\\n\" +\n\t\"\\x12ObservatoryService\\x12\\x90\\x01\\n\" +\n\t\"\\x11GetOutboundStatus\\x12;.xray.core.app.observatory.command.GetOutboundStatusRequest\\x1a<.xray.core.app.observatory.command.GetOutboundStatusResponse\\\"\\x00B\\x80\\x01\\n\" +\n\t\"%com.xray.core.app.observatory.commandP\\x01Z1github.com/xtls/xray-core/app/observatory/command\\xaa\\x02!Xray.Core.App.Observatory.Commandb\\x06proto3\"\n\nvar (\n\tfile_app_observatory_command_command_proto_rawDescOnce sync.Once\n\tfile_app_observatory_command_command_proto_rawDescData []byte\n)\n\nfunc file_app_observatory_command_command_proto_rawDescGZIP() []byte {\n\tfile_app_observatory_command_command_proto_rawDescOnce.Do(func() {\n\t\tfile_app_observatory_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_observatory_command_command_proto_rawDesc), len(file_app_observatory_command_command_proto_rawDesc)))\n\t})\n\treturn file_app_observatory_command_command_proto_rawDescData\n}\n\nvar file_app_observatory_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_app_observatory_command_command_proto_goTypes = []any{\n\t(*GetOutboundStatusRequest)(nil),      // 0: xray.core.app.observatory.command.GetOutboundStatusRequest\n\t(*GetOutboundStatusResponse)(nil),     // 1: xray.core.app.observatory.command.GetOutboundStatusResponse\n\t(*Config)(nil),                        // 2: xray.core.app.observatory.command.Config\n\t(*observatory.ObservationResult)(nil), // 3: xray.core.app.observatory.ObservationResult\n}\nvar file_app_observatory_command_command_proto_depIdxs = []int32{\n\t3, // 0: xray.core.app.observatory.command.GetOutboundStatusResponse.status:type_name -> xray.core.app.observatory.ObservationResult\n\t0, // 1: xray.core.app.observatory.command.ObservatoryService.GetOutboundStatus:input_type -> xray.core.app.observatory.command.GetOutboundStatusRequest\n\t1, // 2: xray.core.app.observatory.command.ObservatoryService.GetOutboundStatus:output_type -> xray.core.app.observatory.command.GetOutboundStatusResponse\n\t2, // [2:3] is the sub-list for method output_type\n\t1, // [1:2] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_app_observatory_command_command_proto_init() }\nfunc file_app_observatory_command_command_proto_init() {\n\tif File_app_observatory_command_command_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_observatory_command_command_proto_rawDesc), len(file_app_observatory_command_command_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_app_observatory_command_command_proto_goTypes,\n\t\tDependencyIndexes: file_app_observatory_command_command_proto_depIdxs,\n\t\tMessageInfos:      file_app_observatory_command_command_proto_msgTypes,\n\t}.Build()\n\tFile_app_observatory_command_command_proto = out.File\n\tfile_app_observatory_command_command_proto_goTypes = nil\n\tfile_app_observatory_command_command_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/observatory/command/command.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.core.app.observatory.command;\noption csharp_namespace = \"Xray.Core.App.Observatory.Command\";\noption go_package = \"github.com/xtls/xray-core/app/observatory/command\";\noption java_package = \"com.xray.core.app.observatory.command\";\noption java_multiple_files = true;\n\nimport \"app/observatory/config.proto\";\n\nmessage GetOutboundStatusRequest {\n}\n\nmessage GetOutboundStatusResponse {\n  xray.core.app.observatory.ObservationResult status = 1;\n}\n\nservice ObservatoryService {\n  rpc GetOutboundStatus(GetOutboundStatusRequest)\n      returns (GetOutboundStatusResponse) {}\n}\n\n\nmessage Config {}"
  },
  {
    "path": "app/observatory/command/command_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.0\n// - protoc             v6.33.5\n// source: app/observatory/command/command.proto\n\npackage command\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tObservatoryService_GetOutboundStatus_FullMethodName = \"/xray.core.app.observatory.command.ObservatoryService/GetOutboundStatus\"\n)\n\n// ObservatoryServiceClient is the client API for ObservatoryService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype ObservatoryServiceClient interface {\n\tGetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error)\n}\n\ntype observatoryServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewObservatoryServiceClient(cc grpc.ClientConnInterface) ObservatoryServiceClient {\n\treturn &observatoryServiceClient{cc}\n}\n\nfunc (c *observatoryServiceClient) GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetOutboundStatusResponse)\n\terr := c.cc.Invoke(ctx, ObservatoryService_GetOutboundStatus_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ObservatoryServiceServer is the server API for ObservatoryService service.\n// All implementations must embed UnimplementedObservatoryServiceServer\n// for forward compatibility.\ntype ObservatoryServiceServer interface {\n\tGetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error)\n\tmustEmbedUnimplementedObservatoryServiceServer()\n}\n\n// UnimplementedObservatoryServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedObservatoryServiceServer struct{}\n\nfunc (UnimplementedObservatoryServiceServer) GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetOutboundStatus not implemented\")\n}\nfunc (UnimplementedObservatoryServiceServer) mustEmbedUnimplementedObservatoryServiceServer() {}\nfunc (UnimplementedObservatoryServiceServer) testEmbeddedByValue()                            {}\n\n// UnsafeObservatoryServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ObservatoryServiceServer will\n// result in compilation errors.\ntype UnsafeObservatoryServiceServer interface {\n\tmustEmbedUnimplementedObservatoryServiceServer()\n}\n\nfunc RegisterObservatoryServiceServer(s grpc.ServiceRegistrar, srv ObservatoryServiceServer) {\n\t// If the following call panics, it indicates UnimplementedObservatoryServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&ObservatoryService_ServiceDesc, srv)\n}\n\nfunc _ObservatoryService_GetOutboundStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetOutboundStatusRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: ObservatoryService_GetOutboundStatus_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, req.(*GetOutboundStatusRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// ObservatoryService_ServiceDesc is the grpc.ServiceDesc for ObservatoryService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar ObservatoryService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"xray.core.app.observatory.command.ObservatoryService\",\n\tHandlerType: (*ObservatoryServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetOutboundStatus\",\n\t\t\tHandler:    _ObservatoryService_GetOutboundStatus_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"app/observatory/command/command.proto\",\n}\n"
  },
  {
    "path": "app/observatory/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/observatory/config.proto\n\npackage observatory\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ObservationResult struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStatus        []*OutboundStatus      `protobuf:\"bytes,1,rep,name=status,proto3\" json:\"status,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ObservationResult) Reset() {\n\t*x = ObservationResult{}\n\tmi := &file_app_observatory_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ObservationResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ObservationResult) ProtoMessage() {}\n\nfunc (x *ObservationResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ObservationResult.ProtoReflect.Descriptor instead.\nfunc (*ObservationResult) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ObservationResult) GetStatus() []*OutboundStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn nil\n}\n\ntype HealthPingMeasurementResult struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAll           int64                  `protobuf:\"varint,1,opt,name=all,proto3\" json:\"all,omitempty\"`\n\tFail          int64                  `protobuf:\"varint,2,opt,name=fail,proto3\" json:\"fail,omitempty\"`\n\tDeviation     int64                  `protobuf:\"varint,3,opt,name=deviation,proto3\" json:\"deviation,omitempty\"`\n\tAverage       int64                  `protobuf:\"varint,4,opt,name=average,proto3\" json:\"average,omitempty\"`\n\tMax           int64                  `protobuf:\"varint,5,opt,name=max,proto3\" json:\"max,omitempty\"`\n\tMin           int64                  `protobuf:\"varint,6,opt,name=min,proto3\" json:\"min,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *HealthPingMeasurementResult) Reset() {\n\t*x = HealthPingMeasurementResult{}\n\tmi := &file_app_observatory_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HealthPingMeasurementResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HealthPingMeasurementResult) ProtoMessage() {}\n\nfunc (x *HealthPingMeasurementResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HealthPingMeasurementResult.ProtoReflect.Descriptor instead.\nfunc (*HealthPingMeasurementResult) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *HealthPingMeasurementResult) GetAll() int64 {\n\tif x != nil {\n\t\treturn x.All\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingMeasurementResult) GetFail() int64 {\n\tif x != nil {\n\t\treturn x.Fail\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingMeasurementResult) GetDeviation() int64 {\n\tif x != nil {\n\t\treturn x.Deviation\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingMeasurementResult) GetAverage() int64 {\n\tif x != nil {\n\t\treturn x.Average\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingMeasurementResult) GetMax() int64 {\n\tif x != nil {\n\t\treturn x.Max\n\t}\n\treturn 0\n}\n\nfunc (x *HealthPingMeasurementResult) GetMin() int64 {\n\tif x != nil {\n\t\treturn x.Min\n\t}\n\treturn 0\n}\n\ntype OutboundStatus struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// @Document Whether this outbound is usable\n\t// @Restriction ReadOnlyForUser\n\tAlive bool `protobuf:\"varint,1,opt,name=alive,proto3\" json:\"alive,omitempty\"`\n\t// @Document The time for probe request to finish.\n\t// @Type time.ms\n\t// @Restriction ReadOnlyForUser\n\tDelay int64 `protobuf:\"varint,2,opt,name=delay,proto3\" json:\"delay,omitempty\"`\n\t// @Document The last error caused this outbound failed to relay probe request\n\t// @Restriction NotMachineReadable\n\tLastErrorReason string `protobuf:\"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3\" json:\"last_error_reason,omitempty\"`\n\t// @Document The outbound tag for this Server\n\t// @Type id.outboundTag\n\tOutboundTag string `protobuf:\"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3\" json:\"outbound_tag,omitempty\"`\n\t// @Document The time this outbound is known to be alive\n\t// @Type id.outboundTag\n\tLastSeenTime int64 `protobuf:\"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3\" json:\"last_seen_time,omitempty\"`\n\t// @Document The time this outbound is tried\n\t// @Type id.outboundTag\n\tLastTryTime   int64                        `protobuf:\"varint,6,opt,name=last_try_time,json=lastTryTime,proto3\" json:\"last_try_time,omitempty\"`\n\tHealthPing    *HealthPingMeasurementResult `protobuf:\"bytes,7,opt,name=health_ping,json=healthPing,proto3\" json:\"health_ping,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OutboundStatus) Reset() {\n\t*x = OutboundStatus{}\n\tmi := &file_app_observatory_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OutboundStatus) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OutboundStatus) ProtoMessage() {}\n\nfunc (x *OutboundStatus) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OutboundStatus.ProtoReflect.Descriptor instead.\nfunc (*OutboundStatus) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *OutboundStatus) GetAlive() bool {\n\tif x != nil {\n\t\treturn x.Alive\n\t}\n\treturn false\n}\n\nfunc (x *OutboundStatus) GetDelay() int64 {\n\tif x != nil {\n\t\treturn x.Delay\n\t}\n\treturn 0\n}\n\nfunc (x *OutboundStatus) GetLastErrorReason() string {\n\tif x != nil {\n\t\treturn x.LastErrorReason\n\t}\n\treturn \"\"\n}\n\nfunc (x *OutboundStatus) GetOutboundTag() string {\n\tif x != nil {\n\t\treturn x.OutboundTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *OutboundStatus) GetLastSeenTime() int64 {\n\tif x != nil {\n\t\treturn x.LastSeenTime\n\t}\n\treturn 0\n}\n\nfunc (x *OutboundStatus) GetLastTryTime() int64 {\n\tif x != nil {\n\t\treturn x.LastTryTime\n\t}\n\treturn 0\n}\n\nfunc (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult {\n\tif x != nil {\n\t\treturn x.HealthPing\n\t}\n\treturn nil\n}\n\ntype ProbeResult struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// @Document Whether this outbound is usable\n\t// @Restriction ReadOnlyForUser\n\tAlive bool `protobuf:\"varint,1,opt,name=alive,proto3\" json:\"alive,omitempty\"`\n\t// @Document The time for probe request to finish.\n\t// @Type time.ms\n\t// @Restriction ReadOnlyForUser\n\tDelay int64 `protobuf:\"varint,2,opt,name=delay,proto3\" json:\"delay,omitempty\"`\n\t// @Document The error caused this outbound failed to relay probe request\n\t// @Restriction NotMachineReadable\n\tLastErrorReason string `protobuf:\"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3\" json:\"last_error_reason,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *ProbeResult) Reset() {\n\t*x = ProbeResult{}\n\tmi := &file_app_observatory_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ProbeResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ProbeResult) ProtoMessage() {}\n\nfunc (x *ProbeResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ProbeResult.ProtoReflect.Descriptor instead.\nfunc (*ProbeResult) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *ProbeResult) GetAlive() bool {\n\tif x != nil {\n\t\treturn x.Alive\n\t}\n\treturn false\n}\n\nfunc (x *ProbeResult) GetDelay() int64 {\n\tif x != nil {\n\t\treturn x.Delay\n\t}\n\treturn 0\n}\n\nfunc (x *ProbeResult) GetLastErrorReason() string {\n\tif x != nil {\n\t\treturn x.LastErrorReason\n\t}\n\treturn \"\"\n}\n\ntype Intensity struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// @Document The time interval for a probe request in ms.\n\t// @Type time.ms\n\tProbeInterval uint32 `protobuf:\"varint,1,opt,name=probe_interval,json=probeInterval,proto3\" json:\"probe_interval,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Intensity) Reset() {\n\t*x = Intensity{}\n\tmi := &file_app_observatory_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Intensity) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Intensity) ProtoMessage() {}\n\nfunc (x *Intensity) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Intensity.ProtoReflect.Descriptor instead.\nfunc (*Intensity) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Intensity) GetProbeInterval() uint32 {\n\tif x != nil {\n\t\treturn x.ProbeInterval\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// @Document The selectors for outbound under observation\n\tSubjectSelector   []string `protobuf:\"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3\" json:\"subject_selector,omitempty\"`\n\tProbeUrl          string   `protobuf:\"bytes,3,opt,name=probe_url,json=probeUrl,proto3\" json:\"probe_url,omitempty\"`\n\tProbeInterval     int64    `protobuf:\"varint,4,opt,name=probe_interval,json=probeInterval,proto3\" json:\"probe_interval,omitempty\"`\n\tEnableConcurrency bool     `protobuf:\"varint,5,opt,name=enable_concurrency,json=enableConcurrency,proto3\" json:\"enable_concurrency,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_observatory_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_observatory_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_observatory_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *Config) GetSubjectSelector() []string {\n\tif x != nil {\n\t\treturn x.SubjectSelector\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetProbeUrl() string {\n\tif x != nil {\n\t\treturn x.ProbeUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetProbeInterval() int64 {\n\tif x != nil {\n\t\treturn x.ProbeInterval\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetEnableConcurrency() bool {\n\tif x != nil {\n\t\treturn x.EnableConcurrency\n\t}\n\treturn false\n}\n\nvar File_app_observatory_config_proto protoreflect.FileDescriptor\n\nconst file_app_observatory_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1capp/observatory/config.proto\\x12\\x19xray.core.app.observatory\\\"V\\n\" +\n\t\"\\x11ObservationResult\\x12A\\n\" +\n\t\"\\x06status\\x18\\x01 \\x03(\\v2).xray.core.app.observatory.OutboundStatusR\\x06status\\\"\\x9f\\x01\\n\" +\n\t\"\\x1bHealthPingMeasurementResult\\x12\\x10\\n\" +\n\t\"\\x03all\\x18\\x01 \\x01(\\x03R\\x03all\\x12\\x12\\n\" +\n\t\"\\x04fail\\x18\\x02 \\x01(\\x03R\\x04fail\\x12\\x1c\\n\" +\n\t\"\\tdeviation\\x18\\x03 \\x01(\\x03R\\tdeviation\\x12\\x18\\n\" +\n\t\"\\aaverage\\x18\\x04 \\x01(\\x03R\\aaverage\\x12\\x10\\n\" +\n\t\"\\x03max\\x18\\x05 \\x01(\\x03R\\x03max\\x12\\x10\\n\" +\n\t\"\\x03min\\x18\\x06 \\x01(\\x03R\\x03min\\\"\\xae\\x02\\n\" +\n\t\"\\x0eOutboundStatus\\x12\\x14\\n\" +\n\t\"\\x05alive\\x18\\x01 \\x01(\\bR\\x05alive\\x12\\x14\\n\" +\n\t\"\\x05delay\\x18\\x02 \\x01(\\x03R\\x05delay\\x12*\\n\" +\n\t\"\\x11last_error_reason\\x18\\x03 \\x01(\\tR\\x0flastErrorReason\\x12!\\n\" +\n\t\"\\foutbound_tag\\x18\\x04 \\x01(\\tR\\voutboundTag\\x12$\\n\" +\n\t\"\\x0elast_seen_time\\x18\\x05 \\x01(\\x03R\\flastSeenTime\\x12\\\"\\n\" +\n\t\"\\rlast_try_time\\x18\\x06 \\x01(\\x03R\\vlastTryTime\\x12W\\n\" +\n\t\"\\vhealth_ping\\x18\\a \\x01(\\v26.xray.core.app.observatory.HealthPingMeasurementResultR\\n\" +\n\t\"healthPing\\\"e\\n\" +\n\t\"\\vProbeResult\\x12\\x14\\n\" +\n\t\"\\x05alive\\x18\\x01 \\x01(\\bR\\x05alive\\x12\\x14\\n\" +\n\t\"\\x05delay\\x18\\x02 \\x01(\\x03R\\x05delay\\x12*\\n\" +\n\t\"\\x11last_error_reason\\x18\\x03 \\x01(\\tR\\x0flastErrorReason\\\"2\\n\" +\n\t\"\\tIntensity\\x12%\\n\" +\n\t\"\\x0eprobe_interval\\x18\\x01 \\x01(\\rR\\rprobeInterval\\\"\\xa6\\x01\\n\" +\n\t\"\\x06Config\\x12)\\n\" +\n\t\"\\x10subject_selector\\x18\\x02 \\x03(\\tR\\x0fsubjectSelector\\x12\\x1b\\n\" +\n\t\"\\tprobe_url\\x18\\x03 \\x01(\\tR\\bprobeUrl\\x12%\\n\" +\n\t\"\\x0eprobe_interval\\x18\\x04 \\x01(\\x03R\\rprobeInterval\\x12-\\n\" +\n\t\"\\x12enable_concurrency\\x18\\x05 \\x01(\\bR\\x11enableConcurrencyB^\\n\" +\n\t\"\\x18com.xray.app.observatoryP\\x01Z)github.com/xtls/xray-core/app/observatory\\xaa\\x02\\x14Xray.App.Observatoryb\\x06proto3\"\n\nvar (\n\tfile_app_observatory_config_proto_rawDescOnce sync.Once\n\tfile_app_observatory_config_proto_rawDescData []byte\n)\n\nfunc file_app_observatory_config_proto_rawDescGZIP() []byte {\n\tfile_app_observatory_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_observatory_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_observatory_config_proto_rawDesc), len(file_app_observatory_config_proto_rawDesc)))\n\t})\n\treturn file_app_observatory_config_proto_rawDescData\n}\n\nvar file_app_observatory_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)\nvar file_app_observatory_config_proto_goTypes = []any{\n\t(*ObservationResult)(nil),           // 0: xray.core.app.observatory.ObservationResult\n\t(*HealthPingMeasurementResult)(nil), // 1: xray.core.app.observatory.HealthPingMeasurementResult\n\t(*OutboundStatus)(nil),              // 2: xray.core.app.observatory.OutboundStatus\n\t(*ProbeResult)(nil),                 // 3: xray.core.app.observatory.ProbeResult\n\t(*Intensity)(nil),                   // 4: xray.core.app.observatory.Intensity\n\t(*Config)(nil),                      // 5: xray.core.app.observatory.Config\n}\nvar file_app_observatory_config_proto_depIdxs = []int32{\n\t2, // 0: xray.core.app.observatory.ObservationResult.status:type_name -> xray.core.app.observatory.OutboundStatus\n\t1, // 1: xray.core.app.observatory.OutboundStatus.health_ping:type_name -> xray.core.app.observatory.HealthPingMeasurementResult\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_app_observatory_config_proto_init() }\nfunc file_app_observatory_config_proto_init() {\n\tif File_app_observatory_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_observatory_config_proto_rawDesc), len(file_app_observatory_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   6,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_observatory_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_observatory_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_observatory_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_observatory_config_proto = out.File\n\tfile_app_observatory_config_proto_goTypes = nil\n\tfile_app_observatory_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/observatory/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.core.app.observatory;\noption csharp_namespace = \"Xray.App.Observatory\";\noption go_package = \"github.com/xtls/xray-core/app/observatory\";\noption java_package = \"com.xray.app.observatory\";\noption java_multiple_files = true;\n\nmessage ObservationResult {\n  repeated OutboundStatus status = 1;\n}\n\nmessage HealthPingMeasurementResult {\n  int64 all = 1;\n  int64 fail = 2;\n  int64 deviation = 3;\n  int64 average = 4;\n  int64 max = 5;\n  int64 min = 6;\n}\n\nmessage OutboundStatus{\n  /* @Document Whether this outbound is usable\n     @Restriction ReadOnlyForUser\n  */\n  bool alive = 1;\n  /* @Document The time for probe request to finish.\n     @Type time.ms\n     @Restriction ReadOnlyForUser\n  */\n  int64 delay = 2;\n  /* @Document The last error caused this outbound failed to relay probe request\n     @Restriction NotMachineReadable\n  */\n  string last_error_reason = 3;\n  /* @Document The outbound tag for this Server\n     @Type id.outboundTag\n  */\n  string outbound_tag = 4;\n  /* @Document The time this outbound is known to be alive\n   @Type id.outboundTag\n*/\n  int64 last_seen_time = 5;\n  /* @Document The time this outbound is tried\n   @Type id.outboundTag\n*/\n  int64 last_try_time = 6;\n\n  HealthPingMeasurementResult health_ping = 7;\n}\n\nmessage ProbeResult{\n  /* @Document Whether this outbound is usable\n     @Restriction ReadOnlyForUser\n  */\n  bool alive = 1;\n  /* @Document The time for probe request to finish.\n     @Type time.ms\n     @Restriction ReadOnlyForUser\n  */\n  int64 delay = 2;\n  /* @Document The error caused this outbound failed to relay probe request\n   @Restriction NotMachineReadable\n*/\n  string last_error_reason = 3;\n}\n\nmessage Intensity{\n  /* @Document The time interval for a probe request in ms.\n     @Type time.ms\n  */\n  uint32 probe_interval = 1;\n}\nmessage Config {\n  /* @Document The selectors for outbound under observation\n  */\n  repeated string subject_selector = 2;\n\n  string probe_url = 3;\n\n  int64 probe_interval = 4;\n\n  bool enable_concurrency = 5;\n}"
  },
  {
    "path": "app/observatory/explainErrors.go",
    "content": "package observatory\n\nimport \"github.com/xtls/xray-core/common/errors\"\n\ntype errorCollector struct {\n\terrors *errors.Error\n}\n\nfunc (e *errorCollector) SubmitError(err error) {\n\tif e.errors == nil {\n\t\te.errors = errors.New(\"underlying connection error\").Base(err)\n\t\treturn\n\t}\n\te.errors = e.errors.Base(errors.New(\"underlying connection error\").Base(err))\n}\n\nfunc newErrorCollector() *errorCollector {\n\treturn &errorCollector{}\n}\n\nfunc (e *errorCollector) UnderlyingError() error {\n\tif e.errors == nil {\n\t\treturn errors.New(\"failed to produce report\")\n\t}\n\treturn e.errors\n}\n"
  },
  {
    "path": "app/observatory/observatory.go",
    "content": "package observatory\n"
  },
  {
    "path": "app/observatory/observer.go",
    "content": "package observatory\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\tv2net \"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/tagged\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype Observer struct {\n\tconfig *Config\n\tctx    context.Context\n\n\tstatusLock sync.Mutex\n\tstatus     []*OutboundStatus\n\n\tfinished *done.Instance\n\n\tohm        outbound.Manager\n\tdispatcher routing.Dispatcher\n}\n\nfunc (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) {\n\treturn &ObservationResult{Status: o.status}, nil\n}\n\nfunc (o *Observer) Type() interface{} {\n\treturn extension.ObservatoryType()\n}\n\nfunc (o *Observer) Start() error {\n\tif o.config != nil && len(o.config.SubjectSelector) != 0 {\n\t\to.finished = done.New()\n\t\tgo o.background()\n\t}\n\treturn nil\n}\n\nfunc (o *Observer) Close() error {\n\tif o.finished != nil {\n\t\treturn o.finished.Close()\n\t}\n\treturn nil\n}\n\nfunc (o *Observer) background() {\n\tfor !o.finished.Done() {\n\t\ths, ok := o.ohm.(outbound.HandlerSelector)\n\t\tif !ok {\n\t\t\terrors.LogInfo(o.ctx, \"outbound.Manager is not a HandlerSelector\")\n\t\t\treturn\n\t\t}\n\n\t\toutbounds := hs.Select(o.config.SubjectSelector)\n\n\t\to.updateStatus(outbounds)\n\n\t\tsleepTime := time.Second * 10\n\t\tif o.config.ProbeInterval != 0 {\n\t\t\tsleepTime = time.Duration(o.config.ProbeInterval)\n\t\t}\n\n\t\tif !o.config.EnableConcurrency {\n\t\t\tsort.Strings(outbounds)\n\t\t\tfor _, v := range outbounds {\n\t\t\t\tresult := o.probe(v)\n\t\t\t\to.updateStatusForResult(v, &result)\n\t\t\t\tif o.finished.Done() {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttime.Sleep(sleepTime)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tch := make(chan struct{}, len(outbounds))\n\n\t\tfor _, v := range outbounds {\n\t\t\tgo func(v string) {\n\t\t\t\tresult := o.probe(v)\n\t\t\t\to.updateStatusForResult(v, &result)\n\t\t\t\tch <- struct{}{}\n\t\t\t}(v)\n\t\t}\n\n\t\tfor range outbounds {\n\t\t\tselect {\n\t\t\tcase <-ch:\n\t\t\tcase <-o.finished.Wait():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(sleepTime)\n\t}\n}\n\nfunc (o *Observer) updateStatus(outbounds []string) {\n\to.statusLock.Lock()\n\tdefer o.statusLock.Unlock()\n\t// TODO should remove old inbound that is removed\n\t_ = outbounds\n}\n\nfunc (o *Observer) probe(outbound string) ProbeResult {\n\terrorCollectorForRequest := newErrorCollector()\n\n\thttpTransport := http.Transport{\n\t\tProxy: func(*http.Request) (*url.URL, error) {\n\t\t\treturn nil, nil\n\t\t},\n\t\tDialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {\n\t\t\tvar connection net.Conn\n\t\t\ttaskErr := task.Run(ctx, func() error {\n\t\t\t\t// MUST use Xray's built in context system\n\t\t\t\tdest, err := v2net.ParseDestination(network + \":\" + addr)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.New(\"cannot understand address\").Base(err)\n\t\t\t\t}\n\t\t\t\ttrackedCtx := session.TrackedConnectionError(o.ctx, errorCollectorForRequest)\n\t\t\t\tconn, err := tagged.Dialer(trackedCtx, o.dispatcher, dest, outbound)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.New(\"cannot dial remote address \", dest).Base(err)\n\t\t\t\t}\n\t\t\t\tconnection = conn\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif taskErr != nil {\n\t\t\t\treturn nil, errors.New(\"cannot finish connection\").Base(taskErr)\n\t\t\t}\n\t\t\treturn connection, nil\n\t\t},\n\t\tTLSHandshakeTimeout: time.Second * 5,\n\t}\n\thttpClient := &http.Client{\n\t\tTransport: &httpTransport,\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t\tJar:     nil,\n\t\tTimeout: time.Second * 5,\n\t}\n\tvar GETTime time.Duration\n\terr := task.Run(o.ctx, func() error {\n\t\tstartTime := time.Now()\n\t\tprobeURL := \"https://www.google.com/generate_204\"\n\t\tif o.config.ProbeUrl != \"\" {\n\t\t\tprobeURL = o.config.ProbeUrl\n\t\t}\n\t\treq, _ := http.NewRequest(http.MethodGet, probeURL, nil)\n\t\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\t\tresponse, err := httpClient.Do(req)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"outbound failed to relay connection\").Base(err)\n\t\t}\n\t\tif response.Body != nil {\n\t\t\tresponse.Body.Close()\n\t\t}\n\t\tendTime := time.Now()\n\t\tGETTime = endTime.Sub(startTime)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tvar errorMessage = \"the outbound \" + outbound + \" is dead: GET request failed:\" + err.Error() + \"with outbound handler report underlying connection failed\"\n\t\terrors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)\n\t\treturn ProbeResult{Alive: false, LastErrorReason: errorMessage}\n\t}\n\terrors.LogInfo(o.ctx, \"the outbound \", outbound, \" is alive:\", GETTime.Seconds())\n\treturn ProbeResult{Alive: true, Delay: GETTime.Milliseconds()}\n}\n\nfunc (o *Observer) updateStatusForResult(outbound string, result *ProbeResult) {\n\to.statusLock.Lock()\n\tdefer o.statusLock.Unlock()\n\tvar status *OutboundStatus\n\tif location := o.findStatusLocationLockHolderOnly(outbound); location != -1 {\n\t\tstatus = o.status[location]\n\t} else {\n\t\tstatus = &OutboundStatus{}\n\t\to.status = append(o.status, status)\n\t}\n\n\tstatus.LastTryTime = time.Now().Unix()\n\tstatus.OutboundTag = outbound\n\tstatus.Alive = result.Alive\n\tif result.Alive {\n\t\tstatus.Delay = result.Delay\n\t\tstatus.LastSeenTime = status.LastTryTime\n\t\tstatus.LastErrorReason = \"\"\n\t} else {\n\t\tstatus.LastErrorReason = result.LastErrorReason\n\t\tstatus.Delay = 99999999\n\t}\n}\n\nfunc (o *Observer) findStatusLocationLockHolderOnly(outbound string) int {\n\tfor i, v := range o.status {\n\t\tif v.OutboundTag == outbound {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc New(ctx context.Context, config *Config) (*Observer, error) {\n\tvar outboundManager outbound.Manager\n\tvar dispatcher routing.Dispatcher\n\terr := core.RequireFeatures(ctx, func(om outbound.Manager, rd routing.Dispatcher) {\n\t\toutboundManager = om\n\t\tdispatcher = rd\n\t})\n\tif err != nil {\n\t\treturn nil, errors.New(\"Cannot get depended features\").Base(err)\n\t}\n\treturn &Observer{\n\t\tconfig:     config,\n\t\tctx:        ctx,\n\t\tohm:        outboundManager,\n\t\tdispatcher: dispatcher,\n\t}, nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/policy/config.go",
    "content": "package policy\n\nimport (\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/features/policy\"\n)\n\n// Duration converts Second to time.Duration.\nfunc (s *Second) Duration() time.Duration {\n\tif s == nil {\n\t\treturn 0\n\t}\n\treturn time.Second * time.Duration(s.Value)\n}\n\nfunc defaultPolicy() *Policy {\n\tp := policy.SessionDefault()\n\n\treturn &Policy{\n\t\tTimeout: &Policy_Timeout{\n\t\t\tHandshake:      &Second{Value: uint32(p.Timeouts.Handshake / time.Second)},\n\t\t\tConnectionIdle: &Second{Value: uint32(p.Timeouts.ConnectionIdle / time.Second)},\n\t\t\tUplinkOnly:     &Second{Value: uint32(p.Timeouts.UplinkOnly / time.Second)},\n\t\t\tDownlinkOnly:   &Second{Value: uint32(p.Timeouts.DownlinkOnly / time.Second)},\n\t\t},\n\t\tBuffer: &Policy_Buffer{\n\t\t\tConnection: p.Buffer.PerConnection,\n\t\t},\n\t}\n}\n\nfunc (p *Policy_Timeout) overrideWith(another *Policy_Timeout) {\n\tif another.Handshake != nil {\n\t\tp.Handshake = &Second{Value: another.Handshake.Value}\n\t}\n\tif another.ConnectionIdle != nil {\n\t\tp.ConnectionIdle = &Second{Value: another.ConnectionIdle.Value}\n\t}\n\tif another.UplinkOnly != nil {\n\t\tp.UplinkOnly = &Second{Value: another.UplinkOnly.Value}\n\t}\n\tif another.DownlinkOnly != nil {\n\t\tp.DownlinkOnly = &Second{Value: another.DownlinkOnly.Value}\n\t}\n}\n\nfunc (p *Policy) overrideWith(another *Policy) {\n\tif another.Timeout != nil {\n\t\tp.Timeout.overrideWith(another.Timeout)\n\t}\n\tif another.Stats != nil && p.Stats == nil {\n\t\tp.Stats = &Policy_Stats{}\n\t\tp.Stats = another.Stats\n\t}\n\tif another.Buffer != nil {\n\t\tp.Buffer = &Policy_Buffer{\n\t\t\tConnection: another.Buffer.Connection,\n\t\t}\n\t}\n}\n\n// ToCorePolicy converts this Policy to policy.Session.\nfunc (p *Policy) ToCorePolicy() policy.Session {\n\tcp := policy.SessionDefault()\n\n\tif p.Timeout != nil {\n\t\tcp.Timeouts.ConnectionIdle = p.Timeout.ConnectionIdle.Duration()\n\t\tcp.Timeouts.Handshake = p.Timeout.Handshake.Duration()\n\t\tcp.Timeouts.DownlinkOnly = p.Timeout.DownlinkOnly.Duration()\n\t\tcp.Timeouts.UplinkOnly = p.Timeout.UplinkOnly.Duration()\n\t}\n\tif p.Stats != nil {\n\t\tcp.Stats.UserUplink = p.Stats.UserUplink\n\t\tcp.Stats.UserDownlink = p.Stats.UserDownlink\n\t\tcp.Stats.UserOnline = p.Stats.UserOnline\n\t}\n\tif p.Buffer != nil {\n\t\tcp.Buffer.PerConnection = p.Buffer.Connection\n\t}\n\treturn cp\n}\n\n// ToCorePolicy converts this SystemPolicy to policy.System.\nfunc (p *SystemPolicy) ToCorePolicy() policy.System {\n\treturn policy.System{\n\t\tStats: policy.SystemStats{\n\t\t\tInboundUplink:    p.Stats.InboundUplink,\n\t\t\tInboundDownlink:  p.Stats.InboundDownlink,\n\t\t\tOutboundUplink:   p.Stats.OutboundUplink,\n\t\t\tOutboundDownlink: p.Stats.OutboundDownlink,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "app/policy/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/policy/config.proto\n\npackage policy\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Second struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         uint32                 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Second) Reset() {\n\t*x = Second{}\n\tmi := &file_app_policy_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Second) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Second) ProtoMessage() {}\n\nfunc (x *Second) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Second.ProtoReflect.Descriptor instead.\nfunc (*Second) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Second) GetValue() uint32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype Policy struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTimeout       *Policy_Timeout        `protobuf:\"bytes,1,opt,name=timeout,proto3\" json:\"timeout,omitempty\"`\n\tStats         *Policy_Stats          `protobuf:\"bytes,2,opt,name=stats,proto3\" json:\"stats,omitempty\"`\n\tBuffer        *Policy_Buffer         `protobuf:\"bytes,3,opt,name=buffer,proto3\" json:\"buffer,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Policy) Reset() {\n\t*x = Policy{}\n\tmi := &file_app_policy_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Policy) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Policy) ProtoMessage() {}\n\nfunc (x *Policy) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Policy.ProtoReflect.Descriptor instead.\nfunc (*Policy) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Policy) GetTimeout() *Policy_Timeout {\n\tif x != nil {\n\t\treturn x.Timeout\n\t}\n\treturn nil\n}\n\nfunc (x *Policy) GetStats() *Policy_Stats {\n\tif x != nil {\n\t\treturn x.Stats\n\t}\n\treturn nil\n}\n\nfunc (x *Policy) GetBuffer() *Policy_Buffer {\n\tif x != nil {\n\t\treturn x.Buffer\n\t}\n\treturn nil\n}\n\ntype SystemPolicy struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStats         *SystemPolicy_Stats    `protobuf:\"bytes,1,opt,name=stats,proto3\" json:\"stats,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SystemPolicy) Reset() {\n\t*x = SystemPolicy{}\n\tmi := &file_app_policy_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SystemPolicy) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SystemPolicy) ProtoMessage() {}\n\nfunc (x *SystemPolicy) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SystemPolicy.ProtoReflect.Descriptor instead.\nfunc (*SystemPolicy) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *SystemPolicy) GetStats() *SystemPolicy_Stats {\n\tif x != nil {\n\t\treturn x.Stats\n\t}\n\treturn nil\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tLevel         map[uint32]*Policy     `protobuf:\"bytes,1,rep,name=level,proto3\" json:\"level,omitempty\" protobuf_key:\"varint,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tSystem        *SystemPolicy          `protobuf:\"bytes,2,opt,name=system,proto3\" json:\"system,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_policy_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Config) GetLevel() map[uint32]*Policy {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetSystem() *SystemPolicy {\n\tif x != nil {\n\t\treturn x.System\n\t}\n\treturn nil\n}\n\n// Timeout is a message for timeout settings in various stages, in seconds.\ntype Policy_Timeout struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tHandshake      *Second                `protobuf:\"bytes,1,opt,name=handshake,proto3\" json:\"handshake,omitempty\"`\n\tConnectionIdle *Second                `protobuf:\"bytes,2,opt,name=connection_idle,json=connectionIdle,proto3\" json:\"connection_idle,omitempty\"`\n\tUplinkOnly     *Second                `protobuf:\"bytes,3,opt,name=uplink_only,json=uplinkOnly,proto3\" json:\"uplink_only,omitempty\"`\n\tDownlinkOnly   *Second                `protobuf:\"bytes,4,opt,name=downlink_only,json=downlinkOnly,proto3\" json:\"downlink_only,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *Policy_Timeout) Reset() {\n\t*x = Policy_Timeout{}\n\tmi := &file_app_policy_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Policy_Timeout) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Policy_Timeout) ProtoMessage() {}\n\nfunc (x *Policy_Timeout) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Policy_Timeout.ProtoReflect.Descriptor instead.\nfunc (*Policy_Timeout) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{1, 0}\n}\n\nfunc (x *Policy_Timeout) GetHandshake() *Second {\n\tif x != nil {\n\t\treturn x.Handshake\n\t}\n\treturn nil\n}\n\nfunc (x *Policy_Timeout) GetConnectionIdle() *Second {\n\tif x != nil {\n\t\treturn x.ConnectionIdle\n\t}\n\treturn nil\n}\n\nfunc (x *Policy_Timeout) GetUplinkOnly() *Second {\n\tif x != nil {\n\t\treturn x.UplinkOnly\n\t}\n\treturn nil\n}\n\nfunc (x *Policy_Timeout) GetDownlinkOnly() *Second {\n\tif x != nil {\n\t\treturn x.DownlinkOnly\n\t}\n\treturn nil\n}\n\ntype Policy_Stats struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUserUplink    bool                   `protobuf:\"varint,1,opt,name=user_uplink,json=userUplink,proto3\" json:\"user_uplink,omitempty\"`\n\tUserDownlink  bool                   `protobuf:\"varint,2,opt,name=user_downlink,json=userDownlink,proto3\" json:\"user_downlink,omitempty\"`\n\tUserOnline    bool                   `protobuf:\"varint,3,opt,name=user_online,json=userOnline,proto3\" json:\"user_online,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Policy_Stats) Reset() {\n\t*x = Policy_Stats{}\n\tmi := &file_app_policy_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Policy_Stats) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Policy_Stats) ProtoMessage() {}\n\nfunc (x *Policy_Stats) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Policy_Stats.ProtoReflect.Descriptor instead.\nfunc (*Policy_Stats) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{1, 1}\n}\n\nfunc (x *Policy_Stats) GetUserUplink() bool {\n\tif x != nil {\n\t\treturn x.UserUplink\n\t}\n\treturn false\n}\n\nfunc (x *Policy_Stats) GetUserDownlink() bool {\n\tif x != nil {\n\t\treturn x.UserDownlink\n\t}\n\treturn false\n}\n\nfunc (x *Policy_Stats) GetUserOnline() bool {\n\tif x != nil {\n\t\treturn x.UserOnline\n\t}\n\treturn false\n}\n\ntype Policy_Buffer struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Buffer size per connection, in bytes. -1 for unlimited buffer.\n\tConnection    int32 `protobuf:\"varint,1,opt,name=connection,proto3\" json:\"connection,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Policy_Buffer) Reset() {\n\t*x = Policy_Buffer{}\n\tmi := &file_app_policy_config_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Policy_Buffer) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Policy_Buffer) ProtoMessage() {}\n\nfunc (x *Policy_Buffer) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Policy_Buffer.ProtoReflect.Descriptor instead.\nfunc (*Policy_Buffer) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{1, 2}\n}\n\nfunc (x *Policy_Buffer) GetConnection() int32 {\n\tif x != nil {\n\t\treturn x.Connection\n\t}\n\treturn 0\n}\n\ntype SystemPolicy_Stats struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tInboundUplink    bool                   `protobuf:\"varint,1,opt,name=inbound_uplink,json=inboundUplink,proto3\" json:\"inbound_uplink,omitempty\"`\n\tInboundDownlink  bool                   `protobuf:\"varint,2,opt,name=inbound_downlink,json=inboundDownlink,proto3\" json:\"inbound_downlink,omitempty\"`\n\tOutboundUplink   bool                   `protobuf:\"varint,3,opt,name=outbound_uplink,json=outboundUplink,proto3\" json:\"outbound_uplink,omitempty\"`\n\tOutboundDownlink bool                   `protobuf:\"varint,4,opt,name=outbound_downlink,json=outboundDownlink,proto3\" json:\"outbound_downlink,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *SystemPolicy_Stats) Reset() {\n\t*x = SystemPolicy_Stats{}\n\tmi := &file_app_policy_config_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SystemPolicy_Stats) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SystemPolicy_Stats) ProtoMessage() {}\n\nfunc (x *SystemPolicy_Stats) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_policy_config_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SystemPolicy_Stats.ProtoReflect.Descriptor instead.\nfunc (*SystemPolicy_Stats) Descriptor() ([]byte, []int) {\n\treturn file_app_policy_config_proto_rawDescGZIP(), []int{2, 0}\n}\n\nfunc (x *SystemPolicy_Stats) GetInboundUplink() bool {\n\tif x != nil {\n\t\treturn x.InboundUplink\n\t}\n\treturn false\n}\n\nfunc (x *SystemPolicy_Stats) GetInboundDownlink() bool {\n\tif x != nil {\n\t\treturn x.InboundDownlink\n\t}\n\treturn false\n}\n\nfunc (x *SystemPolicy_Stats) GetOutboundUplink() bool {\n\tif x != nil {\n\t\treturn x.OutboundUplink\n\t}\n\treturn false\n}\n\nfunc (x *SystemPolicy_Stats) GetOutboundDownlink() bool {\n\tif x != nil {\n\t\treturn x.OutboundDownlink\n\t}\n\treturn false\n}\n\nvar File_app_policy_config_proto protoreflect.FileDescriptor\n\nconst file_app_policy_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x17app/policy/config.proto\\x12\\x0fxray.app.policy\\\"\\x1e\\n\" +\n\t\"\\x06Second\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\rR\\x05value\\\"\\xc7\\x04\\n\" +\n\t\"\\x06Policy\\x129\\n\" +\n\t\"\\atimeout\\x18\\x01 \\x01(\\v2\\x1f.xray.app.policy.Policy.TimeoutR\\atimeout\\x123\\n\" +\n\t\"\\x05stats\\x18\\x02 \\x01(\\v2\\x1d.xray.app.policy.Policy.StatsR\\x05stats\\x126\\n\" +\n\t\"\\x06buffer\\x18\\x03 \\x01(\\v2\\x1e.xray.app.policy.Policy.BufferR\\x06buffer\\x1a\\xfa\\x01\\n\" +\n\t\"\\aTimeout\\x125\\n\" +\n\t\"\\thandshake\\x18\\x01 \\x01(\\v2\\x17.xray.app.policy.SecondR\\thandshake\\x12@\\n\" +\n\t\"\\x0fconnection_idle\\x18\\x02 \\x01(\\v2\\x17.xray.app.policy.SecondR\\x0econnectionIdle\\x128\\n\" +\n\t\"\\vuplink_only\\x18\\x03 \\x01(\\v2\\x17.xray.app.policy.SecondR\\n\" +\n\t\"uplinkOnly\\x12<\\n\" +\n\t\"\\rdownlink_only\\x18\\x04 \\x01(\\v2\\x17.xray.app.policy.SecondR\\fdownlinkOnly\\x1an\\n\" +\n\t\"\\x05Stats\\x12\\x1f\\n\" +\n\t\"\\vuser_uplink\\x18\\x01 \\x01(\\bR\\n\" +\n\t\"userUplink\\x12#\\n\" +\n\t\"\\ruser_downlink\\x18\\x02 \\x01(\\bR\\fuserDownlink\\x12\\x1f\\n\" +\n\t\"\\vuser_online\\x18\\x03 \\x01(\\bR\\n\" +\n\t\"userOnline\\x1a(\\n\" +\n\t\"\\x06Buffer\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"connection\\x18\\x01 \\x01(\\x05R\\n\" +\n\t\"connection\\\"\\xfb\\x01\\n\" +\n\t\"\\fSystemPolicy\\x129\\n\" +\n\t\"\\x05stats\\x18\\x01 \\x01(\\v2#.xray.app.policy.SystemPolicy.StatsR\\x05stats\\x1a\\xaf\\x01\\n\" +\n\t\"\\x05Stats\\x12%\\n\" +\n\t\"\\x0einbound_uplink\\x18\\x01 \\x01(\\bR\\rinboundUplink\\x12)\\n\" +\n\t\"\\x10inbound_downlink\\x18\\x02 \\x01(\\bR\\x0finboundDownlink\\x12'\\n\" +\n\t\"\\x0foutbound_uplink\\x18\\x03 \\x01(\\bR\\x0eoutboundUplink\\x12+\\n\" +\n\t\"\\x11outbound_downlink\\x18\\x04 \\x01(\\bR\\x10outboundDownlink\\\"\\xcc\\x01\\n\" +\n\t\"\\x06Config\\x128\\n\" +\n\t\"\\x05level\\x18\\x01 \\x03(\\v2\\\".xray.app.policy.Config.LevelEntryR\\x05level\\x125\\n\" +\n\t\"\\x06system\\x18\\x02 \\x01(\\v2\\x1d.xray.app.policy.SystemPolicyR\\x06system\\x1aQ\\n\" +\n\t\"\\n\" +\n\t\"LevelEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\rR\\x03key\\x12-\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\v2\\x17.xray.app.policy.PolicyR\\x05value:\\x028\\x01BO\\n\" +\n\t\"\\x13com.xray.app.policyP\\x01Z$github.com/xtls/xray-core/app/policy\\xaa\\x02\\x0fXray.App.Policyb\\x06proto3\"\n\nvar (\n\tfile_app_policy_config_proto_rawDescOnce sync.Once\n\tfile_app_policy_config_proto_rawDescData []byte\n)\n\nfunc file_app_policy_config_proto_rawDescGZIP() []byte {\n\tfile_app_policy_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_policy_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_policy_config_proto_rawDesc), len(file_app_policy_config_proto_rawDesc)))\n\t})\n\treturn file_app_policy_config_proto_rawDescData\n}\n\nvar file_app_policy_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9)\nvar file_app_policy_config_proto_goTypes = []any{\n\t(*Second)(nil),             // 0: xray.app.policy.Second\n\t(*Policy)(nil),             // 1: xray.app.policy.Policy\n\t(*SystemPolicy)(nil),       // 2: xray.app.policy.SystemPolicy\n\t(*Config)(nil),             // 3: xray.app.policy.Config\n\t(*Policy_Timeout)(nil),     // 4: xray.app.policy.Policy.Timeout\n\t(*Policy_Stats)(nil),       // 5: xray.app.policy.Policy.Stats\n\t(*Policy_Buffer)(nil),      // 6: xray.app.policy.Policy.Buffer\n\t(*SystemPolicy_Stats)(nil), // 7: xray.app.policy.SystemPolicy.Stats\n\tnil,                        // 8: xray.app.policy.Config.LevelEntry\n}\nvar file_app_policy_config_proto_depIdxs = []int32{\n\t4,  // 0: xray.app.policy.Policy.timeout:type_name -> xray.app.policy.Policy.Timeout\n\t5,  // 1: xray.app.policy.Policy.stats:type_name -> xray.app.policy.Policy.Stats\n\t6,  // 2: xray.app.policy.Policy.buffer:type_name -> xray.app.policy.Policy.Buffer\n\t7,  // 3: xray.app.policy.SystemPolicy.stats:type_name -> xray.app.policy.SystemPolicy.Stats\n\t8,  // 4: xray.app.policy.Config.level:type_name -> xray.app.policy.Config.LevelEntry\n\t2,  // 5: xray.app.policy.Config.system:type_name -> xray.app.policy.SystemPolicy\n\t0,  // 6: xray.app.policy.Policy.Timeout.handshake:type_name -> xray.app.policy.Second\n\t0,  // 7: xray.app.policy.Policy.Timeout.connection_idle:type_name -> xray.app.policy.Second\n\t0,  // 8: xray.app.policy.Policy.Timeout.uplink_only:type_name -> xray.app.policy.Second\n\t0,  // 9: xray.app.policy.Policy.Timeout.downlink_only:type_name -> xray.app.policy.Second\n\t1,  // 10: xray.app.policy.Config.LevelEntry.value:type_name -> xray.app.policy.Policy\n\t11, // [11:11] is the sub-list for method output_type\n\t11, // [11:11] is the sub-list for method input_type\n\t11, // [11:11] is the sub-list for extension type_name\n\t11, // [11:11] is the sub-list for extension extendee\n\t0,  // [0:11] is the sub-list for field type_name\n}\n\nfunc init() { file_app_policy_config_proto_init() }\nfunc file_app_policy_config_proto_init() {\n\tif File_app_policy_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_policy_config_proto_rawDesc), len(file_app_policy_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   9,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_policy_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_policy_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_policy_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_policy_config_proto = out.File\n\tfile_app_policy_config_proto_goTypes = nil\n\tfile_app_policy_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/policy/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.policy;\noption csharp_namespace = \"Xray.App.Policy\";\noption go_package = \"github.com/xtls/xray-core/app/policy\";\noption java_package = \"com.xray.app.policy\";\noption java_multiple_files = true;\n\nmessage Second {\n  uint32 value = 1;\n}\n\nmessage Policy {\n  // Timeout is a message for timeout settings in various stages, in seconds.\n  message Timeout {\n    Second handshake = 1;\n    Second connection_idle = 2;\n    Second uplink_only = 3;\n    Second downlink_only = 4;\n  }\n\n  message Stats {\n    bool user_uplink = 1;\n    bool user_downlink = 2;\n    bool user_online = 3;\n  }\n\n  message Buffer {\n    // Buffer size per connection, in bytes. -1 for unlimited buffer.\n    int32 connection = 1;\n  }\n\n  Timeout timeout = 1;\n  Stats stats = 2;\n  Buffer buffer = 3;\n}\n\nmessage SystemPolicy {\n  message Stats {\n    bool inbound_uplink = 1;\n    bool inbound_downlink = 2;\n    bool outbound_uplink = 3;\n    bool outbound_downlink = 4;\n  }\n\n  Stats stats = 1;\n}\n\nmessage Config {\n  map<uint32, Policy> level = 1;\n  SystemPolicy system = 2;\n}\n"
  },
  {
    "path": "app/policy/manager.go",
    "content": "package policy\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/features/policy\"\n)\n\n// Instance is an instance of Policy manager.\ntype Instance struct {\n\tlevels map[uint32]*Policy\n\tsystem *SystemPolicy\n}\n\n// New creates new Policy manager instance.\nfunc New(ctx context.Context, config *Config) (*Instance, error) {\n\tm := &Instance{\n\t\tlevels: make(map[uint32]*Policy),\n\t\tsystem: config.System,\n\t}\n\tif len(config.Level) > 0 {\n\t\tfor lv, p := range config.Level {\n\t\t\tpp := defaultPolicy()\n\t\t\tpp.overrideWith(p)\n\t\t\tm.levels[lv] = pp\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\n// Type implements common.HasType.\nfunc (*Instance) Type() interface{} {\n\treturn policy.ManagerType()\n}\n\n// ForLevel implements policy.Manager.\nfunc (m *Instance) ForLevel(level uint32) policy.Session {\n\tif p, ok := m.levels[level]; ok {\n\t\treturn p.ToCorePolicy()\n\t}\n\treturn policy.SessionDefault()\n}\n\n// ForSystem implements policy.Manager.\nfunc (m *Instance) ForSystem() policy.System {\n\tif m.system == nil {\n\t\treturn policy.System{}\n\t}\n\treturn m.system.ToCorePolicy()\n}\n\n// Start implements common.Runnable.Start().\nfunc (m *Instance) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.Close().\nfunc (m *Instance) Close() error {\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/policy/manager_test.go",
    "content": "package policy_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/features/policy\"\n)\n\nfunc TestPolicy(t *testing.T) {\n\tmanager, err := New(context.Background(), &Config{\n\t\tLevel: map[uint32]*Policy{\n\t\t\t0: {\n\t\t\t\tTimeout: &Policy_Timeout{\n\t\t\t\t\tHandshake: &Second{\n\t\t\t\t\t\tValue: 2,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\tpDefault := policy.SessionDefault()\n\n\t{\n\t\tp := manager.ForLevel(0)\n\t\tif p.Timeouts.Handshake != 2*time.Second {\n\t\t\tt.Error(\"expect 2 sec timeout, but got \", p.Timeouts.Handshake)\n\t\t}\n\t\tif p.Timeouts.ConnectionIdle != pDefault.Timeouts.ConnectionIdle {\n\t\t\tt.Error(\"expect \", pDefault.Timeouts.ConnectionIdle, \" sec timeout, but got \", p.Timeouts.ConnectionIdle)\n\t\t}\n\t}\n\n\t{\n\t\tp := manager.ForLevel(1)\n\t\tif p.Timeouts.Handshake != pDefault.Timeouts.Handshake {\n\t\t\tt.Error(\"expect \", pDefault.Timeouts.Handshake, \" sec timeout, but got \", p.Timeouts.Handshake)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/policy/policy.go",
    "content": "// Package policy is an implementation of policy.Manager feature.\npackage policy\n"
  },
  {
    "path": "app/proxyman/command/command.go",
    "content": "package command\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/app/commander\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/inbound\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/proxy\"\n\tgrpc \"google.golang.org/grpc\"\n)\n\n// InboundOperation is the interface for operations that applies to inbound handlers.\ntype InboundOperation interface {\n\t// ApplyInbound applies this operation to the given inbound handler.\n\tApplyInbound(context.Context, inbound.Handler) error\n}\n\n// OutboundOperation is the interface for operations that applies to outbound handlers.\ntype OutboundOperation interface {\n\t// ApplyOutbound applies this operation to the given outbound handler.\n\tApplyOutbound(context.Context, outbound.Handler) error\n}\n\nfunc getInbound(handler inbound.Handler) (proxy.Inbound, error) {\n\tgi, ok := handler.(proxy.GetInbound)\n\tif !ok {\n\t\treturn nil, errors.New(\"can't get inbound proxy from handler.\")\n\t}\n\treturn gi.GetInbound(), nil\n}\n\n// ApplyInbound implements InboundOperation.\nfunc (op *AddUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error {\n\tp, err := getInbound(handler)\n\tif err != nil {\n\t\treturn err\n\t}\n\tum, ok := p.(proxy.UserManager)\n\tif !ok {\n\t\treturn errors.New(\"proxy is not a UserManager\")\n\t}\n\tmUser, err := op.User.ToMemoryUser()\n\tif err != nil {\n\t\treturn errors.New(\"failed to parse user\").Base(err)\n\t}\n\treturn um.AddUser(ctx, mUser)\n}\n\n// ApplyInbound implements InboundOperation.\nfunc (op *RemoveUserOperation) ApplyInbound(ctx context.Context, handler inbound.Handler) error {\n\tp, err := getInbound(handler)\n\tif err != nil {\n\t\treturn err\n\t}\n\tum, ok := p.(proxy.UserManager)\n\tif !ok {\n\t\treturn errors.New(\"proxy is not a UserManager\")\n\t}\n\treturn um.RemoveUser(ctx, op.Email)\n}\n\ntype handlerServer struct {\n\ts   *core.Instance\n\tihm inbound.Manager\n\tohm outbound.Manager\n}\n\nfunc (s *handlerServer) AddInbound(ctx context.Context, request *AddInboundRequest) (*AddInboundResponse, error) {\n\tif err := core.AddInboundHandler(s.s, request.Inbound); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &AddInboundResponse{}, nil\n}\n\nfunc (s *handlerServer) RemoveInbound(ctx context.Context, request *RemoveInboundRequest) (*RemoveInboundResponse, error) {\n\treturn &RemoveInboundResponse{}, s.ihm.RemoveHandler(ctx, request.Tag)\n}\n\nfunc (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundRequest) (*AlterInboundResponse, error) {\n\trawOperation, err := request.Operation.GetInstance()\n\tif err != nil {\n\t\treturn nil, errors.New(\"unknown operation\").Base(err)\n\t}\n\toperation, ok := rawOperation.(InboundOperation)\n\tif !ok {\n\t\treturn nil, errors.New(\"not an inbound operation\")\n\t}\n\n\thandler, err := s.ihm.GetHandler(ctx, request.Tag)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get handler: \", request.Tag).Base(err)\n\t}\n\n\treturn &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler)\n}\n\nfunc (s *handlerServer) ListInbounds(ctx context.Context, request *ListInboundsRequest) (*ListInboundsResponse, error) {\n\thandlers := s.ihm.ListHandlers(ctx)\n\tresponse := &ListInboundsResponse{}\n\tif request.GetIsOnlyTags() {\n\t\tfor _, handler := range handlers {\n\t\t\tresponse.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{\n\t\t\t\tTag: handler.Tag(),\n\t\t\t})\n\t\t}\n\t} else {\n\t\tfor _, handler := range handlers {\n\t\t\tresponse.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{\n\t\t\t\tTag:              handler.Tag(),\n\t\t\t\tReceiverSettings: handler.ReceiverSettings(),\n\t\t\t\tProxySettings:    handler.ProxySettings(),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn response, nil\n}\n\nfunc (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUserResponse, error) {\n\thandler, err := s.ihm.GetHandler(ctx, request.Tag)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get handler: \", request.Tag).Base(err)\n\t}\n\tp, err := getInbound(handler)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tum, ok := p.(proxy.UserManager)\n\tif !ok {\n\t\treturn nil, errors.New(\"proxy is not a UserManager\")\n\t}\n\tif len(request.Email) > 0 {\n\t\treturn &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil\n\t}\n\tvar result = make([]*protocol.User, 0, 100)\n\tusers := um.GetUsers(ctx)\n\tfor _, u := range users {\n\t\tresult = append(result, protocol.ToProtoUser(u))\n\t}\n\treturn &GetInboundUserResponse{Users: result}, nil\n}\n\nfunc (s *handlerServer) GetInboundUsersCount(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUsersCountResponse, error) {\n\thandler, err := s.ihm.GetHandler(ctx, request.Tag)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get handler: \", request.Tag).Base(err)\n\t}\n\tp, err := getInbound(handler)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tum, ok := p.(proxy.UserManager)\n\tif !ok {\n\t\treturn nil, errors.New(\"proxy is not a UserManager\")\n\t}\n\treturn &GetInboundUsersCountResponse{Count: um.GetUsersCount(ctx)}, nil\n}\n\nfunc (s *handlerServer) AddOutbound(ctx context.Context, request *AddOutboundRequest) (*AddOutboundResponse, error) {\n\tif err := core.AddOutboundHandler(s.s, request.Outbound); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &AddOutboundResponse{}, nil\n}\n\nfunc (s *handlerServer) RemoveOutbound(ctx context.Context, request *RemoveOutboundRequest) (*RemoveOutboundResponse, error) {\n\treturn &RemoveOutboundResponse{}, s.ohm.RemoveHandler(ctx, request.Tag)\n}\n\nfunc (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboundRequest) (*AlterOutboundResponse, error) {\n\trawOperation, err := request.Operation.GetInstance()\n\tif err != nil {\n\t\treturn nil, errors.New(\"unknown operation\").Base(err)\n\t}\n\toperation, ok := rawOperation.(OutboundOperation)\n\tif !ok {\n\t\treturn nil, errors.New(\"not an outbound operation\")\n\t}\n\n\thandler := s.ohm.GetHandler(request.Tag)\n\treturn &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler)\n}\n\nfunc (s *handlerServer) ListOutbounds(ctx context.Context, request *ListOutboundsRequest) (*ListOutboundsResponse, error) {\n\thandlers := s.ohm.ListHandlers(ctx)\n\tresponse := &ListOutboundsResponse{}\n\tfor _, handler := range handlers {\n\t\t// Ignore gRPC outbound\n\t\tif _, ok := handler.(*commander.Outbound); ok {\n\t\t\tcontinue\n\t\t}\n\t\tresponse.Outbounds = append(response.Outbounds, &core.OutboundHandlerConfig{\n\t\t\tTag:            handler.Tag(),\n\t\t\tSenderSettings: handler.SenderSettings(),\n\t\t\tProxySettings:  handler.ProxySettings(),\n\t\t})\n\t}\n\treturn response, nil\n}\n\nfunc (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {}\n\ntype service struct {\n\tv *core.Instance\n}\n\nfunc (s *service) Register(server *grpc.Server) {\n\ths := &handlerServer{\n\t\ts: s.v,\n\t}\n\tcommon.Must(s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) {\n\t\ths.ihm = im\n\t\ths.ohm = om\n\t}, false))\n\tRegisterHandlerServiceServer(server, hs)\n\n\t// For compatibility purposes\n\tvCoreDesc := HandlerService_ServiceDesc\n\tvCoreDesc.ServiceName = \"v2ray.core.app.proxyman.command.HandlerService\"\n\tserver.RegisterService(&vCoreDesc, hs)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\ts := core.MustFromContext(ctx)\n\t\treturn &service{v: s}, nil\n\t}))\n}\n"
  },
  {
    "path": "app/proxyman/command/command.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/proxyman/command/command.proto\n\npackage command\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tcore \"github.com/xtls/xray-core/core\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype AddUserOperation struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUser          *protocol.User         `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddUserOperation) Reset() {\n\t*x = AddUserOperation{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddUserOperation) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddUserOperation) ProtoMessage() {}\n\nfunc (x *AddUserOperation) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddUserOperation.ProtoReflect.Descriptor instead.\nfunc (*AddUserOperation) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *AddUserOperation) GetUser() *protocol.User {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\ntype RemoveUserOperation struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail         string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveUserOperation) Reset() {\n\t*x = RemoveUserOperation{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveUserOperation) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveUserOperation) ProtoMessage() {}\n\nfunc (x *RemoveUserOperation) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveUserOperation.ProtoReflect.Descriptor instead.\nfunc (*RemoveUserOperation) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *RemoveUserOperation) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype AddInboundRequest struct {\n\tstate         protoimpl.MessageState     `protogen:\"open.v1\"`\n\tInbound       *core.InboundHandlerConfig `protobuf:\"bytes,1,opt,name=inbound,proto3\" json:\"inbound,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddInboundRequest) Reset() {\n\t*x = AddInboundRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddInboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddInboundRequest) ProtoMessage() {}\n\nfunc (x *AddInboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddInboundRequest.ProtoReflect.Descriptor instead.\nfunc (*AddInboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *AddInboundRequest) GetInbound() *core.InboundHandlerConfig {\n\tif x != nil {\n\t\treturn x.Inbound\n\t}\n\treturn nil\n}\n\ntype AddInboundResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddInboundResponse) Reset() {\n\t*x = AddInboundResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddInboundResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddInboundResponse) ProtoMessage() {}\n\nfunc (x *AddInboundResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddInboundResponse.ProtoReflect.Descriptor instead.\nfunc (*AddInboundResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{3}\n}\n\ntype RemoveInboundRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveInboundRequest) Reset() {\n\t*x = RemoveInboundRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveInboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveInboundRequest) ProtoMessage() {}\n\nfunc (x *RemoveInboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveInboundRequest.ProtoReflect.Descriptor instead.\nfunc (*RemoveInboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *RemoveInboundRequest) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\ntype RemoveInboundResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveInboundResponse) Reset() {\n\t*x = RemoveInboundResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveInboundResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveInboundResponse) ProtoMessage() {}\n\nfunc (x *RemoveInboundResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveInboundResponse.ProtoReflect.Descriptor instead.\nfunc (*RemoveInboundResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{5}\n}\n\ntype AlterInboundRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tOperation     *serial.TypedMessage   `protobuf:\"bytes,2,opt,name=operation,proto3\" json:\"operation,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AlterInboundRequest) Reset() {\n\t*x = AlterInboundRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AlterInboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AlterInboundRequest) ProtoMessage() {}\n\nfunc (x *AlterInboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AlterInboundRequest.ProtoReflect.Descriptor instead.\nfunc (*AlterInboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *AlterInboundRequest) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *AlterInboundRequest) GetOperation() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Operation\n\t}\n\treturn nil\n}\n\ntype AlterInboundResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AlterInboundResponse) Reset() {\n\t*x = AlterInboundResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AlterInboundResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AlterInboundResponse) ProtoMessage() {}\n\nfunc (x *AlterInboundResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AlterInboundResponse.ProtoReflect.Descriptor instead.\nfunc (*AlterInboundResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{7}\n}\n\ntype ListInboundsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIsOnlyTags    bool                   `protobuf:\"varint,1,opt,name=isOnlyTags,proto3\" json:\"isOnlyTags,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListInboundsRequest) Reset() {\n\t*x = ListInboundsRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListInboundsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListInboundsRequest) ProtoMessage() {}\n\nfunc (x *ListInboundsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListInboundsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListInboundsRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *ListInboundsRequest) GetIsOnlyTags() bool {\n\tif x != nil {\n\t\treturn x.IsOnlyTags\n\t}\n\treturn false\n}\n\ntype ListInboundsResponse struct {\n\tstate         protoimpl.MessageState       `protogen:\"open.v1\"`\n\tInbounds      []*core.InboundHandlerConfig `protobuf:\"bytes,1,rep,name=inbounds,proto3\" json:\"inbounds,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListInboundsResponse) Reset() {\n\t*x = ListInboundsResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListInboundsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListInboundsResponse) ProtoMessage() {}\n\nfunc (x *ListInboundsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListInboundsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListInboundsResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *ListInboundsResponse) GetInbounds() []*core.InboundHandlerConfig {\n\tif x != nil {\n\t\treturn x.Inbounds\n\t}\n\treturn nil\n}\n\ntype GetInboundUserRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tEmail         string                 `protobuf:\"bytes,2,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetInboundUserRequest) Reset() {\n\t*x = GetInboundUserRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetInboundUserRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetInboundUserRequest) ProtoMessage() {}\n\nfunc (x *GetInboundUserRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetInboundUserRequest.ProtoReflect.Descriptor instead.\nfunc (*GetInboundUserRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *GetInboundUserRequest) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetInboundUserRequest) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype GetInboundUserResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsers         []*protocol.User       `protobuf:\"bytes,1,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetInboundUserResponse) Reset() {\n\t*x = GetInboundUserResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetInboundUserResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetInboundUserResponse) ProtoMessage() {}\n\nfunc (x *GetInboundUserResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetInboundUserResponse.ProtoReflect.Descriptor instead.\nfunc (*GetInboundUserResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *GetInboundUserResponse) GetUsers() []*protocol.User {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\ntype GetInboundUsersCountResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tCount         int64                  `protobuf:\"varint,1,opt,name=count,proto3\" json:\"count,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetInboundUsersCountResponse) Reset() {\n\t*x = GetInboundUsersCountResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetInboundUsersCountResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetInboundUsersCountResponse) ProtoMessage() {}\n\nfunc (x *GetInboundUsersCountResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetInboundUsersCountResponse.ProtoReflect.Descriptor instead.\nfunc (*GetInboundUsersCountResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *GetInboundUsersCountResponse) GetCount() int64 {\n\tif x != nil {\n\t\treturn x.Count\n\t}\n\treturn 0\n}\n\ntype AddOutboundRequest struct {\n\tstate         protoimpl.MessageState      `protogen:\"open.v1\"`\n\tOutbound      *core.OutboundHandlerConfig `protobuf:\"bytes,1,opt,name=outbound,proto3\" json:\"outbound,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddOutboundRequest) Reset() {\n\t*x = AddOutboundRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddOutboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddOutboundRequest) ProtoMessage() {}\n\nfunc (x *AddOutboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddOutboundRequest.ProtoReflect.Descriptor instead.\nfunc (*AddOutboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *AddOutboundRequest) GetOutbound() *core.OutboundHandlerConfig {\n\tif x != nil {\n\t\treturn x.Outbound\n\t}\n\treturn nil\n}\n\ntype AddOutboundResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddOutboundResponse) Reset() {\n\t*x = AddOutboundResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddOutboundResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddOutboundResponse) ProtoMessage() {}\n\nfunc (x *AddOutboundResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddOutboundResponse.ProtoReflect.Descriptor instead.\nfunc (*AddOutboundResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{14}\n}\n\ntype RemoveOutboundRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveOutboundRequest) Reset() {\n\t*x = RemoveOutboundRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveOutboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveOutboundRequest) ProtoMessage() {}\n\nfunc (x *RemoveOutboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveOutboundRequest.ProtoReflect.Descriptor instead.\nfunc (*RemoveOutboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *RemoveOutboundRequest) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\ntype RemoveOutboundResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveOutboundResponse) Reset() {\n\t*x = RemoveOutboundResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveOutboundResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveOutboundResponse) ProtoMessage() {}\n\nfunc (x *RemoveOutboundResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveOutboundResponse.ProtoReflect.Descriptor instead.\nfunc (*RemoveOutboundResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{16}\n}\n\ntype AlterOutboundRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tOperation     *serial.TypedMessage   `protobuf:\"bytes,2,opt,name=operation,proto3\" json:\"operation,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AlterOutboundRequest) Reset() {\n\t*x = AlterOutboundRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AlterOutboundRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AlterOutboundRequest) ProtoMessage() {}\n\nfunc (x *AlterOutboundRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AlterOutboundRequest.ProtoReflect.Descriptor instead.\nfunc (*AlterOutboundRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *AlterOutboundRequest) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *AlterOutboundRequest) GetOperation() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Operation\n\t}\n\treturn nil\n}\n\ntype AlterOutboundResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AlterOutboundResponse) Reset() {\n\t*x = AlterOutboundResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AlterOutboundResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AlterOutboundResponse) ProtoMessage() {}\n\nfunc (x *AlterOutboundResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[18]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AlterOutboundResponse.ProtoReflect.Descriptor instead.\nfunc (*AlterOutboundResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{18}\n}\n\ntype ListOutboundsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListOutboundsRequest) Reset() {\n\t*x = ListOutboundsRequest{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListOutboundsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListOutboundsRequest) ProtoMessage() {}\n\nfunc (x *ListOutboundsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[19]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListOutboundsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListOutboundsRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{19}\n}\n\ntype ListOutboundsResponse struct {\n\tstate         protoimpl.MessageState        `protogen:\"open.v1\"`\n\tOutbounds     []*core.OutboundHandlerConfig `protobuf:\"bytes,1,rep,name=outbounds,proto3\" json:\"outbounds,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListOutboundsResponse) Reset() {\n\t*x = ListOutboundsResponse{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListOutboundsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListOutboundsResponse) ProtoMessage() {}\n\nfunc (x *ListOutboundsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[20]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListOutboundsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListOutboundsResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *ListOutboundsResponse) GetOutbounds() []*core.OutboundHandlerConfig {\n\tif x != nil {\n\t\treturn x.Outbounds\n\t}\n\treturn nil\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_command_command_proto_msgTypes[21]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_command_command_proto_rawDescGZIP(), []int{21}\n}\n\nvar File_app_proxyman_command_command_proto protoreflect.FileDescriptor\n\nconst file_app_proxyman_command_command_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\\"app/proxyman/command/command.proto\\x12\\x19xray.app.proxyman.command\\x1a\\x1acommon/protocol/user.proto\\x1a!common/serial/typed_message.proto\\x1a\\x11core/config.proto\\\"B\\n\" +\n\t\"\\x10AddUserOperation\\x12.\\n\" +\n\t\"\\x04user\\x18\\x01 \\x01(\\v2\\x1a.xray.common.protocol.UserR\\x04user\\\"+\\n\" +\n\t\"\\x13RemoveUserOperation\\x12\\x14\\n\" +\n\t\"\\x05email\\x18\\x01 \\x01(\\tR\\x05email\\\"N\\n\" +\n\t\"\\x11AddInboundRequest\\x129\\n\" +\n\t\"\\ainbound\\x18\\x01 \\x01(\\v2\\x1f.xray.core.InboundHandlerConfigR\\ainbound\\\"\\x14\\n\" +\n\t\"\\x12AddInboundResponse\\\"(\\n\" +\n\t\"\\x14RemoveInboundRequest\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\\"\\x17\\n\" +\n\t\"\\x15RemoveInboundResponse\\\"g\\n\" +\n\t\"\\x13AlterInboundRequest\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12>\\n\" +\n\t\"\\toperation\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\toperation\\\"\\x16\\n\" +\n\t\"\\x14AlterInboundResponse\\\"5\\n\" +\n\t\"\\x13ListInboundsRequest\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"isOnlyTags\\x18\\x01 \\x01(\\bR\\n\" +\n\t\"isOnlyTags\\\"S\\n\" +\n\t\"\\x14ListInboundsResponse\\x12;\\n\" +\n\t\"\\binbounds\\x18\\x01 \\x03(\\v2\\x1f.xray.core.InboundHandlerConfigR\\binbounds\\\"?\\n\" +\n\t\"\\x15GetInboundUserRequest\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x14\\n\" +\n\t\"\\x05email\\x18\\x02 \\x01(\\tR\\x05email\\\"J\\n\" +\n\t\"\\x16GetInboundUserResponse\\x120\\n\" +\n\t\"\\x05users\\x18\\x01 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\x05users\\\"4\\n\" +\n\t\"\\x1cGetInboundUsersCountResponse\\x12\\x14\\n\" +\n\t\"\\x05count\\x18\\x01 \\x01(\\x03R\\x05count\\\"R\\n\" +\n\t\"\\x12AddOutboundRequest\\x12<\\n\" +\n\t\"\\boutbound\\x18\\x01 \\x01(\\v2 .xray.core.OutboundHandlerConfigR\\boutbound\\\"\\x15\\n\" +\n\t\"\\x13AddOutboundResponse\\\")\\n\" +\n\t\"\\x15RemoveOutboundRequest\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\\"\\x18\\n\" +\n\t\"\\x16RemoveOutboundResponse\\\"h\\n\" +\n\t\"\\x14AlterOutboundRequest\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12>\\n\" +\n\t\"\\toperation\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\toperation\\\"\\x17\\n\" +\n\t\"\\x15AlterOutboundResponse\\\"\\x16\\n\" +\n\t\"\\x14ListOutboundsRequest\\\"W\\n\" +\n\t\"\\x15ListOutboundsResponse\\x12>\\n\" +\n\t\"\\toutbounds\\x18\\x01 \\x03(\\v2 .xray.core.OutboundHandlerConfigR\\toutbounds\\\"\\b\\n\" +\n\t\"\\x06Config2\\xae\\t\\n\" +\n\t\"\\x0eHandlerService\\x12k\\n\" +\n\t\"\\n\" +\n\t\"AddInbound\\x12,.xray.app.proxyman.command.AddInboundRequest\\x1a-.xray.app.proxyman.command.AddInboundResponse\\\"\\x00\\x12t\\n\" +\n\t\"\\rRemoveInbound\\x12/.xray.app.proxyman.command.RemoveInboundRequest\\x1a0.xray.app.proxyman.command.RemoveInboundResponse\\\"\\x00\\x12q\\n\" +\n\t\"\\fAlterInbound\\x12..xray.app.proxyman.command.AlterInboundRequest\\x1a/.xray.app.proxyman.command.AlterInboundResponse\\\"\\x00\\x12q\\n\" +\n\t\"\\fListInbounds\\x12..xray.app.proxyman.command.ListInboundsRequest\\x1a/.xray.app.proxyman.command.ListInboundsResponse\\\"\\x00\\x12x\\n\" +\n\t\"\\x0fGetInboundUsers\\x120.xray.app.proxyman.command.GetInboundUserRequest\\x1a1.xray.app.proxyman.command.GetInboundUserResponse\\\"\\x00\\x12\\x83\\x01\\n\" +\n\t\"\\x14GetInboundUsersCount\\x120.xray.app.proxyman.command.GetInboundUserRequest\\x1a7.xray.app.proxyman.command.GetInboundUsersCountResponse\\\"\\x00\\x12n\\n\" +\n\t\"\\vAddOutbound\\x12-.xray.app.proxyman.command.AddOutboundRequest\\x1a..xray.app.proxyman.command.AddOutboundResponse\\\"\\x00\\x12w\\n\" +\n\t\"\\x0eRemoveOutbound\\x120.xray.app.proxyman.command.RemoveOutboundRequest\\x1a1.xray.app.proxyman.command.RemoveOutboundResponse\\\"\\x00\\x12t\\n\" +\n\t\"\\rAlterOutbound\\x12/.xray.app.proxyman.command.AlterOutboundRequest\\x1a0.xray.app.proxyman.command.AlterOutboundResponse\\\"\\x00\\x12t\\n\" +\n\t\"\\rListOutbounds\\x12/.xray.app.proxyman.command.ListOutboundsRequest\\x1a0.xray.app.proxyman.command.ListOutboundsResponse\\\"\\x00Bm\\n\" +\n\t\"\\x1dcom.xray.app.proxyman.commandP\\x01Z.github.com/xtls/xray-core/app/proxyman/command\\xaa\\x02\\x19Xray.App.Proxyman.Commandb\\x06proto3\"\n\nvar (\n\tfile_app_proxyman_command_command_proto_rawDescOnce sync.Once\n\tfile_app_proxyman_command_command_proto_rawDescData []byte\n)\n\nfunc file_app_proxyman_command_command_proto_rawDescGZIP() []byte {\n\tfile_app_proxyman_command_command_proto_rawDescOnce.Do(func() {\n\t\tfile_app_proxyman_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_proxyman_command_command_proto_rawDesc), len(file_app_proxyman_command_command_proto_rawDesc)))\n\t})\n\treturn file_app_proxyman_command_command_proto_rawDescData\n}\n\nvar file_app_proxyman_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 22)\nvar file_app_proxyman_command_command_proto_goTypes = []any{\n\t(*AddUserOperation)(nil),             // 0: xray.app.proxyman.command.AddUserOperation\n\t(*RemoveUserOperation)(nil),          // 1: xray.app.proxyman.command.RemoveUserOperation\n\t(*AddInboundRequest)(nil),            // 2: xray.app.proxyman.command.AddInboundRequest\n\t(*AddInboundResponse)(nil),           // 3: xray.app.proxyman.command.AddInboundResponse\n\t(*RemoveInboundRequest)(nil),         // 4: xray.app.proxyman.command.RemoveInboundRequest\n\t(*RemoveInboundResponse)(nil),        // 5: xray.app.proxyman.command.RemoveInboundResponse\n\t(*AlterInboundRequest)(nil),          // 6: xray.app.proxyman.command.AlterInboundRequest\n\t(*AlterInboundResponse)(nil),         // 7: xray.app.proxyman.command.AlterInboundResponse\n\t(*ListInboundsRequest)(nil),          // 8: xray.app.proxyman.command.ListInboundsRequest\n\t(*ListInboundsResponse)(nil),         // 9: xray.app.proxyman.command.ListInboundsResponse\n\t(*GetInboundUserRequest)(nil),        // 10: xray.app.proxyman.command.GetInboundUserRequest\n\t(*GetInboundUserResponse)(nil),       // 11: xray.app.proxyman.command.GetInboundUserResponse\n\t(*GetInboundUsersCountResponse)(nil), // 12: xray.app.proxyman.command.GetInboundUsersCountResponse\n\t(*AddOutboundRequest)(nil),           // 13: xray.app.proxyman.command.AddOutboundRequest\n\t(*AddOutboundResponse)(nil),          // 14: xray.app.proxyman.command.AddOutboundResponse\n\t(*RemoveOutboundRequest)(nil),        // 15: xray.app.proxyman.command.RemoveOutboundRequest\n\t(*RemoveOutboundResponse)(nil),       // 16: xray.app.proxyman.command.RemoveOutboundResponse\n\t(*AlterOutboundRequest)(nil),         // 17: xray.app.proxyman.command.AlterOutboundRequest\n\t(*AlterOutboundResponse)(nil),        // 18: xray.app.proxyman.command.AlterOutboundResponse\n\t(*ListOutboundsRequest)(nil),         // 19: xray.app.proxyman.command.ListOutboundsRequest\n\t(*ListOutboundsResponse)(nil),        // 20: xray.app.proxyman.command.ListOutboundsResponse\n\t(*Config)(nil),                       // 21: xray.app.proxyman.command.Config\n\t(*protocol.User)(nil),                // 22: xray.common.protocol.User\n\t(*core.InboundHandlerConfig)(nil),    // 23: xray.core.InboundHandlerConfig\n\t(*serial.TypedMessage)(nil),          // 24: xray.common.serial.TypedMessage\n\t(*core.OutboundHandlerConfig)(nil),   // 25: xray.core.OutboundHandlerConfig\n}\nvar file_app_proxyman_command_command_proto_depIdxs = []int32{\n\t22, // 0: xray.app.proxyman.command.AddUserOperation.user:type_name -> xray.common.protocol.User\n\t23, // 1: xray.app.proxyman.command.AddInboundRequest.inbound:type_name -> xray.core.InboundHandlerConfig\n\t24, // 2: xray.app.proxyman.command.AlterInboundRequest.operation:type_name -> xray.common.serial.TypedMessage\n\t23, // 3: xray.app.proxyman.command.ListInboundsResponse.inbounds:type_name -> xray.core.InboundHandlerConfig\n\t22, // 4: xray.app.proxyman.command.GetInboundUserResponse.users:type_name -> xray.common.protocol.User\n\t25, // 5: xray.app.proxyman.command.AddOutboundRequest.outbound:type_name -> xray.core.OutboundHandlerConfig\n\t24, // 6: xray.app.proxyman.command.AlterOutboundRequest.operation:type_name -> xray.common.serial.TypedMessage\n\t25, // 7: xray.app.proxyman.command.ListOutboundsResponse.outbounds:type_name -> xray.core.OutboundHandlerConfig\n\t2,  // 8: xray.app.proxyman.command.HandlerService.AddInbound:input_type -> xray.app.proxyman.command.AddInboundRequest\n\t4,  // 9: xray.app.proxyman.command.HandlerService.RemoveInbound:input_type -> xray.app.proxyman.command.RemoveInboundRequest\n\t6,  // 10: xray.app.proxyman.command.HandlerService.AlterInbound:input_type -> xray.app.proxyman.command.AlterInboundRequest\n\t8,  // 11: xray.app.proxyman.command.HandlerService.ListInbounds:input_type -> xray.app.proxyman.command.ListInboundsRequest\n\t10, // 12: xray.app.proxyman.command.HandlerService.GetInboundUsers:input_type -> xray.app.proxyman.command.GetInboundUserRequest\n\t10, // 13: xray.app.proxyman.command.HandlerService.GetInboundUsersCount:input_type -> xray.app.proxyman.command.GetInboundUserRequest\n\t13, // 14: xray.app.proxyman.command.HandlerService.AddOutbound:input_type -> xray.app.proxyman.command.AddOutboundRequest\n\t15, // 15: xray.app.proxyman.command.HandlerService.RemoveOutbound:input_type -> xray.app.proxyman.command.RemoveOutboundRequest\n\t17, // 16: xray.app.proxyman.command.HandlerService.AlterOutbound:input_type -> xray.app.proxyman.command.AlterOutboundRequest\n\t19, // 17: xray.app.proxyman.command.HandlerService.ListOutbounds:input_type -> xray.app.proxyman.command.ListOutboundsRequest\n\t3,  // 18: xray.app.proxyman.command.HandlerService.AddInbound:output_type -> xray.app.proxyman.command.AddInboundResponse\n\t5,  // 19: xray.app.proxyman.command.HandlerService.RemoveInbound:output_type -> xray.app.proxyman.command.RemoveInboundResponse\n\t7,  // 20: xray.app.proxyman.command.HandlerService.AlterInbound:output_type -> xray.app.proxyman.command.AlterInboundResponse\n\t9,  // 21: xray.app.proxyman.command.HandlerService.ListInbounds:output_type -> xray.app.proxyman.command.ListInboundsResponse\n\t11, // 22: xray.app.proxyman.command.HandlerService.GetInboundUsers:output_type -> xray.app.proxyman.command.GetInboundUserResponse\n\t12, // 23: xray.app.proxyman.command.HandlerService.GetInboundUsersCount:output_type -> xray.app.proxyman.command.GetInboundUsersCountResponse\n\t14, // 24: xray.app.proxyman.command.HandlerService.AddOutbound:output_type -> xray.app.proxyman.command.AddOutboundResponse\n\t16, // 25: xray.app.proxyman.command.HandlerService.RemoveOutbound:output_type -> xray.app.proxyman.command.RemoveOutboundResponse\n\t18, // 26: xray.app.proxyman.command.HandlerService.AlterOutbound:output_type -> xray.app.proxyman.command.AlterOutboundResponse\n\t20, // 27: xray.app.proxyman.command.HandlerService.ListOutbounds:output_type -> xray.app.proxyman.command.ListOutboundsResponse\n\t18, // [18:28] is the sub-list for method output_type\n\t8,  // [8:18] is the sub-list for method input_type\n\t8,  // [8:8] is the sub-list for extension type_name\n\t8,  // [8:8] is the sub-list for extension extendee\n\t0,  // [0:8] is the sub-list for field type_name\n}\n\nfunc init() { file_app_proxyman_command_command_proto_init() }\nfunc file_app_proxyman_command_command_proto_init() {\n\tif File_app_proxyman_command_command_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_proxyman_command_command_proto_rawDesc), len(file_app_proxyman_command_command_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   22,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_app_proxyman_command_command_proto_goTypes,\n\t\tDependencyIndexes: file_app_proxyman_command_command_proto_depIdxs,\n\t\tMessageInfos:      file_app_proxyman_command_command_proto_msgTypes,\n\t}.Build()\n\tFile_app_proxyman_command_command_proto = out.File\n\tfile_app_proxyman_command_command_proto_goTypes = nil\n\tfile_app_proxyman_command_command_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/proxyman/command/command.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.proxyman.command;\noption csharp_namespace = \"Xray.App.Proxyman.Command\";\noption go_package = \"github.com/xtls/xray-core/app/proxyman/command\";\noption java_package = \"com.xray.app.proxyman.command\";\noption java_multiple_files = true;\n\nimport \"common/protocol/user.proto\";\nimport \"common/serial/typed_message.proto\";\nimport \"core/config.proto\";\n\nmessage AddUserOperation {\n  xray.common.protocol.User user = 1;\n}\n\nmessage RemoveUserOperation {\n  string email = 1;\n}\n\nmessage AddInboundRequest {\n  core.InboundHandlerConfig inbound = 1;\n}\n\nmessage AddInboundResponse {}\n\nmessage RemoveInboundRequest {\n  string tag = 1;\n}\n\nmessage RemoveInboundResponse {}\n\nmessage AlterInboundRequest {\n  string tag = 1;\n  xray.common.serial.TypedMessage operation = 2;\n}\n\nmessage AlterInboundResponse {}\n\nmessage ListInboundsRequest {\n  bool isOnlyTags = 1;\n}\n\nmessage ListInboundsResponse {\n  repeated core.InboundHandlerConfig inbounds = 1;\n}\n\nmessage GetInboundUserRequest {\n  string tag = 1;\n  string email = 2;\n}\n\nmessage GetInboundUserResponse {\n  repeated xray.common.protocol.User users = 1;\n}\n\nmessage GetInboundUsersCountResponse {\n  int64 count = 1;\n}\n\nmessage AddOutboundRequest {\n  core.OutboundHandlerConfig outbound = 1;\n}\n\nmessage AddOutboundResponse {}\n\nmessage RemoveOutboundRequest {\n  string tag = 1;\n}\n\nmessage RemoveOutboundResponse {}\n\nmessage AlterOutboundRequest {\n  string tag = 1;\n  xray.common.serial.TypedMessage operation = 2;\n}\n\nmessage AlterOutboundResponse {}\n\nmessage ListOutboundsRequest {}\n\nmessage ListOutboundsResponse {\n  repeated core.OutboundHandlerConfig outbounds = 1;\n}\n\nservice HandlerService {\n  rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {}\n\n  rpc RemoveInbound(RemoveInboundRequest) returns (RemoveInboundResponse) {}\n\n  rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {}\n\n  rpc ListInbounds(ListInboundsRequest) returns (ListInboundsResponse) {}\n\n  rpc GetInboundUsers(GetInboundUserRequest) returns (GetInboundUserResponse) {}\n\n  rpc GetInboundUsersCount(GetInboundUserRequest) returns (GetInboundUsersCountResponse) {}\n\n  rpc AddOutbound(AddOutboundRequest) returns (AddOutboundResponse) {}\n\n  rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {}\n\n  rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {}\n\n  rpc ListOutbounds(ListOutboundsRequest) returns (ListOutboundsResponse) {}\n}\n\nmessage Config {}\n"
  },
  {
    "path": "app/proxyman/command/command_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.0\n// - protoc             v6.33.5\n// source: app/proxyman/command/command.proto\n\npackage command\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tHandlerService_AddInbound_FullMethodName           = \"/xray.app.proxyman.command.HandlerService/AddInbound\"\n\tHandlerService_RemoveInbound_FullMethodName        = \"/xray.app.proxyman.command.HandlerService/RemoveInbound\"\n\tHandlerService_AlterInbound_FullMethodName         = \"/xray.app.proxyman.command.HandlerService/AlterInbound\"\n\tHandlerService_ListInbounds_FullMethodName         = \"/xray.app.proxyman.command.HandlerService/ListInbounds\"\n\tHandlerService_GetInboundUsers_FullMethodName      = \"/xray.app.proxyman.command.HandlerService/GetInboundUsers\"\n\tHandlerService_GetInboundUsersCount_FullMethodName = \"/xray.app.proxyman.command.HandlerService/GetInboundUsersCount\"\n\tHandlerService_AddOutbound_FullMethodName          = \"/xray.app.proxyman.command.HandlerService/AddOutbound\"\n\tHandlerService_RemoveOutbound_FullMethodName       = \"/xray.app.proxyman.command.HandlerService/RemoveOutbound\"\n\tHandlerService_AlterOutbound_FullMethodName        = \"/xray.app.proxyman.command.HandlerService/AlterOutbound\"\n\tHandlerService_ListOutbounds_FullMethodName        = \"/xray.app.proxyman.command.HandlerService/ListOutbounds\"\n)\n\n// HandlerServiceClient is the client API for HandlerService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype HandlerServiceClient interface {\n\tAddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error)\n\tRemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error)\n\tAlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error)\n\tListInbounds(ctx context.Context, in *ListInboundsRequest, opts ...grpc.CallOption) (*ListInboundsResponse, error)\n\tGetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error)\n\tGetInboundUsersCount(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUsersCountResponse, error)\n\tAddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error)\n\tRemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error)\n\tAlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error)\n\tListOutbounds(ctx context.Context, in *ListOutboundsRequest, opts ...grpc.CallOption) (*ListOutboundsResponse, error)\n}\n\ntype handlerServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewHandlerServiceClient(cc grpc.ClientConnInterface) HandlerServiceClient {\n\treturn &handlerServiceClient{cc}\n}\n\nfunc (c *handlerServiceClient) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AddInboundResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_AddInbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RemoveInboundResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_RemoveInbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AlterInboundResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_AlterInbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) ListInbounds(ctx context.Context, in *ListInboundsRequest, opts ...grpc.CallOption) (*ListInboundsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListInboundsResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_ListInbounds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetInboundUserResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_GetInboundUsers_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) GetInboundUsersCount(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUsersCountResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetInboundUsersCountResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_GetInboundUsersCount_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AddOutboundResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_AddOutbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RemoveOutboundResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_RemoveOutbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AlterOutboundResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_AlterOutbound_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *handlerServiceClient) ListOutbounds(ctx context.Context, in *ListOutboundsRequest, opts ...grpc.CallOption) (*ListOutboundsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListOutboundsResponse)\n\terr := c.cc.Invoke(ctx, HandlerService_ListOutbounds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// HandlerServiceServer is the server API for HandlerService service.\n// All implementations must embed UnimplementedHandlerServiceServer\n// for forward compatibility.\ntype HandlerServiceServer interface {\n\tAddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error)\n\tRemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error)\n\tAlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error)\n\tListInbounds(context.Context, *ListInboundsRequest) (*ListInboundsResponse, error)\n\tGetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error)\n\tGetInboundUsersCount(context.Context, *GetInboundUserRequest) (*GetInboundUsersCountResponse, error)\n\tAddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error)\n\tRemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error)\n\tAlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error)\n\tListOutbounds(context.Context, *ListOutboundsRequest) (*ListOutboundsResponse, error)\n\tmustEmbedUnimplementedHandlerServiceServer()\n}\n\n// UnimplementedHandlerServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedHandlerServiceServer struct{}\n\nfunc (UnimplementedHandlerServiceServer) AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AddInbound not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveInbound not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AlterInbound not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) ListInbounds(context.Context, *ListInboundsRequest) (*ListInboundsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ListInbounds not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetInboundUsers not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) GetInboundUsersCount(context.Context, *GetInboundUserRequest) (*GetInboundUsersCountResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetInboundUsersCount not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AddOutbound not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveOutbound not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AlterOutbound not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) ListOutbounds(context.Context, *ListOutboundsRequest) (*ListOutboundsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ListOutbounds not implemented\")\n}\nfunc (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {}\nfunc (UnimplementedHandlerServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafeHandlerServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to HandlerServiceServer will\n// result in compilation errors.\ntype UnsafeHandlerServiceServer interface {\n\tmustEmbedUnimplementedHandlerServiceServer()\n}\n\nfunc RegisterHandlerServiceServer(s grpc.ServiceRegistrar, srv HandlerServiceServer) {\n\t// If the following call panics, it indicates UnimplementedHandlerServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&HandlerService_ServiceDesc, srv)\n}\n\nfunc _HandlerService_AddInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddInboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).AddInbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_AddInbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).AddInbound(ctx, req.(*AddInboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_RemoveInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RemoveInboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).RemoveInbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_RemoveInbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).RemoveInbound(ctx, req.(*RemoveInboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AlterInboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).AlterInbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_AlterInbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).AlterInbound(ctx, req.(*AlterInboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_ListInbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListInboundsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).ListInbounds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_ListInbounds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).ListInbounds(ctx, req.(*ListInboundsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_GetInboundUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetInboundUserRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).GetInboundUsers(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_GetInboundUsers_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).GetInboundUsers(ctx, req.(*GetInboundUserRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_GetInboundUsersCount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetInboundUserRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).GetInboundUsersCount(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_GetInboundUsersCount_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).GetInboundUsersCount(ctx, req.(*GetInboundUserRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_AddOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddOutboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).AddOutbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_AddOutbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).AddOutbound(ctx, req.(*AddOutboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_RemoveOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RemoveOutboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).RemoveOutbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_RemoveOutbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).RemoveOutbound(ctx, req.(*RemoveOutboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AlterOutboundRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).AlterOutbound(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_AlterOutbound_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).AlterOutbound(ctx, req.(*AlterOutboundRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _HandlerService_ListOutbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListOutboundsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(HandlerServiceServer).ListOutbounds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: HandlerService_ListOutbounds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(HandlerServiceServer).ListOutbounds(ctx, req.(*ListOutboundsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// HandlerService_ServiceDesc is the grpc.ServiceDesc for HandlerService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar HandlerService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"xray.app.proxyman.command.HandlerService\",\n\tHandlerType: (*HandlerServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"AddInbound\",\n\t\t\tHandler:    _HandlerService_AddInbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveInbound\",\n\t\t\tHandler:    _HandlerService_RemoveInbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AlterInbound\",\n\t\t\tHandler:    _HandlerService_AlterInbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListInbounds\",\n\t\t\tHandler:    _HandlerService_ListInbounds_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetInboundUsers\",\n\t\t\tHandler:    _HandlerService_GetInboundUsers_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetInboundUsersCount\",\n\t\t\tHandler:    _HandlerService_GetInboundUsersCount_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AddOutbound\",\n\t\t\tHandler:    _HandlerService_AddOutbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveOutbound\",\n\t\t\tHandler:    _HandlerService_RemoveOutbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AlterOutbound\",\n\t\t\tHandler:    _HandlerService_AlterOutbound_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListOutbounds\",\n\t\t\tHandler:    _HandlerService_ListOutbounds_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"app/proxyman/command/command.proto\",\n}\n"
  },
  {
    "path": "app/proxyman/command/doc.go",
    "content": "package command\n"
  },
  {
    "path": "app/proxyman/config.go",
    "content": "package proxyman\n"
  },
  {
    "path": "app/proxyman/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/proxyman/config.proto\n\npackage proxyman\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tinternet \"github.com/xtls/xray-core/transport/internet\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype InboundConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *InboundConfig) Reset() {\n\t*x = InboundConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InboundConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InboundConfig) ProtoMessage() {}\n\nfunc (x *InboundConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InboundConfig.ProtoReflect.Descriptor instead.\nfunc (*InboundConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype SniffingConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Whether or not to enable content sniffing on an inbound connection.\n\tEnabled bool `protobuf:\"varint,1,opt,name=enabled,proto3\" json:\"enabled,omitempty\"`\n\t// Override target destination if sniff'ed protocol is in the given list.\n\t// Supported values are \"http\", \"tls\", \"fakedns\".\n\tDestinationOverride []string `protobuf:\"bytes,2,rep,name=destination_override,json=destinationOverride,proto3\" json:\"destination_override,omitempty\"`\n\tDomainsExcluded     []string `protobuf:\"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3\" json:\"domains_excluded,omitempty\"`\n\t// Whether should only try to sniff metadata without waiting for client input.\n\t// Can be used to support SMTP like protocol where server send the first\n\t// message.\n\tMetadataOnly  bool `protobuf:\"varint,4,opt,name=metadata_only,json=metadataOnly,proto3\" json:\"metadata_only,omitempty\"`\n\tRouteOnly     bool `protobuf:\"varint,5,opt,name=route_only,json=routeOnly,proto3\" json:\"route_only,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SniffingConfig) Reset() {\n\t*x = SniffingConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SniffingConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SniffingConfig) ProtoMessage() {}\n\nfunc (x *SniffingConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SniffingConfig.ProtoReflect.Descriptor instead.\nfunc (*SniffingConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *SniffingConfig) GetEnabled() bool {\n\tif x != nil {\n\t\treturn x.Enabled\n\t}\n\treturn false\n}\n\nfunc (x *SniffingConfig) GetDestinationOverride() []string {\n\tif x != nil {\n\t\treturn x.DestinationOverride\n\t}\n\treturn nil\n}\n\nfunc (x *SniffingConfig) GetDomainsExcluded() []string {\n\tif x != nil {\n\t\treturn x.DomainsExcluded\n\t}\n\treturn nil\n}\n\nfunc (x *SniffingConfig) GetMetadataOnly() bool {\n\tif x != nil {\n\t\treturn x.MetadataOnly\n\t}\n\treturn false\n}\n\nfunc (x *SniffingConfig) GetRouteOnly() bool {\n\tif x != nil {\n\t\treturn x.RouteOnly\n\t}\n\treturn false\n}\n\ntype ReceiverConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// PortList specifies the ports which the Receiver should listen on.\n\tPortList *net.PortList `protobuf:\"bytes,1,opt,name=port_list,json=portList,proto3\" json:\"port_list,omitempty\"`\n\t// Listen specifies the IP address that the Receiver should listen on.\n\tListen                     *net.IPOrDomain        `protobuf:\"bytes,2,opt,name=listen,proto3\" json:\"listen,omitempty\"`\n\tStreamSettings             *internet.StreamConfig `protobuf:\"bytes,3,opt,name=stream_settings,json=streamSettings,proto3\" json:\"stream_settings,omitempty\"`\n\tReceiveOriginalDestination bool                   `protobuf:\"varint,4,opt,name=receive_original_destination,json=receiveOriginalDestination,proto3\" json:\"receive_original_destination,omitempty\"`\n\tSniffingSettings           *SniffingConfig        `protobuf:\"bytes,6,opt,name=sniffing_settings,json=sniffingSettings,proto3\" json:\"sniffing_settings,omitempty\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *ReceiverConfig) Reset() {\n\t*x = ReceiverConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReceiverConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReceiverConfig) ProtoMessage() {}\n\nfunc (x *ReceiverConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReceiverConfig.ProtoReflect.Descriptor instead.\nfunc (*ReceiverConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *ReceiverConfig) GetPortList() *net.PortList {\n\tif x != nil {\n\t\treturn x.PortList\n\t}\n\treturn nil\n}\n\nfunc (x *ReceiverConfig) GetListen() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Listen\n\t}\n\treturn nil\n}\n\nfunc (x *ReceiverConfig) GetStreamSettings() *internet.StreamConfig {\n\tif x != nil {\n\t\treturn x.StreamSettings\n\t}\n\treturn nil\n}\n\nfunc (x *ReceiverConfig) GetReceiveOriginalDestination() bool {\n\tif x != nil {\n\t\treturn x.ReceiveOriginalDestination\n\t}\n\treturn false\n}\n\nfunc (x *ReceiverConfig) GetSniffingSettings() *SniffingConfig {\n\tif x != nil {\n\t\treturn x.SniffingSettings\n\t}\n\treturn nil\n}\n\ntype InboundHandlerConfig struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag              string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tReceiverSettings *serial.TypedMessage   `protobuf:\"bytes,2,opt,name=receiver_settings,json=receiverSettings,proto3\" json:\"receiver_settings,omitempty\"`\n\tProxySettings    *serial.TypedMessage   `protobuf:\"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3\" json:\"proxy_settings,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *InboundHandlerConfig) Reset() {\n\t*x = InboundHandlerConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InboundHandlerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InboundHandlerConfig) ProtoMessage() {}\n\nfunc (x *InboundHandlerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InboundHandlerConfig.ProtoReflect.Descriptor instead.\nfunc (*InboundHandlerConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *InboundHandlerConfig) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *InboundHandlerConfig) GetReceiverSettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.ReceiverSettings\n\t}\n\treturn nil\n}\n\nfunc (x *InboundHandlerConfig) GetProxySettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.ProxySettings\n\t}\n\treturn nil\n}\n\ntype OutboundConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OutboundConfig) Reset() {\n\t*x = OutboundConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OutboundConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OutboundConfig) ProtoMessage() {}\n\nfunc (x *OutboundConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OutboundConfig.ProtoReflect.Descriptor instead.\nfunc (*OutboundConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{4}\n}\n\ntype SenderConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Send traffic through the given IP. Only IP is allowed.\n\tVia               *net.IPOrDomain         `protobuf:\"bytes,1,opt,name=via,proto3\" json:\"via,omitempty\"`\n\tStreamSettings    *internet.StreamConfig  `protobuf:\"bytes,2,opt,name=stream_settings,json=streamSettings,proto3\" json:\"stream_settings,omitempty\"`\n\tProxySettings     *internet.ProxyConfig   `protobuf:\"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3\" json:\"proxy_settings,omitempty\"`\n\tMultiplexSettings *MultiplexingConfig     `protobuf:\"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3\" json:\"multiplex_settings,omitempty\"`\n\tViaCidr           string                  `protobuf:\"bytes,5,opt,name=via_cidr,json=viaCidr,proto3\" json:\"via_cidr,omitempty\"`\n\tTargetStrategy    internet.DomainStrategy `protobuf:\"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy\" json:\"target_strategy,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *SenderConfig) Reset() {\n\t*x = SenderConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SenderConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SenderConfig) ProtoMessage() {}\n\nfunc (x *SenderConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SenderConfig.ProtoReflect.Descriptor instead.\nfunc (*SenderConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *SenderConfig) GetVia() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Via\n\t}\n\treturn nil\n}\n\nfunc (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {\n\tif x != nil {\n\t\treturn x.StreamSettings\n\t}\n\treturn nil\n}\n\nfunc (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {\n\tif x != nil {\n\t\treturn x.ProxySettings\n\t}\n\treturn nil\n}\n\nfunc (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {\n\tif x != nil {\n\t\treturn x.MultiplexSettings\n\t}\n\treturn nil\n}\n\nfunc (x *SenderConfig) GetViaCidr() string {\n\tif x != nil {\n\t\treturn x.ViaCidr\n\t}\n\treturn \"\"\n}\n\nfunc (x *SenderConfig) GetTargetStrategy() internet.DomainStrategy {\n\tif x != nil {\n\t\treturn x.TargetStrategy\n\t}\n\treturn internet.DomainStrategy(0)\n}\n\ntype MultiplexingConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Whether or not Mux is enabled.\n\tEnabled bool `protobuf:\"varint,1,opt,name=enabled,proto3\" json:\"enabled,omitempty\"`\n\t// Max number of concurrent connections that one Mux connection can handle.\n\tConcurrency int32 `protobuf:\"varint,2,opt,name=concurrency,proto3\" json:\"concurrency,omitempty\"`\n\t// Transport XUDP in another Mux.\n\tXudpConcurrency int32 `protobuf:\"varint,3,opt,name=xudpConcurrency,proto3\" json:\"xudpConcurrency,omitempty\"`\n\t// \"reject\" (default), \"allow\" or \"skip\".\n\tXudpProxyUDP443 string `protobuf:\"bytes,4,opt,name=xudpProxyUDP443,proto3\" json:\"xudpProxyUDP443,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *MultiplexingConfig) Reset() {\n\t*x = MultiplexingConfig{}\n\tmi := &file_app_proxyman_config_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *MultiplexingConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MultiplexingConfig) ProtoMessage() {}\n\nfunc (x *MultiplexingConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_proxyman_config_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MultiplexingConfig.ProtoReflect.Descriptor instead.\nfunc (*MultiplexingConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_proxyman_config_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *MultiplexingConfig) GetEnabled() bool {\n\tif x != nil {\n\t\treturn x.Enabled\n\t}\n\treturn false\n}\n\nfunc (x *MultiplexingConfig) GetConcurrency() int32 {\n\tif x != nil {\n\t\treturn x.Concurrency\n\t}\n\treturn 0\n}\n\nfunc (x *MultiplexingConfig) GetXudpConcurrency() int32 {\n\tif x != nil {\n\t\treturn x.XudpConcurrency\n\t}\n\treturn 0\n}\n\nfunc (x *MultiplexingConfig) GetXudpProxyUDP443() string {\n\tif x != nil {\n\t\treturn x.XudpProxyUDP443\n\t}\n\treturn \"\"\n}\n\nvar File_app_proxyman_config_proto protoreflect.FileDescriptor\n\nconst file_app_proxyman_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x19app/proxyman/config.proto\\x12\\x11xray.app.proxyman\\x1a\\x18common/net/address.proto\\x1a\\x15common/net/port.proto\\x1a\\x1ftransport/internet/config.proto\\x1a!common/serial/typed_message.proto\\\"\\x0f\\n\" +\n\t\"\\rInboundConfig\\\"\\xcc\\x01\\n\" +\n\t\"\\x0eSniffingConfig\\x12\\x18\\n\" +\n\t\"\\aenabled\\x18\\x01 \\x01(\\bR\\aenabled\\x121\\n\" +\n\t\"\\x14destination_override\\x18\\x02 \\x03(\\tR\\x13destinationOverride\\x12)\\n\" +\n\t\"\\x10domains_excluded\\x18\\x03 \\x03(\\tR\\x0fdomainsExcluded\\x12#\\n\" +\n\t\"\\rmetadata_only\\x18\\x04 \\x01(\\bR\\fmetadataOnly\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"route_only\\x18\\x05 \\x01(\\bR\\trouteOnly\\\"\\xe5\\x02\\n\" +\n\t\"\\x0eReceiverConfig\\x126\\n\" +\n\t\"\\tport_list\\x18\\x01 \\x01(\\v2\\x19.xray.common.net.PortListR\\bportList\\x123\\n\" +\n\t\"\\x06listen\\x18\\x02 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\x06listen\\x12N\\n\" +\n\t\"\\x0fstream_settings\\x18\\x03 \\x01(\\v2%.xray.transport.internet.StreamConfigR\\x0estreamSettings\\x12@\\n\" +\n\t\"\\x1creceive_original_destination\\x18\\x04 \\x01(\\bR\\x1areceiveOriginalDestination\\x12N\\n\" +\n\t\"\\x11sniffing_settings\\x18\\x06 \\x01(\\v2!.xray.app.proxyman.SniffingConfigR\\x10sniffingSettingsJ\\x04\\b\\x05\\x10\\x06\\\"\\xc0\\x01\\n\" +\n\t\"\\x14InboundHandlerConfig\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12M\\n\" +\n\t\"\\x11receiver_settings\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\x10receiverSettings\\x12G\\n\" +\n\t\"\\x0eproxy_settings\\x18\\x03 \\x01(\\v2 .xray.common.serial.TypedMessageR\\rproxySettings\\\"\\x10\\n\" +\n\t\"\\x0eOutboundConfig\\\"\\x9d\\x03\\n\" +\n\t\"\\fSenderConfig\\x12-\\n\" +\n\t\"\\x03via\\x18\\x01 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\x03via\\x12N\\n\" +\n\t\"\\x0fstream_settings\\x18\\x02 \\x01(\\v2%.xray.transport.internet.StreamConfigR\\x0estreamSettings\\x12K\\n\" +\n\t\"\\x0eproxy_settings\\x18\\x03 \\x01(\\v2$.xray.transport.internet.ProxyConfigR\\rproxySettings\\x12T\\n\" +\n\t\"\\x12multiplex_settings\\x18\\x04 \\x01(\\v2%.xray.app.proxyman.MultiplexingConfigR\\x11multiplexSettings\\x12\\x19\\n\" +\n\t\"\\bvia_cidr\\x18\\x05 \\x01(\\tR\\aviaCidr\\x12P\\n\" +\n\t\"\\x0ftarget_strategy\\x18\\x06 \\x01(\\x0e2'.xray.transport.internet.DomainStrategyR\\x0etargetStrategy\\\"\\xa4\\x01\\n\" +\n\t\"\\x12MultiplexingConfig\\x12\\x18\\n\" +\n\t\"\\aenabled\\x18\\x01 \\x01(\\bR\\aenabled\\x12 \\n\" +\n\t\"\\vconcurrency\\x18\\x02 \\x01(\\x05R\\vconcurrency\\x12(\\n\" +\n\t\"\\x0fxudpConcurrency\\x18\\x03 \\x01(\\x05R\\x0fxudpConcurrency\\x12(\\n\" +\n\t\"\\x0fxudpProxyUDP443\\x18\\x04 \\x01(\\tR\\x0fxudpProxyUDP443BU\\n\" +\n\t\"\\x15com.xray.app.proxymanP\\x01Z&github.com/xtls/xray-core/app/proxyman\\xaa\\x02\\x11Xray.App.Proxymanb\\x06proto3\"\n\nvar (\n\tfile_app_proxyman_config_proto_rawDescOnce sync.Once\n\tfile_app_proxyman_config_proto_rawDescData []byte\n)\n\nfunc file_app_proxyman_config_proto_rawDescGZIP() []byte {\n\tfile_app_proxyman_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_proxyman_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_proxyman_config_proto_rawDesc), len(file_app_proxyman_config_proto_rawDesc)))\n\t})\n\treturn file_app_proxyman_config_proto_rawDescData\n}\n\nvar file_app_proxyman_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\nvar file_app_proxyman_config_proto_goTypes = []any{\n\t(*InboundConfig)(nil),         // 0: xray.app.proxyman.InboundConfig\n\t(*SniffingConfig)(nil),        // 1: xray.app.proxyman.SniffingConfig\n\t(*ReceiverConfig)(nil),        // 2: xray.app.proxyman.ReceiverConfig\n\t(*InboundHandlerConfig)(nil),  // 3: xray.app.proxyman.InboundHandlerConfig\n\t(*OutboundConfig)(nil),        // 4: xray.app.proxyman.OutboundConfig\n\t(*SenderConfig)(nil),          // 5: xray.app.proxyman.SenderConfig\n\t(*MultiplexingConfig)(nil),    // 6: xray.app.proxyman.MultiplexingConfig\n\t(*net.PortList)(nil),          // 7: xray.common.net.PortList\n\t(*net.IPOrDomain)(nil),        // 8: xray.common.net.IPOrDomain\n\t(*internet.StreamConfig)(nil), // 9: xray.transport.internet.StreamConfig\n\t(*serial.TypedMessage)(nil),   // 10: xray.common.serial.TypedMessage\n\t(*internet.ProxyConfig)(nil),  // 11: xray.transport.internet.ProxyConfig\n\t(internet.DomainStrategy)(0),  // 12: xray.transport.internet.DomainStrategy\n}\nvar file_app_proxyman_config_proto_depIdxs = []int32{\n\t7,  // 0: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList\n\t8,  // 1: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain\n\t9,  // 2: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig\n\t1,  // 3: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig\n\t10, // 4: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage\n\t10, // 5: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage\n\t8,  // 6: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain\n\t9,  // 7: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig\n\t11, // 8: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig\n\t6,  // 9: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig\n\t12, // 10: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy\n\t11, // [11:11] is the sub-list for method output_type\n\t11, // [11:11] is the sub-list for method input_type\n\t11, // [11:11] is the sub-list for extension type_name\n\t11, // [11:11] is the sub-list for extension extendee\n\t0,  // [0:11] is the sub-list for field type_name\n}\n\nfunc init() { file_app_proxyman_config_proto_init() }\nfunc file_app_proxyman_config_proto_init() {\n\tif File_app_proxyman_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_proxyman_config_proto_rawDesc), len(file_app_proxyman_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_proxyman_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_proxyman_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_proxyman_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_proxyman_config_proto = out.File\n\tfile_app_proxyman_config_proto_goTypes = nil\n\tfile_app_proxyman_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/proxyman/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.proxyman;\noption csharp_namespace = \"Xray.App.Proxyman\";\noption go_package = \"github.com/xtls/xray-core/app/proxyman\";\noption java_package = \"com.xray.app.proxyman\";\noption java_multiple_files = true;\n\nimport \"common/net/address.proto\";\nimport \"common/net/port.proto\";\nimport \"transport/internet/config.proto\";\nimport \"common/serial/typed_message.proto\";\n\nmessage InboundConfig {}\n\nmessage SniffingConfig {\n  // Whether or not to enable content sniffing on an inbound connection.\n  bool enabled = 1;\n\n  // Override target destination if sniff'ed protocol is in the given list.\n  // Supported values are \"http\", \"tls\", \"fakedns\".\n  repeated string destination_override = 2;\n  repeated string domains_excluded = 3;\n\n  // Whether should only try to sniff metadata without waiting for client input.\n  // Can be used to support SMTP like protocol where server send the first\n  // message.\n  bool metadata_only = 4;\n\n  bool route_only = 5;\n}\n\nmessage ReceiverConfig {\n  // PortList specifies the ports which the Receiver should listen on.\n  xray.common.net.PortList port_list = 1;\n  // Listen specifies the IP address that the Receiver should listen on.\n  xray.common.net.IPOrDomain listen = 2;\n  xray.transport.internet.StreamConfig stream_settings = 3;\n  bool receive_original_destination = 4;\n  reserved 5;\n  SniffingConfig sniffing_settings = 6;\n}\n\nmessage InboundHandlerConfig {\n  string tag = 1;\n  xray.common.serial.TypedMessage receiver_settings = 2;\n  xray.common.serial.TypedMessage proxy_settings = 3;\n}\n\nmessage OutboundConfig {}\n\nmessage SenderConfig {\n  // Send traffic through the given IP. Only IP is allowed.\n  xray.common.net.IPOrDomain via = 1;\n  xray.transport.internet.StreamConfig stream_settings = 2;\n  xray.transport.internet.ProxyConfig proxy_settings = 3;\n  MultiplexingConfig multiplex_settings = 4;\n  string via_cidr = 5;\n  xray.transport.internet.DomainStrategy target_strategy = 6;\n}\n\nmessage MultiplexingConfig {\n  // Whether or not Mux is enabled.\n  bool enabled = 1;\n  // Max number of concurrent connections that one Mux connection can handle.\n  int32 concurrency = 2;\n  // Transport XUDP in another Mux.\n  int32 xudpConcurrency = 3;\n  // \"reject\" (default), \"allow\" or \"skip\".\n  string xudpProxyUDP443 = 4;\n}\n"
  },
  {
    "path": "app/proxyman/inbound/always.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {\n\tvar uplinkCounter stats.Counter\n\tvar downlinkCounter stats.Counter\n\n\tpolicy := v.GetFeature(policy.ManagerType()).(policy.Manager)\n\tif len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {\n\t\tstatsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)\n\t\tname := \"inbound>>>\" + tag + \">>>traffic>>>uplink\"\n\t\tc, _ := stats.GetOrRegisterCounter(statsManager, name)\n\t\tif c != nil {\n\t\t\tuplinkCounter = c\n\t\t}\n\t}\n\tif len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {\n\t\tstatsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)\n\t\tname := \"inbound>>>\" + tag + \">>>traffic>>>downlink\"\n\t\tc, _ := stats.GetOrRegisterCounter(statsManager, name)\n\t\tif c != nil {\n\t\t\tdownlinkCounter = c\n\t\t}\n\t}\n\n\treturn uplinkCounter, downlinkCounter\n}\n\ntype AlwaysOnInboundHandler struct {\n\tproxyConfig    interface{}\n\treceiverConfig *proxyman.ReceiverConfig\n\tproxy          proxy.Inbound\n\tworkers        []worker\n\tmux            *mux.Server\n\ttag            string\n}\n\nfunc NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {\n\t// Set tag and sniffing config in context before creating proxy\n\t// This allows proxies like TUN to access these settings\n\tctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})\n\tif receiverConfig.SniffingSettings != nil {\n\t\tctx = session.ContextWithContent(ctx, &session.Content{\n\t\t\tSniffingRequest: session.SniffingRequest{\n\t\t\t\tEnabled:                        receiverConfig.SniffingSettings.Enabled,\n\t\t\t\tOverrideDestinationForProtocol: receiverConfig.SniffingSettings.DestinationOverride,\n\t\t\t\tExcludeForDomain:               receiverConfig.SniffingSettings.DomainsExcluded,\n\t\t\t\tMetadataOnly:                   receiverConfig.SniffingSettings.MetadataOnly,\n\t\t\t\tRouteOnly:                      receiverConfig.SniffingSettings.RouteOnly,\n\t\t\t},\n\t\t})\n\t}\n\trawProxy, err := common.CreateObject(ctx, proxyConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp, ok := rawProxy.(proxy.Inbound)\n\tif !ok {\n\t\treturn nil, errors.New(\"not an inbound proxy.\")\n\t}\n\n\th := &AlwaysOnInboundHandler{\n\t\treceiverConfig: receiverConfig,\n\t\tproxyConfig:    proxyConfig,\n\t\tproxy:          p,\n\t\tmux:            mux.NewServer(ctx),\n\t\ttag:            tag,\n\t}\n\n\tuplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag)\n\n\tnl := p.Network()\n\tpl := receiverConfig.PortList\n\taddress := receiverConfig.Listen.AsAddress()\n\tif address == nil {\n\t\taddress = net.AnyIP\n\t}\n\n\tmss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse stream config\").Base(err).AtWarning()\n\t}\n\n\tif receiverConfig.ReceiveOriginalDestination {\n\t\tif mss.SocketSettings == nil {\n\t\t\tmss.SocketSettings = &internet.SocketConfig{}\n\t\t}\n\t\tif mss.SocketSettings.Tproxy == internet.SocketConfig_Off {\n\t\t\tmss.SocketSettings.Tproxy = internet.SocketConfig_Redirect\n\t\t}\n\t\tmss.SocketSettings.ReceiveOriginalDestAddress = true\n\t}\n\tif pl == nil {\n\t\tif net.HasNetwork(nl, net.Network_UNIX) {\n\t\t\terrors.LogDebug(ctx, \"creating unix domain socket worker on \", address)\n\n\t\t\tworker := &dsWorker{\n\t\t\t\taddress:         address,\n\t\t\t\tproxy:           p,\n\t\t\t\tstream:          mss,\n\t\t\t\ttag:             tag,\n\t\t\t\tdispatcher:      h.mux,\n\t\t\t\tsniffingConfig:  receiverConfig.SniffingSettings,\n\t\t\t\tuplinkCounter:   uplinkCounter,\n\t\t\t\tdownlinkCounter: downlinkCounter,\n\t\t\t\tctx:             ctx,\n\t\t\t}\n\t\t\th.workers = append(h.workers, worker)\n\t\t}\n\t}\n\tif pl != nil {\n\t\tfor _, pr := range pl.Range {\n\t\t\tfor port := pr.From; port <= pr.To; port++ {\n\t\t\t\tif net.HasNetwork(nl, net.Network_TCP) {\n\t\t\t\t\terrors.LogDebug(ctx, \"creating stream worker on \", address, \":\", port)\n\n\t\t\t\t\tworker := &tcpWorker{\n\t\t\t\t\t\taddress:         address,\n\t\t\t\t\t\tport:            net.Port(port),\n\t\t\t\t\t\tproxy:           p,\n\t\t\t\t\t\tstream:          mss,\n\t\t\t\t\t\trecvOrigDest:    receiverConfig.ReceiveOriginalDestination,\n\t\t\t\t\t\ttag:             tag,\n\t\t\t\t\t\tdispatcher:      h.mux,\n\t\t\t\t\t\tsniffingConfig:  receiverConfig.SniffingSettings,\n\t\t\t\t\t\tuplinkCounter:   uplinkCounter,\n\t\t\t\t\t\tdownlinkCounter: downlinkCounter,\n\t\t\t\t\t\tctx:             ctx,\n\t\t\t\t\t}\n\t\t\t\t\th.workers = append(h.workers, worker)\n\t\t\t\t}\n\n\t\t\t\tif net.HasNetwork(nl, net.Network_UDP) {\n\t\t\t\t\tworker := &udpWorker{\n\t\t\t\t\t\ttag:             tag,\n\t\t\t\t\t\tproxy:           p,\n\t\t\t\t\t\taddress:         address,\n\t\t\t\t\t\tport:            net.Port(port),\n\t\t\t\t\t\tdispatcher:      h.mux,\n\t\t\t\t\t\tsniffingConfig:  receiverConfig.SniffingSettings,\n\t\t\t\t\t\tuplinkCounter:   uplinkCounter,\n\t\t\t\t\t\tdownlinkCounter: downlinkCounter,\n\t\t\t\t\t\tstream:          mss,\n\t\t\t\t\t\tctx:             ctx,\n\t\t\t\t\t}\n\t\t\t\t\th.workers = append(h.workers, worker)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn h, nil\n}\n\n// Start implements common.Runnable.\nfunc (h *AlwaysOnInboundHandler) Start() error {\n\tfor _, worker := range h.workers {\n\t\tif err := worker.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (h *AlwaysOnInboundHandler) Close() error {\n\tvar errs []error\n\tfor _, worker := range h.workers {\n\t\terrs = append(errs, worker.Close())\n\t}\n\terrs = append(errs, h.mux.Close())\n\tif err := errors.Combine(errs...); err != nil {\n\t\treturn errors.New(\"failed to close all resources\").Base(err)\n\t}\n\treturn nil\n}\n\nfunc (h *AlwaysOnInboundHandler) Tag() string {\n\treturn h.tag\n}\n\nfunc (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound {\n\treturn h.proxy\n}\n\n// ReceiverSettings implements inbound.Handler.\nfunc (h *AlwaysOnInboundHandler) ReceiverSettings() *serial.TypedMessage {\n\treturn serial.ToTypedMessage(h.receiverConfig)\n}\n\n// ProxySettings implements inbound.Handler.\nfunc (h *AlwaysOnInboundHandler) ProxySettings() *serial.TypedMessage {\n\tif v, ok := h.proxyConfig.(proto.Message); ok {\n\t\treturn serial.ToTypedMessage(v)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "app/proxyman/inbound/inbound.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/inbound\"\n)\n\n// Manager manages all inbound handlers.\ntype Manager struct {\n\taccess          sync.RWMutex\n\tuntaggedHandlers []inbound.Handler\n\ttaggedHandlers  map[string]inbound.Handler\n\trunning         bool\n}\n\n// New returns a new Manager for inbound handlers.\nfunc New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) {\n\tm := &Manager{\n\t\ttaggedHandlers: make(map[string]inbound.Handler),\n\t}\n\treturn m, nil\n}\n\n// Type implements common.HasType.\nfunc (*Manager) Type() interface{} {\n\treturn inbound.ManagerType()\n}\n\n// AddHandler implements inbound.Manager.\nfunc (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\ttag := handler.Tag()\n\tif len(tag) > 0 {\n\t\tif _, found := m.taggedHandlers[tag]; found {\n\t\t\treturn errors.New(\"existing tag found: \" + tag)\n\t\t}\n\t\tm.taggedHandlers[tag] = handler\n\t} else {\n\t\tm.untaggedHandlers = append(m.untaggedHandlers, handler)\n\t}\n\n\tif m.running {\n\t\treturn handler.Start()\n\t}\n\n\treturn nil\n}\n\n// GetHandler implements inbound.Manager.\nfunc (m *Manager) GetHandler(ctx context.Context, tag string) (inbound.Handler, error) {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\thandler, found := m.taggedHandlers[tag]\n\tif !found {\n\t\treturn nil, errors.New(\"handler not found: \", tag)\n\t}\n\treturn handler, nil\n}\n\n// RemoveHandler implements inbound.Manager.\nfunc (m *Manager) RemoveHandler(ctx context.Context, tag string) error {\n\tif tag == \"\" {\n\t\treturn common.ErrNoClue\n\t}\n\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif handler, found := m.taggedHandlers[tag]; found {\n\t\tif err := handler.Close(); err != nil {\n\t\t\terrors.LogWarningInner(ctx, err, \"failed to close handler \", tag)\n\t\t}\n\t\tdelete(m.taggedHandlers, tag)\n\t\treturn nil\n\t}\n\n\treturn common.ErrNoClue\n}\n\n// ListHandlers implements inbound.Manager.\nfunc (m *Manager) ListHandlers(ctx context.Context) []inbound.Handler {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tresponse := make([]inbound.Handler, len(m.untaggedHandlers))\n\tcopy(response, m.untaggedHandlers)\n\n\tfor _, v := range m.taggedHandlers {\n\t\tresponse = append(response, v)\n\t}\n\n\treturn response\n}\n\n// Start implements common.Runnable.\nfunc (m *Manager) Start() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tm.running = true\n\n\tfor _, handler := range m.taggedHandlers {\n\t\tif err := handler.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, handler := range m.untaggedHandlers {\n\t\tif err := handler.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (m *Manager) Close() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tm.running = false\n\n\tvar errs []interface{}\n\tfor _, handler := range m.taggedHandlers {\n\t\tif err := handler.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tfor _, handler := range m.untaggedHandlers {\n\t\tif err := handler.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.New(\"failed to close all handlers\").Base(errors.New(serial.Concat(errs...)))\n\t}\n\n\treturn nil\n}\n\n// NewHandler creates a new inbound.Handler based on the given config.\nfunc NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound.Handler, error) {\n\trawReceiverSettings, err := config.ReceiverSettings.GetInstance()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproxySettings, err := config.ProxySettings.GetInstance()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttag := config.Tag\n\n\treceiverSettings, ok := rawReceiverSettings.(*proxyman.ReceiverConfig)\n\tif !ok {\n\t\treturn nil, errors.New(\"not a ReceiverConfig\").AtError()\n\t}\n\n\tstreamSettings := receiverSettings.StreamSettings\n\tif streamSettings != nil && streamSettings.SocketSettings != nil {\n\t\tctx = session.ContextWithSockopt(ctx, &session.Sockopt{\n\t\t\tMark: streamSettings.SocketSettings.Mark,\n\t\t})\n\t}\n\tif streamSettings != nil && streamSettings.ProtocolName == \"splithttp\" {\n\t\tctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP)\n\t}\n\n\treturn NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*proxyman.InboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*proxyman.InboundConfig))\n\t}))\n\tcommon.Must(common.RegisterConfig((*core.InboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewHandler(ctx, config.(*core.InboundHandlerConfig))\n\t}))\n}\n"
  },
  {
    "path": "app/proxyman/inbound/worker.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\tc \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/proxy/hysteria/account\"\n\thyCtx \"github.com/xtls/xray-core/proxy/hysteria/ctx\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\ntype worker interface {\n\tStart() error\n\tClose() error\n\tPort() net.Port\n\tProxy() proxy.Inbound\n}\n\ntype tcpWorker struct {\n\taddress         net.Address\n\tport            net.Port\n\tproxy           proxy.Inbound\n\tstream          *internet.MemoryStreamConfig\n\trecvOrigDest    bool\n\ttag             string\n\tdispatcher      routing.Dispatcher\n\tsniffingConfig  *proxyman.SniffingConfig\n\tuplinkCounter   stats.Counter\n\tdownlinkCounter stats.Counter\n\n\thub internet.Listener\n\n\tctx context.Context\n}\n\nfunc getTProxyType(s *internet.MemoryStreamConfig) internet.SocketConfig_TProxyMode {\n\tif s == nil || s.SocketSettings == nil {\n\t\treturn internet.SocketConfig_Off\n\t}\n\treturn s.SocketSettings.Tproxy\n}\n\nfunc (w *tcpWorker) callback(conn stat.Connection) {\n\tctx, cancel := context.WithCancel(w.ctx)\n\tsid := session.NewID()\n\tctx = c.ContextWithID(ctx, sid)\n\n\toutbounds := []*session.Outbound{{}}\n\tif w.recvOrigDest {\n\t\tvar dest net.Destination\n\t\tswitch getTProxyType(w.stream) {\n\t\tcase internet.SocketConfig_Redirect:\n\t\t\td, err := tcp.GetOriginalDestination(conn)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to get original destination\")\n\t\t\t} else {\n\t\t\t\tdest = d\n\t\t\t}\n\t\tcase internet.SocketConfig_TProxy:\n\t\t\tdest = net.DestinationFromAddr(conn.LocalAddr())\n\t\t}\n\n\t\tif dest.IsValid() {\n\t\t\t// Check if try to connect to this inbound itself (can cause loopback)\n\t\t\tvar isLoopBack bool\n\t\t\tif w.address == net.AnyIP || w.address == net.AnyIPv6 {\n\t\t\t\tif dest.Port.Value() == w.port.Value() && IsLocal(dest.Address.IP()) {\n\t\t\t\t\tisLoopBack = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif w.hub.Addr().String() == dest.NetAddr() {\n\t\t\t\t\tisLoopBack = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif isLoopBack {\n\t\t\t\tcancel()\n\t\t\t\tconn.Close()\n\t\t\t\terrors.LogError(ctx, errors.New(\"loopback connection detected\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\toutbounds[0].Target = dest\n\t\t}\n\t}\n\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\n\tif w.uplinkCounter != nil || w.downlinkCounter != nil {\n\t\tconn = &stat.CounterConnection{\n\t\t\tConnection:   conn,\n\t\t\tReadCounter:  w.uplinkCounter,\n\t\t\tWriteCounter: w.downlinkCounter,\n\t\t}\n\t}\n\tctx = session.ContextWithInbound(ctx, &session.Inbound{\n\t\tSource:  net.DestinationFromAddr(conn.RemoteAddr()),\n\t\tLocal:   net.DestinationFromAddr(conn.LocalAddr()),\n\t\tGateway: net.TCPDestination(w.address, w.port),\n\t\tTag:     w.tag,\n\t\tConn:    conn,\n\t})\n\n\tcontent := new(session.Content)\n\tif w.sniffingConfig != nil {\n\t\tcontent.SniffingRequest.Enabled = w.sniffingConfig.Enabled\n\t\tcontent.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride\n\t\tcontent.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded\n\t\tcontent.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly\n\t\tcontent.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly\n\t}\n\tctx = session.ContextWithContent(ctx, content)\n\n\tif err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"connection ends\")\n\t}\n\tcancel()\n\tconn.Close()\n}\n\nfunc (w *tcpWorker) Proxy() proxy.Inbound {\n\treturn w.proxy\n}\n\nfunc (w *tcpWorker) Start() error {\n\tctx := context.Background()\n\n\ttype HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }\n\tif v, ok := w.proxy.(HysteriaInboundValidator); ok {\n\t\tctx = hyCtx.ContextWithRequireDatagram(ctx, true)\n\t\tctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())\n\t}\n\n\thub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {\n\t\tgo w.callback(conn)\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to listen TCP on \", w.port).AtWarning().Base(err)\n\t}\n\tw.hub = hub\n\treturn nil\n}\n\nfunc (w *tcpWorker) Close() error {\n\tvar errs []interface{}\n\tif w.hub != nil {\n\t\tif err := common.Close(w.hub); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t\tif err := common.Close(w.proxy); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) > 0 {\n\t\treturn errors.New(\"failed to close all resources\").Base(errors.New(serial.Concat(errs...)))\n\t}\n\n\treturn nil\n}\n\nfunc (w *tcpWorker) Port() net.Port {\n\treturn w.port\n}\n\ntype udpConn struct {\n\tlastActivityTime int64 // in seconds\n\treader           buf.Reader\n\twriter           buf.Writer\n\toutput           func([]byte) (int, error)\n\tremote           net.Addr\n\tlocal            net.Addr\n\tdone             *done.Instance\n\tuplink           stats.Counter\n\tdownlink         stats.Counter\n\tinactive         bool\n\tcancel           context.CancelFunc\n}\n\nfunc (c *udpConn) setInactive() {\n\tc.inactive = true\n}\n\nfunc (c *udpConn) updateActivity() {\n\tatomic.StoreInt64(&c.lastActivityTime, time.Now().Unix())\n}\n\n// ReadMultiBuffer implements buf.Reader\nfunc (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tmb, err := c.reader.ReadMultiBuffer()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.updateActivity()\n\n\tif c.uplink != nil {\n\t\tc.uplink.Add(int64(mb.Len()))\n\t}\n\n\treturn mb, nil\n}\n\nfunc (c *udpConn) Read(buf []byte) (int, error) {\n\tpanic(\"not implemented\")\n}\n\n// Write implements io.Writer.\nfunc (c *udpConn) Write(buf []byte) (int, error) {\n\tn, err := c.output(buf)\n\tif c.downlink != nil {\n\t\tc.downlink.Add(int64(n))\n\t}\n\tif err == nil {\n\t\tc.updateActivity()\n\t}\n\treturn n, err\n}\n\nfunc (c *udpConn) Close() error {\n\tif c.cancel != nil {\n\t\tc.cancel()\n\t}\n\tcommon.Must(c.done.Close())\n\tcommon.Must(common.Close(c.writer))\n\treturn nil\n}\n\nfunc (c *udpConn) RemoteAddr() net.Addr {\n\treturn c.remote\n}\n\nfunc (c *udpConn) LocalAddr() net.Addr {\n\treturn c.local\n}\n\nfunc (*udpConn) SetDeadline(time.Time) error {\n\treturn nil\n}\n\nfunc (*udpConn) SetReadDeadline(time.Time) error {\n\treturn nil\n}\n\nfunc (*udpConn) SetWriteDeadline(time.Time) error {\n\treturn nil\n}\n\ntype connID struct {\n\tsrc  net.Destination\n\tdest net.Destination\n}\n\ntype udpWorker struct {\n\tsync.RWMutex\n\n\tproxy           proxy.Inbound\n\thub             *udp.Hub\n\taddress         net.Address\n\tport            net.Port\n\ttag             string\n\tstream          *internet.MemoryStreamConfig\n\tdispatcher      routing.Dispatcher\n\tsniffingConfig  *proxyman.SniffingConfig\n\tuplinkCounter   stats.Counter\n\tdownlinkCounter stats.Counter\n\n\tchecker    *task.Periodic\n\tactiveConn map[connID]*udpConn\n\n\tctx  context.Context\n\tcone bool\n}\n\nfunc (w *udpWorker) getConnection(id connID) (*udpConn, bool) {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tif conn, found := w.activeConn[id]; found && !conn.done.Done() {\n\t\tconn.updateActivity()\n\t\treturn conn, true\n\t}\n\n\tpReader, pWriter := pipe.New(pipe.DiscardOverflow(), pipe.WithSizeLimit(16*1024))\n\tconn := &udpConn{\n\t\treader: pReader,\n\t\twriter: pWriter,\n\t\toutput: func(b []byte) (int, error) {\n\t\t\treturn w.hub.WriteTo(b, id.src)\n\t\t},\n\t\tremote: &net.UDPAddr{\n\t\t\tIP:   id.src.Address.IP(),\n\t\t\tPort: int(id.src.Port),\n\t\t},\n\t\tlocal: &net.UDPAddr{\n\t\t\tIP:   w.address.IP(),\n\t\t\tPort: int(w.port),\n\t\t},\n\t\tdone:     done.New(),\n\t\tuplink:   w.uplinkCounter,\n\t\tdownlink: w.downlinkCounter,\n\t}\n\tw.activeConn[id] = conn\n\n\tconn.updateActivity()\n\treturn conn, false\n}\n\nfunc (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest net.Destination) {\n\tid := connID{\n\t\tsrc: source,\n\t}\n\tif originalDest.IsValid() {\n\t\tif !w.cone {\n\t\t\tid.dest = originalDest\n\t\t}\n\t\tb.UDP = &originalDest\n\t}\n\tconn, existing := w.getConnection(id)\n\n\t// payload will be discarded in pipe is full.\n\tconn.writer.WriteMultiBuffer(buf.MultiBuffer{b})\n\n\tif !existing {\n\t\tcommon.Must(w.checker.Start())\n\n\t\tgo func() {\n\t\t\tctx, cancel := context.WithCancel(w.ctx)\n\t\t\tconn.cancel = cancel\n\t\t\tsid := session.NewID()\n\t\t\tctx = c.ContextWithID(ctx, sid)\n\n\t\t\toutbounds := []*session.Outbound{{}}\n\t\t\tif originalDest.IsValid() {\n\t\t\t\toutbounds[0].Target = originalDest\n\t\t\t}\n\t\t\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\t\t\tlocal := net.DestinationFromAddr(w.hub.Addr())\n\t\t\tif local.Address == net.AnyIP || local.Address == net.AnyIPv6 {\n\t\t\t\tif source.Address.Family().IsIPv4() {\n\t\t\t\t\tlocal.Address = net.AnyIP\n\t\t\t\t} else if source.Address.Family().IsIPv6() {\n\t\t\t\t\tlocal.Address = net.AnyIPv6\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx = session.ContextWithInbound(ctx, &session.Inbound{\n\t\t\t\tSource:  source,\n\t\t\t\tLocal:   local, // Due to some limitations, in UDP connections, localIP is always equal to listen interface IP\n\t\t\t\tGateway: net.UDPDestination(w.address, w.port),\n\t\t\t\tTag:     w.tag,\n\t\t\t})\n\t\t\tcontent := new(session.Content)\n\t\t\tif w.sniffingConfig != nil {\n\t\t\t\tcontent.SniffingRequest.Enabled = w.sniffingConfig.Enabled\n\t\t\t\tcontent.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride\n\t\t\t\tcontent.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded\n\t\t\t\tcontent.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly\n\t\t\t\tcontent.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly\n\t\t\t}\n\t\t\tctx = session.ContextWithContent(ctx, content)\n\t\t\tif err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {\n\t\t\t\terrors.LogInfoInner(ctx, err, \"connection ends\")\n\t\t\t}\n\t\t\tconn.Close()\n\t\t\t// conn not removed by checker TODO may be lock worker here is better\n\t\t\tif !conn.inactive {\n\t\t\t\tconn.setInactive()\n\t\t\t\tw.removeConn(id)\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc (w *udpWorker) removeConn(id connID) {\n\tw.Lock()\n\tdelete(w.activeConn, id)\n\tw.Unlock()\n}\n\nfunc (w *udpWorker) handlePackets() {\n\treceive := w.hub.Receive()\n\tfor payload := range receive {\n\t\tw.callback(payload.Payload, payload.Source, payload.Target)\n\t}\n}\n\nfunc (w *udpWorker) clean() error {\n\tnowSec := time.Now().Unix()\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tif len(w.activeConn) == 0 {\n\t\treturn errors.New(\"no more connections. stopping...\")\n\t}\n\n\tfor addr, conn := range w.activeConn {\n\t\tif nowSec-atomic.LoadInt64(&conn.lastActivityTime) > 2*60 {\n\t\t\tif !conn.inactive {\n\t\t\t\tconn.setInactive()\n\t\t\t\tdelete(w.activeConn, addr)\n\t\t\t}\n\t\t\tconn.Close()\n\t\t}\n\t}\n\n\tif len(w.activeConn) == 0 {\n\t\tw.activeConn = make(map[connID]*udpConn, 16)\n\t}\n\n\treturn nil\n}\n\nfunc (w *udpWorker) Start() error {\n\tw.activeConn = make(map[connID]*udpConn, 16)\n\tctx := context.Background()\n\th, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw.cone = w.ctx.Value(\"cone\").(bool)\n\n\tw.checker = &task.Periodic{\n\t\tInterval: time.Minute,\n\t\tExecute:  w.clean,\n\t}\n\n\tw.hub = h\n\tgo w.handlePackets()\n\treturn nil\n}\n\nfunc (w *udpWorker) Close() error {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tvar errs []interface{}\n\n\tif w.hub != nil {\n\t\tif err := w.hub.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif w.checker != nil {\n\t\tif err := w.checker.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\tif err := common.Close(w.proxy); err != nil {\n\t\terrs = append(errs, err)\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.New(\"failed to close all resources\").Base(errors.New(serial.Concat(errs...)))\n\t}\n\treturn nil\n}\n\nfunc (w *udpWorker) Port() net.Port {\n\treturn w.port\n}\n\nfunc (w *udpWorker) Proxy() proxy.Inbound {\n\treturn w.proxy\n}\n\ntype dsWorker struct {\n\taddress         net.Address\n\tproxy           proxy.Inbound\n\tstream          *internet.MemoryStreamConfig\n\ttag             string\n\tdispatcher      routing.Dispatcher\n\tsniffingConfig  *proxyman.SniffingConfig\n\tuplinkCounter   stats.Counter\n\tdownlinkCounter stats.Counter\n\n\thub internet.Listener\n\n\tctx context.Context\n}\n\nfunc (w *dsWorker) callback(conn stat.Connection) {\n\tctx, cancel := context.WithCancel(w.ctx)\n\tsid := session.NewID()\n\tctx = c.ContextWithID(ctx, sid)\n\n\tif w.uplinkCounter != nil || w.downlinkCounter != nil {\n\t\tconn = &stat.CounterConnection{\n\t\t\tConnection:   conn,\n\t\t\tReadCounter:  w.uplinkCounter,\n\t\t\tWriteCounter: w.downlinkCounter,\n\t\t}\n\t}\n\tctx = session.ContextWithInbound(ctx, &session.Inbound{\n\t\tSource:  net.DestinationFromAddr(conn.RemoteAddr()),\n\t\tLocal:   net.DestinationFromAddr(conn.LocalAddr()),\n\t\tGateway: net.UnixDestination(w.address),\n\t\tTag:     w.tag,\n\t\tConn:    conn,\n\t})\n\n\tcontent := new(session.Content)\n\tif w.sniffingConfig != nil {\n\t\tcontent.SniffingRequest.Enabled = w.sniffingConfig.Enabled\n\t\tcontent.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride\n\t\tcontent.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded\n\t\tcontent.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly\n\t\tcontent.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly\n\t}\n\tctx = session.ContextWithContent(ctx, content)\n\n\tif err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"connection ends\")\n\t}\n\tcancel()\n\tif err := conn.Close(); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to close connection\")\n\t}\n}\n\nfunc (w *dsWorker) Proxy() proxy.Inbound {\n\treturn w.proxy\n}\n\nfunc (w *dsWorker) Port() net.Port {\n\treturn net.Port(0)\n}\n\nfunc (w *dsWorker) Start() error {\n\tctx := context.Background()\n\thub, err := internet.ListenUnix(ctx, w.address, w.stream, func(conn stat.Connection) {\n\t\tgo w.callback(conn)\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to listen Unix Domain Socket on \", w.address).AtWarning().Base(err)\n\t}\n\tw.hub = hub\n\treturn nil\n}\n\nfunc (w *dsWorker) Close() error {\n\tvar errs []interface{}\n\tif w.hub != nil {\n\t\tif err := common.Close(w.hub); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t\tif err := common.Close(w.proxy); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) > 0 {\n\t\treturn errors.New(\"failed to close all resources\").Base(errors.New(serial.Concat(errs...)))\n\t}\n\n\treturn nil\n}\n\nfunc IsLocal(ip net.IP) bool {\n\taddrs, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn false\n\t}\n\tfor _, addr := range addrs {\n\t\tif ipnet, ok := addr.(*net.IPNet); ok {\n\t\t\tif ipnet.IP.Equal(ip) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "app/proxyman/outbound/handler.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\tgoerrors \"errors\"\n\t\"io\"\n\t\"math/big\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {\n\tvar uplinkCounter stats.Counter\n\tvar downlinkCounter stats.Counter\n\n\tpolicy := v.GetFeature(policy.ManagerType()).(policy.Manager)\n\tif len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {\n\t\tstatsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)\n\t\tname := \"outbound>>>\" + tag + \">>>traffic>>>uplink\"\n\t\tc, _ := stats.GetOrRegisterCounter(statsManager, name)\n\t\tif c != nil {\n\t\t\tuplinkCounter = c\n\t\t}\n\t}\n\tif len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {\n\t\tstatsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)\n\t\tname := \"outbound>>>\" + tag + \">>>traffic>>>downlink\"\n\t\tc, _ := stats.GetOrRegisterCounter(statsManager, name)\n\t\tif c != nil {\n\t\t\tdownlinkCounter = c\n\t\t}\n\t}\n\n\treturn uplinkCounter, downlinkCounter\n}\n\n// Handler implements outbound.Handler.\ntype Handler struct {\n\ttag             string\n\tsenderSettings  *proxyman.SenderConfig\n\tstreamSettings  *internet.MemoryStreamConfig\n\tproxyConfig     proto.Message\n\tproxy           proxy.Outbound\n\toutboundManager outbound.Manager\n\tmux             *mux.ClientManager\n\txudp            *mux.ClientManager\n\tudp443          string\n\tuplinkCounter   stats.Counter\n\tdownlinkCounter stats.Counter\n}\n\n// NewHandler creates a new Handler based on the given configuration.\nfunc NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbound.Handler, error) {\n\tv := core.MustFromContext(ctx)\n\tuplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)\n\th := &Handler{\n\t\ttag:             config.Tag,\n\t\toutboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),\n\t\tuplinkCounter:   uplinkCounter,\n\t\tdownlinkCounter: downlinkCounter,\n\t}\n\n\tif config.SenderSettings != nil {\n\t\tsenderSettings, err := config.SenderSettings.GetInstance()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch s := senderSettings.(type) {\n\t\tcase *proxyman.SenderConfig:\n\t\t\th.senderSettings = s\n\t\t\tmss, err := internet.ToMemoryStreamConfig(s.StreamSettings)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to parse stream settings\").Base(err).AtWarning()\n\t\t\t}\n\t\t\th.streamSettings = mss\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"settings is not SenderConfig\")\n\t\t}\n\t}\n\n\tproxyConfig, err := config.ProxySettings.GetInstance()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\th.proxyConfig = proxyConfig\n\n\tctx = session.ContextWithFullHandler(ctx, h)\n\n\trawProxyHandler, err := common.CreateObject(ctx, proxyConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproxyHandler, ok := rawProxyHandler.(proxy.Outbound)\n\tif !ok {\n\t\treturn nil, errors.New(\"not an outbound handler\")\n\t}\n\n\tif h.senderSettings != nil && h.senderSettings.MultiplexSettings != nil {\n\t\tif config := h.senderSettings.MultiplexSettings; config.Enabled {\n\t\t\tif config.Concurrency < 0 {\n\t\t\t\th.mux = &mux.ClientManager{Enabled: false}\n\t\t\t}\n\t\t\tif config.Concurrency == 0 {\n\t\t\t\tconfig.Concurrency = 8 // same as before\n\t\t\t}\n\t\t\tif config.Concurrency > 0 {\n\t\t\t\th.mux = &mux.ClientManager{\n\t\t\t\t\tEnabled: true,\n\t\t\t\t\tPicker: &mux.IncrementalWorkerPicker{\n\t\t\t\t\t\tFactory: &mux.DialingWorkerFactory{\n\t\t\t\t\t\t\tProxy:  proxyHandler,\n\t\t\t\t\t\t\tDialer: h,\n\t\t\t\t\t\t\tStrategy: mux.ClientStrategy{\n\t\t\t\t\t\t\t\tMaxConcurrency: uint32(config.Concurrency),\n\t\t\t\t\t\t\t\tMaxConnection:  128,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t\tif config.XudpConcurrency < 0 {\n\t\t\t\th.xudp = &mux.ClientManager{Enabled: false}\n\t\t\t}\n\t\t\tif config.XudpConcurrency == 0 {\n\t\t\t\th.xudp = nil // same as before\n\t\t\t}\n\t\t\tif config.XudpConcurrency > 0 {\n\t\t\t\th.xudp = &mux.ClientManager{\n\t\t\t\t\tEnabled: true,\n\t\t\t\t\tPicker: &mux.IncrementalWorkerPicker{\n\t\t\t\t\t\tFactory: &mux.DialingWorkerFactory{\n\t\t\t\t\t\t\tProxy:  proxyHandler,\n\t\t\t\t\t\t\tDialer: h,\n\t\t\t\t\t\t\tStrategy: mux.ClientStrategy{\n\t\t\t\t\t\t\t\tMaxConcurrency: uint32(config.XudpConcurrency),\n\t\t\t\t\t\t\t\tMaxConnection:  128,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t\th.udp443 = config.XudpProxyUDP443\n\t\t}\n\t}\n\n\th.proxy = proxyHandler\n\treturn h, nil\n}\n\n// Tag implements outbound.Handler.\nfunc (h *Handler) Tag() string {\n\treturn h.tag\n}\n\n// Dispatch implements proxy.Outbound.Dispatch.\nfunc (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tcontent := session.ContentFromContext(ctx)\n\tif h.senderSettings != nil && h.senderSettings.TargetStrategy.HasStrategy() && ob.Target.Address.Family().IsDomain() && (content == nil || !content.SkipDNSResolve) {\n\t\tstrategy := h.senderSettings.TargetStrategy\n\t\tif ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil {\n\t\t\tstrategy = strategy.GetDynamicStrategy(ob.OriginalTarget.Address.Family())\n\t\t}\n\t\tips, err := internet.LookupForIP(ob.Target.Address.Domain(), strategy, nil)\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(ctx, err, \"failed to resolve ip for target \", ob.Target.Address.Domain())\n\t\t\tif h.senderSettings.TargetStrategy.ForceIP() {\n\t\t\t\terr := errors.New(\"failed to resolve ip for target \", ob.Target.Address.Domain()).Base(err)\n\t\t\t\tsession.SubmitOutboundErrorToOriginator(ctx, err)\n\t\t\t\tcommon.Interrupt(link.Writer)\n\t\t\t\tcommon.Interrupt(link.Reader)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t} else {\n\t\t\tunchangedDomain := ob.Target.Address.Domain()\n\t\t\tob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])\n\t\t\terrors.LogInfo(ctx, \"target: \", unchangedDomain, \" resolved to: \", ob.Target.Address.String())\n\t\t}\n\t}\n\tif ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address {\n\t\tlink.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}\n\t\tlink.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}\n\t}\n\tif h.mux != nil {\n\t\ttest := func(err error) {\n\t\t\tif err != nil {\n\t\t\t\terr := errors.New(\"failed to process mux outbound traffic\").Base(err)\n\t\t\t\tsession.SubmitOutboundErrorToOriginator(ctx, err)\n\t\t\t\terrors.LogInfo(ctx, err.Error())\n\t\t\t\tcommon.Interrupt(link.Writer)\n\t\t\t\tcommon.Interrupt(link.Reader)\n\t\t\t}\n\t\t}\n\t\tif ob.Target.Network == net.Network_UDP && ob.Target.Port == 443 {\n\t\t\tswitch h.udp443 {\n\t\t\tcase \"reject\":\n\t\t\t\ttest(errors.New(\"XUDP rejected UDP/443 traffic\").AtInfo())\n\t\t\t\treturn\n\t\t\tcase \"skip\":\n\t\t\t\tgoto out\n\t\t\t}\n\t\t}\n\t\tif h.xudp != nil && ob.Target.Network == net.Network_UDP {\n\t\t\tif !h.xudp.Enabled {\n\t\t\t\tgoto out\n\t\t\t}\n\t\t\ttest(h.xudp.Dispatch(ctx, link))\n\t\t\treturn\n\t\t}\n\t\tif h.mux.Enabled {\n\t\t\ttest(h.mux.Dispatch(ctx, link))\n\t\t\treturn\n\t\t}\n\t}\nout:\n\terr := h.proxy.Process(ctx, link, h)\n\tvar errC error\n\tif err != nil {\n\t\terrC = errors.Cause(err)\n\t\tif goerrors.Is(errC, io.EOF) || goerrors.Is(errC, io.ErrClosedPipe) || goerrors.Is(errC, context.Canceled) {\n\t\t\terr = nil\n\t\t}\n\t}\n\tif err != nil {\n\t\t// Ensure outbound ray is properly closed.\n\t\terr := errors.New(\"failed to process outbound traffic\").Base(err)\n\t\tsession.SubmitOutboundErrorToOriginator(ctx, err)\n\t\terrors.LogInfo(ctx, err.Error())\n\t\tcommon.Interrupt(link.Writer)\n\t} else {\n\t\tif errC != nil && goerrors.Is(errC, io.ErrClosedPipe) {\n\t\t\tcommon.Interrupt(link.Writer)\n\t\t} else {\n\t\t\tcommon.Close(link.Writer)\n\t\t}\n\t}\n\tcommon.Interrupt(link.Reader)\n}\n\nfunc (h *Handler) DestIpAddress() net.IP {\n\treturn internet.DestIpAddress()\n}\n\n// Dial implements internet.Dialer.\nfunc (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {\n\tif h.senderSettings != nil {\n\n\t\tif h.senderSettings.ProxySettings.HasTag() {\n\n\t\t\ttag := h.senderSettings.ProxySettings.Tag\n\t\t\thandler := h.outboundManager.GetHandler(tag)\n\t\t\tif handler != nil {\n\t\t\t\terrors.LogDebug(ctx, \"proxying to \", tag, \" for dest \", dest)\n\t\t\t\toutbounds := session.OutboundsFromContext(ctx)\n\t\t\t\tctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{\n\t\t\t\t\tTarget: dest,\n\t\t\t\t\tTag:    tag,\n\t\t\t\t})) // add another outbound in session ctx\n\t\t\t\topts := pipe.OptionsFromContext(ctx)\n\t\t\t\tuplinkReader, uplinkWriter := pipe.New(opts...)\n\t\t\t\tdownlinkReader, downlinkWriter := pipe.New(opts...)\n\n\t\t\t\tgo handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})\n\t\t\t\tconn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))\n\n\t\t\t\tif config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {\n\t\t\t\t\ttlsConfig := config.GetTLSConfig(tls.WithDestination(dest))\n\t\t\t\t\tconn = tls.Client(conn, tlsConfig)\n\t\t\t\t}\n\n\t\t\t\treturn h.getStatCouterConnection(conn), nil\n\t\t\t}\n\n\t\t\terrors.LogError(ctx, \"failed to get outbound handler with tag: \", tag)\n\t\t\treturn nil, errors.New(\"failed to get outbound handler with tag: \" + tag)\n\t\t}\n\n\t\tif h.senderSettings.Via != nil {\n\t\t\toutbounds := session.OutboundsFromContext(ctx)\n\t\t\tob := outbounds[len(outbounds)-1]\n\t\t\th.SetOutboundGateway(ctx, ob)\n\t\t}\n\n\t}\n\n\tif conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {\n\t\treturn conn, err\n\t}\n\n\tconn, err := internet.Dial(ctx, dest, h.streamSettings)\n\tconn = h.getStatCouterConnection(conn)\n\toutbounds := session.OutboundsFromContext(ctx)\n\tif outbounds != nil {\n\t\tob := outbounds[len(outbounds)-1]\n\t\tob.Conn = conn\n\t} else {\n\t\t// for Vision's pre-connect\n\t}\n\treturn conn, err\n}\n\nfunc (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {\n\tif ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil && !h.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {\n\t\tvar domain string\n\t\taddr := h.senderSettings.Via.AsAddress()\n\t\tdomain = h.senderSettings.Via.GetDomain()\n\t\tswitch {\n\t\tcase h.senderSettings.ViaCidr != \"\":\n\t\t\tob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)\n\n\t\tcase domain == \"origin\":\n\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\t\t\tif inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {\n\t\t\t\t\tob.Gateway = inbound.Local.Address\n\t\t\t\t\terrors.LogDebug(ctx, \"use inbound local ip as sendthrough: \", inbound.Local.Address.String())\n\t\t\t\t}\n\t\t\t}\n\t\tcase domain == \"srcip\":\n\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\t\t\tif inbound.Source.IsValid() && inbound.Source.Address.Family().IsIP() {\n\t\t\t\t\tob.Gateway = inbound.Source.Address\n\t\t\t\t\terrors.LogDebug(ctx, \"use inbound source ip as sendthrough: \", inbound.Source.Address.String())\n\t\t\t\t}\n\t\t\t}\n\t\t//case addr.Family().IsDomain():\n\t\tdefault:\n\t\t\tob.Gateway = addr\n\n\t\t}\n\n\t}\n}\n\nfunc (h *Handler) getStatCouterConnection(conn stat.Connection) stat.Connection {\n\tif h.uplinkCounter != nil || h.downlinkCounter != nil {\n\t\treturn &stat.CounterConnection{\n\t\t\tConnection:   conn,\n\t\t\tReadCounter:  h.downlinkCounter,\n\t\t\tWriteCounter: h.uplinkCounter,\n\t\t}\n\t}\n\treturn conn\n}\n\n// GetOutbound implements proxy.GetOutbound.\nfunc (h *Handler) GetOutbound() proxy.Outbound {\n\treturn h.proxy\n}\n\n// Start implements common.Runnable.\nfunc (h *Handler) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (h *Handler) Close() error {\n\tcommon.Close(h.mux)\n\tcommon.Close(h.proxy)\n\treturn nil\n}\n\n// SenderSettings implements outbound.Handler.\nfunc (h *Handler) SenderSettings() *serial.TypedMessage {\n\treturn serial.ToTypedMessage(h.senderSettings)\n}\n\n// ProxySettings implements outbound.Handler.\nfunc (h *Handler) ProxySettings() *serial.TypedMessage {\n\treturn serial.ToTypedMessage(h.proxyConfig)\n}\n\nfunc ParseRandomIP(addr net.Address, prefix string) net.Address {\n\n\t_, ipnet, _ := net.ParseCIDR(addr.IP().String() + \"/\" + prefix)\n\n\tones, bits := ipnet.Mask.Size()\n\tsubnetSize := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones))\n\n\trnd, _ := rand.Int(rand.Reader, subnetSize)\n\n\tstartInt := new(big.Int).SetBytes(ipnet.IP)\n\trndInt := new(big.Int).Add(startInt, rnd)\n\n\trndBytes := rndInt.Bytes()\n\tpadded := make([]byte, len(ipnet.IP))\n\tcopy(padded[len(padded)-len(rndBytes):], rndBytes)\n\n\treturn net.ParseAddress(net.IP(padded).String())\n}\n"
  },
  {
    "path": "app/proxyman/outbound/handler_test.go",
    "content": "package outbound_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t. \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\t\"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc TestInterfaces(t *testing.T) {\n\t_ = (outbound.Handler)(new(Handler))\n\t_ = (outbound.Manager)(new(Manager))\n}\n\nconst xrayKey core.XrayKey = 1\n\nfunc TestOutboundWithoutStatCounter(t *testing.T) {\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&stats.Config{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tSystem: &policy.SystemPolicy{\n\t\t\t\t\tStats: &policy.SystemPolicy_Stats{\n\t\t\t\t\t\tInboundUplink: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t}\n\n\tv, _ := core.New(config)\n\tv.AddFeature((outbound.Manager)(new(Manager)))\n\tctx := context.WithValue(context.Background(), xrayKey, v)\n\tctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})\n\th, _ := NewHandler(ctx, &core.OutboundHandlerConfig{\n\t\tTag:           \"tag\",\n\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t})\n\tconn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), 13146))\n\t_, ok := conn.(*stat.CounterConnection)\n\tif ok {\n\t\tt.Errorf(\"Expected conn to not be CounterConnection\")\n\t}\n}\n\nfunc TestOutboundWithStatCounter(t *testing.T) {\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&stats.Config{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tSystem: &policy.SystemPolicy{\n\t\t\t\t\tStats: &policy.SystemPolicy_Stats{\n\t\t\t\t\t\tOutboundUplink:   true,\n\t\t\t\t\t\tOutboundDownlink: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t}\n\n\tv, _ := core.New(config)\n\tv.AddFeature((outbound.Manager)(new(Manager)))\n\tctx := context.WithValue(context.Background(), xrayKey, v)\n\tctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})\n\th, _ := NewHandler(ctx, &core.OutboundHandlerConfig{\n\t\tTag:           \"tag\",\n\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t})\n\tconn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), 13146))\n\t_, ok := conn.(*stat.CounterConnection)\n\tif !ok {\n\t\tt.Errorf(\"Expected conn to be CounterConnection\")\n\t}\n}\n\nfunc TestTagsCache(t *testing.T) {\n\n\ttest_duration := 10 * time.Second\n\tthreads_num := 50\n\tdelay := 10 * time.Millisecond\n\ttags_prefix := \"node\"\n\n\ttags := sync.Map{}\n\tcounter := atomic.Uint64{}\n\n\tohm, err := New(context.Background(), &proxyman.OutboundConfig{})\n\tif err != nil {\n\t\tt.Error(\"failed to create outbound handler manager\")\n\t}\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{},\n\t}\n\tv, _ := core.New(config)\n\tv.AddFeature(ohm)\n\tctx := context.WithValue(context.Background(), xrayKey, v)\n\n\tstop_add_rm := false\n\twg_add_rm := sync.WaitGroup{}\n\taddHandlers := func() {\n\t\tdefer wg_add_rm.Done()\n\t\tfor !stop_add_rm {\n\t\t\ttime.Sleep(delay)\n\t\t\tidx := counter.Add(1)\n\t\t\ttag := fmt.Sprintf(\"%s%d\", tags_prefix, idx)\n\t\t\tcfg := &core.OutboundHandlerConfig{\n\t\t\t\tTag:           tag,\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t}\n\t\t\tif h, err := NewHandler(ctx, cfg); err == nil {\n\t\t\t\tif err := ohm.AddHandler(ctx, h); err == nil {\n\t\t\t\t\t// t.Log(\"add handler:\", tag)\n\t\t\t\t\ttags.Store(tag, nil)\n\t\t\t\t} else {\n\t\t\t\t\tt.Error(\"failed to add handler:\", tag)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tt.Error(\"failed to create handler:\", tag)\n\t\t\t}\n\t\t}\n\t}\n\n\trmHandlers := func() {\n\t\tdefer wg_add_rm.Done()\n\t\tfor !stop_add_rm {\n\t\t\ttime.Sleep(delay)\n\t\t\ttags.Range(func(key interface{}, value interface{}) bool {\n\t\t\t\tif _, ok := tags.LoadAndDelete(key); ok {\n\t\t\t\t\t// t.Log(\"remove handler:\", key)\n\t\t\t\t\tohm.RemoveHandler(ctx, key.(string))\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\n\tselectors := []string{tags_prefix}\n\twg_get := sync.WaitGroup{}\n\tstop_get := false\n\tgetTags := func() {\n\t\tdefer wg_get.Done()\n\t\tfor !stop_get {\n\t\t\ttime.Sleep(delay)\n\t\t\t_ = ohm.Select(selectors)\n\t\t\t// t.Logf(\"get tags: %v\", tag)\n\t\t}\n\t}\n\n\tfor i := 0; i < threads_num; i++ {\n\t\twg_add_rm.Add(2)\n\t\tgo rmHandlers()\n\t\tgo addHandlers()\n\t\twg_get.Add(1)\n\t\tgo getTags()\n\t}\n\n\ttime.Sleep(test_duration)\n\tstop_add_rm = true\n\twg_add_rm.Wait()\n\tstop_get = true\n\twg_get.Wait()\n}\n"
  },
  {
    "path": "app/proxyman/outbound/outbound.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n)\n\n// Manager is to manage all outbound handlers.\ntype Manager struct {\n\taccess           sync.RWMutex\n\tdefaultHandler   outbound.Handler\n\ttaggedHandler    map[string]outbound.Handler\n\tuntaggedHandlers []outbound.Handler\n\trunning          bool\n\ttagsCache        *sync.Map\n}\n\n// New creates a new Manager.\nfunc New(ctx context.Context, config *proxyman.OutboundConfig) (*Manager, error) {\n\tm := &Manager{\n\t\ttaggedHandler: make(map[string]outbound.Handler),\n\t\ttagsCache:     &sync.Map{},\n\t}\n\treturn m, nil\n}\n\n// Type implements common.HasType.\nfunc (m *Manager) Type() interface{} {\n\treturn outbound.ManagerType()\n}\n\n// Start implements core.Feature\nfunc (m *Manager) Start() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tm.running = true\n\n\tfor _, h := range m.taggedHandler {\n\t\tif err := h.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, h := range m.untaggedHandlers {\n\t\tif err := h.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Close implements core.Feature\nfunc (m *Manager) Close() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tm.running = false\n\n\tvar errs []error\n\tfor _, h := range m.taggedHandler {\n\t\terrs = append(errs, h.Close())\n\t}\n\n\tfor _, h := range m.untaggedHandlers {\n\t\terrs = append(errs, h.Close())\n\t}\n\n\treturn errors.Combine(errs...)\n}\n\n// GetDefaultHandler implements outbound.Manager.\nfunc (m *Manager) GetDefaultHandler() outbound.Handler {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tif m.defaultHandler == nil {\n\t\treturn nil\n\t}\n\treturn m.defaultHandler\n}\n\n// GetHandler implements outbound.Manager.\nfunc (m *Manager) GetHandler(tag string) outbound.Handler {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\tif handler, found := m.taggedHandler[tag]; found {\n\t\treturn handler\n\t}\n\treturn nil\n}\n\n// AddHandler implements outbound.Manager.\nfunc (m *Manager) AddHandler(ctx context.Context, handler outbound.Handler) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tm.tagsCache = &sync.Map{}\n\n\tif m.defaultHandler == nil {\n\t\tm.defaultHandler = handler\n\t}\n\n\ttag := handler.Tag()\n\tif len(tag) > 0 {\n\t\tif _, found := m.taggedHandler[tag]; found {\n\t\t\treturn errors.New(\"existing tag found: \" + tag)\n\t\t}\n\t\tm.taggedHandler[tag] = handler\n\t} else {\n\t\tm.untaggedHandlers = append(m.untaggedHandlers, handler)\n\t}\n\n\tif m.running {\n\t\treturn handler.Start()\n\t}\n\n\treturn nil\n}\n\n// RemoveHandler implements outbound.Manager.\nfunc (m *Manager) RemoveHandler(ctx context.Context, tag string) error {\n\tif tag == \"\" {\n\t\treturn common.ErrNoClue\n\t}\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tm.tagsCache = &sync.Map{}\n\n\tdelete(m.taggedHandler, tag)\n\tif m.defaultHandler != nil && m.defaultHandler.Tag() == tag {\n\t\tm.defaultHandler = nil\n\t}\n\n\treturn nil\n}\n\n// ListHandlers implements outbound.Manager.\nfunc (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tresponse := make([]outbound.Handler, len(m.untaggedHandlers))\n\tcopy(response, m.untaggedHandlers)\n\n\tfor _, v := range m.taggedHandler {\n\t\tresponse = append(response, v)\n\t}\n\n\treturn response\n}\n\n// Select implements outbound.HandlerSelector.\nfunc (m *Manager) Select(selectors []string) []string {\n\n\tkey := strings.Join(selectors, \",\")\n\tif cache, ok := m.tagsCache.Load(key); ok {\n\t\treturn cache.([]string)\n\t}\n\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\ttags := make([]string, 0, len(selectors))\n\n\tfor tag := range m.taggedHandler {\n\t\tfor _, selector := range selectors {\n\t\t\tif strings.HasPrefix(tag, selector) {\n\t\t\t\ttags = append(tags, tag)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tsort.Strings(tags)\n\tm.tagsCache.Store(key, tags)\n\n\treturn tags\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*proxyman.OutboundConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*proxyman.OutboundConfig))\n\t}))\n\tcommon.Must(common.RegisterConfig((*core.OutboundHandlerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewHandler(ctx, config.(*core.OutboundHandlerConfig))\n\t}))\n}\n"
  },
  {
    "path": "app/proxyman/outbound/uot.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/sagernet/sing/common/uot\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) {\n\tif dest.Address == nil {\n\t\treturn nil, errors.New(\"nil destination address\")\n\t}\n\tif !dest.Address.Family().IsDomain() {\n\t\treturn nil, os.ErrInvalid\n\t}\n\tvar uotVersion int\n\tif dest.Address.Domain() == uot.MagicAddress {\n\t\tuotVersion = uot.Version\n\t} else if dest.Address.Domain() == uot.LegacyMagicAddress {\n\t\tuotVersion = uot.LegacyVersion\n\t} else {\n\t\treturn nil, os.ErrInvalid\n\t}\n\tpacketConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings)\n\tif err != nil {\n\t\treturn nil, errors.New(\"unable to listen socket\").Base(err)\n\t}\n\tconn := uot.NewServerConn(packetConn, uotVersion)\n\treturn h.getStatCouterConnection(conn), nil\n}\n"
  },
  {
    "path": "app/reverse/bridge.go",
    "content": "package reverse\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// Bridge is a component in reverse proxy, that relays connections from Portal to local address.\ntype Bridge struct {\n\tdispatcher  routing.Dispatcher\n\ttag         string\n\tdomain      string\n\tworkers     []*BridgeWorker\n\tmonitorTask *task.Periodic\n}\n\n// NewBridge creates a new Bridge instance.\nfunc NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) {\n\tif config.Tag == \"\" {\n\t\treturn nil, errors.New(\"bridge tag is empty\")\n\t}\n\tif config.Domain == \"\" {\n\t\treturn nil, errors.New(\"bridge domain is empty\")\n\t}\n\n\tb := &Bridge{\n\t\tdispatcher: dispatcher,\n\t\ttag:        config.Tag,\n\t\tdomain:     config.Domain,\n\t}\n\tb.monitorTask = &task.Periodic{\n\t\tExecute:  b.monitor,\n\t\tInterval: time.Second * 2,\n\t}\n\treturn b, nil\n}\n\nfunc (b *Bridge) cleanup() {\n\tvar activeWorkers []*BridgeWorker\n\n\tfor _, w := range b.workers {\n\t\tif w.IsActive() {\n\t\t\tactiveWorkers = append(activeWorkers, w)\n\t\t}\n\t\tif w.Closed() {\n\t\t\tif w.Timer != nil {\n\t\t\t\tw.Timer.SetTimeout(0)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(activeWorkers) != len(b.workers) {\n\t\tb.workers = activeWorkers\n\t}\n}\n\nfunc (b *Bridge) monitor() error {\n\tb.cleanup()\n\n\tvar numConnections uint32\n\tvar numWorker uint32\n\n\tfor _, w := range b.workers {\n\t\tif w.IsActive() {\n\t\t\tnumConnections += w.Connections()\n\t\t\tnumWorker++\n\t\t}\n\t}\n\n\tif numWorker == 0 || numConnections/numWorker > 16 {\n\t\tworker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher)\n\t\tif err != nil {\n\t\t\terrors.LogWarningInner(context.Background(), err, \"failed to create bridge worker\")\n\t\t\treturn nil\n\t\t}\n\t\tb.workers = append(b.workers, worker)\n\t}\n\n\treturn nil\n}\n\nfunc (b *Bridge) Start() error {\n\treturn b.monitorTask.Start()\n}\n\nfunc (b *Bridge) Close() error {\n\treturn b.monitorTask.Close()\n}\n\ntype BridgeWorker struct {\n\tTag        string\n\tWorker     *mux.ServerWorker\n\tDispatcher routing.Dispatcher\n\tState      Control_State\n\tTimer      *signal.ActivityTimer\n}\n\nfunc NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) {\n\tctx := context.Background()\n\tctx = session.ContextWithInbound(ctx, &session.Inbound{\n\t\tTag: tag,\n\t})\n\tlink, err := d.Dispatch(ctx, net.Destination{\n\t\tNetwork: net.Network_TCP,\n\t\tAddress: net.DomainAddress(domain),\n\t\tPort:    0,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw := &BridgeWorker{\n\t\tDispatcher: d,\n\t\tTag:        tag,\n\t}\n\n\tworker, err := mux.NewServerWorker(context.Background(), w, link)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tw.Worker = worker\n\n\tterminate := func() {\n\t\tworker.Close()\n\t}\n\tw.Timer = signal.CancelAfterInactivity(ctx, terminate, 60*time.Second)\n\treturn w, nil\n}\n\nfunc (w *BridgeWorker) Type() interface{} {\n\treturn routing.DispatcherType()\n}\n\nfunc (w *BridgeWorker) Start() error {\n\treturn nil\n}\n\nfunc (w *BridgeWorker) Close() error {\n\treturn nil\n}\n\nfunc (w *BridgeWorker) IsActive() bool {\n\treturn w.State == Control_ACTIVE && !w.Worker.Closed()\n}\n\nfunc (w *BridgeWorker) Closed() bool {\n\treturn w.Worker.Closed()\n}\n\nfunc (w *BridgeWorker) Connections() uint32 {\n\treturn w.Worker.ActiveConnections()\n}\n\nfunc (w *BridgeWorker) handleInternalConn(link *transport.Link) {\n\treader := link.Reader\n\tfor {\n\t\tmb, err := reader.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\tif w.Timer != nil {\n\t\t\t\tif w.Closed() {\n\t\t\t\t\tw.Timer.SetTimeout(0)\n\t\t\t\t} else {\n\t\t\t\t\tw.Timer.SetTimeout(24 * time.Hour)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif w.Timer != nil {\n\t\t\tw.Timer.Update()\n\t\t}\n\t\tfor _, b := range mb {\n\t\t\tvar ctl Control\n\t\t\tif err := proto.Unmarshal(b.Bytes(), &ctl); err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to parse proto message\")\n\t\t\t\tif w.Timer != nil {\n\t\t\t\t\tw.Timer.SetTimeout(0)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ctl.State != w.State {\n\t\t\t\tw.State = ctl.State\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {\n\tif !isInternalDomain(dest) {\n\t\tif session.InboundFromContext(ctx) == nil {\n\t\t\tctx = session.ContextWithInbound(ctx, &session.Inbound{\n\t\t\t\tTag: w.Tag,\n\t\t\t})\n\t\t}\n\t\treturn w.Dispatcher.Dispatch(ctx, dest)\n\t}\n\n\topt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}\n\tuplinkReader, uplinkWriter := pipe.New(opt...)\n\tdownlinkReader, downlinkWriter := pipe.New(opt...)\n\n\tgo w.handleInternalConn(&transport.Link{\n\t\tReader: downlinkReader,\n\t\tWriter: uplinkWriter,\n\t})\n\n\treturn &transport.Link{\n\t\tReader: uplinkReader,\n\t\tWriter: downlinkWriter,\n\t}, nil\n}\n\nfunc (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error {\n\tif !isInternalDomain(dest) {\n\t\tif session.InboundFromContext(ctx) == nil {\n\t\t\tctx = session.ContextWithInbound(ctx, &session.Inbound{\n\t\t\t\tTag: w.Tag,\n\t\t\t})\n\t\t}\n\t\treturn w.Dispatcher.DispatchLink(ctx, dest, link)\n\t}\n\tw.handleInternalConn(link)\n\n\treturn nil\n}\n"
  },
  {
    "path": "app/reverse/config.go",
    "content": "package reverse\n\nimport (\n\t\"crypto/rand\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n)\n\nfunc (c *Control) FillInRandom() {\n\trandomLength := dice.Roll(64)\n\trandomLength++\n\tc.Random = make([]byte, randomLength)\n\tio.ReadFull(rand.Reader, c.Random)\n}\n"
  },
  {
    "path": "app/reverse/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/reverse/config.proto\n\npackage reverse\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Control_State int32\n\nconst (\n\tControl_ACTIVE Control_State = 0\n\tControl_DRAIN  Control_State = 1\n)\n\n// Enum value maps for Control_State.\nvar (\n\tControl_State_name = map[int32]string{\n\t\t0: \"ACTIVE\",\n\t\t1: \"DRAIN\",\n\t}\n\tControl_State_value = map[string]int32{\n\t\t\"ACTIVE\": 0,\n\t\t\"DRAIN\":  1,\n\t}\n)\n\nfunc (x Control_State) Enum() *Control_State {\n\tp := new(Control_State)\n\t*p = x\n\treturn p\n}\n\nfunc (x Control_State) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Control_State) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_app_reverse_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Control_State) Type() protoreflect.EnumType {\n\treturn &file_app_reverse_config_proto_enumTypes[0]\n}\n\nfunc (x Control_State) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Control_State.Descriptor instead.\nfunc (Control_State) EnumDescriptor() ([]byte, []int) {\n\treturn file_app_reverse_config_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Control struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tState         Control_State          `protobuf:\"varint,1,opt,name=state,proto3,enum=xray.app.reverse.Control_State\" json:\"state,omitempty\"`\n\tRandom        []byte                 `protobuf:\"bytes,99,opt,name=random,proto3\" json:\"random,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Control) Reset() {\n\t*x = Control{}\n\tmi := &file_app_reverse_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Control) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Control) ProtoMessage() {}\n\nfunc (x *Control) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_reverse_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Control.ProtoReflect.Descriptor instead.\nfunc (*Control) Descriptor() ([]byte, []int) {\n\treturn file_app_reverse_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Control) GetState() Control_State {\n\tif x != nil {\n\t\treturn x.State\n\t}\n\treturn Control_ACTIVE\n}\n\nfunc (x *Control) GetRandom() []byte {\n\tif x != nil {\n\t\treturn x.Random\n\t}\n\treturn nil\n}\n\ntype BridgeConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tDomain        string                 `protobuf:\"bytes,2,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *BridgeConfig) Reset() {\n\t*x = BridgeConfig{}\n\tmi := &file_app_reverse_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *BridgeConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BridgeConfig) ProtoMessage() {}\n\nfunc (x *BridgeConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_reverse_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use BridgeConfig.ProtoReflect.Descriptor instead.\nfunc (*BridgeConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_reverse_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *BridgeConfig) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *BridgeConfig) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\ntype PortalConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tDomain        string                 `protobuf:\"bytes,2,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PortalConfig) Reset() {\n\t*x = PortalConfig{}\n\tmi := &file_app_reverse_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PortalConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PortalConfig) ProtoMessage() {}\n\nfunc (x *PortalConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_reverse_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PortalConfig.ProtoReflect.Descriptor instead.\nfunc (*PortalConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_reverse_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *PortalConfig) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *PortalConfig) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tBridgeConfig  []*BridgeConfig        `protobuf:\"bytes,1,rep,name=bridge_config,json=bridgeConfig,proto3\" json:\"bridge_config,omitempty\"`\n\tPortalConfig  []*PortalConfig        `protobuf:\"bytes,2,rep,name=portal_config,json=portalConfig,proto3\" json:\"portal_config,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_reverse_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_reverse_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_reverse_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Config) GetBridgeConfig() []*BridgeConfig {\n\tif x != nil {\n\t\treturn x.BridgeConfig\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetPortalConfig() []*PortalConfig {\n\tif x != nil {\n\t\treturn x.PortalConfig\n\t}\n\treturn nil\n}\n\nvar File_app_reverse_config_proto protoreflect.FileDescriptor\n\nconst file_app_reverse_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x18app/reverse/config.proto\\x12\\x10xray.app.reverse\\\"x\\n\" +\n\t\"\\aControl\\x125\\n\" +\n\t\"\\x05state\\x18\\x01 \\x01(\\x0e2\\x1f.xray.app.reverse.Control.StateR\\x05state\\x12\\x16\\n\" +\n\t\"\\x06random\\x18c \\x01(\\fR\\x06random\\\"\\x1e\\n\" +\n\t\"\\x05State\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06ACTIVE\\x10\\x00\\x12\\t\\n\" +\n\t\"\\x05DRAIN\\x10\\x01\\\"8\\n\" +\n\t\"\\fBridgeConfig\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x01(\\tR\\x06domain\\\"8\\n\" +\n\t\"\\fPortalConfig\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x01(\\tR\\x06domain\\\"\\x92\\x01\\n\" +\n\t\"\\x06Config\\x12C\\n\" +\n\t\"\\rbridge_config\\x18\\x01 \\x03(\\v2\\x1e.xray.app.reverse.BridgeConfigR\\fbridgeConfig\\x12C\\n\" +\n\t\"\\rportal_config\\x18\\x02 \\x03(\\v2\\x1e.xray.app.reverse.PortalConfigR\\fportalConfigBV\\n\" +\n\t\"\\x16com.xray.proxy.reverseP\\x01Z%github.com/xtls/xray-core/app/reverse\\xaa\\x02\\x12Xray.Proxy.Reverseb\\x06proto3\"\n\nvar (\n\tfile_app_reverse_config_proto_rawDescOnce sync.Once\n\tfile_app_reverse_config_proto_rawDescData []byte\n)\n\nfunc file_app_reverse_config_proto_rawDescGZIP() []byte {\n\tfile_app_reverse_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_reverse_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_reverse_config_proto_rawDesc), len(file_app_reverse_config_proto_rawDesc)))\n\t})\n\treturn file_app_reverse_config_proto_rawDescData\n}\n\nvar file_app_reverse_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_app_reverse_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_app_reverse_config_proto_goTypes = []any{\n\t(Control_State)(0),   // 0: xray.app.reverse.Control.State\n\t(*Control)(nil),      // 1: xray.app.reverse.Control\n\t(*BridgeConfig)(nil), // 2: xray.app.reverse.BridgeConfig\n\t(*PortalConfig)(nil), // 3: xray.app.reverse.PortalConfig\n\t(*Config)(nil),       // 4: xray.app.reverse.Config\n}\nvar file_app_reverse_config_proto_depIdxs = []int32{\n\t0, // 0: xray.app.reverse.Control.state:type_name -> xray.app.reverse.Control.State\n\t2, // 1: xray.app.reverse.Config.bridge_config:type_name -> xray.app.reverse.BridgeConfig\n\t3, // 2: xray.app.reverse.Config.portal_config:type_name -> xray.app.reverse.PortalConfig\n\t3, // [3:3] is the sub-list for method output_type\n\t3, // [3:3] is the sub-list for method input_type\n\t3, // [3:3] is the sub-list for extension type_name\n\t3, // [3:3] is the sub-list for extension extendee\n\t0, // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_app_reverse_config_proto_init() }\nfunc file_app_reverse_config_proto_init() {\n\tif File_app_reverse_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_reverse_config_proto_rawDesc), len(file_app_reverse_config_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_reverse_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_reverse_config_proto_depIdxs,\n\t\tEnumInfos:         file_app_reverse_config_proto_enumTypes,\n\t\tMessageInfos:      file_app_reverse_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_reverse_config_proto = out.File\n\tfile_app_reverse_config_proto_goTypes = nil\n\tfile_app_reverse_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/reverse/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.reverse;\noption csharp_namespace = \"Xray.Proxy.Reverse\";\noption go_package = \"github.com/xtls/xray-core/app/reverse\";\noption java_package = \"com.xray.proxy.reverse\";\noption java_multiple_files = true;\n\nmessage Control {\n  enum State {\n    ACTIVE = 0;\n    DRAIN = 1;\n  }\n\n  State state = 1;\n  bytes random = 99;\n}\n\nmessage BridgeConfig {\n  string tag = 1;\n  string domain = 2;\n}\n\nmessage PortalConfig {\n  string tag = 1;\n  string domain = 2;\n}\n\nmessage Config {\n  repeated BridgeConfig bridge_config = 1;\n  repeated PortalConfig portal_config = 2;\n}\n"
  },
  {
    "path": "app/reverse/portal.go",
    "content": "package reverse\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype Portal struct {\n\tohm    outbound.Manager\n\ttag    string\n\tdomain string\n\tpicker *StaticMuxPicker\n\tclient *mux.ClientManager\n}\n\nfunc NewPortal(config *PortalConfig, ohm outbound.Manager) (*Portal, error) {\n\tif config.Tag == \"\" {\n\t\treturn nil, errors.New(\"portal tag is empty\")\n\t}\n\n\tif config.Domain == \"\" {\n\t\treturn nil, errors.New(\"portal domain is empty\")\n\t}\n\n\tpicker, err := NewStaticMuxPicker()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Portal{\n\t\tohm:    ohm,\n\t\ttag:    config.Tag,\n\t\tdomain: config.Domain,\n\t\tpicker: picker,\n\t\tclient: &mux.ClientManager{\n\t\t\tPicker: picker,\n\t\t},\n\t}, nil\n}\n\nfunc (p *Portal) Start() error {\n\treturn p.ohm.AddHandler(context.Background(), &Outbound{\n\t\tportal: p,\n\t\ttag:    p.tag,\n\t})\n}\n\nfunc (p *Portal) Close() error {\n\treturn p.ohm.RemoveHandler(context.Background(), p.tag)\n}\n\nfunc (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif ob == nil {\n\t\treturn errors.New(\"outbound metadata not found\").AtError()\n\t}\n\n\tif isDomain(ob.Target, p.domain) {\n\t\tmuxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create mux client worker\").Base(err).AtWarning()\n\t\t}\n\n\t\tworker, err := NewPortalWorker(muxClient)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create portal worker\").Base(err)\n\t\t}\n\n\t\tp.picker.AddWorker(worker)\n\n\t\tif _, ok := link.Reader.(*pipe.Reader); !ok {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\tcase <-muxClient.WaitClosed():\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tif ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address {\n\t\tlink.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}\n\t\tlink.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}\n\t}\n\n\treturn p.client.Dispatch(ctx, link)\n}\n\ntype Outbound struct {\n\tportal *Portal\n\ttag    string\n}\n\nfunc (o *Outbound) Tag() string {\n\treturn o.tag\n}\n\nfunc (o *Outbound) Dispatch(ctx context.Context, link *transport.Link) {\n\tif err := o.portal.HandleConnection(ctx, link); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to process reverse connection\")\n\t\tcommon.Interrupt(link.Writer)\n\t\tcommon.Interrupt(link.Reader)\n\t}\n}\n\nfunc (o *Outbound) Start() error {\n\treturn nil\n}\n\nfunc (o *Outbound) Close() error {\n\treturn nil\n}\n\n// SenderSettings implements outbound.Handler.\nfunc (o *Outbound) SenderSettings() *serial.TypedMessage {\n\treturn nil\n}\n\n// ProxySettings implements outbound.Handler.\nfunc (o *Outbound) ProxySettings() *serial.TypedMessage {\n\treturn nil\n}\n\ntype StaticMuxPicker struct {\n\taccess  sync.Mutex\n\tworkers []*PortalWorker\n\tcTask   *task.Periodic\n}\n\nfunc NewStaticMuxPicker() (*StaticMuxPicker, error) {\n\tp := &StaticMuxPicker{}\n\tp.cTask = &task.Periodic{\n\t\tExecute:  p.cleanup,\n\t\tInterval: time.Second * 30,\n\t}\n\tp.cTask.Start()\n\treturn p, nil\n}\n\nfunc (p *StaticMuxPicker) cleanup() error {\n\tp.access.Lock()\n\tdefer p.access.Unlock()\n\n\tvar activeWorkers []*PortalWorker\n\tfor _, w := range p.workers {\n\t\tif !w.Closed() {\n\t\t\tactiveWorkers = append(activeWorkers, w)\n\t\t} else {\n\t\t\tw.timer.SetTimeout(0)\n\t\t}\n\t}\n\n\tif len(activeWorkers) != len(p.workers) {\n\t\tp.workers = activeWorkers\n\t}\n\n\treturn nil\n}\n\nfunc (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) {\n\tp.access.Lock()\n\tdefer p.access.Unlock()\n\n\tif len(p.workers) == 0 {\n\t\treturn nil, errors.New(\"empty worker list\")\n\t}\n\n\tvar minIdx int = -1\n\tvar minConn uint32 = 9999\n\tfor i, w := range p.workers {\n\t\tif w.draining {\n\t\t\tcontinue\n\t\t}\n\t\tif w.IsFull() {\n\t\t\tcontinue\n\t\t}\n\t\tif w.client.ActiveConnections() < minConn {\n\t\t\tminConn = w.client.ActiveConnections()\n\t\t\tminIdx = i\n\t\t}\n\t}\n\n\tif minIdx == -1 {\n\t\tfor i, w := range p.workers {\n\t\t\tif w.IsFull() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif w.client.ActiveConnections() < minConn {\n\t\t\t\tminConn = w.client.ActiveConnections()\n\t\t\t\tminIdx = i\n\t\t\t}\n\t\t}\n\t}\n\n\tif minIdx != -1 {\n\t\treturn p.workers[minIdx].client, nil\n\t}\n\n\treturn nil, errors.New(\"no mux client worker available\")\n}\n\nfunc (p *StaticMuxPicker) AddWorker(worker *PortalWorker) {\n\tp.access.Lock()\n\tdefer p.access.Unlock()\n\n\tp.workers = append(p.workers, worker)\n}\n\ntype PortalWorker struct {\n\tclient   *mux.ClientWorker\n\tcontrol  *task.Periodic\n\twriter   buf.Writer\n\treader   buf.Reader\n\tdraining bool\n\tcounter  uint32\n\ttimer    *signal.ActivityTimer\n}\n\nfunc NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {\n\topt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}\n\tuplinkReader, uplinkWriter := pipe.New(opt...)\n\tdownlinkReader, downlinkWriter := pipe.New(opt...)\n\n\tctx := context.Background()\n\toutbounds := []*session.Outbound{{\n\t\tTarget: net.UDPDestination(net.DomainAddress(internalDomain), 0),\n\t}}\n\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\tf := client.Dispatch(ctx, &transport.Link{\n\t\tReader: uplinkReader,\n\t\tWriter: downlinkWriter,\n\t})\n\tif !f {\n\t\treturn nil, errors.New(\"unable to dispatch control connection\")\n\t}\n\tterminate := func() {\n\t\tclient.Close()\n\t}\n\tw := &PortalWorker{\n\t\tclient: client,\n\t\treader: downlinkReader,\n\t\twriter: uplinkWriter,\n\t\ttimer:  signal.CancelAfterInactivity(ctx, terminate, 24*time.Hour), // // prevent leak\n\t}\n\tw.control = &task.Periodic{\n\t\tExecute:  w.heartbeat,\n\t\tInterval: time.Second * 2,\n\t}\n\tw.control.Start()\n\treturn w, nil\n}\n\nfunc (w *PortalWorker) heartbeat() error {\n\tif w.Closed() {\n\t\treturn errors.New(\"client worker stopped\")\n\t}\n\n\tif w.draining || w.writer == nil {\n\t\treturn errors.New(\"already disposed\")\n\t}\n\n\tmsg := &Control{}\n\tmsg.FillInRandom()\n\n\tif w.client.TotalConnections() > 256 {\n\t\tw.draining = true\n\t\tmsg.State = Control_DRAIN\n\n\t\tdefer func() {\n\t\t\tcommon.Close(w.writer)\n\t\t\tcommon.Interrupt(w.reader)\n\t\t\tw.writer = nil\n\t\t}()\n\t}\n\n\tw.counter = (w.counter + 1) % 5\n\tif w.draining || w.counter == 1 {\n\t\tb, err := proto.Marshal(msg)\n\t\tcommon.Must(err)\n\t\tmb := buf.MergeBytes(nil, b)\n\t\tw.timer.Update()\n\t\treturn w.writer.WriteMultiBuffer(mb)\n\t}\n\treturn nil\n}\n\nfunc (w *PortalWorker) IsFull() bool {\n\treturn w.client.IsFull()\n}\n\nfunc (w *PortalWorker) Closed() bool {\n\treturn w.client.Closed()\n}\n"
  },
  {
    "path": "app/reverse/portal_test.go",
    "content": "package reverse_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/reverse\"\n\t\"github.com/xtls/xray-core/common\"\n)\n\nfunc TestStaticPickerEmpty(t *testing.T) {\n\tpicker, err := reverse.NewStaticMuxPicker()\n\tcommon.Must(err)\n\tworker, err := picker.PickAvailable()\n\tif err == nil {\n\t\tt.Error(\"expected error, but nil\")\n\t}\n\tif worker != nil {\n\t\tt.Error(\"expected nil worker, but not nil\")\n\t}\n}\n"
  },
  {
    "path": "app/reverse/reverse.go",
    "content": "package reverse\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\nconst (\n\tinternalDomain = \"reverse\"\n)\n\nfunc isDomain(dest net.Destination, domain string) bool {\n\treturn dest.Address.Family().IsDomain() && dest.Address.Domain() == domain\n}\n\nfunc isInternalDomain(dest net.Destination) bool {\n\treturn isDomain(dest, internalDomain)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tr := new(Reverse)\n\t\tif err := core.RequireFeatures(ctx, func(d routing.Dispatcher, om outbound.Manager) error {\n\t\t\treturn r.Init(config.(*Config), d, om)\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn r, nil\n\t}))\n}\n\ntype Reverse struct {\n\tbridges []*Bridge\n\tportals []*Portal\n}\n\nfunc (r *Reverse) Init(config *Config, d routing.Dispatcher, ohm outbound.Manager) error {\n\tfor _, bConfig := range config.BridgeConfig {\n\t\tb, err := NewBridge(bConfig, d)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.bridges = append(r.bridges, b)\n\t}\n\n\tfor _, pConfig := range config.PortalConfig {\n\t\tp, err := NewPortal(pConfig, ohm)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.portals = append(r.portals, p)\n\t}\n\n\treturn nil\n}\n\nfunc (r *Reverse) Type() interface{} {\n\treturn (*Reverse)(nil)\n}\n\nfunc (r *Reverse) Start() error {\n\tfor _, b := range r.bridges {\n\t\tif err := b.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, p := range r.portals {\n\t\tif err := p.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *Reverse) Close() error {\n\tvar errs []error\n\tfor _, b := range r.bridges {\n\t\terrs = append(errs, b.Close())\n\t}\n\n\tfor _, p := range r.portals {\n\t\terrs = append(errs, p.Close())\n\t}\n\n\treturn errors.Combine(errs...)\n}\n"
  },
  {
    "path": "app/router/balancing.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\tsync \"sync\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n)\n\ntype BalancingStrategy interface {\n\tPickOutbound([]string) string\n}\n\ntype BalancingPrincipleTarget interface {\n\tGetPrincipleTarget([]string) []string\n}\n\ntype RoundRobinStrategy struct {\n\tFallbackTag string\n\n\tctx         context.Context\n\tobservatory extension.Observatory\n\tmu          sync.Mutex\n\tindex       int\n}\n\nfunc (s *RoundRobinStrategy) InjectContext(ctx context.Context) {\n\ts.ctx = ctx\n\tif len(s.FallbackTag) > 0 {\n\t\tcommon.Must(core.RequireFeatures(s.ctx, func(observatory extension.Observatory) error {\n\t\t\ts.observatory = observatory\n\t\t\treturn nil\n\t\t}))\n\t}\n}\n\nfunc (s *RoundRobinStrategy) GetPrincipleTarget(strings []string) []string {\n\treturn strings\n}\n\nfunc (s *RoundRobinStrategy) PickOutbound(tags []string) string {\n\tif s.observatory != nil {\n\t\tobserveReport, err := s.observatory.GetObservation(s.ctx)\n\t\tif err == nil {\n\t\t\taliveTags := make([]string, 0)\n\t\t\tif result, ok := observeReport.(*observatory.ObservationResult); ok {\n\t\t\t\tstatus := result.Status\n\t\t\t\tstatusMap := make(map[string]*observatory.OutboundStatus)\n\t\t\t\tfor _, outboundStatus := range status {\n\t\t\t\t\tstatusMap[outboundStatus.OutboundTag] = outboundStatus\n\t\t\t\t}\n\t\t\t\tfor _, candidate := range tags {\n\t\t\t\t\tif outboundStatus, found := statusMap[candidate]; found {\n\t\t\t\t\t\tif outboundStatus.Alive {\n\t\t\t\t\t\t\taliveTags = append(aliveTags, candidate)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// unfound candidate is considered alive\n\t\t\t\t\t\taliveTags = append(aliveTags, candidate)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttags = aliveTags\n\t\t\t}\n\t\t}\n\t}\n\n\tn := len(tags)\n\tif n == 0 {\n\t\t// goes to fallbackTag\n\t\treturn \"\"\n\t}\n\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\ttag := tags[s.index%n]\n\ts.index = (s.index + 1) % n\n\treturn tag\n}\n\ntype Balancer struct {\n\tselectors   []string\n\tstrategy    BalancingStrategy\n\tohm         outbound.Manager\n\tfallbackTag string\n\n\toverride override\n}\n\n// PickOutbound picks the tag of a outbound\nfunc (b *Balancer) PickOutbound() (string, error) {\n\tcandidates, err := b.SelectOutbounds()\n\tif err != nil {\n\t\tif b.fallbackTag != \"\" {\n\t\t\terrors.LogInfo(context.Background(), \"fallback to [\", b.fallbackTag, \"], due to error: \", err)\n\t\t\treturn b.fallbackTag, nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\tvar tag string\n\tif o := b.override.Get(); o != \"\" {\n\t\ttag = o\n\t} else {\n\t\ttag = b.strategy.PickOutbound(candidates)\n\t}\n\tif tag == \"\" {\n\t\tif b.fallbackTag != \"\" {\n\t\t\terrors.LogInfo(context.Background(), \"fallback to [\", b.fallbackTag, \"], due to empty tag returned\")\n\t\t\treturn b.fallbackTag, nil\n\t\t}\n\t\t// will use default handler\n\t\treturn \"\", errors.New(\"balancing strategy returns empty tag\")\n\t}\n\treturn tag, nil\n}\n\nfunc (b *Balancer) InjectContext(ctx context.Context) {\n\tif contextReceiver, ok := b.strategy.(extension.ContextReceiver); ok {\n\t\tcontextReceiver.InjectContext(ctx)\n\t}\n}\n\n// SelectOutbounds select outbounds with selectors of the Balancer\nfunc (b *Balancer) SelectOutbounds() ([]string, error) {\n\ths, ok := b.ohm.(outbound.HandlerSelector)\n\tif !ok {\n\t\treturn nil, errors.New(\"outbound.Manager is not a HandlerSelector\")\n\t}\n\ttags := hs.Select(b.selectors)\n\treturn tags, nil\n}\n\n// GetPrincipleTarget implements routing.BalancerPrincipleTarget\nfunc (r *Router) GetPrincipleTarget(tag string) ([]string, error) {\n\tif b, ok := r.balancers[tag]; ok {\n\t\tif s, ok := b.strategy.(BalancingPrincipleTarget); ok {\n\t\t\tcandidates, err := b.SelectOutbounds()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"unable to select outbounds\").Base(err)\n\t\t\t}\n\t\t\treturn s.GetPrincipleTarget(candidates), nil\n\t\t}\n\t\treturn nil, errors.New(\"unsupported GetPrincipleTarget\")\n\t}\n\treturn nil, errors.New(\"cannot find tag\")\n}\n\n// SetOverrideTarget implements routing.BalancerOverrider\nfunc (r *Router) SetOverrideTarget(tag, target string) error {\n\tif b, ok := r.balancers[tag]; ok {\n\t\tb.override.Put(target)\n\t\treturn nil\n\t}\n\treturn errors.New(\"cannot find tag\")\n}\n\n// GetOverrideTarget implements routing.BalancerOverrider\nfunc (r *Router) GetOverrideTarget(tag string) (string, error) {\n\tif b, ok := r.balancers[tag]; ok {\n\t\treturn b.override.Get(), nil\n\t}\n\treturn \"\", errors.New(\"cannot find tag\")\n}\n"
  },
  {
    "path": "app/router/balancing_override.go",
    "content": "package router\n\nimport (\n\tsync \"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nfunc (r *Router) OverrideBalancer(balancer string, target string) error {\n\tvar b *Balancer\n\tfor tag, bl := range r.balancers {\n\t\tif tag == balancer {\n\t\t\tb = bl\n\t\t\tbreak\n\t\t}\n\t}\n\tif b == nil {\n\t\treturn errors.New(\"balancer '\", balancer, \"' not found\")\n\t}\n\tb.override.Put(target)\n\treturn nil\n}\n\ntype overrideSettings struct {\n\ttarget string\n}\n\ntype override struct {\n\taccess   sync.RWMutex\n\tsettings overrideSettings\n}\n\n// Get gets the override settings\nfunc (o *override) Get() string {\n\to.access.RLock()\n\tdefer o.access.RUnlock()\n\treturn o.settings.target\n}\n\n// Put updates the override settings\nfunc (o *override) Put(target string) {\n\to.access.Lock()\n\tdefer o.access.Unlock()\n\to.settings.target = target\n}\n\n// Clear clears the override settings\nfunc (o *override) Clear() {\n\to.access.Lock()\n\tdefer o.access.Unlock()\n\to.settings.target = \"\"\n}\n"
  },
  {
    "path": "app/router/command/command.go",
    "content": "package command\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"google.golang.org/grpc\"\n)\n\n// routingServer is an implementation of RoutingService.\ntype routingServer struct {\n\trouter       routing.Router\n\troutingStats stats.Channel\n}\n\nfunc (s *routingServer) GetBalancerInfo(ctx context.Context, request *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error) {\n\tvar ret GetBalancerInfoResponse\n\tret.Balancer = &BalancerMsg{}\n\tif bo, ok := s.router.(routing.BalancerOverrider); ok {\n\t\t{\n\t\t\tres, err := bo.GetOverrideTarget(request.GetTag())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tret.Balancer.Override = &OverrideInfo{\n\t\t\t\tTarget: res,\n\t\t\t}\n\t\t}\n\t}\n\n\tif pt, ok := s.router.(routing.BalancerPrincipleTarget); ok {\n\t\t{\n\t\t\tres, err := pt.GetPrincipleTarget(request.GetTag())\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(ctx, err, \"unable to obtain principle target\")\n\t\t\t} else {\n\t\t\t\tret.Balancer.PrincipleTarget = &PrincipleTargetInfo{Tag: res}\n\t\t\t}\n\t\t}\n\t}\n\treturn &ret, nil\n}\n\nfunc (s *routingServer) OverrideBalancerTarget(ctx context.Context, request *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) {\n\tif bo, ok := s.router.(routing.BalancerOverrider); ok {\n\t\treturn &OverrideBalancerTargetResponse{}, bo.SetOverrideTarget(request.BalancerTag, request.Target)\n\t}\n\treturn nil, errors.New(\"unsupported router implementation\")\n}\n\nfunc (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*AddRuleResponse, error) {\n\tif bo, ok := s.router.(routing.Router); ok {\n\t\treturn &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)\n\t}\n\treturn nil, errors.New(\"unsupported router implementation\")\n\n}\n\nfunc (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {\n\tif bo, ok := s.router.(routing.Router); ok {\n\t\treturn &RemoveRuleResponse{}, bo.RemoveRule(request.RuleTag)\n\t}\n\treturn nil, errors.New(\"unsupported router implementation\")\n}\n\nfunc (s *routingServer) ListRule(ctx context.Context, request *ListRuleRequest) (*ListRuleResponse, error) {\n\tif bo, ok := s.router.(routing.Router); ok {\n\t\tresponse := &ListRuleResponse{}\n\t\tfor _, v := range bo.ListRule() {\n\t\t\tresponse.Rules = append(response.Rules, &ListRuleItem{\n\t\t\t\tTag:     v.GetOutboundTag(),\n\t\t\t\tRuleTag: v.GetRuleTag(),\n\t\t\t})\n\t\t}\n\t\treturn response, nil\n\t}\n\treturn nil, errors.New(\"unsupported router implementation\")\n}\n\n// NewRoutingServer creates a statistics service with statistics manager.\nfunc NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer {\n\treturn &routingServer{\n\t\trouter:       router,\n\t\troutingStats: routingStats,\n\t}\n}\n\nfunc (s *routingServer) TestRoute(ctx context.Context, request *TestRouteRequest) (*RoutingContext, error) {\n\tif request.RoutingContext == nil {\n\t\treturn nil, errors.New(\"Invalid routing request.\")\n\t}\n\troute, err := s.router.PickRoute(AsRoutingContext(request.RoutingContext))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif request.PublishResult && s.routingStats != nil {\n\t\tctx, _ := context.WithTimeout(context.Background(), 4*time.Second)\n\t\ts.routingStats.Publish(ctx, route)\n\t}\n\treturn AsProtobufMessage(request.FieldSelectors)(route), nil\n}\n\nfunc (s *routingServer) SubscribeRoutingStats(request *SubscribeRoutingStatsRequest, stream RoutingService_SubscribeRoutingStatsServer) error {\n\tif s.routingStats == nil {\n\t\treturn errors.New(\"Routing statistics not enabled.\")\n\t}\n\tgenMessage := AsProtobufMessage(request.FieldSelectors)\n\tsubscriber, err := stats.SubscribeRunnableChannel(s.routingStats)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer stats.UnsubscribeClosableChannel(s.routingStats, subscriber)\n\tfor {\n\t\tselect {\n\t\tcase value, ok := <-subscriber:\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"Upstream closed the subscriber channel.\")\n\t\t\t}\n\t\t\troute, ok := value.(routing.Route)\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"Upstream sent malformed statistics.\")\n\t\t\t}\n\t\t\terr := stream.Send(genMessage(route))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase <-stream.Context().Done():\n\t\t\treturn stream.Context().Err()\n\t\t}\n\t}\n}\n\nfunc (s *routingServer) mustEmbedUnimplementedRoutingServiceServer() {}\n\ntype service struct {\n\tv *core.Instance\n}\n\nfunc (s *service) Register(server *grpc.Server) {\n\tcommon.Must(s.v.RequireFeatures(func(router routing.Router, stats stats.Manager) {\n\t\trs := NewRoutingServer(router, nil)\n\t\tRegisterRoutingServiceServer(server, rs)\n\n\t\t// For compatibility purposes\n\t\tvCoreDesc := RoutingService_ServiceDesc\n\t\tvCoreDesc.ServiceName = \"v2ray.core.app.router.command.RoutingService\"\n\t\tserver.RegisterService(&vCoreDesc, rs)\n\t}, false))\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\ts := core.MustFromContext(ctx)\n\t\treturn &service{v: s}, nil\n\t}))\n}\n"
  },
  {
    "path": "app/router/command/command.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/router/command/command.proto\n\npackage command\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// RoutingContext is the context with information relative to routing process.\n// It conforms to the structure of xray.features.routing.Context and\n// xray.features.routing.Route.\ntype RoutingContext struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tInboundTag        string                 `protobuf:\"bytes,1,opt,name=InboundTag,proto3\" json:\"InboundTag,omitempty\"`\n\tNetwork           net.Network            `protobuf:\"varint,2,opt,name=Network,proto3,enum=xray.common.net.Network\" json:\"Network,omitempty\"`\n\tSourceIPs         [][]byte               `protobuf:\"bytes,3,rep,name=SourceIPs,proto3\" json:\"SourceIPs,omitempty\"`\n\tTargetIPs         [][]byte               `protobuf:\"bytes,4,rep,name=TargetIPs,proto3\" json:\"TargetIPs,omitempty\"`\n\tSourcePort        uint32                 `protobuf:\"varint,5,opt,name=SourcePort,proto3\" json:\"SourcePort,omitempty\"`\n\tTargetPort        uint32                 `protobuf:\"varint,6,opt,name=TargetPort,proto3\" json:\"TargetPort,omitempty\"`\n\tTargetDomain      string                 `protobuf:\"bytes,7,opt,name=TargetDomain,proto3\" json:\"TargetDomain,omitempty\"`\n\tProtocol          string                 `protobuf:\"bytes,8,opt,name=Protocol,proto3\" json:\"Protocol,omitempty\"`\n\tUser              string                 `protobuf:\"bytes,9,opt,name=User,proto3\" json:\"User,omitempty\"`\n\tAttributes        map[string]string      `protobuf:\"bytes,10,rep,name=Attributes,proto3\" json:\"Attributes,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tOutboundGroupTags []string               `protobuf:\"bytes,11,rep,name=OutboundGroupTags,proto3\" json:\"OutboundGroupTags,omitempty\"`\n\tOutboundTag       string                 `protobuf:\"bytes,12,opt,name=OutboundTag,proto3\" json:\"OutboundTag,omitempty\"`\n\tLocalIPs          [][]byte               `protobuf:\"bytes,13,rep,name=LocalIPs,proto3\" json:\"LocalIPs,omitempty\"`\n\tLocalPort         uint32                 `protobuf:\"varint,14,opt,name=LocalPort,proto3\" json:\"LocalPort,omitempty\"`\n\tVlessRoute        uint32                 `protobuf:\"varint,15,opt,name=VlessRoute,proto3\" json:\"VlessRoute,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *RoutingContext) Reset() {\n\t*x = RoutingContext{}\n\tmi := &file_app_router_command_command_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RoutingContext) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RoutingContext) ProtoMessage() {}\n\nfunc (x *RoutingContext) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RoutingContext.ProtoReflect.Descriptor instead.\nfunc (*RoutingContext) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *RoutingContext) GetInboundTag() string {\n\tif x != nil {\n\t\treturn x.InboundTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingContext) GetNetwork() net.Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn net.Network(0)\n}\n\nfunc (x *RoutingContext) GetSourceIPs() [][]byte {\n\tif x != nil {\n\t\treturn x.SourceIPs\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingContext) GetTargetIPs() [][]byte {\n\tif x != nil {\n\t\treturn x.TargetIPs\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingContext) GetSourcePort() uint32 {\n\tif x != nil {\n\t\treturn x.SourcePort\n\t}\n\treturn 0\n}\n\nfunc (x *RoutingContext) GetTargetPort() uint32 {\n\tif x != nil {\n\t\treturn x.TargetPort\n\t}\n\treturn 0\n}\n\nfunc (x *RoutingContext) GetTargetDomain() string {\n\tif x != nil {\n\t\treturn x.TargetDomain\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingContext) GetProtocol() string {\n\tif x != nil {\n\t\treturn x.Protocol\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingContext) GetUser() string {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingContext) GetAttributes() map[string]string {\n\tif x != nil {\n\t\treturn x.Attributes\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingContext) GetOutboundGroupTags() []string {\n\tif x != nil {\n\t\treturn x.OutboundGroupTags\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingContext) GetOutboundTag() string {\n\tif x != nil {\n\t\treturn x.OutboundTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingContext) GetLocalIPs() [][]byte {\n\tif x != nil {\n\t\treturn x.LocalIPs\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingContext) GetLocalPort() uint32 {\n\tif x != nil {\n\t\treturn x.LocalPort\n\t}\n\treturn 0\n}\n\nfunc (x *RoutingContext) GetVlessRoute() uint32 {\n\tif x != nil {\n\t\treturn x.VlessRoute\n\t}\n\treturn 0\n}\n\n// SubscribeRoutingStatsRequest subscribes to routing statistics channel if\n// opened by xray-core.\n// * FieldSelectors selects a subset of fields in routing statistics to return.\n// Valid selectors:\n//   - inbound: Selects connection's inbound tag.\n//   - network: Selects connection's network.\n//   - ip: Equivalent as \"ip_source\" and \"ip_target\", selects both source and\n//     target IP.\n//   - port: Equivalent as \"port_source\" and \"port_target\", selects both source\n//     and target port.\n//   - domain: Selects target domain.\n//   - protocol: Select connection's protocol.\n//   - user: Select connection's inbound user email.\n//   - attributes: Select connection's additional attributes.\n//   - outbound: Equivalent as \"outbound\" and \"outbound_group\", select both\n//     outbound tag and outbound group tags.\n//\n// * If FieldSelectors is left empty, all fields will be returned.\ntype SubscribeRoutingStatsRequest struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tFieldSelectors []string               `protobuf:\"bytes,1,rep,name=FieldSelectors,proto3\" json:\"FieldSelectors,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *SubscribeRoutingStatsRequest) Reset() {\n\t*x = SubscribeRoutingStatsRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SubscribeRoutingStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SubscribeRoutingStatsRequest) ProtoMessage() {}\n\nfunc (x *SubscribeRoutingStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SubscribeRoutingStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*SubscribeRoutingStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *SubscribeRoutingStatsRequest) GetFieldSelectors() []string {\n\tif x != nil {\n\t\treturn x.FieldSelectors\n\t}\n\treturn nil\n}\n\n// TestRouteRequest manually tests a routing result according to the routing\n// context message.\n// * RoutingContext is the routing message without outbound information.\n// * FieldSelectors selects the fields to return in the routing result. All\n// fields are returned if left empty.\n// * PublishResult broadcasts the routing result to routing statistics channel\n// if set true.\ntype TestRouteRequest struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tRoutingContext *RoutingContext        `protobuf:\"bytes,1,opt,name=RoutingContext,proto3\" json:\"RoutingContext,omitempty\"`\n\tFieldSelectors []string               `protobuf:\"bytes,2,rep,name=FieldSelectors,proto3\" json:\"FieldSelectors,omitempty\"`\n\tPublishResult  bool                   `protobuf:\"varint,3,opt,name=PublishResult,proto3\" json:\"PublishResult,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *TestRouteRequest) Reset() {\n\t*x = TestRouteRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TestRouteRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TestRouteRequest) ProtoMessage() {}\n\nfunc (x *TestRouteRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TestRouteRequest.ProtoReflect.Descriptor instead.\nfunc (*TestRouteRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *TestRouteRequest) GetRoutingContext() *RoutingContext {\n\tif x != nil {\n\t\treturn x.RoutingContext\n\t}\n\treturn nil\n}\n\nfunc (x *TestRouteRequest) GetFieldSelectors() []string {\n\tif x != nil {\n\t\treturn x.FieldSelectors\n\t}\n\treturn nil\n}\n\nfunc (x *TestRouteRequest) GetPublishResult() bool {\n\tif x != nil {\n\t\treturn x.PublishResult\n\t}\n\treturn false\n}\n\ntype PrincipleTargetInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           []string               `protobuf:\"bytes,1,rep,name=tag,proto3\" json:\"tag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PrincipleTargetInfo) Reset() {\n\t*x = PrincipleTargetInfo{}\n\tmi := &file_app_router_command_command_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PrincipleTargetInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PrincipleTargetInfo) ProtoMessage() {}\n\nfunc (x *PrincipleTargetInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PrincipleTargetInfo.ProtoReflect.Descriptor instead.\nfunc (*PrincipleTargetInfo) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *PrincipleTargetInfo) GetTag() []string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn nil\n}\n\ntype OverrideInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTarget        string                 `protobuf:\"bytes,2,opt,name=target,proto3\" json:\"target,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OverrideInfo) Reset() {\n\t*x = OverrideInfo{}\n\tmi := &file_app_router_command_command_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OverrideInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OverrideInfo) ProtoMessage() {}\n\nfunc (x *OverrideInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OverrideInfo.ProtoReflect.Descriptor instead.\nfunc (*OverrideInfo) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *OverrideInfo) GetTarget() string {\n\tif x != nil {\n\t\treturn x.Target\n\t}\n\treturn \"\"\n}\n\ntype BalancerMsg struct {\n\tstate           protoimpl.MessageState `protogen:\"open.v1\"`\n\tOverride        *OverrideInfo          `protobuf:\"bytes,5,opt,name=override,proto3\" json:\"override,omitempty\"`\n\tPrincipleTarget *PrincipleTargetInfo   `protobuf:\"bytes,6,opt,name=principle_target,json=principleTarget,proto3\" json:\"principle_target,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *BalancerMsg) Reset() {\n\t*x = BalancerMsg{}\n\tmi := &file_app_router_command_command_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *BalancerMsg) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BalancerMsg) ProtoMessage() {}\n\nfunc (x *BalancerMsg) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use BalancerMsg.ProtoReflect.Descriptor instead.\nfunc (*BalancerMsg) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *BalancerMsg) GetOverride() *OverrideInfo {\n\tif x != nil {\n\t\treturn x.Override\n\t}\n\treturn nil\n}\n\nfunc (x *BalancerMsg) GetPrincipleTarget() *PrincipleTargetInfo {\n\tif x != nil {\n\t\treturn x.PrincipleTarget\n\t}\n\treturn nil\n}\n\ntype GetBalancerInfoRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetBalancerInfoRequest) Reset() {\n\t*x = GetBalancerInfoRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetBalancerInfoRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetBalancerInfoRequest) ProtoMessage() {}\n\nfunc (x *GetBalancerInfoRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetBalancerInfoRequest.ProtoReflect.Descriptor instead.\nfunc (*GetBalancerInfoRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *GetBalancerInfoRequest) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\ntype GetBalancerInfoResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tBalancer      *BalancerMsg           `protobuf:\"bytes,1,opt,name=balancer,proto3\" json:\"balancer,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetBalancerInfoResponse) Reset() {\n\t*x = GetBalancerInfoResponse{}\n\tmi := &file_app_router_command_command_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetBalancerInfoResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetBalancerInfoResponse) ProtoMessage() {}\n\nfunc (x *GetBalancerInfoResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetBalancerInfoResponse.ProtoReflect.Descriptor instead.\nfunc (*GetBalancerInfoResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *GetBalancerInfoResponse) GetBalancer() *BalancerMsg {\n\tif x != nil {\n\t\treturn x.Balancer\n\t}\n\treturn nil\n}\n\ntype OverrideBalancerTargetRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tBalancerTag   string                 `protobuf:\"bytes,1,opt,name=balancerTag,proto3\" json:\"balancerTag,omitempty\"`\n\tTarget        string                 `protobuf:\"bytes,2,opt,name=target,proto3\" json:\"target,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OverrideBalancerTargetRequest) Reset() {\n\t*x = OverrideBalancerTargetRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OverrideBalancerTargetRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OverrideBalancerTargetRequest) ProtoMessage() {}\n\nfunc (x *OverrideBalancerTargetRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OverrideBalancerTargetRequest.ProtoReflect.Descriptor instead.\nfunc (*OverrideBalancerTargetRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *OverrideBalancerTargetRequest) GetBalancerTag() string {\n\tif x != nil {\n\t\treturn x.BalancerTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *OverrideBalancerTargetRequest) GetTarget() string {\n\tif x != nil {\n\t\treturn x.Target\n\t}\n\treturn \"\"\n}\n\ntype OverrideBalancerTargetResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OverrideBalancerTargetResponse) Reset() {\n\t*x = OverrideBalancerTargetResponse{}\n\tmi := &file_app_router_command_command_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OverrideBalancerTargetResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OverrideBalancerTargetResponse) ProtoMessage() {}\n\nfunc (x *OverrideBalancerTargetResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OverrideBalancerTargetResponse.ProtoReflect.Descriptor instead.\nfunc (*OverrideBalancerTargetResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{9}\n}\n\ntype AddRuleRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tConfig        *serial.TypedMessage   `protobuf:\"bytes,1,opt,name=config,proto3\" json:\"config,omitempty\"`\n\tShouldAppend  bool                   `protobuf:\"varint,2,opt,name=shouldAppend,proto3\" json:\"shouldAppend,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddRuleRequest) Reset() {\n\t*x = AddRuleRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddRuleRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddRuleRequest) ProtoMessage() {}\n\nfunc (x *AddRuleRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddRuleRequest.ProtoReflect.Descriptor instead.\nfunc (*AddRuleRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *AddRuleRequest) GetConfig() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Config\n\t}\n\treturn nil\n}\n\nfunc (x *AddRuleRequest) GetShouldAppend() bool {\n\tif x != nil {\n\t\treturn x.ShouldAppend\n\t}\n\treturn false\n}\n\ntype AddRuleResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AddRuleResponse) Reset() {\n\t*x = AddRuleResponse{}\n\tmi := &file_app_router_command_command_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AddRuleResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AddRuleResponse) ProtoMessage() {}\n\nfunc (x *AddRuleResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AddRuleResponse.ProtoReflect.Descriptor instead.\nfunc (*AddRuleResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{11}\n}\n\ntype RemoveRuleRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRuleTag       string                 `protobuf:\"bytes,1,opt,name=ruleTag,proto3\" json:\"ruleTag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveRuleRequest) Reset() {\n\t*x = RemoveRuleRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveRuleRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveRuleRequest) ProtoMessage() {}\n\nfunc (x *RemoveRuleRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveRuleRequest.ProtoReflect.Descriptor instead.\nfunc (*RemoveRuleRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *RemoveRuleRequest) GetRuleTag() string {\n\tif x != nil {\n\t\treturn x.RuleTag\n\t}\n\treturn \"\"\n}\n\ntype RemoveRuleResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveRuleResponse) Reset() {\n\t*x = RemoveRuleResponse{}\n\tmi := &file_app_router_command_command_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveRuleResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveRuleResponse) ProtoMessage() {}\n\nfunc (x *RemoveRuleResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveRuleResponse.ProtoReflect.Descriptor instead.\nfunc (*RemoveRuleResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{13}\n}\n\ntype ListRuleRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRuleRequest) Reset() {\n\t*x = ListRuleRequest{}\n\tmi := &file_app_router_command_command_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRuleRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRuleRequest) ProtoMessage() {}\n\nfunc (x *ListRuleRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRuleRequest.ProtoReflect.Descriptor instead.\nfunc (*ListRuleRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{14}\n}\n\ntype ListRuleItem struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tRuleTag       string                 `protobuf:\"bytes,2,opt,name=ruleTag,proto3\" json:\"ruleTag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRuleItem) Reset() {\n\t*x = ListRuleItem{}\n\tmi := &file_app_router_command_command_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRuleItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRuleItem) ProtoMessage() {}\n\nfunc (x *ListRuleItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRuleItem.ProtoReflect.Descriptor instead.\nfunc (*ListRuleItem) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ListRuleItem) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *ListRuleItem) GetRuleTag() string {\n\tif x != nil {\n\t\treturn x.RuleTag\n\t}\n\treturn \"\"\n}\n\ntype ListRuleResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRules         []*ListRuleItem        `protobuf:\"bytes,1,rep,name=rules,proto3\" json:\"rules,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRuleResponse) Reset() {\n\t*x = ListRuleResponse{}\n\tmi := &file_app_router_command_command_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRuleResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRuleResponse) ProtoMessage() {}\n\nfunc (x *ListRuleResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRuleResponse.ProtoReflect.Descriptor instead.\nfunc (*ListRuleResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *ListRuleResponse) GetRules() []*ListRuleItem {\n\tif x != nil {\n\t\treturn x.Rules\n\t}\n\treturn nil\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_router_command_command_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_command_command_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_router_command_command_proto_rawDescGZIP(), []int{17}\n}\n\nvar File_app_router_command_command_proto protoreflect.FileDescriptor\n\nconst file_app_router_command_command_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\" app/router/command/command.proto\\x12\\x17xray.app.router.command\\x1a\\x18common/net/network.proto\\x1a!common/serial/typed_message.proto\\\"\\xf6\\x04\\n\" +\n\t\"\\x0eRoutingContext\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"InboundTag\\x18\\x01 \\x01(\\tR\\n\" +\n\t\"InboundTag\\x122\\n\" +\n\t\"\\aNetwork\\x18\\x02 \\x01(\\x0e2\\x18.xray.common.net.NetworkR\\aNetwork\\x12\\x1c\\n\" +\n\t\"\\tSourceIPs\\x18\\x03 \\x03(\\fR\\tSourceIPs\\x12\\x1c\\n\" +\n\t\"\\tTargetIPs\\x18\\x04 \\x03(\\fR\\tTargetIPs\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"SourcePort\\x18\\x05 \\x01(\\rR\\n\" +\n\t\"SourcePort\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"TargetPort\\x18\\x06 \\x01(\\rR\\n\" +\n\t\"TargetPort\\x12\\\"\\n\" +\n\t\"\\fTargetDomain\\x18\\a \\x01(\\tR\\fTargetDomain\\x12\\x1a\\n\" +\n\t\"\\bProtocol\\x18\\b \\x01(\\tR\\bProtocol\\x12\\x12\\n\" +\n\t\"\\x04User\\x18\\t \\x01(\\tR\\x04User\\x12W\\n\" +\n\t\"\\n\" +\n\t\"Attributes\\x18\\n\" +\n\t\" \\x03(\\v27.xray.app.router.command.RoutingContext.AttributesEntryR\\n\" +\n\t\"Attributes\\x12,\\n\" +\n\t\"\\x11OutboundGroupTags\\x18\\v \\x03(\\tR\\x11OutboundGroupTags\\x12 \\n\" +\n\t\"\\vOutboundTag\\x18\\f \\x01(\\tR\\vOutboundTag\\x12\\x1a\\n\" +\n\t\"\\bLocalIPs\\x18\\r \\x03(\\fR\\bLocalIPs\\x12\\x1c\\n\" +\n\t\"\\tLocalPort\\x18\\x0e \\x01(\\rR\\tLocalPort\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"VlessRoute\\x18\\x0f \\x01(\\rR\\n\" +\n\t\"VlessRoute\\x1a=\\n\" +\n\t\"\\x0fAttributesEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\"F\\n\" +\n\t\"\\x1cSubscribeRoutingStatsRequest\\x12&\\n\" +\n\t\"\\x0eFieldSelectors\\x18\\x01 \\x03(\\tR\\x0eFieldSelectors\\\"\\xb1\\x01\\n\" +\n\t\"\\x10TestRouteRequest\\x12O\\n\" +\n\t\"\\x0eRoutingContext\\x18\\x01 \\x01(\\v2'.xray.app.router.command.RoutingContextR\\x0eRoutingContext\\x12&\\n\" +\n\t\"\\x0eFieldSelectors\\x18\\x02 \\x03(\\tR\\x0eFieldSelectors\\x12$\\n\" +\n\t\"\\rPublishResult\\x18\\x03 \\x01(\\bR\\rPublishResult\\\"'\\n\" +\n\t\"\\x13PrincipleTargetInfo\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x03(\\tR\\x03tag\\\"&\\n\" +\n\t\"\\fOverrideInfo\\x12\\x16\\n\" +\n\t\"\\x06target\\x18\\x02 \\x01(\\tR\\x06target\\\"\\xa9\\x01\\n\" +\n\t\"\\vBalancerMsg\\x12A\\n\" +\n\t\"\\boverride\\x18\\x05 \\x01(\\v2%.xray.app.router.command.OverrideInfoR\\boverride\\x12W\\n\" +\n\t\"\\x10principle_target\\x18\\x06 \\x01(\\v2,.xray.app.router.command.PrincipleTargetInfoR\\x0fprincipleTarget\\\"*\\n\" +\n\t\"\\x16GetBalancerInfoRequest\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\\"[\\n\" +\n\t\"\\x17GetBalancerInfoResponse\\x12@\\n\" +\n\t\"\\bbalancer\\x18\\x01 \\x01(\\v2$.xray.app.router.command.BalancerMsgR\\bbalancer\\\"Y\\n\" +\n\t\"\\x1dOverrideBalancerTargetRequest\\x12 \\n\" +\n\t\"\\vbalancerTag\\x18\\x01 \\x01(\\tR\\vbalancerTag\\x12\\x16\\n\" +\n\t\"\\x06target\\x18\\x02 \\x01(\\tR\\x06target\\\" \\n\" +\n\t\"\\x1eOverrideBalancerTargetResponse\\\"n\\n\" +\n\t\"\\x0eAddRuleRequest\\x128\\n\" +\n\t\"\\x06config\\x18\\x01 \\x01(\\v2 .xray.common.serial.TypedMessageR\\x06config\\x12\\\"\\n\" +\n\t\"\\fshouldAppend\\x18\\x02 \\x01(\\bR\\fshouldAppend\\\"\\x11\\n\" +\n\t\"\\x0fAddRuleResponse\\\"-\\n\" +\n\t\"\\x11RemoveRuleRequest\\x12\\x18\\n\" +\n\t\"\\aruleTag\\x18\\x01 \\x01(\\tR\\aruleTag\\\"\\x14\\n\" +\n\t\"\\x12RemoveRuleResponse\\\"\\x11\\n\" +\n\t\"\\x0fListRuleRequest\\\":\\n\" +\n\t\"\\fListRuleItem\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12\\x18\\n\" +\n\t\"\\aruleTag\\x18\\x02 \\x01(\\tR\\aruleTag\\\"O\\n\" +\n\t\"\\x10ListRuleResponse\\x12;\\n\" +\n\t\"\\x05rules\\x18\\x01 \\x03(\\v2%.xray.app.router.command.ListRuleItemR\\x05rules\\\"\\b\\n\" +\n\t\"\\x06Config2\\xa2\\x06\\n\" +\n\t\"\\x0eRoutingService\\x12{\\n\" +\n\t\"\\x15SubscribeRoutingStats\\x125.xray.app.router.command.SubscribeRoutingStatsRequest\\x1a'.xray.app.router.command.RoutingContext\\\"\\x000\\x01\\x12a\\n\" +\n\t\"\\tTestRoute\\x12).xray.app.router.command.TestRouteRequest\\x1a'.xray.app.router.command.RoutingContext\\\"\\x00\\x12v\\n\" +\n\t\"\\x0fGetBalancerInfo\\x12/.xray.app.router.command.GetBalancerInfoRequest\\x1a0.xray.app.router.command.GetBalancerInfoResponse\\\"\\x00\\x12\\x8b\\x01\\n\" +\n\t\"\\x16OverrideBalancerTarget\\x126.xray.app.router.command.OverrideBalancerTargetRequest\\x1a7.xray.app.router.command.OverrideBalancerTargetResponse\\\"\\x00\\x12^\\n\" +\n\t\"\\aAddRule\\x12'.xray.app.router.command.AddRuleRequest\\x1a(.xray.app.router.command.AddRuleResponse\\\"\\x00\\x12g\\n\" +\n\t\"\\n\" +\n\t\"RemoveRule\\x12*.xray.app.router.command.RemoveRuleRequest\\x1a+.xray.app.router.command.RemoveRuleResponse\\\"\\x00\\x12a\\n\" +\n\t\"\\bListRule\\x12(.xray.app.router.command.ListRuleRequest\\x1a).xray.app.router.command.ListRuleResponse\\\"\\x00Bg\\n\" +\n\t\"\\x1bcom.xray.app.router.commandP\\x01Z,github.com/xtls/xray-core/app/router/command\\xaa\\x02\\x17Xray.App.Router.Commandb\\x06proto3\"\n\nvar (\n\tfile_app_router_command_command_proto_rawDescOnce sync.Once\n\tfile_app_router_command_command_proto_rawDescData []byte\n)\n\nfunc file_app_router_command_command_proto_rawDescGZIP() []byte {\n\tfile_app_router_command_command_proto_rawDescOnce.Do(func() {\n\t\tfile_app_router_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_router_command_command_proto_rawDesc), len(file_app_router_command_command_proto_rawDesc)))\n\t})\n\treturn file_app_router_command_command_proto_rawDescData\n}\n\nvar file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 19)\nvar file_app_router_command_command_proto_goTypes = []any{\n\t(*RoutingContext)(nil),                 // 0: xray.app.router.command.RoutingContext\n\t(*SubscribeRoutingStatsRequest)(nil),   // 1: xray.app.router.command.SubscribeRoutingStatsRequest\n\t(*TestRouteRequest)(nil),               // 2: xray.app.router.command.TestRouteRequest\n\t(*PrincipleTargetInfo)(nil),            // 3: xray.app.router.command.PrincipleTargetInfo\n\t(*OverrideInfo)(nil),                   // 4: xray.app.router.command.OverrideInfo\n\t(*BalancerMsg)(nil),                    // 5: xray.app.router.command.BalancerMsg\n\t(*GetBalancerInfoRequest)(nil),         // 6: xray.app.router.command.GetBalancerInfoRequest\n\t(*GetBalancerInfoResponse)(nil),        // 7: xray.app.router.command.GetBalancerInfoResponse\n\t(*OverrideBalancerTargetRequest)(nil),  // 8: xray.app.router.command.OverrideBalancerTargetRequest\n\t(*OverrideBalancerTargetResponse)(nil), // 9: xray.app.router.command.OverrideBalancerTargetResponse\n\t(*AddRuleRequest)(nil),                 // 10: xray.app.router.command.AddRuleRequest\n\t(*AddRuleResponse)(nil),                // 11: xray.app.router.command.AddRuleResponse\n\t(*RemoveRuleRequest)(nil),              // 12: xray.app.router.command.RemoveRuleRequest\n\t(*RemoveRuleResponse)(nil),             // 13: xray.app.router.command.RemoveRuleResponse\n\t(*ListRuleRequest)(nil),                // 14: xray.app.router.command.ListRuleRequest\n\t(*ListRuleItem)(nil),                   // 15: xray.app.router.command.ListRuleItem\n\t(*ListRuleResponse)(nil),               // 16: xray.app.router.command.ListRuleResponse\n\t(*Config)(nil),                         // 17: xray.app.router.command.Config\n\tnil,                                    // 18: xray.app.router.command.RoutingContext.AttributesEntry\n\t(net.Network)(0),                       // 19: xray.common.net.Network\n\t(*serial.TypedMessage)(nil),            // 20: xray.common.serial.TypedMessage\n}\nvar file_app_router_command_command_proto_depIdxs = []int32{\n\t19, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network\n\t18, // 1: xray.app.router.command.RoutingContext.Attributes:type_name -> xray.app.router.command.RoutingContext.AttributesEntry\n\t0,  // 2: xray.app.router.command.TestRouteRequest.RoutingContext:type_name -> xray.app.router.command.RoutingContext\n\t4,  // 3: xray.app.router.command.BalancerMsg.override:type_name -> xray.app.router.command.OverrideInfo\n\t3,  // 4: xray.app.router.command.BalancerMsg.principle_target:type_name -> xray.app.router.command.PrincipleTargetInfo\n\t5,  // 5: xray.app.router.command.GetBalancerInfoResponse.balancer:type_name -> xray.app.router.command.BalancerMsg\n\t20, // 6: xray.app.router.command.AddRuleRequest.config:type_name -> xray.common.serial.TypedMessage\n\t15, // 7: xray.app.router.command.ListRuleResponse.rules:type_name -> xray.app.router.command.ListRuleItem\n\t1,  // 8: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest\n\t2,  // 9: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest\n\t6,  // 10: xray.app.router.command.RoutingService.GetBalancerInfo:input_type -> xray.app.router.command.GetBalancerInfoRequest\n\t8,  // 11: xray.app.router.command.RoutingService.OverrideBalancerTarget:input_type -> xray.app.router.command.OverrideBalancerTargetRequest\n\t10, // 12: xray.app.router.command.RoutingService.AddRule:input_type -> xray.app.router.command.AddRuleRequest\n\t12, // 13: xray.app.router.command.RoutingService.RemoveRule:input_type -> xray.app.router.command.RemoveRuleRequest\n\t14, // 14: xray.app.router.command.RoutingService.ListRule:input_type -> xray.app.router.command.ListRuleRequest\n\t0,  // 15: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext\n\t0,  // 16: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext\n\t7,  // 17: xray.app.router.command.RoutingService.GetBalancerInfo:output_type -> xray.app.router.command.GetBalancerInfoResponse\n\t9,  // 18: xray.app.router.command.RoutingService.OverrideBalancerTarget:output_type -> xray.app.router.command.OverrideBalancerTargetResponse\n\t11, // 19: xray.app.router.command.RoutingService.AddRule:output_type -> xray.app.router.command.AddRuleResponse\n\t13, // 20: xray.app.router.command.RoutingService.RemoveRule:output_type -> xray.app.router.command.RemoveRuleResponse\n\t16, // 21: xray.app.router.command.RoutingService.ListRule:output_type -> xray.app.router.command.ListRuleResponse\n\t15, // [15:22] is the sub-list for method output_type\n\t8,  // [8:15] is the sub-list for method input_type\n\t8,  // [8:8] is the sub-list for extension type_name\n\t8,  // [8:8] is the sub-list for extension extendee\n\t0,  // [0:8] is the sub-list for field type_name\n}\n\nfunc init() { file_app_router_command_command_proto_init() }\nfunc file_app_router_command_command_proto_init() {\n\tif File_app_router_command_command_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_command_command_proto_rawDesc), len(file_app_router_command_command_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   19,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_app_router_command_command_proto_goTypes,\n\t\tDependencyIndexes: file_app_router_command_command_proto_depIdxs,\n\t\tMessageInfos:      file_app_router_command_command_proto_msgTypes,\n\t}.Build()\n\tFile_app_router_command_command_proto = out.File\n\tfile_app_router_command_command_proto_goTypes = nil\n\tfile_app_router_command_command_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/router/command/command.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.router.command;\noption csharp_namespace = \"Xray.App.Router.Command\";\noption go_package = \"github.com/xtls/xray-core/app/router/command\";\noption java_package = \"com.xray.app.router.command\";\noption java_multiple_files = true;\n\nimport \"common/net/network.proto\";\nimport \"common/serial/typed_message.proto\";\n\n// RoutingContext is the context with information relative to routing process.\n// It conforms to the structure of xray.features.routing.Context and\n// xray.features.routing.Route.\nmessage RoutingContext {\n  string InboundTag = 1;\n  xray.common.net.Network Network = 2;\n  repeated bytes SourceIPs = 3;\n  repeated bytes TargetIPs = 4;\n  uint32 SourcePort = 5;\n  uint32 TargetPort = 6;\n  string TargetDomain = 7;\n  string Protocol = 8;\n  string User = 9;\n  map<string, string> Attributes = 10;\n  repeated string OutboundGroupTags = 11;\n  string OutboundTag = 12;\n  repeated bytes LocalIPs = 13;\n  uint32 LocalPort = 14;\n  uint32 VlessRoute = 15;\n}\n\n// SubscribeRoutingStatsRequest subscribes to routing statistics channel if\n// opened by xray-core.\n// * FieldSelectors selects a subset of fields in routing statistics to return.\n// Valid selectors:\n//  - inbound: Selects connection's inbound tag.\n//  - network: Selects connection's network.\n//  - ip: Equivalent as \"ip_source\" and \"ip_target\", selects both source and\n//  target IP.\n//  - port: Equivalent as \"port_source\" and \"port_target\", selects both source\n//  and target port.\n//  - domain: Selects target domain.\n//  - protocol: Select connection's protocol.\n//  - user: Select connection's inbound user email.\n//  - attributes: Select connection's additional attributes.\n//  - outbound: Equivalent as \"outbound\" and \"outbound_group\", select both\n//  outbound tag and outbound group tags.\n// * If FieldSelectors is left empty, all fields will be returned.\nmessage SubscribeRoutingStatsRequest {\n  repeated string FieldSelectors = 1;\n}\n\n// TestRouteRequest manually tests a routing result according to the routing\n// context message.\n// * RoutingContext is the routing message without outbound information.\n// * FieldSelectors selects the fields to return in the routing result. All\n// fields are returned if left empty.\n// * PublishResult broadcasts the routing result to routing statistics channel\n// if set true.\nmessage TestRouteRequest {\n  RoutingContext RoutingContext = 1;\n  repeated string FieldSelectors = 2;\n  bool PublishResult = 3;\n}\n\nmessage PrincipleTargetInfo {\n  repeated string tag = 1;\n}\n\nmessage OverrideInfo {\n  string target = 2;\n}\n\nmessage BalancerMsg {\n  OverrideInfo override = 5;\n  PrincipleTargetInfo principle_target = 6;\n}\n\nmessage GetBalancerInfoRequest {\n  string tag = 1;\n}\n\nmessage GetBalancerInfoResponse {\n  BalancerMsg balancer = 1;\n}\n\nmessage OverrideBalancerTargetRequest {\n  string balancerTag = 1;\n  string target = 2;\n}\n\nmessage OverrideBalancerTargetResponse {}\n\nmessage AddRuleRequest {\n  xray.common.serial.TypedMessage config = 1;\n  bool shouldAppend = 2;\n}\nmessage AddRuleResponse {}\n\nmessage RemoveRuleRequest {\n  string ruleTag = 1;\n}\n\nmessage RemoveRuleResponse {}\n\nmessage ListRuleRequest {}\n\nmessage ListRuleItem {\n  string tag = 1;\n  string ruleTag = 2;\n}\n\nmessage ListRuleResponse{\n  repeated ListRuleItem rules = 1;\n}\n\nservice RoutingService {\n  rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest)\n      returns (stream RoutingContext) {}\n  rpc TestRoute(TestRouteRequest) returns (RoutingContext) {}\n\n  rpc GetBalancerInfo(GetBalancerInfoRequest) returns (GetBalancerInfoResponse){}\n  rpc OverrideBalancerTarget(OverrideBalancerTargetRequest) returns (OverrideBalancerTargetResponse) {}\n  \n  rpc AddRule(AddRuleRequest) returns (AddRuleResponse) {}\n  rpc RemoveRule(RemoveRuleRequest) returns (RemoveRuleResponse) {}\n\n  rpc ListRule(ListRuleRequest) returns (ListRuleResponse) {}\n}\n\nmessage Config {}\n"
  },
  {
    "path": "app/router/command/command_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.0\n// - protoc             v6.33.5\n// source: app/router/command/command.proto\n\npackage command\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tRoutingService_SubscribeRoutingStats_FullMethodName  = \"/xray.app.router.command.RoutingService/SubscribeRoutingStats\"\n\tRoutingService_TestRoute_FullMethodName              = \"/xray.app.router.command.RoutingService/TestRoute\"\n\tRoutingService_GetBalancerInfo_FullMethodName        = \"/xray.app.router.command.RoutingService/GetBalancerInfo\"\n\tRoutingService_OverrideBalancerTarget_FullMethodName = \"/xray.app.router.command.RoutingService/OverrideBalancerTarget\"\n\tRoutingService_AddRule_FullMethodName                = \"/xray.app.router.command.RoutingService/AddRule\"\n\tRoutingService_RemoveRule_FullMethodName             = \"/xray.app.router.command.RoutingService/RemoveRule\"\n\tRoutingService_ListRule_FullMethodName               = \"/xray.app.router.command.RoutingService/ListRule\"\n)\n\n// RoutingServiceClient is the client API for RoutingService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype RoutingServiceClient interface {\n\tSubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RoutingContext], error)\n\tTestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error)\n\tGetBalancerInfo(ctx context.Context, in *GetBalancerInfoRequest, opts ...grpc.CallOption) (*GetBalancerInfoResponse, error)\n\tOverrideBalancerTarget(ctx context.Context, in *OverrideBalancerTargetRequest, opts ...grpc.CallOption) (*OverrideBalancerTargetResponse, error)\n\tAddRule(ctx context.Context, in *AddRuleRequest, opts ...grpc.CallOption) (*AddRuleResponse, error)\n\tRemoveRule(ctx context.Context, in *RemoveRuleRequest, opts ...grpc.CallOption) (*RemoveRuleResponse, error)\n\tListRule(ctx context.Context, in *ListRuleRequest, opts ...grpc.CallOption) (*ListRuleResponse, error)\n}\n\ntype routingServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewRoutingServiceClient(cc grpc.ClientConnInterface) RoutingServiceClient {\n\treturn &routingServiceClient{cc}\n}\n\nfunc (c *routingServiceClient) SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RoutingContext], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &RoutingService_ServiceDesc.Streams[0], RoutingService_SubscribeRoutingStats_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SubscribeRoutingStatsRequest, RoutingContext]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype RoutingService_SubscribeRoutingStatsClient = grpc.ServerStreamingClient[RoutingContext]\n\nfunc (c *routingServiceClient) TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RoutingContext)\n\terr := c.cc.Invoke(ctx, RoutingService_TestRoute_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *routingServiceClient) GetBalancerInfo(ctx context.Context, in *GetBalancerInfoRequest, opts ...grpc.CallOption) (*GetBalancerInfoResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetBalancerInfoResponse)\n\terr := c.cc.Invoke(ctx, RoutingService_GetBalancerInfo_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *routingServiceClient) OverrideBalancerTarget(ctx context.Context, in *OverrideBalancerTargetRequest, opts ...grpc.CallOption) (*OverrideBalancerTargetResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(OverrideBalancerTargetResponse)\n\terr := c.cc.Invoke(ctx, RoutingService_OverrideBalancerTarget_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *routingServiceClient) AddRule(ctx context.Context, in *AddRuleRequest, opts ...grpc.CallOption) (*AddRuleResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AddRuleResponse)\n\terr := c.cc.Invoke(ctx, RoutingService_AddRule_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *routingServiceClient) RemoveRule(ctx context.Context, in *RemoveRuleRequest, opts ...grpc.CallOption) (*RemoveRuleResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RemoveRuleResponse)\n\terr := c.cc.Invoke(ctx, RoutingService_RemoveRule_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *routingServiceClient) ListRule(ctx context.Context, in *ListRuleRequest, opts ...grpc.CallOption) (*ListRuleResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRuleResponse)\n\terr := c.cc.Invoke(ctx, RoutingService_ListRule_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// RoutingServiceServer is the server API for RoutingService service.\n// All implementations must embed UnimplementedRoutingServiceServer\n// for forward compatibility.\ntype RoutingServiceServer interface {\n\tSubscribeRoutingStats(*SubscribeRoutingStatsRequest, grpc.ServerStreamingServer[RoutingContext]) error\n\tTestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error)\n\tGetBalancerInfo(context.Context, *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error)\n\tOverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error)\n\tAddRule(context.Context, *AddRuleRequest) (*AddRuleResponse, error)\n\tRemoveRule(context.Context, *RemoveRuleRequest) (*RemoveRuleResponse, error)\n\tListRule(context.Context, *ListRuleRequest) (*ListRuleResponse, error)\n\tmustEmbedUnimplementedRoutingServiceServer()\n}\n\n// UnimplementedRoutingServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedRoutingServiceServer struct{}\n\nfunc (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRoutingStatsRequest, grpc.ServerStreamingServer[RoutingContext]) error {\n\treturn status.Error(codes.Unimplemented, \"method SubscribeRoutingStats not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method TestRoute not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) GetBalancerInfo(context.Context, *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetBalancerInfo not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method OverrideBalancerTarget not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) AddRule(context.Context, *AddRuleRequest) (*AddRuleResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AddRule not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) RemoveRule(context.Context, *RemoveRuleRequest) (*RemoveRuleResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveRule not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) ListRule(context.Context, *ListRuleRequest) (*ListRuleResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ListRule not implemented\")\n}\nfunc (UnimplementedRoutingServiceServer) mustEmbedUnimplementedRoutingServiceServer() {}\nfunc (UnimplementedRoutingServiceServer) testEmbeddedByValue()                        {}\n\n// UnsafeRoutingServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to RoutingServiceServer will\n// result in compilation errors.\ntype UnsafeRoutingServiceServer interface {\n\tmustEmbedUnimplementedRoutingServiceServer()\n}\n\nfunc RegisterRoutingServiceServer(s grpc.ServiceRegistrar, srv RoutingServiceServer) {\n\t// If the following call panics, it indicates UnimplementedRoutingServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&RoutingService_ServiceDesc, srv)\n}\n\nfunc _RoutingService_SubscribeRoutingStats_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SubscribeRoutingStatsRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(RoutingServiceServer).SubscribeRoutingStats(m, &grpc.GenericServerStream[SubscribeRoutingStatsRequest, RoutingContext]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype RoutingService_SubscribeRoutingStatsServer = grpc.ServerStreamingServer[RoutingContext]\n\nfunc _RoutingService_TestRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TestRouteRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RoutingServiceServer).TestRoute(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RoutingService_TestRoute_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RoutingServiceServer).TestRoute(ctx, req.(*TestRouteRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _RoutingService_GetBalancerInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetBalancerInfoRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RoutingServiceServer).GetBalancerInfo(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RoutingService_GetBalancerInfo_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RoutingServiceServer).GetBalancerInfo(ctx, req.(*GetBalancerInfoRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _RoutingService_OverrideBalancerTarget_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(OverrideBalancerTargetRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RoutingServiceServer).OverrideBalancerTarget(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RoutingService_OverrideBalancerTarget_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RoutingServiceServer).OverrideBalancerTarget(ctx, req.(*OverrideBalancerTargetRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _RoutingService_AddRule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AddRuleRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RoutingServiceServer).AddRule(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RoutingService_AddRule_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RoutingServiceServer).AddRule(ctx, req.(*AddRuleRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _RoutingService_RemoveRule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RemoveRuleRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RoutingServiceServer).RemoveRule(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RoutingService_RemoveRule_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RoutingServiceServer).RemoveRule(ctx, req.(*RemoveRuleRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _RoutingService_ListRule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRuleRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(RoutingServiceServer).ListRule(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: RoutingService_ListRule_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(RoutingServiceServer).ListRule(ctx, req.(*ListRuleRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// RoutingService_ServiceDesc is the grpc.ServiceDesc for RoutingService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar RoutingService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"xray.app.router.command.RoutingService\",\n\tHandlerType: (*RoutingServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"TestRoute\",\n\t\t\tHandler:    _RoutingService_TestRoute_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetBalancerInfo\",\n\t\t\tHandler:    _RoutingService_GetBalancerInfo_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"OverrideBalancerTarget\",\n\t\t\tHandler:    _RoutingService_OverrideBalancerTarget_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AddRule\",\n\t\t\tHandler:    _RoutingService_AddRule_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveRule\",\n\t\t\tHandler:    _RoutingService_RemoveRule_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListRule\",\n\t\t\tHandler:    _RoutingService_ListRule_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"SubscribeRoutingStats\",\n\t\t\tHandler:       _RoutingService_SubscribeRoutingStats_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"app/router/command/command.proto\",\n}\n"
  },
  {
    "path": "app/router/command/command_test.go",
    "content": "package command_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t. \"github.com/xtls/xray-core/app/router/command\"\n\t\"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/testing/mocks\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/test/bufconn\"\n)\n\nfunc TestServiceSubscribeRoutingStats(t *testing.T) {\n\tc := stats.NewChannel(&stats.ChannelConfig{\n\t\tSubscriberLimit: 1,\n\t\tBufferSize:      0,\n\t\tBlocking:        true,\n\t})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\tlis := bufconn.Listen(1024 * 1024)\n\tbufDialer := func(context.Context, string) (net.Conn, error) {\n\t\treturn lis.Dial()\n\t}\n\n\ttestCases := []*RoutingContext{\n\t\t{InboundTag: \"in\", OutboundTag: \"out\"},\n\t\t{TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: \"out\"},\n\t\t{TargetDomain: \"example.com\", TargetPort: 443, OutboundTag: \"out\"},\n\t\t{SourcePort: 9999, TargetPort: 9999, OutboundTag: \"out\"},\n\t\t{Network: net.Network_UDP, OutboundGroupTags: []string{\"outergroup\", \"innergroup\"}, OutboundTag: \"out\"},\n\t\t{Protocol: \"bittorrent\", OutboundTag: \"blocked\"},\n\t\t{User: \"example@example.com\", OutboundTag: \"out\"},\n\t\t{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{\"attr\": \"value\"}, OutboundTag: \"out\"},\n\t}\n\terrCh := make(chan error)\n\n\t// Server goroutine\n\tgo func() {\n\t\tserver := grpc.NewServer()\n\t\tRegisterRoutingServiceServer(server, NewRoutingServer(nil, c))\n\t\terrCh <- server.Serve(lis)\n\t}()\n\n\t// Publisher goroutine\n\tgo func() {\n\t\tpublishTestCases := func() error {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tfor { // Wait until there's one subscriber in routing stats channel\n\t\t\t\tif len(c.Subscribers()) > 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\treturn ctx.Err()\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, tc := range testCases {\n\t\t\t\tc.Publish(context.Background(), AsRoutingRoute(tc))\n\t\t\t\ttime.Sleep(time.Millisecond)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tif err := publishTestCases(); err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t}()\n\n\t// Client goroutine\n\tgo func() {\n\t\tdefer lis.Close()\n\t\tconn, err := grpc.DialContext(context.Background(), \"bufnet\", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials()))\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\t\tclient := NewRoutingServiceClient(conn)\n\n\t\t// Test retrieving all fields\n\t\ttestRetrievingAllFields := func() error {\n\t\t\tstreamCtx, streamClose := context.WithCancel(context.Background())\n\n\t\t\t// Test the unsubscription of stream works well\n\t\t\tdefer func() {\n\t\t\t\tstreamClose()\n\t\t\t\ttimeOutCtx, timeout := context.WithTimeout(context.Background(), time.Second)\n\t\t\t\tdefer timeout()\n\t\t\t\tfor { // Wait until there's no subscriber in routing stats channel\n\t\t\t\t\tif len(c.Subscribers()) == 0 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif timeOutCtx.Err() != nil {\n\t\t\t\t\t\tt.Error(\"unexpected subscribers not decreased in channel\", timeOutCtx.Err())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tstream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfor _, tc := range testCases {\n\t\t\t\tmsg, err := stream.Recv()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif r := cmp.Diff(msg, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != \"\" {\n\t\t\t\t\tt.Error(r)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Test that double subscription will fail\n\t\t\terrStream, err := client.SubscribeRoutingStats(context.Background(), &SubscribeRoutingStatsRequest{\n\t\t\t\tFieldSelectors: []string{\"ip\", \"port\", \"domain\", \"outbound\"},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := errStream.Recv(); err == nil {\n\t\t\t\tt.Error(\"unexpected successful subscription\")\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\n\t\tif err := testRetrievingAllFields(); err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t\terrCh <- nil // Client passed all tests successfully\n\t}()\n\n\t// Wait for goroutines to complete\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase err := <-errCh:\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestServiceSubscribeSubsetOfFields(t *testing.T) {\n\tc := stats.NewChannel(&stats.ChannelConfig{\n\t\tSubscriberLimit: 1,\n\t\tBufferSize:      0,\n\t\tBlocking:        true,\n\t})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\tlis := bufconn.Listen(1024 * 1024)\n\tbufDialer := func(context.Context, string) (net.Conn, error) {\n\t\treturn lis.Dial()\n\t}\n\n\ttestCases := []*RoutingContext{\n\t\t{InboundTag: \"in\", OutboundTag: \"out\"},\n\t\t{TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: \"out\"},\n\t\t{TargetDomain: \"example.com\", TargetPort: 443, OutboundTag: \"out\"},\n\t\t{SourcePort: 9999, TargetPort: 9999, OutboundTag: \"out\"},\n\t\t{Network: net.Network_UDP, OutboundGroupTags: []string{\"outergroup\", \"innergroup\"}, OutboundTag: \"out\"},\n\t\t{Protocol: \"bittorrent\", OutboundTag: \"blocked\"},\n\t\t{User: \"example@example.com\", OutboundTag: \"out\"},\n\t\t{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{\"attr\": \"value\"}, OutboundTag: \"out\"},\n\t}\n\terrCh := make(chan error)\n\n\t// Server goroutine\n\tgo func() {\n\t\tserver := grpc.NewServer()\n\t\tRegisterRoutingServiceServer(server, NewRoutingServer(nil, c))\n\t\terrCh <- server.Serve(lis)\n\t}()\n\n\t// Publisher goroutine\n\tgo func() {\n\t\tpublishTestCases := func() error {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second)\n\t\t\tdefer cancel()\n\t\t\tfor { // Wait until there's one subscriber in routing stats channel\n\t\t\t\tif len(c.Subscribers()) > 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\treturn ctx.Err()\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, tc := range testCases {\n\t\t\t\tc.Publish(context.Background(), AsRoutingRoute(tc))\n\t\t\t\ttime.Sleep(time.Millisecond)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tif err := publishTestCases(); err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t}()\n\n\t// Client goroutine\n\tgo func() {\n\t\tdefer lis.Close()\n\t\tconn, err := grpc.DialContext(context.Background(), \"bufnet\", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials()))\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\t\tclient := NewRoutingServiceClient(conn)\n\n\t\t// Test retrieving only a subset of fields\n\t\ttestRetrievingSubsetOfFields := func() error {\n\t\t\tstreamCtx, streamClose := context.WithCancel(context.Background())\n\t\t\tdefer streamClose()\n\t\t\tstream, err := client.SubscribeRoutingStats(streamCtx, &SubscribeRoutingStatsRequest{\n\t\t\t\tFieldSelectors: []string{\"ip\", \"port\", \"domain\", \"outbound\"},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfor _, tc := range testCases {\n\t\t\t\tmsg, err := stream.Recv()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tstat := &RoutingContext{ // Only a subset of stats is retrieved\n\t\t\t\t\tSourceIPs:         tc.SourceIPs,\n\t\t\t\t\tTargetIPs:         tc.TargetIPs,\n\t\t\t\t\tSourcePort:        tc.SourcePort,\n\t\t\t\t\tTargetPort:        tc.TargetPort,\n\t\t\t\t\tTargetDomain:      tc.TargetDomain,\n\t\t\t\t\tOutboundGroupTags: tc.OutboundGroupTags,\n\t\t\t\t\tOutboundTag:       tc.OutboundTag,\n\t\t\t\t}\n\t\t\t\tif r := cmp.Diff(msg, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != \"\" {\n\t\t\t\t\tt.Error(r)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t\tif err := testRetrievingSubsetOfFields(); err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t\terrCh <- nil // Client passed all tests successfully\n\t}()\n\n\t// Wait for goroutines to complete\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase err := <-errCh:\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestServiceTestRoute(t *testing.T) {\n\tc := stats.NewChannel(&stats.ChannelConfig{\n\t\tSubscriberLimit: 1,\n\t\tBufferSize:      16,\n\t\tBlocking:        true,\n\t})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\tr := new(router.Router)\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\tcommon.Must(r.Init(context.TODO(), &router.Config{\n\t\tRule: []*router.RoutingRule{\n\t\t\t{\n\t\t\t\tInboundTag: []string{\"in\"},\n\t\t\t\tTargetTag:  &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tProtocol:  []string{\"bittorrent\"},\n\t\t\t\tTargetTag: &router.RoutingRule_Tag{Tag: \"blocked\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tPortList:  &net.PortList{Range: []*net.PortRange{{From: 8080, To: 8080}}},\n\t\t\t\tTargetTag: &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tSourcePortList: &net.PortList{Range: []*net.PortRange{{From: 9999, To: 9999}}},\n\t\t\t\tTargetTag:      &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDomain:    []*router.Domain{{Type: router.Domain_Domain, Value: \"com\"}},\n\t\t\t\tTargetTag: &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tSourceGeoip: []*router.GeoIP{{CountryCode: \"private\", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},\n\t\t\t\tTargetTag:   &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tUserEmail: []string{\"example@example.com\"},\n\t\t\t\tTargetTag: &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tNetworks:  []net.Network{net.Network_UDP, net.Network_TCP},\n\t\t\t\tTargetTag: &router.RoutingRule_Tag{Tag: \"out\"},\n\t\t\t},\n\t\t},\n\t}, mocks.NewDNSClient(mockCtl), mocks.NewOutboundManager(mockCtl), nil))\n\n\tlis := bufconn.Listen(1024 * 1024)\n\tbufDialer := func(context.Context, string) (net.Conn, error) {\n\t\treturn lis.Dial()\n\t}\n\n\terrCh := make(chan error)\n\n\t// Server goroutine\n\tgo func() {\n\t\tserver := grpc.NewServer()\n\t\tRegisterRoutingServiceServer(server, NewRoutingServer(r, c))\n\t\terrCh <- server.Serve(lis)\n\t}()\n\n\t// Client goroutine\n\tgo func() {\n\t\tdefer lis.Close()\n\t\tconn, err := grpc.DialContext(context.Background(), \"bufnet\", grpc.WithContextDialer(bufDialer), grpc.WithTransportCredentials(insecure.NewCredentials()))\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t\tdefer conn.Close()\n\t\tclient := NewRoutingServiceClient(conn)\n\n\t\ttestCases := []*RoutingContext{\n\t\t\t{InboundTag: \"in\", OutboundTag: \"out\"},\n\t\t\t{TargetIPs: [][]byte{{1, 2, 3, 4}}, TargetPort: 8080, OutboundTag: \"out\"},\n\t\t\t{TargetDomain: \"example.com\", TargetPort: 443, OutboundTag: \"out\"},\n\t\t\t{SourcePort: 9999, TargetPort: 9999, OutboundTag: \"out\"},\n\t\t\t{Network: net.Network_UDP, Protocol: \"bittorrent\", OutboundTag: \"blocked\"},\n\t\t\t{User: \"example@example.com\", OutboundTag: \"out\"},\n\t\t\t{SourceIPs: [][]byte{{127, 0, 0, 1}}, Attributes: map[string]string{\"attr\": \"value\"}, OutboundTag: \"out\"},\n\t\t}\n\n\t\t// Test simple TestRoute\n\t\ttestSimple := func() error {\n\t\t\tfor _, tc := range testCases {\n\t\t\t\troute, err := client.TestRoute(context.Background(), &TestRouteRequest{RoutingContext: tc})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif r := cmp.Diff(route, tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != \"\" {\n\t\t\t\t\tt.Error(r)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\t// Test TestRoute with special options\n\t\ttestOptions := func() error {\n\t\t\tsub, err := c.Subscribe()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, tc := range testCases {\n\t\t\t\troute, err := client.TestRoute(context.Background(), &TestRouteRequest{\n\t\t\t\t\tRoutingContext: tc,\n\t\t\t\t\tFieldSelectors: []string{\"ip\", \"port\", \"domain\", \"outbound\"},\n\t\t\t\t\tPublishResult:  true,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tstat := &RoutingContext{ // Only a subset of stats is retrieved\n\t\t\t\t\tSourceIPs:         tc.SourceIPs,\n\t\t\t\t\tTargetIPs:         tc.TargetIPs,\n\t\t\t\t\tSourcePort:        tc.SourcePort,\n\t\t\t\t\tTargetPort:        tc.TargetPort,\n\t\t\t\t\tTargetDomain:      tc.TargetDomain,\n\t\t\t\t\tOutboundGroupTags: tc.OutboundGroupTags,\n\t\t\t\t\tOutboundTag:       tc.OutboundTag,\n\t\t\t\t}\n\t\t\t\tif r := cmp.Diff(route, stat, cmpopts.IgnoreUnexported(RoutingContext{})); r != \"\" {\n\t\t\t\t\tt.Error(r)\n\t\t\t\t}\n\t\t\t\tselect { // Check that routing result has been published to statistics channel\n\t\t\t\tcase msg, received := <-sub:\n\t\t\t\t\tif route, ok := msg.(routing.Route); received && ok {\n\t\t\t\t\t\tif r := cmp.Diff(AsProtobufMessage(nil)(route), tc, cmpopts.IgnoreUnexported(RoutingContext{})); r != \"\" {\n\t\t\t\t\t\t\tt.Error(r)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Error(\"unexpected failure in receiving published routing result for testcase\", tc)\n\t\t\t\t\t}\n\t\t\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\t\t\tt.Error(\"unexpected failure in receiving published routing result\", tc)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tif err := testSimple(); err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t\tif err := testOptions(); err != nil {\n\t\t\terrCh <- err\n\t\t}\n\t\terrCh <- nil // Client passed all tests successfully\n\t}()\n\n\t// Wait for goroutines to complete\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase err := <-errCh:\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "app/router/command/config.go",
    "content": "package command\n\nimport (\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\n// routingContext is an wrapper of protobuf RoutingContext as implementation of routing.Context and routing.Route.\ntype routingContext struct {\n\t*RoutingContext\n}\n\nfunc (c routingContext) GetSourceIPs() []net.IP {\n\treturn mapBytesToIPs(c.RoutingContext.GetSourceIPs())\n}\n\nfunc (c routingContext) GetSourcePort() net.Port {\n\treturn net.Port(c.RoutingContext.GetSourcePort())\n}\n\nfunc (c routingContext) GetTargetIPs() []net.IP {\n\treturn mapBytesToIPs(c.RoutingContext.GetTargetIPs())\n}\n\nfunc (c routingContext) GetTargetPort() net.Port {\n\treturn net.Port(c.RoutingContext.GetTargetPort())\n}\n\nfunc (c routingContext) GetLocalIPs() []net.IP {\n\treturn mapBytesToIPs(c.RoutingContext.GetLocalIPs())\n}\n\nfunc (c routingContext) GetLocalPort() net.Port {\n\treturn net.Port(c.RoutingContext.GetLocalPort())\n}\n\nfunc (c routingContext) GetVlessRoute() net.Port {\n\treturn net.Port(c.RoutingContext.GetVlessRoute())\n}\n\nfunc (c routingContext) GetRuleTag() string {\n\treturn \"\"\n}\n\n// GetSkipDNSResolve is a mock implementation here to match the interface,\n// SkipDNSResolve is set from dns module, no use if coming from a protobuf object?\n// TODO: please confirm @Vigilans\nfunc (c routingContext) GetSkipDNSResolve() bool {\n\treturn false\n}\n\n// AsRoutingContext converts a protobuf RoutingContext into an implementation of routing.Context.\nfunc AsRoutingContext(r *RoutingContext) routing.Context {\n\treturn routingContext{r}\n}\n\n// AsRoutingRoute converts a protobuf RoutingContext into an implementation of routing.Route.\nfunc AsRoutingRoute(r *RoutingContext) routing.Route {\n\treturn routingContext{r}\n}\n\nvar fieldMap = map[string]func(*RoutingContext, routing.Route){\n\t\"inbound\":        func(s *RoutingContext, r routing.Route) { s.InboundTag = r.GetInboundTag() },\n\t\"network\":        func(s *RoutingContext, r routing.Route) { s.Network = r.GetNetwork() },\n\t\"ip_source\":      func(s *RoutingContext, r routing.Route) { s.SourceIPs = mapIPsToBytes(r.GetSourceIPs()) },\n\t\"ip_target\":      func(s *RoutingContext, r routing.Route) { s.TargetIPs = mapIPsToBytes(r.GetTargetIPs()) },\n\t\"ip_local\":       func(s *RoutingContext, r routing.Route) { s.LocalIPs = mapIPsToBytes(r.GetLocalIPs()) },\n\t\"port_source\":    func(s *RoutingContext, r routing.Route) { s.SourcePort = uint32(r.GetSourcePort()) },\n\t\"port_target\":    func(s *RoutingContext, r routing.Route) { s.TargetPort = uint32(r.GetTargetPort()) },\n\t\"port_local\":     func(s *RoutingContext, r routing.Route) { s.LocalPort = uint32(r.GetLocalPort()) },\n\t\"domain\":         func(s *RoutingContext, r routing.Route) { s.TargetDomain = r.GetTargetDomain() },\n\t\"protocol\":       func(s *RoutingContext, r routing.Route) { s.Protocol = r.GetProtocol() },\n\t\"user\":           func(s *RoutingContext, r routing.Route) { s.User = r.GetUser() },\n\t\"attributes\":     func(s *RoutingContext, r routing.Route) { s.Attributes = r.GetAttributes() },\n\t\"outbound_group\": func(s *RoutingContext, r routing.Route) { s.OutboundGroupTags = r.GetOutboundGroupTags() },\n\t\"outbound\":       func(s *RoutingContext, r routing.Route) { s.OutboundTag = r.GetOutboundTag() },\n}\n\n// AsProtobufMessage takes selectors of fields and returns a function to convert routing.Route to protobuf RoutingContext.\nfunc AsProtobufMessage(fieldSelectors []string) func(routing.Route) *RoutingContext {\n\tinitializers := []func(*RoutingContext, routing.Route){}\n\tfor field, init := range fieldMap {\n\t\tif len(fieldSelectors) == 0 { // If selectors not set, retrieve all fields\n\t\t\tinitializers = append(initializers, init)\n\t\t\tcontinue\n\t\t}\n\t\tfor _, selector := range fieldSelectors {\n\t\t\tif strings.HasPrefix(field, selector) {\n\t\t\t\tinitializers = append(initializers, init)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn func(ctx routing.Route) *RoutingContext {\n\t\tmessage := new(RoutingContext)\n\t\tfor _, init := range initializers {\n\t\t\tinit(message, ctx)\n\t\t}\n\t\treturn message\n\t}\n}\n\nfunc mapBytesToIPs(bytes [][]byte) []net.IP {\n\tvar ips []net.IP\n\tfor _, rawIP := range bytes {\n\t\tips = append(ips, net.IP(rawIP))\n\t}\n\treturn ips\n}\n\nfunc mapIPsToBytes(ips []net.IP) [][]byte {\n\tvar bytes [][]byte\n\tfor _, ip := range ips {\n\t\tbytes = append(bytes, []byte(ip))\n\t}\n\treturn bytes\n}\n"
  },
  {
    "path": "app/router/condition.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\ntype Condition interface {\n\tApply(ctx routing.Context) bool\n}\n\ntype ConditionChan []Condition\n\nfunc NewConditionChan() *ConditionChan {\n\tvar condChan ConditionChan = make([]Condition, 0, 8)\n\treturn &condChan\n}\n\nfunc (v *ConditionChan) Add(cond Condition) *ConditionChan {\n\t*v = append(*v, cond)\n\treturn v\n}\n\n// Apply applies all conditions registered in this chan.\nfunc (v *ConditionChan) Apply(ctx routing.Context) bool {\n\tfor _, cond := range *v {\n\t\tif !cond.Apply(ctx) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (v *ConditionChan) Len() int {\n\treturn len(*v)\n}\n\nvar matcherTypeMap = map[Domain_Type]strmatcher.Type{\n\tDomain_Plain:  strmatcher.Substr,\n\tDomain_Regex:  strmatcher.Regex,\n\tDomain_Domain: strmatcher.Domain,\n\tDomain_Full:   strmatcher.Full,\n}\n\ntype DomainMatcher struct {\n\tMatchers strmatcher.IndexMatcher\n}\n\nfunc SerializeDomainMatcher(domains []*Domain, w io.Writer) error {\n\n\tg := strmatcher.NewMphMatcherGroup()\n\tfor _, d := range domains {\n\t\tmatcherType, f := matcherTypeMap[d.Type]\n\t\tif !f {\n\t\t\tcontinue\n\t\t}\n\n\t\t_, err := g.AddPattern(d.Value, matcherType)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tg.Build()\n\t// serialize\n\treturn g.Serialize(w)\n}\n\nfunc NewDomainMatcherFromBuffer(data []byte) (*strmatcher.MphMatcherGroup, error) {\n\tmatcher, err := strmatcher.NewMphMatcherGroupFromBuffer(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn matcher, nil\n}\n\nfunc NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {\n\tg := strmatcher.NewMphMatcherGroup()\n\tfor i, d := range domains {\n\t\tdomains[i] = nil\n\t\tmatcherType, f := matcherTypeMap[d.Type]\n\t\tif !f {\n\t\t\terrors.LogError(context.Background(), \"ignore unsupported domain type \", d.Type, \" of rule \", d.Value)\n\t\t\tcontinue\n\t\t}\n\t\t_, err := g.AddPattern(d.Value, matcherType)\n\t\tif err != nil {\n\t\t\terrors.LogErrorInner(context.Background(), err, \"ignore domain rule \", d.Type, \" \", d.Value)\n\t\t\tcontinue\n\t\t}\n\t}\n\tg.Build()\n\treturn &DomainMatcher{\n\t\tMatchers: g,\n\t}, nil\n}\n\nfunc (m *DomainMatcher) ApplyDomain(domain string) bool {\n\treturn len(m.Matchers.Match(strings.ToLower(domain))) > 0\n}\n\n// Apply implements Condition.\nfunc (m *DomainMatcher) Apply(ctx routing.Context) bool {\n\tdomain := ctx.GetTargetDomain()\n\tif len(domain) == 0 {\n\t\treturn false\n\t}\n\treturn m.ApplyDomain(domain)\n}\n\ntype MatcherAsType byte\n\nconst (\n\tMatcherAsType_Local MatcherAsType = iota\n\tMatcherAsType_Source\n\tMatcherAsType_Target\n\tMatcherAsType_VlessRoute // for port\n)\n\ntype IPMatcher struct {\n\tmatcher GeoIPMatcher\n\tasType  MatcherAsType\n}\n\nfunc NewIPMatcher(geoips []*GeoIP, asType MatcherAsType) (*IPMatcher, error) {\n\tmatcher, err := BuildOptimizedGeoIPMatcher(geoips...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &IPMatcher{matcher: matcher, asType: asType}, nil\n}\n\n// Apply implements Condition.\nfunc (m *IPMatcher) Apply(ctx routing.Context) bool {\n\tvar ips []net.IP\n\n\tswitch m.asType {\n\tcase MatcherAsType_Local:\n\t\tips = ctx.GetLocalIPs()\n\tcase MatcherAsType_Source:\n\t\tips = ctx.GetSourceIPs()\n\tcase MatcherAsType_Target:\n\t\tips = ctx.GetTargetIPs()\n\tdefault:\n\t\tpanic(\"unk asType\")\n\t}\n\n\treturn m.matcher.AnyMatch(ips)\n}\n\ntype PortMatcher struct {\n\tport   net.MemoryPortList\n\tasType MatcherAsType\n}\n\n// NewPortMatcher create a new port matcher that can match source or local or destination port\nfunc NewPortMatcher(list *net.PortList, asType MatcherAsType) *PortMatcher {\n\treturn &PortMatcher{\n\t\tport:   net.PortListFromProto(list),\n\t\tasType: asType,\n\t}\n}\n\n// Apply implements Condition.\nfunc (v *PortMatcher) Apply(ctx routing.Context) bool {\n\tswitch v.asType {\n\tcase MatcherAsType_Local:\n\t\treturn v.port.Contains(ctx.GetLocalPort())\n\tcase MatcherAsType_Source:\n\t\treturn v.port.Contains(ctx.GetSourcePort())\n\tcase MatcherAsType_Target:\n\t\treturn v.port.Contains(ctx.GetTargetPort())\n\tcase MatcherAsType_VlessRoute:\n\t\treturn v.port.Contains(ctx.GetVlessRoute())\n\tdefault:\n\t\tpanic(\"unk asType\")\n\t}\n}\n\ntype NetworkMatcher struct {\n\tlist [8]bool\n}\n\nfunc NewNetworkMatcher(network []net.Network) NetworkMatcher {\n\tvar matcher NetworkMatcher\n\tfor _, n := range network {\n\t\tmatcher.list[int(n)] = true\n\t}\n\treturn matcher\n}\n\n// Apply implements Condition.\nfunc (v NetworkMatcher) Apply(ctx routing.Context) bool {\n\treturn v.list[int(ctx.GetNetwork())]\n}\n\ntype UserMatcher struct {\n\tuser    []string\n\tpattern []*regexp.Regexp\n}\n\nfunc NewUserMatcher(users []string) *UserMatcher {\n\tusersCopy := make([]string, 0, len(users))\n\tpatternsCopy := make([]*regexp.Regexp, 0, len(users))\n\tfor _, user := range users {\n\t\tif len(user) > 0 {\n\t\t\tif len(user) > 7 && strings.HasPrefix(user, \"regexp:\") {\n\t\t\t\tif re, err := regexp.Compile(user[7:]); err == nil {\n\t\t\t\t\tpatternsCopy = append(patternsCopy, re)\n\t\t\t\t}\n\t\t\t\t// Items of users slice with an invalid regexp syntax are ignored.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tusersCopy = append(usersCopy, user)\n\t\t}\n\t}\n\treturn &UserMatcher{\n\t\tuser:    usersCopy,\n\t\tpattern: patternsCopy,\n\t}\n}\n\n// Apply implements Condition.\nfunc (v *UserMatcher) Apply(ctx routing.Context) bool {\n\tuser := ctx.GetUser()\n\tif len(user) == 0 {\n\t\treturn false\n\t}\n\tfor _, u := range v.user {\n\t\tif u == user {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, re := range v.pattern {\n\t\tif re.MatchString(user) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype InboundTagMatcher struct {\n\ttags []string\n}\n\nfunc NewInboundTagMatcher(tags []string) *InboundTagMatcher {\n\ttagsCopy := make([]string, 0, len(tags))\n\tfor _, tag := range tags {\n\t\tif len(tag) > 0 {\n\t\t\ttagsCopy = append(tagsCopy, tag)\n\t\t}\n\t}\n\treturn &InboundTagMatcher{\n\t\ttags: tagsCopy,\n\t}\n}\n\n// Apply implements Condition.\nfunc (v *InboundTagMatcher) Apply(ctx routing.Context) bool {\n\ttag := ctx.GetInboundTag()\n\tif len(tag) == 0 {\n\t\treturn false\n\t}\n\tfor _, t := range v.tags {\n\t\tif t == tag {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype ProtocolMatcher struct {\n\tprotocols []string\n}\n\nfunc NewProtocolMatcher(protocols []string) *ProtocolMatcher {\n\tpCopy := make([]string, 0, len(protocols))\n\n\tfor _, p := range protocols {\n\t\tif len(p) > 0 {\n\t\t\tpCopy = append(pCopy, p)\n\t\t}\n\t}\n\n\treturn &ProtocolMatcher{\n\t\tprotocols: pCopy,\n\t}\n}\n\n// Apply implements Condition.\nfunc (m *ProtocolMatcher) Apply(ctx routing.Context) bool {\n\tprotocol := ctx.GetProtocol()\n\tif len(protocol) == 0 {\n\t\treturn false\n\t}\n\tfor _, p := range m.protocols {\n\t\tif strings.HasPrefix(protocol, p) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype AttributeMatcher struct {\n\tconfiguredKeys map[string]*regexp.Regexp\n}\n\n// Match implements attributes matching.\nfunc (m *AttributeMatcher) Match(attrs map[string]string) bool {\n\t// header keys are case insensitive most likely. So we do a convert\n\thttpHeaders := make(map[string]string)\n\tfor key, value := range attrs {\n\t\thttpHeaders[strings.ToLower(key)] = value\n\t}\n\tfor key, regex := range m.configuredKeys {\n\t\tif a, ok := httpHeaders[key]; !ok || !regex.MatchString(a) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Apply implements Condition.\nfunc (m *AttributeMatcher) Apply(ctx routing.Context) bool {\n\tattributes := ctx.GetAttributes()\n\tif attributes == nil {\n\t\treturn false\n\t}\n\treturn m.Match(attributes)\n}\n\ntype ProcessNameMatcher struct {\n\tProcessNames  []string\n\tAbsPaths      []string\n\tFolders       []string\n\tMatchXraySelf bool\n}\n\nfunc NewProcessNameMatcher(names []string) *ProcessNameMatcher {\n\tprocessNames := []string{}\n\tfolders := []string{}\n\tabsPaths := []string{}\n\tmatchXraySelf := false\n\tfor _, name := range names {\n\t\tif name == \"self/\" {\n\t\t\tmatchXraySelf = true\n\t\t\tcontinue\n\t\t}\n\t\t// replace xray/ with self executable path\n\t\tif name == \"xray/\" {\n\t\t\txrayPath, err := os.Executable()\n\t\t\tif err != nil {\n\t\t\t\terrors.LogError(context.Background(), \"Failed to get xray executable path: \", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tname = xrayPath\n\t\t}\n\t\tname := filepath.ToSlash(name)\n\t\t// /usr/bin/\n\t\tif strings.HasSuffix(name, \"/\") {\n\t\t\tfolders = append(folders, name)\n\t\t\tcontinue\n\t\t}\n\t\t// /usr/bin/curl\n\t\tif strings.Contains(name, \"/\") {\n\t\t\tabsPaths = append(absPaths, name)\n\t\t\tcontinue\n\t\t}\n\t\t// curl.exe or curl\n\t\tprocessNames = append(processNames, strings.TrimSuffix(name, \".exe\"))\n\t}\n\treturn &ProcessNameMatcher{\n\t\tProcessNames:  processNames,\n\t\tAbsPaths:      absPaths,\n\t\tFolders:       folders,\n\t\tMatchXraySelf: matchXraySelf,\n\t}\n}\n\nfunc (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {\n\tif len(ctx.GetSourceIPs()) == 0 {\n\t\treturn false\n\t}\n\tsrcPort := ctx.GetSourcePort().String()\n\tsrcIP := ctx.GetSourceIPs()[0].String()\n\tvar network string\n\tswitch ctx.GetNetwork() {\n\tcase net.Network_TCP:\n\t\tnetwork = \"tcp\"\n\tcase net.Network_UDP:\n\t\tnetwork = \"udp\"\n\tdefault:\n\t\treturn false\n\t}\n\tsrc, err := net.ParseDestination(strings.Join([]string{network, srcIP, srcPort}, \":\"))\n\tif err != nil {\n\t\treturn false\n\t}\n\tpid, name, absPath, err := net.FindProcess(src)\n\tif err != nil {\n\t\tif err != net.ErrNotLocal {\n\t\t\terrors.LogError(context.Background(), \"Unables to find local process name: \", err)\n\t\t}\n\t\treturn false\n\t}\n\tif m.MatchXraySelf {\n\t\tif pid == os.Getpid() {\n\t\t\treturn true\n\t\t}\n\t}\n\tif slices.Contains(m.ProcessNames, name) {\n\t\treturn true\n\t}\n\tif slices.Contains(m.AbsPaths, absPath) {\n\t\treturn true\n\t}\n\tfor _, f := range m.Folders {\n\t\tif strings.HasPrefix(absPath, f) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "app/router/condition_geoip.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\n\t\"go4.org/netipx\"\n)\n\ntype GeoIPMatcher interface {\n\t// TODO: (PERF) all net.IP -> netipx.Addr\n\n\t// Invalid IP always return false.\n\tMatch(ip net.IP) bool\n\n\t// Returns true if *any* IP is valid and match.\n\tAnyMatch(ips []net.IP) bool\n\n\t// Returns true only if *all* IPs are valid and match. Any invalid IP, or non-matching valid IP, causes false.\n\tMatches(ips []net.IP) bool\n\n\t// Filters IPs. Invalid IPs are silently dropped and not included in either result.\n\tFilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP)\n\n\tToggleReverse()\n\n\tSetReverse(reverse bool)\n}\n\ntype GeoIPSet struct {\n\tipv4, ipv6 *netipx.IPSet\n\tmax4, max6 uint8\n}\n\ntype HeuristicGeoIPMatcher struct {\n\tipset   *GeoIPSet\n\treverse bool\n}\n\ntype ipBucket struct {\n\trep netip.Addr\n\tips []net.IP\n}\n\n// Match implements GeoIPMatcher.\nfunc (m *HeuristicGeoIPMatcher) Match(ip net.IP) bool {\n\tipx, ok := netipx.FromStdIP(ip)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn m.matchAddr(ipx)\n}\n\nfunc (m *HeuristicGeoIPMatcher) matchAddr(ipx netip.Addr) bool {\n\tif ipx.Is4() {\n\t\treturn m.ipset.ipv4.Contains(ipx) != m.reverse\n\t}\n\tif ipx.Is6() {\n\t\treturn m.ipset.ipv6.Contains(ipx) != m.reverse\n\t}\n\treturn false\n}\n\n// AnyMatch implements GeoIPMatcher.\nfunc (m *HeuristicGeoIPMatcher) AnyMatch(ips []net.IP) bool {\n\tn := len(ips)\n\tif n == 0 {\n\t\treturn false\n\t}\n\n\tif n == 1 {\n\t\treturn m.Match(ips[0])\n\t}\n\n\theur4 := m.ipset.max4 <= 24\n\theur6 := m.ipset.max6 <= 64\n\tif !heur4 && !heur6 {\n\t\tfor _, ip := range ips {\n\t\t\tif ipx, ok := netipx.FromStdIP(ip); ok {\n\t\t\t\tif m.matchAddr(ipx) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tbuckets := make(map[[9]byte]struct{}, n)\n\tfor _, ip := range ips {\n\t\tkey, ok := prefixKeyFromIP(ip)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\theur := (key[0] == 4 && heur4) || (key[0] == 6 && heur6)\n\t\tif heur {\n\t\t\tif _, exists := buckets[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tipx, ok := netipx.FromStdIP(ip)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif m.matchAddr(ipx) {\n\t\t\treturn true\n\t\t}\n\t\tif heur {\n\t\t\tbuckets[key] = struct{}{}\n\t\t}\n\t}\n\treturn false\n}\n\n// Matches implements GeoIPMatcher.\nfunc (m *HeuristicGeoIPMatcher) Matches(ips []net.IP) bool {\n\tn := len(ips)\n\tif n == 0 {\n\t\treturn false\n\t}\n\n\tif n == 1 {\n\t\treturn m.Match(ips[0])\n\t}\n\n\theur4 := m.ipset.max4 <= 24\n\theur6 := m.ipset.max6 <= 64\n\tif !heur4 && !heur6 {\n\t\tfor _, ip := range ips {\n\t\t\tipx, ok := netipx.FromStdIP(ip)\n\t\t\tif !ok {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif !m.matchAddr(ipx) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tbuckets := make(map[[9]byte]netip.Addr, n)\n\tprecise := make([]netip.Addr, 0, n)\n\n\tfor _, ip := range ips {\n\t\tkey, ok := prefixKeyFromIP(ip)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tif (key[0] == 4 && heur4) || (key[0] == 6 && heur6) {\n\t\t\tif _, exists := buckets[key]; !exists {\n\t\t\t\tipx, ok := netipx.FromStdIP(ip)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tbuckets[key] = ipx\n\t\t\t}\n\t\t} else {\n\t\t\tipx, ok := netipx.FromStdIP(ip)\n\t\t\tif !ok {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tprecise = append(precise, ipx)\n\t\t}\n\t}\n\n\tfor _, ipx := range buckets {\n\t\tif !m.matchAddr(ipx) {\n\t\t\treturn false\n\t\t}\n\t}\n\tfor _, ipx := range precise {\n\t\tif !m.matchAddr(ipx) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc prefixKeyFromIP(ip net.IP) (key [9]byte, ok bool) {\n\tif ip4 := ip.To4(); ip4 != nil {\n\t\tkey[0] = 4\n\t\tkey[1] = ip4[0]\n\t\tkey[2] = ip4[1]\n\t\tkey[3] = ip4[2] // /24\n\t\treturn key, true\n\t}\n\tif ip16 := ip.To16(); ip16 != nil {\n\t\tkey[0] = 6\n\t\tkey[1] = ip16[0]\n\t\tkey[2] = ip16[1]\n\t\tkey[3] = ip16[2]\n\t\tkey[4] = ip16[3]\n\t\tkey[5] = ip16[4]\n\t\tkey[6] = ip16[5]\n\t\tkey[7] = ip16[6]\n\t\tkey[8] = ip16[7] // /64\n\t\treturn key, true\n\t}\n\treturn key, false // illegal\n}\n\n// FilterIPs implements GeoIPMatcher.\nfunc (m *HeuristicGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {\n\tn := len(ips)\n\tif n == 0 {\n\t\treturn []net.IP{}, []net.IP{}\n\t}\n\n\tif n == 1 {\n\t\tipx, ok := netipx.FromStdIP(ips[0])\n\t\tif !ok {\n\t\t\treturn []net.IP{}, []net.IP{}\n\t\t}\n\t\tif m.matchAddr(ipx) {\n\t\t\treturn ips, []net.IP{}\n\t\t}\n\t\treturn []net.IP{}, ips\n\t}\n\n\theur4 := m.ipset.max4 <= 24\n\theur6 := m.ipset.max6 <= 64\n\tif !heur4 && !heur6 {\n\t\tmatched = make([]net.IP, 0, n)\n\t\tunmatched = make([]net.IP, 0, n)\n\t\tfor _, ip := range ips {\n\t\t\tipx, ok := netipx.FromStdIP(ip)\n\t\t\tif !ok {\n\t\t\t\tcontinue // illegal ip, ignore\n\t\t\t}\n\t\t\tif m.matchAddr(ipx) {\n\t\t\t\tmatched = append(matched, ip)\n\t\t\t} else {\n\t\t\t\tunmatched = append(unmatched, ip)\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\tbuckets := make(map[[9]byte]*ipBucket, n)\n\tprecise := make([]net.IP, 0, n)\n\n\tfor _, ip := range ips {\n\t\tkey, ok := prefixKeyFromIP(ip)\n\t\tif !ok {\n\t\t\tcontinue // illegal ip, ignore\n\t\t}\n\n\t\tif (key[0] == 4 && !heur4) || (key[0] == 6 && !heur6) {\n\t\t\tprecise = append(precise, ip)\n\t\t\tcontinue\n\t\t}\n\n\t\tb, exists := buckets[key]\n\t\tif !exists {\n\t\t\t// build bucket\n\t\t\tipx, ok := netipx.FromStdIP(ip)\n\t\t\tif !ok {\n\t\t\t\tcontinue // illegal ip, ignore\n\t\t\t}\n\t\t\tb = &ipBucket{\n\t\t\t\trep: ipx,\n\t\t\t\tips: make([]net.IP, 0, 4), // for dns answer\n\t\t\t}\n\t\t\tbuckets[key] = b\n\t\t}\n\t\tb.ips = append(b.ips, ip)\n\t}\n\n\tmatched = make([]net.IP, 0, n)\n\tunmatched = make([]net.IP, 0, n)\n\tfor _, b := range buckets {\n\t\tif m.matchAddr(b.rep) {\n\t\t\tmatched = append(matched, b.ips...)\n\t\t} else {\n\t\t\tunmatched = append(unmatched, b.ips...)\n\t\t}\n\t}\n\tfor _, ip := range precise {\n\t\tipx, ok := netipx.FromStdIP(ip)\n\t\tif !ok {\n\t\t\tcontinue // illegal ip, ignore\n\t\t}\n\t\tif m.matchAddr(ipx) {\n\t\t\tmatched = append(matched, ip)\n\t\t} else {\n\t\t\tunmatched = append(unmatched, ip)\n\t\t}\n\t}\n\treturn\n}\n\n// ToggleReverse implements GeoIPMatcher.\nfunc (m *HeuristicGeoIPMatcher) ToggleReverse() {\n\tm.reverse = !m.reverse\n}\n\n// SetReverse implements GeoIPMatcher.\nfunc (m *HeuristicGeoIPMatcher) SetReverse(reverse bool) {\n\tm.reverse = reverse\n}\n\ntype GeneralMultiGeoIPMatcher struct {\n\tmatchers []GeoIPMatcher\n}\n\n// Match implements GeoIPMatcher.\nfunc (mm *GeneralMultiGeoIPMatcher) Match(ip net.IP) bool {\n\tfor _, m := range mm.matchers {\n\t\tif m.Match(ip) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// AnyMatch implements GeoIPMatcher.\nfunc (mm *GeneralMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {\n\tfor _, m := range mm.matchers {\n\t\tif m.AnyMatch(ips) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Matches implements GeoIPMatcher.\nfunc (mm *GeneralMultiGeoIPMatcher) Matches(ips []net.IP) bool {\n\tfor _, m := range mm.matchers {\n\t\tif m.Matches(ips) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// FilterIPs implements GeoIPMatcher.\nfunc (mm *GeneralMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {\n\tmatched = make([]net.IP, 0, len(ips))\n\tunmatched = ips\n\tfor _, m := range mm.matchers {\n\t\tif len(unmatched) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tvar mtch []net.IP\n\t\tmtch, unmatched = m.FilterIPs(unmatched)\n\t\tif len(mtch) > 0 {\n\t\t\tmatched = append(matched, mtch...)\n\t\t}\n\t}\n\treturn\n}\n\n// ToggleReverse implements GeoIPMatcher.\nfunc (mm *GeneralMultiGeoIPMatcher) ToggleReverse() {\n\tfor _, m := range mm.matchers {\n\t\tm.ToggleReverse()\n\t}\n}\n\n// SetReverse implements GeoIPMatcher.\nfunc (mm *GeneralMultiGeoIPMatcher) SetReverse(reverse bool) {\n\tfor _, m := range mm.matchers {\n\t\tm.SetReverse(reverse)\n\t}\n}\n\ntype HeuristicMultiGeoIPMatcher struct {\n\tmatchers []*HeuristicGeoIPMatcher\n}\n\n// Match implements GeoIPMatcher.\nfunc (mm *HeuristicMultiGeoIPMatcher) Match(ip net.IP) bool {\n\tipx, ok := netipx.FromStdIP(ip)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tfor _, m := range mm.matchers {\n\t\tif m.matchAddr(ipx) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// AnyMatch implements GeoIPMatcher.\nfunc (mm *HeuristicMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {\n\tn := len(ips)\n\tif n == 0 {\n\t\treturn false\n\t}\n\n\tif n == 1 {\n\t\treturn mm.Match(ips[0])\n\t}\n\n\tbuckets := make(map[[9]byte]struct{}, n)\n\tfor _, ip := range ips {\n\t\tvar ipx netip.Addr\n\t\tstate := uint8(0) // 0 = Not initialized, 1 = Initialized, 4 = IPv4 can be skipped, 6 = IPv6 can be skipped\n\t\tfor _, m := range mm.matchers {\n\t\t\theur4 := m.ipset.max4 <= 24\n\t\t\theur6 := m.ipset.max6 <= 64\n\n\t\t\tif state == 0 && (heur4 || heur6) {\n\t\t\t\tkey, ok := prefixKeyFromIP(ip)\n\t\t\t\tif !ok {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif _, exists := buckets[key]; exists {\n\t\t\t\t\tstate = key[0]\n\t\t\t\t} else {\n\t\t\t\t\tbuckets[key] = struct{}{}\n\t\t\t\t\tstate = 1\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (heur4 && state == 4) || (heur6 && state == 6) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif !ipx.IsValid() {\n\t\t\t\tnipx, ok := netipx.FromStdIP(ip)\n\t\t\t\tif !ok {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tipx = nipx\n\t\t\t}\n\t\t\tif m.matchAddr(ipx) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// Matches implements GeoIPMatcher.\nfunc (mm *HeuristicMultiGeoIPMatcher) Matches(ips []net.IP) bool {\n\tn := len(ips)\n\tif n == 0 {\n\t\treturn false\n\t}\n\n\tif n == 1 {\n\t\treturn mm.Match(ips[0])\n\t}\n\n\tvar views ipViews\n\tfor _, m := range mm.matchers {\n\t\tif !views.ensureForMatcher(m, ips) {\n\t\t\treturn false\n\t\t}\n\n\t\tmatched := true\n\t\tif m.ipset.max4 <= 24 {\n\t\t\tfor _, ipx := range views.buckets4 {\n\t\t\t\tif !m.matchAddr(ipx) {\n\t\t\t\t\tmatched = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, ipx := range views.precise4 {\n\t\t\t\tif !m.matchAddr(ipx) {\n\t\t\t\t\tmatched = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\tcontinue\n\t\t}\n\n\t\tif m.ipset.max6 <= 64 {\n\t\t\tfor _, ipx := range views.buckets6 {\n\t\t\t\tif !m.matchAddr(ipx) {\n\t\t\t\t\tmatched = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, ipx := range views.precise6 {\n\t\t\t\tif !m.matchAddr(ipx) {\n\t\t\t\t\tmatched = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif matched {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype ipViews struct {\n\tbuckets4, buckets6 map[[9]byte]netip.Addr\n\tprecise4, precise6 []netip.Addr\n}\n\nfunc (v *ipViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) bool {\n\tneedHeur4 := m.ipset.max4 <= 24 && v.buckets4 == nil\n\tneedHeur6 := m.ipset.max6 <= 64 && v.buckets6 == nil\n\tneedPrec4 := m.ipset.max4 > 24 && v.precise4 == nil\n\tneedPrec6 := m.ipset.max6 > 64 && v.precise6 == nil\n\n\tif !needHeur4 && !needHeur6 && !needPrec4 && !needPrec6 {\n\t\treturn true\n\t}\n\n\tif needHeur4 {\n\t\tv.buckets4 = make(map[[9]byte]netip.Addr, len(ips))\n\t}\n\tif needHeur6 {\n\t\tv.buckets6 = make(map[[9]byte]netip.Addr, len(ips))\n\t}\n\tif needPrec4 {\n\t\tv.precise4 = make([]netip.Addr, 0, len(ips))\n\t}\n\tif needPrec6 {\n\t\tv.precise6 = make([]netip.Addr, 0, len(ips))\n\t}\n\n\tfor _, ip := range ips {\n\t\tkey, ok := prefixKeyFromIP(ip)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch key[0] {\n\t\tcase 4:\n\t\t\tvar ipx netip.Addr\n\t\t\tif needHeur4 {\n\t\t\t\tif _, exists := v.buckets4[key]; !exists {\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tv.buckets4[key] = ipx\n\t\t\t\t}\n\t\t\t}\n\t\t\tif needPrec4 {\n\t\t\t\tif !ipx.IsValid() {\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tv.precise4 = append(v.precise4, ipx)\n\t\t\t}\n\t\tcase 6:\n\t\t\tvar ipx netip.Addr\n\t\t\tif needHeur6 {\n\t\t\t\tif _, exists := v.buckets6[key]; !exists {\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tv.buckets6[key] = ipx\n\t\t\t\t}\n\t\t\t}\n\t\t\tif needPrec6 {\n\t\t\t\tif !ipx.IsValid() {\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tv.precise6 = append(v.precise6, ipx)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// FilterIPs implements GeoIPMatcher.\nfunc (mm *HeuristicMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {\n\tn := len(ips)\n\tif n == 0 {\n\t\treturn []net.IP{}, []net.IP{}\n\t}\n\n\tif n == 1 {\n\t\tipx, ok := netipx.FromStdIP(ips[0])\n\t\tif !ok {\n\t\t\treturn []net.IP{}, []net.IP{}\n\t\t}\n\t\tfor _, m := range mm.matchers {\n\t\t\tif m.matchAddr(ipx) {\n\t\t\t\treturn ips, []net.IP{}\n\t\t\t}\n\t\t}\n\t\treturn []net.IP{}, ips\n\t}\n\n\tvar views ipBucketViews\n\n\tmatched = make([]net.IP, 0, n)\n\tfor _, m := range mm.matchers {\n\t\tviews.ensureForMatcher(m, ips)\n\n\t\tif m.ipset.max4 <= 24 {\n\t\t\tfor key, b := range views.buckets4 {\n\t\t\t\tif b == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m.matchAddr(b.rep) {\n\t\t\t\t\tviews.buckets4[key] = nil\n\t\t\t\t\tmatched = append(matched, b.ips...)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor ipx, ip := range views.precise4 {\n\t\t\t\tif ip == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m.matchAddr(ipx) {\n\t\t\t\t\tviews.precise4[ipx] = nil\n\t\t\t\t\tmatched = append(matched, ip)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif m.ipset.max6 <= 64 {\n\t\t\tfor key, b := range views.buckets6 {\n\t\t\t\tif b == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m.matchAddr(b.rep) {\n\t\t\t\t\tviews.buckets6[key] = nil\n\t\t\t\t\tmatched = append(matched, b.ips...)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor ipx, ip := range views.precise6 {\n\t\t\t\tif ip == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif m.matchAddr(ipx) {\n\t\t\t\t\tviews.precise6[ipx] = nil\n\t\t\t\t\tmatched = append(matched, ip)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tunmatched = make([]net.IP, 0, n-len(matched))\n\tif views.buckets4 != nil {\n\t\tfor _, b := range views.buckets4 {\n\t\t\tif b == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunmatched = append(unmatched, b.ips...)\n\t\t}\n\t}\n\tif views.precise4 != nil {\n\t\tfor _, ip := range views.precise4 {\n\t\t\tif ip == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunmatched = append(unmatched, ip)\n\t\t}\n\t}\n\tif views.buckets6 != nil {\n\t\tfor _, b := range views.buckets6 {\n\t\t\tif b == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunmatched = append(unmatched, b.ips...)\n\t\t}\n\t}\n\tif views.precise6 != nil {\n\t\tfor _, ip := range views.precise6 {\n\t\t\tif ip == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunmatched = append(unmatched, ip)\n\t\t}\n\t}\n\n\treturn\n}\n\ntype ipBucketViews struct {\n\tbuckets4, buckets6 map[[9]byte]*ipBucket\n\tprecise4, precise6 map[netip.Addr]net.IP\n}\n\nfunc (v *ipBucketViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) {\n\tneedHeur4 := m.ipset.max4 <= 24 && v.buckets4 == nil\n\tneedHeur6 := m.ipset.max6 <= 64 && v.buckets6 == nil\n\tneedPrec4 := m.ipset.max4 > 24 && v.precise4 == nil\n\tneedPrec6 := m.ipset.max6 > 64 && v.precise6 == nil\n\n\tif !needHeur4 && !needHeur6 && !needPrec4 && !needPrec6 {\n\t\treturn\n\t}\n\n\tif needHeur4 {\n\t\tv.buckets4 = make(map[[9]byte]*ipBucket, len(ips))\n\t}\n\tif needHeur6 {\n\t\tv.buckets6 = make(map[[9]byte]*ipBucket, len(ips))\n\t}\n\tif needPrec4 {\n\t\tv.precise4 = make(map[netip.Addr]net.IP, len(ips))\n\t}\n\tif needPrec6 {\n\t\tv.precise6 = make(map[netip.Addr]net.IP, len(ips))\n\t}\n\n\tfor _, ip := range ips {\n\t\tkey, ok := prefixKeyFromIP(ip)\n\t\tif !ok {\n\t\t\tcontinue // illegal ip, ignore\n\t\t}\n\n\t\tswitch key[0] {\n\t\tcase 4:\n\t\t\tvar ipx netip.Addr\n\t\t\tif needHeur4 {\n\t\t\t\tb, exists := v.buckets4[key]\n\t\t\t\tif !exists {\n\t\t\t\t\t// build bucket\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue // illegal ip, ignore\n\t\t\t\t\t}\n\t\t\t\t\tb = &ipBucket{\n\t\t\t\t\t\trep: ipx,\n\t\t\t\t\t\tips: make([]net.IP, 0, 4), // for dns answer\n\t\t\t\t\t}\n\t\t\t\t\tv.buckets4[key] = b\n\t\t\t\t}\n\t\t\t\tb.ips = append(b.ips, ip)\n\t\t\t}\n\t\t\tif needPrec4 {\n\t\t\t\tif !ipx.IsValid() {\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue // illegal ip, ignore\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tv.precise4[ipx] = ip\n\t\t\t}\n\t\tcase 6:\n\t\t\tvar ipx netip.Addr\n\t\t\tif needHeur6 {\n\t\t\t\tb, exists := v.buckets6[key]\n\t\t\t\tif !exists {\n\t\t\t\t\t// build bucket\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue // illegal ip, ignore\n\t\t\t\t\t}\n\t\t\t\t\tb = &ipBucket{\n\t\t\t\t\t\trep: ipx,\n\t\t\t\t\t\tips: make([]net.IP, 0, 4), // for dns answer\n\t\t\t\t\t}\n\t\t\t\t\tv.buckets6[key] = b\n\t\t\t\t}\n\t\t\t\tb.ips = append(b.ips, ip)\n\t\t\t}\n\t\t\tif needPrec6 {\n\t\t\t\tif !ipx.IsValid() {\n\t\t\t\t\tipx, ok = netipx.FromStdIP(ip)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue // illegal ip, ignore\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tv.precise6[ipx] = ip\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ToggleReverse implements GeoIPMatcher.\nfunc (mm *HeuristicMultiGeoIPMatcher) ToggleReverse() {\n\tfor _, m := range mm.matchers {\n\t\tm.ToggleReverse()\n\t}\n}\n\n// SetReverse implements GeoIPMatcher.\nfunc (mm *HeuristicMultiGeoIPMatcher) SetReverse(reverse bool) {\n\tfor _, m := range mm.matchers {\n\t\tm.SetReverse(reverse)\n\t}\n}\n\ntype GeoIPSetFactory struct {\n\tsync.Mutex\n\tshared map[string]*GeoIPSet // TODO: cleanup\n}\n\nvar ipsetFactory = GeoIPSetFactory{shared: make(map[string]*GeoIPSet)}\n\nfunc (f *GeoIPSetFactory) GetOrCreate(key string, cidrGroups [][]*CIDR) (*GeoIPSet, error) {\n\tf.Lock()\n\tdefer f.Unlock()\n\n\tif ipset := f.shared[key]; ipset != nil {\n\t\treturn ipset, nil\n\t}\n\n\tipset, err := f.Create(cidrGroups...)\n\tif err == nil {\n\t\tf.shared[key] = ipset\n\t}\n\treturn ipset, err\n}\n\nfunc (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {\n\tvar ipv4Builder, ipv6Builder netipx.IPSetBuilder\n\n\tfor _, cidrGroup := range cidrGroups {\n\t\tfor i, cidrEntry := range cidrGroup {\n\t\t\tcidrGroup[i] = nil\n\t\t\tipBytes := cidrEntry.GetIp()\n\t\t\tprefixLen := int(cidrEntry.GetPrefix())\n\n\t\t\taddr, ok := netip.AddrFromSlice(ipBytes)\n\t\t\tif !ok {\n\t\t\t\terrors.LogError(context.Background(), \"ignore invalid IP byte slice: \", ipBytes)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tprefix := netip.PrefixFrom(addr, prefixLen)\n\t\t\tif !prefix.IsValid() {\n\t\t\t\terrors.LogError(context.Background(), \"ignore created invalid prefix from addr \", addr, \" and length \", prefixLen)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif addr.Is4() {\n\t\t\t\tipv4Builder.AddPrefix(prefix)\n\t\t\t} else if addr.Is6() {\n\t\t\t\tipv6Builder.AddPrefix(prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\tipv4, err := ipv4Builder.IPSet()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to build IPv4 set\").Base(err)\n\t}\n\tipv6, err := ipv6Builder.IPSet()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to build IPv6 set\").Base(err)\n\t}\n\n\tvar max4, max6 int\n\n\tfor _, p := range ipv4.Prefixes() {\n\t\tif b := p.Bits(); b > max4 {\n\t\t\tmax4 = b\n\t\t}\n\t}\n\tfor _, p := range ipv6.Prefixes() {\n\t\tif b := p.Bits(); b > max6 {\n\t\t\tmax6 = b\n\t\t}\n\t}\n\n\tif max4 == 0 {\n\t\tmax4 = 0xff\n\t}\n\tif max6 == 0 {\n\t\tmax6 = 0xff\n\t}\n\n\treturn &GeoIPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil\n}\n\nfunc BuildOptimizedGeoIPMatcher(geoips ...*GeoIP) (GeoIPMatcher, error) {\n\tn := len(geoips)\n\tif n == 0 {\n\t\treturn nil, errors.New(\"no geoip configs provided\")\n\t}\n\n\tvar subs []*HeuristicGeoIPMatcher\n\tpos := make([]*GeoIP, 0, n)\n\tneg := make([]*GeoIP, 0, n/2)\n\n\tfor _, geoip := range geoips {\n\t\tif geoip == nil {\n\t\t\treturn nil, errors.New(\"geoip entry is nil\")\n\t\t}\n\t\tif geoip.CountryCode == \"\" {\n\t\t\tipset, err := ipsetFactory.Create(geoip.Cidr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tsubs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: geoip.ReverseMatch})\n\t\t\tcontinue\n\t\t}\n\t\tif !geoip.ReverseMatch {\n\t\t\tpos = append(pos, geoip)\n\t\t} else {\n\t\t\tneg = append(neg, geoip)\n\t\t}\n\t}\n\n\tbuildIPSet := func(mergeables []*GeoIP) (*GeoIPSet, error) {\n\t\tn := len(mergeables)\n\t\tif n == 0 {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\tsort.Slice(mergeables, func(i, j int) bool {\n\t\t\tgi, gj := mergeables[i], mergeables[j]\n\t\t\treturn gi.CountryCode < gj.CountryCode\n\t\t})\n\n\t\tvar sb strings.Builder\n\t\tsb.Grow(n * 3) // xx,\n\t\tcidrGroups := make([][]*CIDR, 0, n)\n\t\tvar last *GeoIP\n\t\tfor i, geoip := range mergeables {\n\t\t\tif i == 0 || (geoip.CountryCode != last.CountryCode) {\n\t\t\t\tlast = geoip\n\t\t\t\tsb.WriteString(geoip.CountryCode)\n\t\t\t\tsb.WriteString(\",\")\n\t\t\t\tcidrGroups = append(cidrGroups, geoip.Cidr)\n\t\t\t}\n\t\t}\n\n\t\treturn ipsetFactory.GetOrCreate(sb.String(), cidrGroups)\n\t}\n\n\tipset, err := buildIPSet(pos)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ipset != nil {\n\t\tsubs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})\n\t}\n\n\tipset, err = buildIPSet(neg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ipset != nil {\n\t\tsubs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: true})\n\t}\n\n\tswitch len(subs) {\n\tcase 0:\n\t\treturn nil, errors.New(\"no valid geoip matcher\")\n\tcase 1:\n\t\treturn subs[0], nil\n\tdefault:\n\t\treturn &HeuristicMultiGeoIPMatcher{matchers: subs}, nil\n\t}\n}\n"
  },
  {
    "path": "app/router/condition_geoip_test.go",
    "content": "package router_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc getAssetPath(file string) (string, error) {\n\tpath := platform.GetAssetLocation(file)\n\t_, err := os.Stat(path)\n\tif os.IsNotExist(err) {\n\t\tpath := filepath.Join(\"..\", \"..\", \"resources\", file)\n\t\t_, err := os.Stat(path)\n\t\tif os.IsNotExist(err) {\n\t\t\treturn \"\", fmt.Errorf(\"can't find %s in standard asset locations or {project_root}/resources\", file)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"can't stat %s: %v\", path, err)\n\t\t}\n\t\treturn path, nil\n\t}\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"can't stat %s: %v\", path, err)\n\t}\n\n\treturn path, nil\n}\n\nfunc TestGeoIPMatcher(t *testing.T) {\n\tcidrList := []*router.CIDR{\n\t\t{Ip: []byte{0, 0, 0, 0}, Prefix: 8},\n\t\t{Ip: []byte{10, 0, 0, 0}, Prefix: 8},\n\t\t{Ip: []byte{100, 64, 0, 0}, Prefix: 10},\n\t\t{Ip: []byte{127, 0, 0, 0}, Prefix: 8},\n\t\t{Ip: []byte{169, 254, 0, 0}, Prefix: 16},\n\t\t{Ip: []byte{172, 16, 0, 0}, Prefix: 12},\n\t\t{Ip: []byte{192, 0, 0, 0}, Prefix: 24},\n\t\t{Ip: []byte{192, 0, 2, 0}, Prefix: 24},\n\t\t{Ip: []byte{192, 168, 0, 0}, Prefix: 16},\n\t\t{Ip: []byte{192, 18, 0, 0}, Prefix: 15},\n\t\t{Ip: []byte{198, 51, 100, 0}, Prefix: 24},\n\t\t{Ip: []byte{203, 0, 113, 0}, Prefix: 24},\n\t\t{Ip: []byte{8, 8, 8, 8}, Prefix: 32},\n\t\t{Ip: []byte{91, 108, 4, 0}, Prefix: 16},\n\t}\n\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: cidrList,\n\t})\n\tcommon.Must(err)\n\n\ttestCases := []struct {\n\t\tInput  string\n\t\tOutput bool\n\t}{\n\t\t{\n\t\t\tInput:  \"192.168.1.1\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tInput:  \"192.0.0.0\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tInput:  \"192.0.1.0\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tInput:  \"0.1.0.0\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tInput:  \"1.0.0.1\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tInput:  \"8.8.8.7\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tInput:  \"8.8.8.8\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tInput:  \"2001:cdba::3257:9652\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tInput:  \"91.108.255.254\",\n\t\t\tOutput: true,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tip := net.ParseAddress(testCase.Input).IP()\n\t\tactual := matcher.Match(ip)\n\t\tif actual != testCase.Output {\n\t\t\tt.Error(\"expect input\", testCase.Input, \"to be\", testCase.Output, \", but actually\", actual)\n\t\t}\n\t}\n}\n\nfunc TestGeoIPMatcherRegression(t *testing.T) {\n\tcidrList := []*router.CIDR{\n\t\t{Ip: []byte{98, 108, 20, 0}, Prefix: 22},\n\t\t{Ip: []byte{98, 108, 20, 0}, Prefix: 23},\n\t}\n\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: cidrList,\n\t})\n\tcommon.Must(err)\n\n\ttestCases := []struct {\n\t\tInput  string\n\t\tOutput bool\n\t}{\n\t\t{\n\t\t\tInput:  \"98.108.22.11\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tInput:  \"98.108.25.0\",\n\t\t\tOutput: false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tip := net.ParseAddress(testCase.Input).IP()\n\t\tactual := matcher.Match(ip)\n\t\tif actual != testCase.Output {\n\t\t\tt.Error(\"expect input\", testCase.Input, \"to be\", testCase.Output, \", but actually\", actual)\n\t\t}\n\t}\n}\n\nfunc TestGeoIPReverseMatcher(t *testing.T) {\n\tcidrList := []*router.CIDR{\n\t\t{Ip: []byte{8, 8, 8, 8}, Prefix: 32},\n\t\t{Ip: []byte{91, 108, 4, 0}, Prefix: 16},\n\t}\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: cidrList,\n\t})\n\tcommon.Must(err)\n\tmatcher.SetReverse(true) // Reverse match\n\n\ttestCases := []struct {\n\t\tInput  string\n\t\tOutput bool\n\t}{\n\t\t{\n\t\t\tInput:  \"8.8.8.8\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tInput:  \"2001:cdba::3257:9652\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tInput:  \"91.108.255.254\",\n\t\t\tOutput: false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tip := net.ParseAddress(testCase.Input).IP()\n\t\tactual := matcher.Match(ip)\n\t\tif actual != testCase.Output {\n\t\t\tt.Error(\"expect input\", testCase.Input, \"to be\", testCase.Output, \", but actually\", actual)\n\t\t}\n\t}\n}\n\nfunc TestGeoIPMatcher4CN(t *testing.T) {\n\tips, err := loadGeoIP(\"CN\")\n\tcommon.Must(err)\n\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: ips,\n\t})\n\tcommon.Must(err)\n\n\tif matcher.Match([]byte{8, 8, 8, 8}) {\n\t\tt.Error(\"expect CN geoip doesn't contain 8.8.8.8, but actually does\")\n\t}\n}\n\nfunc TestGeoIPMatcher6US(t *testing.T) {\n\tips, err := loadGeoIP(\"US\")\n\tcommon.Must(err)\n\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: ips,\n\t})\n\tcommon.Must(err)\n\n\tif !matcher.Match(net.ParseAddress(\"2001:4860:4860::8888\").IP()) {\n\t\tt.Error(\"expect US geoip contain 2001:4860:4860::8888, but actually not\")\n\t}\n}\n\nfunc loadGeoIP(country string) ([]*router.CIDR, error) {\n\tpath, err := getAssetPath(\"geoip.dat\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgeoipBytes, err := filesystem.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar geoipList router.GeoIPList\n\tif err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, geoip := range geoipList.Entry {\n\t\tif geoip.CountryCode == country {\n\t\t\treturn geoip.Cidr, nil\n\t\t}\n\t}\n\n\tpanic(\"country not found: \" + country)\n}\n\nfunc BenchmarkGeoIPMatcher4CN(b *testing.B) {\n\tips, err := loadGeoIP(\"CN\")\n\tcommon.Must(err)\n\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: ips,\n\t})\n\tcommon.Must(err)\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = matcher.Match([]byte{8, 8, 8, 8})\n\t}\n}\n\nfunc BenchmarkGeoIPMatcher6US(b *testing.B) {\n\tips, err := loadGeoIP(\"US\")\n\tcommon.Must(err)\n\n\tmatcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{\n\t\tCidr: ips,\n\t})\n\tcommon.Must(err)\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = matcher.Match(net.ParseAddress(\"2001:4860:4860::8888\").IP())\n\t}\n}\n"
  },
  {
    "path": "app/router/condition_serialize_test.go",
    "content": "package router_test\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n)\n\nfunc TestDomainMatcherSerialization(t *testing.T) {\n\n\tdomains := []*router.Domain{\n\t\t{Type: router.Domain_Domain, Value: \"google.com\"},\n\t\t{Type: router.Domain_Domain, Value: \"v2ray.com\"},\n\t\t{Type: router.Domain_Full, Value: \"full.example.com\"},\n\t}\n\n\tvar buf bytes.Buffer\n\tif err := router.SerializeDomainMatcher(domains, &buf); err != nil {\n\t\tt.Fatalf(\"Serialize failed: %v\", err)\n\t}\n\n\tmatcher, err := router.NewDomainMatcherFromBuffer(buf.Bytes())\n\tif err != nil {\n\t\tt.Fatalf(\"Deserialize failed: %v\", err)\n\t}\n\n\tdMatcher := &router.DomainMatcher{\n\t\tMatchers: matcher,\n\t}\n\ttestCases := []struct {\n\t\tInput string\n\t\tMatch bool\n\t}{\n\t\t{\"google.com\", true},\n\t\t{\"maps.google.com\", true},\n\t\t{\"v2ray.com\", true},\n\t\t{\"full.example.com\", true},\n\n\t\t{\"example.com\", false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tif res := dMatcher.ApplyDomain(tc.Input); res != tc.Match {\n\t\t\tt.Errorf(\"Match(%s) = %v, want %v\", tc.Input, res, tc.Match)\n\t\t}\n\t}\n}\n\nfunc TestGeoSiteSerialization(t *testing.T) {\n\tsites := []*router.GeoSite{\n\t\t{\n\t\t\tCountryCode: \"CN\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Domain, Value: \"baidu.cn\"},\n\t\t\t\t{Type: router.Domain_Domain, Value: \"qq.com\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tCountryCode: \"US\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Domain, Value: \"google.com\"},\n\t\t\t\t{Type: router.Domain_Domain, Value: \"facebook.com\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tvar buf bytes.Buffer\n\tif err := router.SerializeGeoSiteList(sites, nil, nil, &buf); err != nil {\n\t\tt.Fatalf(\"SerializeGeoSiteList failed: %v\", err)\n\t}\n\n\ttmp := t.TempDir()\n\tpath := filepath.Join(tmp, \"matcher.cache\")\n\n\tf, err := os.Create(path)\n\trequire.NoError(t, err)\n\t_, err = f.Write(buf.Bytes())\n\trequire.NoError(t, err)\n\tf.Close()\n\n\tf, err = os.Open(path)\n\trequire.NoError(t, err)\n\tdefer f.Close()\n\n\trequire.NoError(t, err)\n\tdata, _ := filesystem.ReadFile(path)\n\n\t// cn\n\tgp, err := router.LoadGeoSiteMatcher(bytes.NewReader(data), \"CN\")\n\tif err != nil {\n\t\tt.Fatalf(\"LoadGeoSiteMatcher(CN) failed: %v\", err)\n\t}\n\n\tcnMatcher := &router.DomainMatcher{\n\t\tMatchers: gp,\n\t}\n\n\tif !cnMatcher.ApplyDomain(\"baidu.cn\") {\n\t\tt.Error(\"CN matcher should match baidu.cn\")\n\t}\n\tif cnMatcher.ApplyDomain(\"google.com\") {\n\t\tt.Error(\"CN matcher should NOT match google.com\")\n\t}\n\n\t// us\n\tgp, err = router.LoadGeoSiteMatcher(bytes.NewReader(data), \"US\")\n\tif err != nil {\n\t\tt.Fatalf(\"LoadGeoSiteMatcher(US) failed: %v\", err)\n\t}\n\n\tusMatcher := &router.DomainMatcher{\n\t\tMatchers: gp,\n\t}\n\tif !usMatcher.ApplyDomain(\"google.com\") {\n\t\tt.Error(\"US matcher should match google.com\")\n\t}\n\tif usMatcher.ApplyDomain(\"baidu.cn\") {\n\t\tt.Error(\"US matcher should NOT match baidu.cn\")\n\t}\n\n\t// unknown\n\t_, err = router.LoadGeoSiteMatcher(bytes.NewReader(data), \"unknown\")\n\tif err == nil {\n\t\tt.Error(\"LoadGeoSiteMatcher(unknown) should fail\")\n\t}\n}\nfunc TestGeoSiteSerializationWithDeps(t *testing.T) {\n\tsites := []*router.GeoSite{\n\t\t{\n\t\t\tCountryCode: \"geosite:cn\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Domain, Value: \"baidu.cn\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tCountryCode: \"geosite:google@cn\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Domain, Value: \"google.cn\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tCountryCode: \"rule-1\",\n\t\t\tDomain: []*router.Domain{\n\t\t\t\t{Type: router.Domain_Domain, Value: \"google.com\"},\n\t\t\t},\n\t\t},\n\t}\n\tdeps := map[string][]string{\n\t\t\"rule-1\": {\"geosite:cn\", \"geosite:google@cn\"},\n\t}\n\n\tvar buf bytes.Buffer\n\terr := router.SerializeGeoSiteList(sites, deps, nil, &buf)\n\trequire.NoError(t, err)\n\n\tmatcher, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), \"rule-1\")\n\trequire.NoError(t, err)\n\n\trequire.True(t, matcher.Match(\"google.com\") != nil)\n\trequire.True(t, matcher.Match(\"baidu.cn\") != nil)\n\trequire.True(t, matcher.Match(\"google.cn\") != nil)\n}\n"
  },
  {
    "path": "app/router/condition_test.go",
    "content": "package router_test\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/protocol/http\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\trouting_session \"github.com/xtls/xray-core/features/routing/session\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc withBackground() routing.Context {\n\treturn &routing_session.Context{}\n}\n\nfunc withOutbound(outbound *session.Outbound) routing.Context {\n\treturn &routing_session.Context{Outbound: outbound}\n}\n\nfunc withInbound(inbound *session.Inbound) routing.Context {\n\treturn &routing_session.Context{Inbound: inbound}\n}\n\nfunc withContent(content *session.Content) routing.Context {\n\treturn &routing_session.Context{Content: content}\n}\n\nfunc TestRoutingRule(t *testing.T) {\n\ttype ruleTest struct {\n\t\tinput  routing.Context\n\t\toutput bool\n\t}\n\n\tcases := []struct {\n\t\trule *RoutingRule\n\t\ttest []ruleTest\n\t}{\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tDomain: []*Domain{\n\t\t\t\t\t{\n\t\t\t\t\t\tValue: \"example.com\",\n\t\t\t\t\t\tType:  Domain_Plain,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tValue: \"google.com\",\n\t\t\t\t\t\tType:  Domain_Domain,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tValue: \"^facebook\\\\.com$\",\n\t\t\t\t\t\tType:  Domain_Regex,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"example.com\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"www.example.com.www\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"example.co\"), 80)}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"www.google.com\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"facebook.com\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"www.facebook.com\"), 80)}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withBackground(),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tGeoip: []*GeoIP{\n\t\t\t\t\t{\n\t\t\t\t\t\tCidr: []*CIDR{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     []byte{8, 8, 8, 8},\n\t\t\t\t\t\t\t\tPrefix: 32,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     []byte{8, 8, 8, 8},\n\t\t\t\t\t\t\t\tPrefix: 32,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     net.ParseAddress(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\").IP(),\n\t\t\t\t\t\t\t\tPrefix: 128,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress(\"8.8.8.8\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress(\"8.8.4.4\"), 80)}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withBackground(),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tSourceGeoip: []*GeoIP{\n\t\t\t\t\t{\n\t\t\t\t\t\tCidr: []*CIDR{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     []byte{192, 168, 0, 0},\n\t\t\t\t\t\t\t\tPrefix: 16,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress(\"192.168.0.1\"), 80)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress(\"10.0.0.1\"), 80)}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tUserEmail: []string{\n\t\t\t\t\t\"admin@example.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: \"admin@example.com\"}}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: \"love@example.com\"}}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withBackground(),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tProtocol: []string{\"http\"},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tInboundTag: []string{\"test\", \"test1\"},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Tag: \"test\"}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Tag: \"test2\"}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tPortList: &net.PortList{\n\t\t\t\t\tRange: []*net.PortRange{\n\t\t\t\t\t\t{From: 443, To: 443},\n\t\t\t\t\t\t{From: 1000, To: 1100},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 443)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1100)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1005)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 53)}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tSourcePortList: &net.PortList{\n\t\t\t\t\tRange: []*net.PortRange{\n\t\t\t\t\t\t{From: 123, To: 123},\n\t\t\t\t\t\t{From: 9993, To: 9999},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 123)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9999)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9994)}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tinput:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 53)}),\n\t\t\t\t\toutput: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tProtocol: []string{\"http\"},\n\t\t\t\tAttributes: map[string]string{\n\t\t\t\t\t\":path\": \"/test\",\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withContent(&session.Content{Protocol: \"http/1.1\", Attributes: map[string]string{\":path\": \"/test/1\"}}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trule: &RoutingRule{\n\t\t\t\tAttributes: map[string]string{\n\t\t\t\t\t\"Custom\": \"p([a-z]+)ch\",\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: []ruleTest{\n\t\t\t\t{\n\t\t\t\t\tinput:  withContent(&session.Content{Attributes: map[string]string{\"custom\": \"peach\"}}),\n\t\t\t\t\toutput: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range cases {\n\t\tcond, err := test.rule.BuildCondition()\n\t\tcommon.Must(err)\n\n\t\tfor _, subtest := range test.test {\n\t\t\tactual := cond.Apply(subtest.input)\n\t\t\tif actual != subtest.output {\n\t\t\t\tt.Error(\"test case failed: \", subtest.input, \" expected \", subtest.output, \" but got \", actual)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc loadGeoSite(country string) ([]*Domain, error) {\n\tpath, err := getAssetPath(\"geosite.dat\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgeositeBytes, err := filesystem.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar geositeList GeoSiteList\n\tif err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, site := range geositeList.Entry {\n\t\tif site.CountryCode == country {\n\t\t\treturn site.Domain, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"country not found: \" + country)\n}\n\nfunc TestChinaSites(t *testing.T) {\n\tdomains, err := loadGeoSite(\"CN\")\n\tcommon.Must(err)\n\n\tacMatcher, err := NewMphMatcherGroup(domains)\n\tcommon.Must(err)\n\n\ttype TestCase struct {\n\t\tDomain string\n\t\tOutput bool\n\t}\n\ttestCases := []TestCase{\n\t\t{\n\t\t\tDomain: \"163.com\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tDomain: \"163.com\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tDomain: \"164.com\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tDomain: \"164.com\",\n\t\t\tOutput: false,\n\t\t},\n\t}\n\n\tfor i := 0; i < 1024; i++ {\n\t\ttestCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + \".not-exists.com\", Output: false})\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tr := acMatcher.ApplyDomain(testCase.Domain)\n\t\tif r != testCase.Output {\n\t\t\tt.Error(\"ACDomainMatcher expected output \", testCase.Output, \" for domain \", testCase.Domain, \" but got \", r)\n\t\t}\n\t}\n}\n\nfunc BenchmarkMphDomainMatcher(b *testing.B) {\n\tdomains, err := loadGeoSite(\"CN\")\n\tcommon.Must(err)\n\n\tmatcher, err := NewMphMatcherGroup(domains)\n\tcommon.Must(err)\n\n\ttype TestCase struct {\n\t\tDomain string\n\t\tOutput bool\n\t}\n\ttestCases := []TestCase{\n\t\t{\n\t\t\tDomain: \"163.com\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tDomain: \"163.com\",\n\t\t\tOutput: true,\n\t\t},\n\t\t{\n\t\t\tDomain: \"164.com\",\n\t\t\tOutput: false,\n\t\t},\n\t\t{\n\t\t\tDomain: \"164.com\",\n\t\t\tOutput: false,\n\t\t},\n\t}\n\n\tfor i := 0; i < 1024; i++ {\n\t\ttestCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + \".not-exists.com\", Output: false})\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, testCase := range testCases {\n\t\t\t_ = matcher.ApplyDomain(testCase.Domain)\n\t\t}\n\t}\n}\n\nfunc BenchmarkMultiGeoIPMatcher(b *testing.B) {\n\tvar geoips []*GeoIP\n\n\t{\n\t\tips, err := loadGeoIP(\"CN\")\n\t\tcommon.Must(err)\n\t\tgeoips = append(geoips, &GeoIP{\n\t\t\tCountryCode: \"CN\",\n\t\t\tCidr:        ips,\n\t\t})\n\t}\n\n\t{\n\t\tips, err := loadGeoIP(\"JP\")\n\t\tcommon.Must(err)\n\t\tgeoips = append(geoips, &GeoIP{\n\t\t\tCountryCode: \"JP\",\n\t\t\tCidr:        ips,\n\t\t})\n\t}\n\n\t{\n\t\tips, err := loadGeoIP(\"CA\")\n\t\tcommon.Must(err)\n\t\tgeoips = append(geoips, &GeoIP{\n\t\t\tCountryCode: \"CA\",\n\t\t\tCidr:        ips,\n\t\t})\n\t}\n\n\t{\n\t\tips, err := loadGeoIP(\"US\")\n\t\tcommon.Must(err)\n\t\tgeoips = append(geoips, &GeoIP{\n\t\t\tCountryCode: \"US\",\n\t\t\tCidr:        ips,\n\t\t})\n\t}\n\n\tmatcher, err := NewIPMatcher(geoips, MatcherAsType_Target)\n\tcommon.Must(err)\n\n\tctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress(\"8.8.8.8\"), 80)})\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = matcher.Apply(ctx)\n\t}\n}\n"
  },
  {
    "path": "app/router/config.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\ntype Rule struct {\n\tTag       string\n\tRuleTag   string\n\tBalancer  *Balancer\n\tCondition Condition\n\tWebhook   *WebhookNotifier\n}\n\nfunc (r *Rule) GetTag() (string, error) {\n\tif r.Balancer != nil {\n\t\treturn r.Balancer.PickOutbound()\n\t}\n\treturn r.Tag, nil\n}\n\n// Apply checks rule matching of current routing context.\nfunc (r *Rule) Apply(ctx routing.Context) bool {\n\treturn r.Condition.Apply(ctx)\n}\n\nfunc (rr *RoutingRule) BuildCondition() (Condition, error) {\n\tconds := NewConditionChan()\n\n\tif len(rr.InboundTag) > 0 {\n\t\tconds.Add(NewInboundTagMatcher(rr.InboundTag))\n\t}\n\n\tif len(rr.Networks) > 0 {\n\t\tconds.Add(NewNetworkMatcher(rr.Networks))\n\t}\n\n\tif len(rr.Protocol) > 0 {\n\t\tconds.Add(NewProtocolMatcher(rr.Protocol))\n\t}\n\n\tif rr.PortList != nil {\n\t\tconds.Add(NewPortMatcher(rr.PortList, MatcherAsType_Target))\n\t}\n\n\tif rr.SourcePortList != nil {\n\t\tconds.Add(NewPortMatcher(rr.SourcePortList, MatcherAsType_Source))\n\t}\n\n\tif rr.LocalPortList != nil {\n\t\tconds.Add(NewPortMatcher(rr.LocalPortList, MatcherAsType_Local))\n\t}\n\n\tif rr.VlessRouteList != nil {\n\t\tconds.Add(NewPortMatcher(rr.VlessRouteList, MatcherAsType_VlessRoute))\n\t}\n\n\tif len(rr.UserEmail) > 0 {\n\t\tconds.Add(NewUserMatcher(rr.UserEmail))\n\t}\n\n\tif len(rr.Attributes) > 0 {\n\t\tconfiguredKeys := make(map[string]*regexp.Regexp)\n\t\tfor key, value := range rr.Attributes {\n\t\t\tconfiguredKeys[strings.ToLower(key)] = regexp.MustCompile(value)\n\t\t}\n\t\tconds.Add(&AttributeMatcher{configuredKeys})\n\t}\n\n\tif len(rr.Geoip) > 0 {\n\t\tcond, err := NewIPMatcher(rr.Geoip, MatcherAsType_Target)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconds.Add(cond)\n\t\trr.Geoip = nil\n\t\truntime.GC()\n\t}\n\n\tif len(rr.SourceGeoip) > 0 {\n\t\tcond, err := NewIPMatcher(rr.SourceGeoip, MatcherAsType_Source)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconds.Add(cond)\n\t\trr.SourceGeoip = nil\n\t\truntime.GC()\n\t}\n\n\tif len(rr.LocalGeoip) > 0 {\n\t\tcond, err := NewIPMatcher(rr.LocalGeoip, MatcherAsType_Local)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconds.Add(cond)\n\t\terrors.LogWarning(context.Background(), \"Due to some limitations, in UDP connections, localIP is always equal to listen interface IP, so \\\"localIP\\\" rule condition does not work properly on UDP inbound connections that listen on all interfaces\")\n\t\trr.LocalGeoip = nil\n\t\truntime.GC()\n\t}\n\n\tif len(rr.Domain) > 0 {\n\t\tvar matcher *DomainMatcher\n\t\tvar err error\n\t\t// Check if domain matcher cache is provided via environment\n\t\tdomainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return \"\" })\n\n\t\tif domainMatcherPath != \"\" {\n\t\t\tmatcher, err = GetDomainMatcherWithRuleTag(domainMatcherPath, rr.RuleTag)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to build domain condition from cached MphDomainMatcher\").Base(err)\n\t\t\t}\n\t\t\terrors.LogDebug(context.Background(), \"MphDomainMatcher loaded from cache for \", rr.RuleTag, \" rule tag)\")\n\n\t\t} else {\n\t\t\tmatcher, err = NewMphMatcherGroup(rr.Domain)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to build domain condition with MphDomainMatcher\").Base(err)\n\t\t\t}\n\t\t\terrors.LogDebug(context.Background(), \"MphDomainMatcher is enabled for \", len(rr.Domain), \" domain rule(s)\")\n\t\t}\n\t\tconds.Add(matcher)\n\t\trr.Domain = nil\n\t\truntime.GC()\n\t}\n\n\tif len(rr.Process) > 0 {\n\t\tconds.Add(NewProcessNameMatcher(rr.Process))\n\t}\n\n\tif conds.Len() == 0 {\n\t\treturn nil, errors.New(\"this rule has no effective fields\").AtWarning()\n\t}\n\n\treturn conds, nil\n}\n\n// Build builds the balancing rule\nfunc (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatcher) (*Balancer, error) {\n\tswitch strings.ToLower(br.Strategy) {\n\tcase \"leastping\":\n\t\treturn &Balancer{\n\t\t\tselectors:   br.OutboundSelector,\n\t\t\tstrategy:    &LeastPingStrategy{},\n\t\t\tfallbackTag: br.FallbackTag,\n\t\t\tohm:         ohm,\n\t\t}, nil\n\tcase \"roundrobin\":\n\t\treturn &Balancer{\n\t\t\tselectors:   br.OutboundSelector,\n\t\t\tstrategy:    &RoundRobinStrategy{FallbackTag: br.FallbackTag},\n\t\t\tfallbackTag: br.FallbackTag,\n\t\t\tohm:         ohm,\n\t\t}, nil\n\tcase \"leastload\":\n\t\ti, err := br.StrategySettings.GetInstance()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ts, ok := i.(*StrategyLeastLoadConfig)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"not a StrategyLeastLoadConfig\").AtError()\n\t\t}\n\t\tleastLoadStrategy := NewLeastLoadStrategy(s)\n\t\treturn &Balancer{\n\t\t\tselectors:   br.OutboundSelector,\n\t\t\tohm:         ohm,\n\t\t\tfallbackTag: br.FallbackTag,\n\t\t\tstrategy:    leastLoadStrategy,\n\t\t}, nil\n\tcase \"random\":\n\t\tfallthrough\n\tcase \"\":\n\t\treturn &Balancer{\n\t\t\tselectors:   br.OutboundSelector,\n\t\t\tohm:         ohm,\n\t\t\tfallbackTag: br.FallbackTag,\n\t\t\tstrategy:    &RandomStrategy{FallbackTag: br.FallbackTag},\n\t\t}, nil\n\tdefault:\n\t\treturn nil, errors.New(\"unrecognized balancer type\")\n\t}\n}\n\nfunc GetDomainMatcherWithRuleTag(domainMatcherPath string, ruleTag string) (*DomainMatcher, error) {\n\tf, err := filesystem.NewFileReader(domainMatcherPath)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to load file: \", domainMatcherPath).Base(err)\n\t}\n\tdefer f.Close()\n\n\tg, err := LoadGeoSiteMatcher(f, ruleTag)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to load file:\", domainMatcherPath).Base(err)\n\t}\n\treturn &DomainMatcher{\n\t\tMatchers: g,\n\t}, nil\n\n}\n"
  },
  {
    "path": "app/router/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/router/config.proto\n\npackage router\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Type of domain value.\ntype Domain_Type int32\n\nconst (\n\t// The value is used as is.\n\tDomain_Plain Domain_Type = 0\n\t// The value is used as a regular expression.\n\tDomain_Regex Domain_Type = 1\n\t// The value is a root domain.\n\tDomain_Domain Domain_Type = 2\n\t// The value is a domain.\n\tDomain_Full Domain_Type = 3\n)\n\n// Enum value maps for Domain_Type.\nvar (\n\tDomain_Type_name = map[int32]string{\n\t\t0: \"Plain\",\n\t\t1: \"Regex\",\n\t\t2: \"Domain\",\n\t\t3: \"Full\",\n\t}\n\tDomain_Type_value = map[string]int32{\n\t\t\"Plain\":  0,\n\t\t\"Regex\":  1,\n\t\t\"Domain\": 2,\n\t\t\"Full\":   3,\n\t}\n)\n\nfunc (x Domain_Type) Enum() *Domain_Type {\n\tp := new(Domain_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x Domain_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Domain_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_app_router_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Domain_Type) Type() protoreflect.EnumType {\n\treturn &file_app_router_config_proto_enumTypes[0]\n}\n\nfunc (x Domain_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Domain_Type.Descriptor instead.\nfunc (Domain_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Config_DomainStrategy int32\n\nconst (\n\t// Use domain as is.\n\tConfig_AsIs Config_DomainStrategy = 0\n\t// Resolve to IP if the domain doesn't match any rules.\n\tConfig_IpIfNonMatch Config_DomainStrategy = 2\n\t// Resolve to IP if any rule requires IP matching.\n\tConfig_IpOnDemand Config_DomainStrategy = 3\n)\n\n// Enum value maps for Config_DomainStrategy.\nvar (\n\tConfig_DomainStrategy_name = map[int32]string{\n\t\t0: \"AsIs\",\n\t\t2: \"IpIfNonMatch\",\n\t\t3: \"IpOnDemand\",\n\t}\n\tConfig_DomainStrategy_value = map[string]int32{\n\t\t\"AsIs\":         0,\n\t\t\"IpIfNonMatch\": 2,\n\t\t\"IpOnDemand\":   3,\n\t}\n)\n\nfunc (x Config_DomainStrategy) Enum() *Config_DomainStrategy {\n\tp := new(Config_DomainStrategy)\n\t*p = x\n\treturn p\n}\n\nfunc (x Config_DomainStrategy) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_app_router_config_proto_enumTypes[1].Descriptor()\n}\n\nfunc (Config_DomainStrategy) Type() protoreflect.EnumType {\n\treturn &file_app_router_config_proto_enumTypes[1]\n}\n\nfunc (x Config_DomainStrategy) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Config_DomainStrategy.Descriptor instead.\nfunc (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{11, 0}\n}\n\n// Domain for routing decision.\ntype Domain struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Domain matching type.\n\tType Domain_Type `protobuf:\"varint,1,opt,name=type,proto3,enum=xray.app.router.Domain_Type\" json:\"type,omitempty\"`\n\t// Domain value.\n\tValue string `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\t// Attributes of this domain. May be used for filtering.\n\tAttribute     []*Domain_Attribute `protobuf:\"bytes,3,rep,name=attribute,proto3\" json:\"attribute,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Domain) Reset() {\n\t*x = Domain{}\n\tmi := &file_app_router_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Domain) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Domain) ProtoMessage() {}\n\nfunc (x *Domain) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Domain.ProtoReflect.Descriptor instead.\nfunc (*Domain) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Domain) GetType() Domain_Type {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn Domain_Plain\n}\n\nfunc (x *Domain) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\nfunc (x *Domain) GetAttribute() []*Domain_Attribute {\n\tif x != nil {\n\t\treturn x.Attribute\n\t}\n\treturn nil\n}\n\n// IP for routing decision, in CIDR form.\ntype CIDR struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// IP address, should be either 4 or 16 bytes.\n\tIp []byte `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\t// Number of leading ones in the network mask.\n\tPrefix        uint32 `protobuf:\"varint,2,opt,name=prefix,proto3\" json:\"prefix,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CIDR) Reset() {\n\t*x = CIDR{}\n\tmi := &file_app_router_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CIDR) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CIDR) ProtoMessage() {}\n\nfunc (x *CIDR) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CIDR.ProtoReflect.Descriptor instead.\nfunc (*CIDR) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CIDR) GetIp() []byte {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *CIDR) GetPrefix() uint32 {\n\tif x != nil {\n\t\treturn x.Prefix\n\t}\n\treturn 0\n}\n\ntype GeoIP struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tCountryCode   string                 `protobuf:\"bytes,1,opt,name=country_code,json=countryCode,proto3\" json:\"country_code,omitempty\"`\n\tCidr          []*CIDR                `protobuf:\"bytes,2,rep,name=cidr,proto3\" json:\"cidr,omitempty\"`\n\tReverseMatch  bool                   `protobuf:\"varint,3,opt,name=reverse_match,json=reverseMatch,proto3\" json:\"reverse_match,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GeoIP) Reset() {\n\t*x = GeoIP{}\n\tmi := &file_app_router_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GeoIP) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GeoIP) ProtoMessage() {}\n\nfunc (x *GeoIP) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.\nfunc (*GeoIP) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *GeoIP) GetCountryCode() string {\n\tif x != nil {\n\t\treturn x.CountryCode\n\t}\n\treturn \"\"\n}\n\nfunc (x *GeoIP) GetCidr() []*CIDR {\n\tif x != nil {\n\t\treturn x.Cidr\n\t}\n\treturn nil\n}\n\nfunc (x *GeoIP) GetReverseMatch() bool {\n\tif x != nil {\n\t\treturn x.ReverseMatch\n\t}\n\treturn false\n}\n\ntype GeoIPList struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEntry         []*GeoIP               `protobuf:\"bytes,1,rep,name=entry,proto3\" json:\"entry,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GeoIPList) Reset() {\n\t*x = GeoIPList{}\n\tmi := &file_app_router_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GeoIPList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GeoIPList) ProtoMessage() {}\n\nfunc (x *GeoIPList) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.\nfunc (*GeoIPList) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *GeoIPList) GetEntry() []*GeoIP {\n\tif x != nil {\n\t\treturn x.Entry\n\t}\n\treturn nil\n}\n\ntype GeoSite struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tCountryCode   string                 `protobuf:\"bytes,1,opt,name=country_code,json=countryCode,proto3\" json:\"country_code,omitempty\"`\n\tDomain        []*Domain              `protobuf:\"bytes,2,rep,name=domain,proto3\" json:\"domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GeoSite) Reset() {\n\t*x = GeoSite{}\n\tmi := &file_app_router_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GeoSite) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GeoSite) ProtoMessage() {}\n\nfunc (x *GeoSite) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead.\nfunc (*GeoSite) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *GeoSite) GetCountryCode() string {\n\tif x != nil {\n\t\treturn x.CountryCode\n\t}\n\treturn \"\"\n}\n\nfunc (x *GeoSite) GetDomain() []*Domain {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn nil\n}\n\ntype GeoSiteList struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEntry         []*GeoSite             `protobuf:\"bytes,1,rep,name=entry,proto3\" json:\"entry,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GeoSiteList) Reset() {\n\t*x = GeoSiteList{}\n\tmi := &file_app_router_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GeoSiteList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GeoSiteList) ProtoMessage() {}\n\nfunc (x *GeoSiteList) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead.\nfunc (*GeoSiteList) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *GeoSiteList) GetEntry() []*GeoSite {\n\tif x != nil {\n\t\treturn x.Entry\n\t}\n\treturn nil\n}\n\ntype RoutingRule struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Types that are valid to be assigned to TargetTag:\n\t//\n\t//\t*RoutingRule_Tag\n\t//\t*RoutingRule_BalancingTag\n\tTargetTag isRoutingRule_TargetTag `protobuf_oneof:\"target_tag\"`\n\tRuleTag   string                  `protobuf:\"bytes,19,opt,name=rule_tag,json=ruleTag,proto3\" json:\"rule_tag,omitempty\"`\n\t// List of domains for target domain matching.\n\tDomain []*Domain `protobuf:\"bytes,2,rep,name=domain,proto3\" json:\"domain,omitempty\"`\n\t// List of GeoIPs for target IP address matching. If this entry exists, the\n\t// cidr above will have no effect. GeoIP fields with the same country code are\n\t// supposed to contain exactly same content. They will be merged during\n\t// runtime. For customized GeoIPs, please leave country code empty.\n\tGeoip []*GeoIP `protobuf:\"bytes,10,rep,name=geoip,proto3\" json:\"geoip,omitempty\"`\n\t// List of ports.\n\tPortList *net.PortList `protobuf:\"bytes,14,opt,name=port_list,json=portList,proto3\" json:\"port_list,omitempty\"`\n\t// List of networks for matching.\n\tNetworks []net.Network `protobuf:\"varint,13,rep,packed,name=networks,proto3,enum=xray.common.net.Network\" json:\"networks,omitempty\"`\n\t// List of GeoIPs for source IP address matching. If this entry exists, the\n\t// source_cidr above will have no effect.\n\tSourceGeoip []*GeoIP `protobuf:\"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3\" json:\"source_geoip,omitempty\"`\n\t// List of ports for source port matching.\n\tSourcePortList *net.PortList     `protobuf:\"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3\" json:\"source_port_list,omitempty\"`\n\tUserEmail      []string          `protobuf:\"bytes,7,rep,name=user_email,json=userEmail,proto3\" json:\"user_email,omitempty\"`\n\tInboundTag     []string          `protobuf:\"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3\" json:\"inbound_tag,omitempty\"`\n\tProtocol       []string          `protobuf:\"bytes,9,rep,name=protocol,proto3\" json:\"protocol,omitempty\"`\n\tAttributes     map[string]string `protobuf:\"bytes,15,rep,name=attributes,proto3\" json:\"attributes,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tLocalGeoip     []*GeoIP          `protobuf:\"bytes,17,rep,name=local_geoip,json=localGeoip,proto3\" json:\"local_geoip,omitempty\"`\n\tLocalPortList  *net.PortList     `protobuf:\"bytes,18,opt,name=local_port_list,json=localPortList,proto3\" json:\"local_port_list,omitempty\"`\n\tVlessRouteList *net.PortList     `protobuf:\"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3\" json:\"vless_route_list,omitempty\"`\n\tProcess        []string          `protobuf:\"bytes,21,rep,name=process,proto3\" json:\"process,omitempty\"`\n\tWebhook        *WebhookConfig    `protobuf:\"bytes,22,opt,name=webhook,proto3\" json:\"webhook,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *RoutingRule) Reset() {\n\t*x = RoutingRule{}\n\tmi := &file_app_router_config_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RoutingRule) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RoutingRule) ProtoMessage() {}\n\nfunc (x *RoutingRule) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RoutingRule.ProtoReflect.Descriptor instead.\nfunc (*RoutingRule) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *RoutingRule) GetTargetTag() isRoutingRule_TargetTag {\n\tif x != nil {\n\t\treturn x.TargetTag\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetTag() string {\n\tif x != nil {\n\t\tif x, ok := x.TargetTag.(*RoutingRule_Tag); ok {\n\t\t\treturn x.Tag\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingRule) GetBalancingTag() string {\n\tif x != nil {\n\t\tif x, ok := x.TargetTag.(*RoutingRule_BalancingTag); ok {\n\t\t\treturn x.BalancingTag\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingRule) GetRuleTag() string {\n\tif x != nil {\n\t\treturn x.RuleTag\n\t}\n\treturn \"\"\n}\n\nfunc (x *RoutingRule) GetDomain() []*Domain {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetGeoip() []*GeoIP {\n\tif x != nil {\n\t\treturn x.Geoip\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetPortList() *net.PortList {\n\tif x != nil {\n\t\treturn x.PortList\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetNetworks() []net.Network {\n\tif x != nil {\n\t\treturn x.Networks\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetSourceGeoip() []*GeoIP {\n\tif x != nil {\n\t\treturn x.SourceGeoip\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetSourcePortList() *net.PortList {\n\tif x != nil {\n\t\treturn x.SourcePortList\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetUserEmail() []string {\n\tif x != nil {\n\t\treturn x.UserEmail\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetInboundTag() []string {\n\tif x != nil {\n\t\treturn x.InboundTag\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetProtocol() []string {\n\tif x != nil {\n\t\treturn x.Protocol\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetAttributes() map[string]string {\n\tif x != nil {\n\t\treturn x.Attributes\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetLocalGeoip() []*GeoIP {\n\tif x != nil {\n\t\treturn x.LocalGeoip\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetLocalPortList() *net.PortList {\n\tif x != nil {\n\t\treturn x.LocalPortList\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetVlessRouteList() *net.PortList {\n\tif x != nil {\n\t\treturn x.VlessRouteList\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetProcess() []string {\n\tif x != nil {\n\t\treturn x.Process\n\t}\n\treturn nil\n}\n\nfunc (x *RoutingRule) GetWebhook() *WebhookConfig {\n\tif x != nil {\n\t\treturn x.Webhook\n\t}\n\treturn nil\n}\n\ntype isRoutingRule_TargetTag interface {\n\tisRoutingRule_TargetTag()\n}\n\ntype RoutingRule_Tag struct {\n\t// Tag of outbound that this rule is pointing to.\n\tTag string `protobuf:\"bytes,1,opt,name=tag,proto3,oneof\"`\n}\n\ntype RoutingRule_BalancingTag struct {\n\t// Tag of routing balancer.\n\tBalancingTag string `protobuf:\"bytes,12,opt,name=balancing_tag,json=balancingTag,proto3,oneof\"`\n}\n\nfunc (*RoutingRule_Tag) isRoutingRule_TargetTag() {}\n\nfunc (*RoutingRule_BalancingTag) isRoutingRule_TargetTag() {}\n\ntype WebhookConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUrl           string                 `protobuf:\"bytes,1,opt,name=url,proto3\" json:\"url,omitempty\"`\n\tDeduplication uint32                 `protobuf:\"varint,2,opt,name=deduplication,proto3\" json:\"deduplication,omitempty\"`\n\tHeaders       map[string]string      `protobuf:\"bytes,3,rep,name=headers,proto3\" json:\"headers,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WebhookConfig) Reset() {\n\t*x = WebhookConfig{}\n\tmi := &file_app_router_config_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WebhookConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WebhookConfig) ProtoMessage() {}\n\nfunc (x *WebhookConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WebhookConfig.ProtoReflect.Descriptor instead.\nfunc (*WebhookConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *WebhookConfig) GetUrl() string {\n\tif x != nil {\n\t\treturn x.Url\n\t}\n\treturn \"\"\n}\n\nfunc (x *WebhookConfig) GetDeduplication() uint32 {\n\tif x != nil {\n\t\treturn x.Deduplication\n\t}\n\treturn 0\n}\n\nfunc (x *WebhookConfig) GetHeaders() map[string]string {\n\tif x != nil {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\ntype BalancingRule struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag              string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tOutboundSelector []string               `protobuf:\"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3\" json:\"outbound_selector,omitempty\"`\n\tStrategy         string                 `protobuf:\"bytes,3,opt,name=strategy,proto3\" json:\"strategy,omitempty\"`\n\tStrategySettings *serial.TypedMessage   `protobuf:\"bytes,4,opt,name=strategy_settings,json=strategySettings,proto3\" json:\"strategy_settings,omitempty\"`\n\tFallbackTag      string                 `protobuf:\"bytes,5,opt,name=fallback_tag,json=fallbackTag,proto3\" json:\"fallback_tag,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *BalancingRule) Reset() {\n\t*x = BalancingRule{}\n\tmi := &file_app_router_config_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *BalancingRule) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BalancingRule) ProtoMessage() {}\n\nfunc (x *BalancingRule) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead.\nfunc (*BalancingRule) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *BalancingRule) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *BalancingRule) GetOutboundSelector() []string {\n\tif x != nil {\n\t\treturn x.OutboundSelector\n\t}\n\treturn nil\n}\n\nfunc (x *BalancingRule) GetStrategy() string {\n\tif x != nil {\n\t\treturn x.Strategy\n\t}\n\treturn \"\"\n}\n\nfunc (x *BalancingRule) GetStrategySettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.StrategySettings\n\t}\n\treturn nil\n}\n\nfunc (x *BalancingRule) GetFallbackTag() string {\n\tif x != nil {\n\t\treturn x.FallbackTag\n\t}\n\treturn \"\"\n}\n\ntype StrategyWeight struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRegexp        bool                   `protobuf:\"varint,1,opt,name=regexp,proto3\" json:\"regexp,omitempty\"`\n\tMatch         string                 `protobuf:\"bytes,2,opt,name=match,proto3\" json:\"match,omitempty\"`\n\tValue         float32                `protobuf:\"fixed32,3,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *StrategyWeight) Reset() {\n\t*x = StrategyWeight{}\n\tmi := &file_app_router_config_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StrategyWeight) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StrategyWeight) ProtoMessage() {}\n\nfunc (x *StrategyWeight) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use StrategyWeight.ProtoReflect.Descriptor instead.\nfunc (*StrategyWeight) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *StrategyWeight) GetRegexp() bool {\n\tif x != nil {\n\t\treturn x.Regexp\n\t}\n\treturn false\n}\n\nfunc (x *StrategyWeight) GetMatch() string {\n\tif x != nil {\n\t\treturn x.Match\n\t}\n\treturn \"\"\n}\n\nfunc (x *StrategyWeight) GetValue() float32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype StrategyLeastLoadConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// weight settings\n\tCosts []*StrategyWeight `protobuf:\"bytes,2,rep,name=costs,proto3\" json:\"costs,omitempty\"`\n\t// RTT baselines for selecting, int64 values of time.Duration\n\tBaselines []int64 `protobuf:\"varint,3,rep,packed,name=baselines,proto3\" json:\"baselines,omitempty\"`\n\t// expected nodes count to select\n\tExpected int32 `protobuf:\"varint,4,opt,name=expected,proto3\" json:\"expected,omitempty\"`\n\t// max acceptable rtt, filter away high delay nodes. default 0\n\tMaxRTT int64 `protobuf:\"varint,5,opt,name=maxRTT,proto3\" json:\"maxRTT,omitempty\"`\n\t// acceptable failure rate\n\tTolerance     float32 `protobuf:\"fixed32,6,opt,name=tolerance,proto3\" json:\"tolerance,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *StrategyLeastLoadConfig) Reset() {\n\t*x = StrategyLeastLoadConfig{}\n\tmi := &file_app_router_config_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StrategyLeastLoadConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StrategyLeastLoadConfig) ProtoMessage() {}\n\nfunc (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use StrategyLeastLoadConfig.ProtoReflect.Descriptor instead.\nfunc (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight {\n\tif x != nil {\n\t\treturn x.Costs\n\t}\n\treturn nil\n}\n\nfunc (x *StrategyLeastLoadConfig) GetBaselines() []int64 {\n\tif x != nil {\n\t\treturn x.Baselines\n\t}\n\treturn nil\n}\n\nfunc (x *StrategyLeastLoadConfig) GetExpected() int32 {\n\tif x != nil {\n\t\treturn x.Expected\n\t}\n\treturn 0\n}\n\nfunc (x *StrategyLeastLoadConfig) GetMaxRTT() int64 {\n\tif x != nil {\n\t\treturn x.MaxRTT\n\t}\n\treturn 0\n}\n\nfunc (x *StrategyLeastLoadConfig) GetTolerance() float32 {\n\tif x != nil {\n\t\treturn x.Tolerance\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tDomainStrategy Config_DomainStrategy  `protobuf:\"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.app.router.Config_DomainStrategy\" json:\"domain_strategy,omitempty\"`\n\tRule           []*RoutingRule         `protobuf:\"bytes,2,rep,name=rule,proto3\" json:\"rule,omitempty\"`\n\tBalancingRule  []*BalancingRule       `protobuf:\"bytes,3,rep,name=balancing_rule,json=balancingRule,proto3\" json:\"balancing_rule,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_router_config_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *Config) GetDomainStrategy() Config_DomainStrategy {\n\tif x != nil {\n\t\treturn x.DomainStrategy\n\t}\n\treturn Config_AsIs\n}\n\nfunc (x *Config) GetRule() []*RoutingRule {\n\tif x != nil {\n\t\treturn x.Rule\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetBalancingRule() []*BalancingRule {\n\tif x != nil {\n\t\treturn x.BalancingRule\n\t}\n\treturn nil\n}\n\ntype Domain_Attribute struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\tKey   string                 `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\t// Types that are valid to be assigned to TypedValue:\n\t//\n\t//\t*Domain_Attribute_BoolValue\n\t//\t*Domain_Attribute_IntValue\n\tTypedValue    isDomain_Attribute_TypedValue `protobuf_oneof:\"typed_value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Domain_Attribute) Reset() {\n\t*x = Domain_Attribute{}\n\tmi := &file_app_router_config_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Domain_Attribute) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Domain_Attribute) ProtoMessage() {}\n\nfunc (x *Domain_Attribute) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_router_config_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead.\nfunc (*Domain_Attribute) Descriptor() ([]byte, []int) {\n\treturn file_app_router_config_proto_rawDescGZIP(), []int{0, 0}\n}\n\nfunc (x *Domain_Attribute) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue {\n\tif x != nil {\n\t\treturn x.TypedValue\n\t}\n\treturn nil\n}\n\nfunc (x *Domain_Attribute) GetBoolValue() bool {\n\tif x != nil {\n\t\tif x, ok := x.TypedValue.(*Domain_Attribute_BoolValue); ok {\n\t\t\treturn x.BoolValue\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (x *Domain_Attribute) GetIntValue() int64 {\n\tif x != nil {\n\t\tif x, ok := x.TypedValue.(*Domain_Attribute_IntValue); ok {\n\t\t\treturn x.IntValue\n\t\t}\n\t}\n\treturn 0\n}\n\ntype isDomain_Attribute_TypedValue interface {\n\tisDomain_Attribute_TypedValue()\n}\n\ntype Domain_Attribute_BoolValue struct {\n\tBoolValue bool `protobuf:\"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof\"`\n}\n\ntype Domain_Attribute_IntValue struct {\n\tIntValue int64 `protobuf:\"varint,3,opt,name=int_value,json=intValue,proto3,oneof\"`\n}\n\nfunc (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {}\n\nfunc (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {}\n\nvar File_app_router_config_proto protoreflect.FileDescriptor\n\nconst file_app_router_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x17app/router/config.proto\\x12\\x0fxray.app.router\\x1a!common/serial/typed_message.proto\\x1a\\x15common/net/port.proto\\x1a\\x18common/net/network.proto\\\"\\xb3\\x02\\n\" +\n\t\"\\x06Domain\\x120\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\x1c.xray.app.router.Domain.TypeR\\x04type\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value\\x12?\\n\" +\n\t\"\\tattribute\\x18\\x03 \\x03(\\v2!.xray.app.router.Domain.AttributeR\\tattribute\\x1al\\n\" +\n\t\"\\tAttribute\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x1f\\n\" +\n\t\"\\n\" +\n\t\"bool_value\\x18\\x02 \\x01(\\bH\\x00R\\tboolValue\\x12\\x1d\\n\" +\n\t\"\\tint_value\\x18\\x03 \\x01(\\x03H\\x00R\\bintValueB\\r\\n\" +\n\t\"\\vtyped_value\\\"2\\n\" +\n\t\"\\x04Type\\x12\\t\\n\" +\n\t\"\\x05Plain\\x10\\x00\\x12\\t\\n\" +\n\t\"\\x05Regex\\x10\\x01\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06Domain\\x10\\x02\\x12\\b\\n\" +\n\t\"\\x04Full\\x10\\x03\\\".\\n\" +\n\t\"\\x04CIDR\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\fR\\x02ip\\x12\\x16\\n\" +\n\t\"\\x06prefix\\x18\\x02 \\x01(\\rR\\x06prefix\\\"z\\n\" +\n\t\"\\x05GeoIP\\x12!\\n\" +\n\t\"\\fcountry_code\\x18\\x01 \\x01(\\tR\\vcountryCode\\x12)\\n\" +\n\t\"\\x04cidr\\x18\\x02 \\x03(\\v2\\x15.xray.app.router.CIDRR\\x04cidr\\x12#\\n\" +\n\t\"\\rreverse_match\\x18\\x03 \\x01(\\bR\\freverseMatch\\\"9\\n\" +\n\t\"\\tGeoIPList\\x12,\\n\" +\n\t\"\\x05entry\\x18\\x01 \\x03(\\v2\\x16.xray.app.router.GeoIPR\\x05entry\\\"]\\n\" +\n\t\"\\aGeoSite\\x12!\\n\" +\n\t\"\\fcountry_code\\x18\\x01 \\x01(\\tR\\vcountryCode\\x12/\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x03(\\v2\\x17.xray.app.router.DomainR\\x06domain\\\"=\\n\" +\n\t\"\\vGeoSiteList\\x12.\\n\" +\n\t\"\\x05entry\\x18\\x01 \\x03(\\v2\\x18.xray.app.router.GeoSiteR\\x05entry\\\"\\xbc\\a\\n\" +\n\t\"\\vRoutingRule\\x12\\x12\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tH\\x00R\\x03tag\\x12%\\n\" +\n\t\"\\rbalancing_tag\\x18\\f \\x01(\\tH\\x00R\\fbalancingTag\\x12\\x19\\n\" +\n\t\"\\brule_tag\\x18\\x13 \\x01(\\tR\\aruleTag\\x12/\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x03(\\v2\\x17.xray.app.router.DomainR\\x06domain\\x12,\\n\" +\n\t\"\\x05geoip\\x18\\n\" +\n\t\" \\x03(\\v2\\x16.xray.app.router.GeoIPR\\x05geoip\\x126\\n\" +\n\t\"\\tport_list\\x18\\x0e \\x01(\\v2\\x19.xray.common.net.PortListR\\bportList\\x124\\n\" +\n\t\"\\bnetworks\\x18\\r \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\bnetworks\\x129\\n\" +\n\t\"\\fsource_geoip\\x18\\v \\x03(\\v2\\x16.xray.app.router.GeoIPR\\vsourceGeoip\\x12C\\n\" +\n\t\"\\x10source_port_list\\x18\\x10 \\x01(\\v2\\x19.xray.common.net.PortListR\\x0esourcePortList\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_email\\x18\\a \\x03(\\tR\\tuserEmail\\x12\\x1f\\n\" +\n\t\"\\vinbound_tag\\x18\\b \\x03(\\tR\\n\" +\n\t\"inboundTag\\x12\\x1a\\n\" +\n\t\"\\bprotocol\\x18\\t \\x03(\\tR\\bprotocol\\x12L\\n\" +\n\t\"\\n\" +\n\t\"attributes\\x18\\x0f \\x03(\\v2,.xray.app.router.RoutingRule.AttributesEntryR\\n\" +\n\t\"attributes\\x127\\n\" +\n\t\"\\vlocal_geoip\\x18\\x11 \\x03(\\v2\\x16.xray.app.router.GeoIPR\\n\" +\n\t\"localGeoip\\x12A\\n\" +\n\t\"\\x0flocal_port_list\\x18\\x12 \\x01(\\v2\\x19.xray.common.net.PortListR\\rlocalPortList\\x12C\\n\" +\n\t\"\\x10vless_route_list\\x18\\x14 \\x01(\\v2\\x19.xray.common.net.PortListR\\x0evlessRouteList\\x12\\x18\\n\" +\n\t\"\\aprocess\\x18\\x15 \\x03(\\tR\\aprocess\\x128\\n\" +\n\t\"\\awebhook\\x18\\x16 \\x01(\\v2\\x1e.xray.app.router.WebhookConfigR\\awebhook\\x1a=\\n\" +\n\t\"\\x0fAttributesEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\f\\n\" +\n\t\"\\n\" +\n\t\"target_tag\\\"\\xca\\x01\\n\" +\n\t\"\\rWebhookConfig\\x12\\x10\\n\" +\n\t\"\\x03url\\x18\\x01 \\x01(\\tR\\x03url\\x12$\\n\" +\n\t\"\\rdeduplication\\x18\\x02 \\x01(\\rR\\rdeduplication\\x12E\\n\" +\n\t\"\\aheaders\\x18\\x03 \\x03(\\v2+.xray.app.router.WebhookConfig.HeadersEntryR\\aheaders\\x1a:\\n\" +\n\t\"\\fHeadersEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\"\\xdc\\x01\\n\" +\n\t\"\\rBalancingRule\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12+\\n\" +\n\t\"\\x11outbound_selector\\x18\\x02 \\x03(\\tR\\x10outboundSelector\\x12\\x1a\\n\" +\n\t\"\\bstrategy\\x18\\x03 \\x01(\\tR\\bstrategy\\x12M\\n\" +\n\t\"\\x11strategy_settings\\x18\\x04 \\x01(\\v2 .xray.common.serial.TypedMessageR\\x10strategySettings\\x12!\\n\" +\n\t\"\\ffallback_tag\\x18\\x05 \\x01(\\tR\\vfallbackTag\\\"T\\n\" +\n\t\"\\x0eStrategyWeight\\x12\\x16\\n\" +\n\t\"\\x06regexp\\x18\\x01 \\x01(\\bR\\x06regexp\\x12\\x14\\n\" +\n\t\"\\x05match\\x18\\x02 \\x01(\\tR\\x05match\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x03 \\x01(\\x02R\\x05value\\\"\\xc0\\x01\\n\" +\n\t\"\\x17StrategyLeastLoadConfig\\x125\\n\" +\n\t\"\\x05costs\\x18\\x02 \\x03(\\v2\\x1f.xray.app.router.StrategyWeightR\\x05costs\\x12\\x1c\\n\" +\n\t\"\\tbaselines\\x18\\x03 \\x03(\\x03R\\tbaselines\\x12\\x1a\\n\" +\n\t\"\\bexpected\\x18\\x04 \\x01(\\x05R\\bexpected\\x12\\x16\\n\" +\n\t\"\\x06maxRTT\\x18\\x05 \\x01(\\x03R\\x06maxRTT\\x12\\x1c\\n\" +\n\t\"\\ttolerance\\x18\\x06 \\x01(\\x02R\\ttolerance\\\"\\x90\\x02\\n\" +\n\t\"\\x06Config\\x12O\\n\" +\n\t\"\\x0fdomain_strategy\\x18\\x01 \\x01(\\x0e2&.xray.app.router.Config.DomainStrategyR\\x0edomainStrategy\\x120\\n\" +\n\t\"\\x04rule\\x18\\x02 \\x03(\\v2\\x1c.xray.app.router.RoutingRuleR\\x04rule\\x12E\\n\" +\n\t\"\\x0ebalancing_rule\\x18\\x03 \\x03(\\v2\\x1e.xray.app.router.BalancingRuleR\\rbalancingRule\\\"<\\n\" +\n\t\"\\x0eDomainStrategy\\x12\\b\\n\" +\n\t\"\\x04AsIs\\x10\\x00\\x12\\x10\\n\" +\n\t\"\\fIpIfNonMatch\\x10\\x02\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"IpOnDemand\\x10\\x03BO\\n\" +\n\t\"\\x13com.xray.app.routerP\\x01Z$github.com/xtls/xray-core/app/router\\xaa\\x02\\x0fXray.App.Routerb\\x06proto3\"\n\nvar (\n\tfile_app_router_config_proto_rawDescOnce sync.Once\n\tfile_app_router_config_proto_rawDescData []byte\n)\n\nfunc file_app_router_config_proto_rawDescGZIP() []byte {\n\tfile_app_router_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_router_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)))\n\t})\n\treturn file_app_router_config_proto_rawDescData\n}\n\nvar file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 15)\nvar file_app_router_config_proto_goTypes = []any{\n\t(Domain_Type)(0),                // 0: xray.app.router.Domain.Type\n\t(Config_DomainStrategy)(0),      // 1: xray.app.router.Config.DomainStrategy\n\t(*Domain)(nil),                  // 2: xray.app.router.Domain\n\t(*CIDR)(nil),                    // 3: xray.app.router.CIDR\n\t(*GeoIP)(nil),                   // 4: xray.app.router.GeoIP\n\t(*GeoIPList)(nil),               // 5: xray.app.router.GeoIPList\n\t(*GeoSite)(nil),                 // 6: xray.app.router.GeoSite\n\t(*GeoSiteList)(nil),             // 7: xray.app.router.GeoSiteList\n\t(*RoutingRule)(nil),             // 8: xray.app.router.RoutingRule\n\t(*WebhookConfig)(nil),           // 9: xray.app.router.WebhookConfig\n\t(*BalancingRule)(nil),           // 10: xray.app.router.BalancingRule\n\t(*StrategyWeight)(nil),          // 11: xray.app.router.StrategyWeight\n\t(*StrategyLeastLoadConfig)(nil), // 12: xray.app.router.StrategyLeastLoadConfig\n\t(*Config)(nil),                  // 13: xray.app.router.Config\n\t(*Domain_Attribute)(nil),        // 14: xray.app.router.Domain.Attribute\n\tnil,                             // 15: xray.app.router.RoutingRule.AttributesEntry\n\tnil,                             // 16: xray.app.router.WebhookConfig.HeadersEntry\n\t(*net.PortList)(nil),            // 17: xray.common.net.PortList\n\t(net.Network)(0),                // 18: xray.common.net.Network\n\t(*serial.TypedMessage)(nil),     // 19: xray.common.serial.TypedMessage\n}\nvar file_app_router_config_proto_depIdxs = []int32{\n\t0,  // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type\n\t14, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute\n\t3,  // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR\n\t4,  // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP\n\t2,  // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain\n\t6,  // 5: xray.app.router.GeoSiteList.entry:type_name -> xray.app.router.GeoSite\n\t2,  // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain\n\t4,  // 7: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP\n\t17, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList\n\t18, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network\n\t4,  // 10: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP\n\t17, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList\n\t15, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry\n\t4,  // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP\n\t17, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList\n\t17, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList\n\t9,  // 16: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig\n\t16, // 17: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry\n\t19, // 18: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage\n\t11, // 19: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight\n\t1,  // 20: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy\n\t8,  // 21: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule\n\t10, // 22: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule\n\t23, // [23:23] is the sub-list for method output_type\n\t23, // [23:23] is the sub-list for method input_type\n\t23, // [23:23] is the sub-list for extension type_name\n\t23, // [23:23] is the sub-list for extension extendee\n\t0,  // [0:23] is the sub-list for field type_name\n}\n\nfunc init() { file_app_router_config_proto_init() }\nfunc file_app_router_config_proto_init() {\n\tif File_app_router_config_proto != nil {\n\t\treturn\n\t}\n\tfile_app_router_config_proto_msgTypes[6].OneofWrappers = []any{\n\t\t(*RoutingRule_Tag)(nil),\n\t\t(*RoutingRule_BalancingTag)(nil),\n\t}\n\tfile_app_router_config_proto_msgTypes[12].OneofWrappers = []any{\n\t\t(*Domain_Attribute_BoolValue)(nil),\n\t\t(*Domain_Attribute_IntValue)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)),\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   15,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_router_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_router_config_proto_depIdxs,\n\t\tEnumInfos:         file_app_router_config_proto_enumTypes,\n\t\tMessageInfos:      file_app_router_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_router_config_proto = out.File\n\tfile_app_router_config_proto_goTypes = nil\n\tfile_app_router_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/router/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.router;\noption csharp_namespace = \"Xray.App.Router\";\noption go_package = \"github.com/xtls/xray-core/app/router\";\noption java_package = \"com.xray.app.router\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\nimport \"common/net/port.proto\";\nimport \"common/net/network.proto\";\n\n// Domain for routing decision.\nmessage Domain {\n  // Type of domain value.\n  enum Type {\n    // The value is used as is.\n    Plain = 0;\n    // The value is used as a regular expression.\n    Regex = 1;\n    // The value is a root domain.\n    Domain = 2;\n    // The value is a domain.\n    Full = 3;\n  }\n\n  // Domain matching type.\n  Type type = 1;\n\n  // Domain value.\n  string value = 2;\n\n  message Attribute {\n    string key = 1;\n\n    oneof typed_value {\n      bool bool_value = 2;\n      int64 int_value = 3;\n    }\n  }\n\n  // Attributes of this domain. May be used for filtering.\n  repeated Attribute attribute = 3;\n}\n\n// IP for routing decision, in CIDR form.\nmessage CIDR {\n  // IP address, should be either 4 or 16 bytes.\n  bytes ip = 1;\n\n  // Number of leading ones in the network mask.\n  uint32 prefix = 2;\n}\n\nmessage GeoIP {\n  string country_code = 1;\n  repeated CIDR cidr = 2;\n  bool reverse_match = 3;\n}\n\nmessage GeoIPList {\n  repeated GeoIP entry = 1;\n}\n\nmessage GeoSite {\n  string country_code = 1;\n  repeated Domain domain = 2;\n}\n\nmessage GeoSiteList {\n  repeated GeoSite entry = 1;\n}\n\nmessage RoutingRule {\n  oneof target_tag {\n    // Tag of outbound that this rule is pointing to.\n    string tag = 1;\n\n    // Tag of routing balancer.\n    string balancing_tag = 12;\n  }\n    string rule_tag = 19;\n\n  // List of domains for target domain matching.\n  repeated Domain domain = 2;\n\n  // List of GeoIPs for target IP address matching. If this entry exists, the\n  // cidr above will have no effect. GeoIP fields with the same country code are\n  // supposed to contain exactly same content. They will be merged during\n  // runtime. For customized GeoIPs, please leave country code empty.\n  repeated GeoIP geoip = 10;\n\n  // List of ports.\n  xray.common.net.PortList port_list = 14;\n\n  // List of networks for matching.\n  repeated xray.common.net.Network networks = 13;\n\n  // List of GeoIPs for source IP address matching. If this entry exists, the\n  // source_cidr above will have no effect.\n  repeated GeoIP source_geoip = 11;\n\n  // List of ports for source port matching.\n  xray.common.net.PortList source_port_list = 16;\n\n  repeated string user_email = 7;\n  repeated string inbound_tag = 8;\n  repeated string protocol = 9;\n\n  map<string, string> attributes = 15;\n\n  repeated GeoIP local_geoip = 17;\n  xray.common.net.PortList local_port_list = 18;\n\n  xray.common.net.PortList vless_route_list = 20;\n  repeated string process = 21;\n  WebhookConfig webhook = 22;\n}\n\nmessage WebhookConfig {\n  string url = 1;\n  uint32 deduplication = 2;\n  map<string, string> headers = 3;\n}\n\nmessage BalancingRule {\n  string tag = 1;\n  repeated string outbound_selector = 2;\n  string strategy = 3;\n  xray.common.serial.TypedMessage strategy_settings = 4;\n  string fallback_tag = 5;\n}\n\nmessage StrategyWeight {\n  bool regexp = 1;\n  string match = 2;\n  float value =3;\n}\n\nmessage StrategyLeastLoadConfig {\n  // weight settings\n  repeated StrategyWeight costs = 2;\n  // RTT baselines for selecting, int64 values of time.Duration\n  repeated int64 baselines = 3;\n  // expected nodes count to select\n  int32 expected = 4;\n  // max acceptable rtt, filter away high delay nodes. default 0\n  int64 maxRTT = 5;\n  // acceptable failure rate\n  float tolerance = 6;\n}\n\nmessage Config {\n  enum DomainStrategy {\n    // Use domain as is.\n    AsIs = 0;\n\n    // [Deprecated] Always resolve IP for domains.\n    // UseIp = 1;\n\n    // Resolve to IP if the domain doesn't match any rules.\n    IpIfNonMatch = 2;\n\n    // Resolve to IP if any rule requires IP matching.\n    IpOnDemand = 3;\n  }\n  DomainStrategy domain_strategy = 1;\n  repeated RoutingRule rule = 2;\n  repeated BalancingRule balancing_rule = 3;\n}\n"
  },
  {
    "path": "app/router/geosite_compact.go",
    "content": "package router\n\nimport (\n\t\"encoding/gob\"\n\t\"errors\"\n\t\"io\"\n\t\"runtime\"\n\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n)\n\ntype geoSiteListGob struct {\n\tSites map[string][]byte\n\tDeps  map[string][]string\n\tHosts map[string][]string\n}\n\nfunc SerializeGeoSiteList(sites []*GeoSite, deps map[string][]string, hosts map[string][]string, w io.Writer) error {\n\tdata := geoSiteListGob{\n\t\tSites: make(map[string][]byte),\n\t\tDeps:  deps,\n\t\tHosts: hosts,\n\t}\n\n\tfor _, site := range sites {\n\t\tif site == nil {\n\t\t\tcontinue\n\t\t}\n\t\tvar buf bytesWriter\n\t\tif err := SerializeDomainMatcher(site.Domain, &buf); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata.Sites[site.CountryCode] = buf.Bytes()\n\t}\n\n\treturn gob.NewEncoder(w).Encode(data)\n}\n\ntype bytesWriter struct {\n\tdata []byte\n}\n\nfunc (w *bytesWriter) Write(p []byte) (n int, err error) {\n\tw.data = append(w.data, p...)\n\treturn len(p), nil\n}\n\nfunc (w *bytesWriter) Bytes() []byte {\n\treturn w.data\n}\n\nfunc LoadGeoSiteMatcher(r io.Reader, countryCode string) (strmatcher.IndexMatcher, error) {\n\tvar data geoSiteListGob\n\tif err := gob.NewDecoder(r).Decode(&data); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn loadWithDeps(&data, countryCode, make(map[string]bool))\n}\n\nfunc loadWithDeps(data *geoSiteListGob, code string, visited map[string]bool) (strmatcher.IndexMatcher, error) {\n\tif visited[code] {\n\t\treturn nil, errors.New(\"cyclic dependency\")\n\t}\n\tvisited[code] = true\n\n\tvar matchers []strmatcher.IndexMatcher\n\n\tif siteData, ok := data.Sites[code]; ok {\n\t\tm, err := NewDomainMatcherFromBuffer(siteData)\n\t\tif err == nil {\n\t\t\tmatchers = append(matchers, m)\n\t\t}\n\t}\n\n\tif deps, ok := data.Deps[code]; ok {\n\t\tfor _, dep := range deps {\n\t\t\tm, err := loadWithDeps(data, dep, visited)\n\t\t\tif err == nil {\n\t\t\t\tmatchers = append(matchers, m)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(matchers) == 0 {\n\t\treturn nil, errors.New(\"matcher not found for: \" + code)\n\t}\n\tif len(matchers) == 1 {\n\t\treturn matchers[0], nil\n\t}\n\truntime.GC()\n\treturn &strmatcher.IndexMatcherGroup{Matchers: matchers}, nil\n}\nfunc LoadGeoSiteHosts(r io.Reader) (map[string][]string, error) {\n\tvar data geoSiteListGob\n\tif err := gob.NewDecoder(r).Decode(&data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn data.Hosts, nil\n}\n"
  },
  {
    "path": "app/router/router.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\trouting_dns \"github.com/xtls/xray-core/features/routing/dns\"\n)\n\n// Router is an implementation of routing.Router.\ntype Router struct {\n\tdomainStrategy Config_DomainStrategy\n\trules          []*Rule\n\tbalancers      map[string]*Balancer\n\tdns            dns.Client\n\n\tctx        context.Context\n\tohm        outbound.Manager\n\tdispatcher routing.Dispatcher\n\tmu         sync.Mutex\n}\n\n// Route is an implementation of routing.Route.\ntype Route struct {\n\trouting.Context\n\toutboundGroupTags []string\n\toutboundTag       string\n\truleTag           string\n}\n\n// Init initializes the Router.\nfunc (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm outbound.Manager, dispatcher routing.Dispatcher) error {\n\tr.domainStrategy = config.DomainStrategy\n\tr.dns = d\n\tr.ctx = ctx\n\tr.ohm = ohm\n\tr.dispatcher = dispatcher\n\n\tr.balancers = make(map[string]*Balancer, len(config.BalancingRule))\n\tfor _, rule := range config.BalancingRule {\n\t\tbalancer, err := rule.Build(ohm, dispatcher)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbalancer.InjectContext(ctx)\n\t\tr.balancers[rule.Tag] = balancer\n\t}\n\n\tr.rules = make([]*Rule, 0, len(config.Rule))\n\tfor _, rule := range config.Rule {\n\t\tcond, err := rule.BuildCondition()\n\t\tif err != nil {\n\t\t\tr.closeWebhooks()\n\t\t\treturn err\n\t\t}\n\t\trr := &Rule{\n\t\t\tCondition: cond,\n\t\t\tTag:       rule.GetTag(),\n\t\t\tRuleTag:   rule.GetRuleTag(),\n\t\t}\n\t\tif wh := rule.GetWebhook(); wh != nil {\n\t\t\tnotifier, err := NewWebhookNotifier(wh)\n\t\t\tif err != nil {\n\t\t\t\tr.closeWebhooks()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\trr.Webhook = notifier\n\t\t}\n\t\tbtag := rule.GetBalancingTag()\n\t\tif len(btag) > 0 {\n\t\t\tbrule, found := r.balancers[btag]\n\t\t\tif !found {\n\t\t\t\tif rr.Webhook != nil {\n\t\t\t\t\trr.Webhook.Close()\n\t\t\t\t}\n\t\t\t\tr.closeWebhooks()\n\t\t\t\treturn errors.New(\"balancer \", btag, \" not found\")\n\t\t\t}\n\t\t\trr.Balancer = brule\n\t\t}\n\t\tr.rules = append(r.rules, rr)\n\t}\n\n\treturn nil\n}\n\n// PickRoute implements routing.Router.\nfunc (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {\n\toriginalCtx := ctx\n\trule, ctx, err := r.pickRouteInternal(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttag, err := rule.GetTag()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif rule.Webhook != nil {\n\t\trule.Webhook.Fire(originalCtx, tag)\n\t}\n\treturn &Route{Context: ctx, outboundTag: tag, ruleTag: rule.RuleTag}, nil\n}\n\n// AddRule implements routing.Router.\nfunc (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {\n\n\tinst, err := config.GetInstance()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c, ok := inst.(*Config); ok {\n\t\treturn r.ReloadRules(c, shouldAppend)\n\t}\n\treturn errors.New(\"AddRule: config type error\")\n}\n\nfunc (r *Router) ReloadRules(config *Config, shouldAppend bool) error {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif !shouldAppend {\n\t\tfor _, rule := range r.rules {\n\t\t\tif rule.Webhook != nil {\n\t\t\t\trule.Webhook.Close()\n\t\t\t}\n\t\t}\n\t\tr.balancers = make(map[string]*Balancer, len(config.BalancingRule))\n\t\tr.rules = make([]*Rule, 0, len(config.Rule))\n\t}\n\tfor _, rule := range config.BalancingRule {\n\t\t_, found := r.balancers[rule.Tag]\n\t\tif found {\n\t\t\treturn errors.New(\"duplicate balancer tag\")\n\t\t}\n\t\tbalancer, err := rule.Build(r.ohm, r.dispatcher)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbalancer.InjectContext(r.ctx)\n\t\tr.balancers[rule.Tag] = balancer\n\t}\n\n\tstartIdx := len(r.rules)\n\tcloseNewWebhooks := func() {\n\t\tfor i := startIdx; i < len(r.rules); i++ {\n\t\t\tif r.rules[i].Webhook != nil {\n\t\t\t\tr.rules[i].Webhook.Close()\n\t\t\t}\n\t\t}\n\t\tr.rules = r.rules[:startIdx]\n\t}\n\n\tfor _, rule := range config.Rule {\n\t\tif r.RuleExists(rule.GetRuleTag()) {\n\t\t\tcloseNewWebhooks()\n\t\t\treturn errors.New(\"duplicate ruleTag \", rule.GetRuleTag())\n\t\t}\n\t\tcond, err := rule.BuildCondition()\n\t\tif err != nil {\n\t\t\tcloseNewWebhooks()\n\t\t\treturn err\n\t\t}\n\t\trr := &Rule{\n\t\t\tCondition: cond,\n\t\t\tTag:       rule.GetTag(),\n\t\t\tRuleTag:   rule.GetRuleTag(),\n\t\t}\n\t\tif wh := rule.GetWebhook(); wh != nil {\n\t\t\tnotifier, err := NewWebhookNotifier(wh)\n\t\t\tif err != nil {\n\t\t\t\tcloseNewWebhooks()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\trr.Webhook = notifier\n\t\t}\n\t\tbtag := rule.GetBalancingTag()\n\t\tif len(btag) > 0 {\n\t\t\tbrule, found := r.balancers[btag]\n\t\t\tif !found {\n\t\t\t\tif rr.Webhook != nil {\n\t\t\t\t\trr.Webhook.Close()\n\t\t\t\t}\n\t\t\t\tcloseNewWebhooks()\n\t\t\t\treturn errors.New(\"balancer \", btag, \" not found\")\n\t\t\t}\n\t\t\trr.Balancer = brule\n\t\t}\n\t\tr.rules = append(r.rules, rr)\n\t}\n\n\treturn nil\n}\n\nfunc (r *Router) RuleExists(tag string) bool {\n\tif tag != \"\" {\n\t\tfor _, rule := range r.rules {\n\t\t\tif rule.RuleTag == tag {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// RemoveRule implements routing.Router.\nfunc (r *Router) RemoveRule(tag string) error {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tnewRules := []*Rule{}\n\tif tag != \"\" {\n\t\tfor _, rule := range r.rules {\n\t\t\tif rule.RuleTag != tag {\n\t\t\t\tnewRules = append(newRules, rule)\n\t\t\t} else if rule.Webhook != nil {\n\t\t\t\trule.Webhook.Close()\n\t\t\t}\n\t\t}\n\t\tr.rules = newRules\n\t\treturn nil\n\t}\n\treturn errors.New(\"empty tag name!\")\n\n}\n\n// ListRule implements routing.Router\nfunc (r *Router) ListRule() []routing.Route {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\truleList := make([]routing.Route, 0)\n\tfor _, rule := range r.rules {\n\t\truleList = append(ruleList, &Route{\n\t\t\toutboundTag: rule.Tag,\n\t\t\truleTag:     rule.RuleTag,\n\t\t})\n\t}\n\treturn ruleList\n}\n\nfunc (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, error) {\n\t// SkipDNSResolve is set from DNS module.\n\t// the DOH remote server maybe a domain name,\n\t// this prevents cycle resolving dead loop\n\tskipDNSResolve := ctx.GetSkipDNSResolve()\n\n\tif r.domainStrategy == Config_IpOnDemand && !skipDNSResolve {\n\t\tctx = routing_dns.ContextWithDNSClient(ctx, r.dns)\n\t}\n\n\tfor _, rule := range r.rules {\n\t\tif rule.Apply(ctx) {\n\t\t\treturn rule, ctx, nil\n\t\t}\n\t}\n\n\tif r.domainStrategy != Config_IpIfNonMatch || len(ctx.GetTargetDomain()) == 0 || skipDNSResolve {\n\t\treturn nil, ctx, common.ErrNoClue\n\t}\n\n\tctx = routing_dns.ContextWithDNSClient(ctx, r.dns)\n\n\t// Try applying rules again if we have IPs.\n\tfor _, rule := range r.rules {\n\t\tif rule.Apply(ctx) {\n\t\t\treturn rule, ctx, nil\n\t\t}\n\t}\n\n\treturn nil, ctx, common.ErrNoClue\n}\n\n// Start implements common.Runnable.\nfunc (r *Router) Start() error {\n\treturn nil\n}\n\n// closeWebhooks closes all webhook notifiers in the current rule set.\nfunc (r *Router) closeWebhooks() {\n\tfor _, rule := range r.rules {\n\t\tif rule.Webhook != nil {\n\t\t\trule.Webhook.Close()\n\t\t}\n\t}\n}\n\n// Close implements common.Closable.\nfunc (r *Router) Close() error {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tr.closeWebhooks()\n\treturn nil\n}\n\n// Type implements common.HasType.\nfunc (*Router) Type() interface{} {\n\treturn routing.RouterType()\n}\n\n// GetOutboundGroupTags implements routing.Route.\nfunc (r *Route) GetOutboundGroupTags() []string {\n\treturn r.outboundGroupTags\n}\n\n// GetOutboundTag implements routing.Route.\nfunc (r *Route) GetOutboundTag() string {\n\treturn r.outboundTag\n}\n\nfunc (r *Route) GetRuleTag() string {\n\treturn r.ruleTag\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tr := new(Router)\n\t\tif err := core.RequireFeatures(ctx, func(d dns.Client, ohm outbound.Manager, dispatcher routing.Dispatcher) error {\n\t\t\treturn r.Init(ctx, config.(*Config), d, ohm, dispatcher)\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn r, nil\n\t}))\n}\n"
  },
  {
    "path": "app/router/router_test.go",
    "content": "package router_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t. \"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\trouting_session \"github.com/xtls/xray-core/features/routing/session\"\n\t\"github.com/xtls/xray-core/testing/mocks\"\n)\n\ntype mockOutboundManager struct {\n\toutbound.Manager\n\toutbound.HandlerSelector\n}\n\nfunc TestSimpleRouter(t *testing.T) {\n\tconfig := &Config{\n\t\tRule: []*RoutingRule{\n\t\t\t{\n\t\t\t\tTargetTag: &RoutingRule_Tag{\n\t\t\t\t\tTag: \"test\",\n\t\t\t\t},\n\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t},\n\t\t},\n\t}\n\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockDNS := mocks.NewDNSClient(mockCtl)\n\tmockOhm := mocks.NewOutboundManager(mockCtl)\n\tmockHs := mocks.NewOutboundHandlerSelector(mockCtl)\n\n\tr := new(Router)\n\tcommon.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{\n\t\tManager:         mockOhm,\n\t\tHandlerSelector: mockHs,\n\t}, nil))\n\n\tctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"example.com\"), 80),\n\t}})\n\troute, err := r.PickRoute(routing_session.AsRoutingContext(ctx))\n\tcommon.Must(err)\n\tif tag := route.GetOutboundTag(); tag != \"test\" {\n\t\tt.Error(\"expect tag 'test', bug actually \", tag)\n\t}\n}\n\nfunc TestSimpleBalancer(t *testing.T) {\n\tconfig := &Config{\n\t\tRule: []*RoutingRule{\n\t\t\t{\n\t\t\t\tTargetTag: &RoutingRule_BalancingTag{\n\t\t\t\t\tBalancingTag: \"balance\",\n\t\t\t\t},\n\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t},\n\t\t},\n\t\tBalancingRule: []*BalancingRule{\n\t\t\t{\n\t\t\t\tTag:              \"balance\",\n\t\t\t\tOutboundSelector: []string{\"test-\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockDNS := mocks.NewDNSClient(mockCtl)\n\tmockOhm := mocks.NewOutboundManager(mockCtl)\n\tmockHs := mocks.NewOutboundHandlerSelector(mockCtl)\n\n\tmockHs.EXPECT().Select(gomock.Eq([]string{\"test-\"})).Return([]string{\"test\"})\n\n\tr := new(Router)\n\tcommon.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{\n\t\tManager:         mockOhm,\n\t\tHandlerSelector: mockHs,\n\t}, nil))\n\n\tctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"example.com\"), 80),\n\t}})\n\troute, err := r.PickRoute(routing_session.AsRoutingContext(ctx))\n\tcommon.Must(err)\n\tif tag := route.GetOutboundTag(); tag != \"test\" {\n\t\tt.Error(\"expect tag 'test', bug actually \", tag)\n\t}\n}\n\n/*\n\nDo not work right now: need a full client setup\n\nfunc TestLeastLoadBalancer(t *testing.T) {\n\tconfig := &Config{\n\t\tRule: []*RoutingRule{\n\t\t\t{\n\t\t\t\tTargetTag: &RoutingRule_BalancingTag{\n\t\t\t\t\tBalancingTag: \"balance\",\n\t\t\t\t},\n\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t},\n\t\t},\n\t\tBalancingRule: []*BalancingRule{\n\t\t\t{\n\t\t\t\tTag:              \"balance\",\n\t\t\t\tOutboundSelector: []string{\"test-\"},\n\t\t\t\tStrategy:         \"leastLoad\",\n\t\t\t\tStrategySettings: serial.ToTypedMessage(&StrategyLeastLoadConfig{\n\t\t\t\t\tBaselines:   nil,\n\t\t\t\t\tExpected:    1,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockDNS := mocks.NewDNSClient(mockCtl)\n\tmockOhm := mocks.NewOutboundManager(mockCtl)\n\tmockHs := mocks.NewOutboundHandlerSelector(mockCtl)\n\n\tmockHs.EXPECT().Select(gomock.Eq([]string{\"test-\"})).Return([]string{\"test1\"})\n\n\tr := new(Router)\n\tcommon.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{\n\t\tManager:         mockOhm,\n\t\tHandlerSelector: mockHs,\n\t}, nil))\n\tctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress(\"v2ray.com\"), 80)})\n\troute, err := r.PickRoute(routing_session.AsRoutingContext(ctx))\n\tcommon.Must(err)\n\tif tag := route.GetOutboundTag(); tag != \"test1\" {\n\t\tt.Error(\"expect tag 'test1', bug actually \", tag)\n\t}\n}*/\n\nfunc TestIPOnDemand(t *testing.T) {\n\tconfig := &Config{\n\t\tDomainStrategy: Config_IpOnDemand,\n\t\tRule: []*RoutingRule{\n\t\t\t{\n\t\t\t\tTargetTag: &RoutingRule_Tag{\n\t\t\t\t\tTag: \"test\",\n\t\t\t\t},\n\t\t\t\tGeoip: []*GeoIP{\n\t\t\t\t\t{\n\t\t\t\t\t\tCidr: []*CIDR{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     []byte{192, 168, 0, 0},\n\t\t\t\t\t\t\t\tPrefix: 16,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockDNS := mocks.NewDNSClient(mockCtl)\n\tmockDNS.EXPECT().LookupIP(gomock.Eq(\"example.com\"), dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t\tFakeEnable: false,\n\t}).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()\n\n\tr := new(Router)\n\tcommon.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))\n\n\tctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"example.com\"), 80),\n\t}})\n\troute, err := r.PickRoute(routing_session.AsRoutingContext(ctx))\n\tcommon.Must(err)\n\tif tag := route.GetOutboundTag(); tag != \"test\" {\n\t\tt.Error(\"expect tag 'test', bug actually \", tag)\n\t}\n}\n\nfunc TestIPIfNonMatchDomain(t *testing.T) {\n\tconfig := &Config{\n\t\tDomainStrategy: Config_IpIfNonMatch,\n\t\tRule: []*RoutingRule{\n\t\t\t{\n\t\t\t\tTargetTag: &RoutingRule_Tag{\n\t\t\t\t\tTag: \"test\",\n\t\t\t\t},\n\t\t\t\tGeoip: []*GeoIP{\n\t\t\t\t\t{\n\t\t\t\t\t\tCidr: []*CIDR{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     []byte{192, 168, 0, 0},\n\t\t\t\t\t\t\t\tPrefix: 16,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockDNS := mocks.NewDNSClient(mockCtl)\n\tmockDNS.EXPECT().LookupIP(gomock.Eq(\"example.com\"), dns.IPOption{\n\t\tIPv4Enable: true,\n\t\tIPv6Enable: true,\n\t\tFakeEnable: false,\n\t}).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()\n\n\tr := new(Router)\n\tcommon.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))\n\n\tctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"example.com\"), 80),\n\t}})\n\troute, err := r.PickRoute(routing_session.AsRoutingContext(ctx))\n\tcommon.Must(err)\n\tif tag := route.GetOutboundTag(); tag != \"test\" {\n\t\tt.Error(\"expect tag 'test', bug actually \", tag)\n\t}\n}\n\nfunc TestIPIfNonMatchIP(t *testing.T) {\n\tconfig := &Config{\n\t\tDomainStrategy: Config_IpIfNonMatch,\n\t\tRule: []*RoutingRule{\n\t\t\t{\n\t\t\t\tTargetTag: &RoutingRule_Tag{\n\t\t\t\t\tTag: \"test\",\n\t\t\t\t},\n\t\t\t\tGeoip: []*GeoIP{\n\t\t\t\t\t{\n\t\t\t\t\t\tCidr: []*CIDR{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp:     []byte{127, 0, 0, 0},\n\t\t\t\t\t\t\t\tPrefix: 8,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockDNS := mocks.NewDNSClient(mockCtl)\n\n\tr := new(Router)\n\tcommon.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))\n\n\tctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.LocalHostIP, 80),\n\t}})\n\troute, err := r.PickRoute(routing_session.AsRoutingContext(ctx))\n\tcommon.Must(err)\n\tif tag := route.GetOutboundTag(); tag != \"test\" {\n\t\tt.Error(\"expect tag 'test', bug actually \", tag)\n\t}\n}\n"
  },
  {
    "path": "app/router/strategy_leastload.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n)\n\n// LeastLoadStrategy represents a least load balancing strategy\ntype LeastLoadStrategy struct {\n\tsettings *StrategyLeastLoadConfig\n\tcosts    *WeightManager\n\n\tobserver extension.Observatory\n\n\tctx context.Context\n}\n\nfunc (l *LeastLoadStrategy) GetPrincipleTarget(strings []string) []string {\n\tvar ret []string\n\tnodes := l.pickOutbounds(strings)\n\tfor _, v := range nodes {\n\t\tret = append(ret, v.Tag)\n\t}\n\treturn ret\n}\n\n// NewLeastLoadStrategy creates a new LeastLoadStrategy with settings\nfunc NewLeastLoadStrategy(settings *StrategyLeastLoadConfig) *LeastLoadStrategy {\n\treturn &LeastLoadStrategy{\n\t\tsettings: settings,\n\t\tcosts: NewWeightManager(\n\t\t\tsettings.Costs, 1,\n\t\t\tfunc(value, cost float64) float64 {\n\t\t\t\treturn value * math.Pow(cost, 0.5)\n\t\t\t},\n\t\t),\n\t}\n}\n\n// node is a minimal copy of HealthCheckResult\n// we don't use HealthCheckResult directly because\n// it may change by health checker during routing\ntype node struct {\n\tTag              string\n\tCountAll         int\n\tCountFail        int\n\tRTTAverage       time.Duration\n\tRTTDeviation     time.Duration\n\tRTTDeviationCost time.Duration\n}\n\nfunc (s *LeastLoadStrategy) InjectContext(ctx context.Context) {\n\ts.ctx = ctx\n\tcommon.Must(core.RequireFeatures(s.ctx, func(observatory extension.Observatory) error {\n\t\ts.observer = observatory\n\t\treturn nil\n\t}))\n}\n\nfunc (s *LeastLoadStrategy) PickOutbound(candidates []string) string {\n\tselects := s.pickOutbounds(candidates)\n\tcount := len(selects)\n\tif count == 0 {\n\t\t// goes to fallbackTag\n\t\treturn \"\"\n\t}\n\treturn selects[dice.Roll(count)].Tag\n}\n\nfunc (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {\n\tqualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))\n\tselects := s.selectLeastLoad(qualified)\n\treturn selects\n}\n\n// selectLeastLoad selects nodes according to Baselines and Expected Count.\n//\n// The strategy always improves network response speed, not matter which mode below is configured.\n// But they can still have different priorities.\n//\n// 1. Bandwidth priority: no Baseline + Expected Count > 0.: selects `Expected Count` of nodes.\n// (one if Expected Count <= 0)\n//\n// 2. Bandwidth priority advanced: Baselines + Expected Count > 0.\n// Select `Expected Count` amount of nodes, and also those near them according to baselines.\n// In other words, it selects according to different Baselines, until one of them matches\n// the Expected Count, if no Baseline matches, Expected Count applied.\n//\n// 3. Speed priority: Baselines + `Expected Count <= 0`.\n// go through all baselines until find selects, if not, select none. Used in combination\n// with 'balancer.fallbackTag', it means: selects qualified nodes or use the fallback.\nfunc (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {\n\tif len(nodes) == 0 {\n\t\terrors.LogInfo(s.ctx, \"least load: no qualified outbound\")\n\t\treturn nil\n\t}\n\texpected := int(s.settings.Expected)\n\tavailableCount := len(nodes)\n\tif expected > availableCount {\n\t\treturn nodes\n\t}\n\n\tif expected <= 0 {\n\t\texpected = 1\n\t}\n\tif len(s.settings.Baselines) == 0 {\n\t\treturn nodes[:expected]\n\t}\n\n\tcount := 0\n\t// go through all base line until find expected selects\n\tfor _, b := range s.settings.Baselines {\n\t\tbaseline := time.Duration(b)\n\t\tfor i := count; i < availableCount; i++ {\n\t\t\tif nodes[i].RTTDeviationCost >= baseline {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcount = i + 1\n\t\t}\n\t\t// don't continue if find expected selects\n\t\tif count >= expected {\n\t\t\terrors.LogDebug(s.ctx, \"applied baseline: \", baseline)\n\t\t\tbreak\n\t\t}\n\t}\n\tif s.settings.Expected > 0 && count < expected {\n\t\tcount = expected\n\t}\n\treturn nodes[:count]\n}\n\nfunc (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node {\n\tif s.observer == nil {\n\t\terrors.LogError(s.ctx, \"observer is nil\")\n\t\treturn make([]*node, 0)\n\t}\n\tobserveResult, err := s.observer.GetObservation(s.ctx)\n\tif err != nil {\n\t\terrors.LogInfoInner(s.ctx, err, \"cannot get observation\")\n\t\treturn make([]*node, 0)\n\t}\n\n\tresults := observeResult.(*observatory.ObservationResult)\n\n\toutboundlist := outboundList(candidates)\n\n\tvar ret []*node\n\n\tfor _, v := range results.Status {\n\t\tif v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) {\n\t\t\trecord := &node{\n\t\t\t\tTag:              v.OutboundTag,\n\t\t\t\tCountAll:         1,\n\t\t\t\tCountFail:        1,\n\t\t\t\tRTTAverage:       time.Duration(v.Delay) * time.Millisecond,\n\t\t\t\tRTTDeviation:     time.Duration(v.Delay) * time.Millisecond,\n\t\t\t\tRTTDeviationCost: time.Duration(s.costs.Apply(v.OutboundTag, float64(time.Duration(v.Delay)*time.Millisecond))),\n\t\t\t}\n\n\t\t\tif v.HealthPing != nil {\n\t\t\t\trecord.RTTAverage = time.Duration(v.HealthPing.Average)\n\t\t\t\trecord.RTTDeviation = time.Duration(v.HealthPing.Deviation)\n\t\t\t\trecord.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation)))\n\t\t\t\trecord.CountAll = int(v.HealthPing.All)\n\t\t\t\trecord.CountFail = int(v.HealthPing.Fail)\n\n\t\t\t}\n\t\t\tret = append(ret, record)\n\t\t}\n\t}\n\n\tleastloadSort(ret)\n\treturn ret\n}\n\nfunc leastloadSort(nodes []*node) {\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tleft := nodes[i]\n\t\tright := nodes[j]\n\t\tif left.RTTDeviationCost != right.RTTDeviationCost {\n\t\t\treturn left.RTTDeviationCost < right.RTTDeviationCost\n\t\t}\n\t\tif left.RTTAverage != right.RTTAverage {\n\t\t\treturn left.RTTAverage < right.RTTAverage\n\t\t}\n\t\tif left.CountFail != right.CountFail {\n\t\t\treturn left.CountFail < right.CountFail\n\t\t}\n\t\tif left.CountAll != right.CountAll {\n\t\t\treturn left.CountAll > right.CountAll\n\t\t}\n\t\treturn left.Tag < right.Tag\n\t})\n}\n"
  },
  {
    "path": "app/router/strategy_leastload_test.go",
    "content": "package router\n\nimport (\n\t\"testing\"\n)\n\n/*\nSplit into multiple package, need to be tested separately\n\n\tfunc TestSelectLeastLoad(t *testing.T) {\n\t\tsettings := &StrategyLeastLoadConfig{\n\t\t\tHealthCheck: &HealthPingConfig{\n\t\t\t\tSamplingCount: 10,\n\t\t\t},\n\t\t\tExpected: 1,\n\t\t\tMaxRTT:   int64(time.Millisecond * time.Duration(800)),\n\t\t}\n\t\tstrategy := NewLeastLoadStrategy(settings)\n\t\t// std 40\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(60))\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(140))\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(60))\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(140))\n\t\t// std 60\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(40))\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(160))\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(40))\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(160))\n\t\t// std 0, but >MaxRTT\n\t\tstrategy.PutResult(\"c\", time.Millisecond*time.Duration(1000))\n\t\tstrategy.PutResult(\"c\", time.Millisecond*time.Duration(1000))\n\t\tstrategy.PutResult(\"c\", time.Millisecond*time.Duration(1000))\n\t\tstrategy.PutResult(\"c\", time.Millisecond*time.Duration(1000))\n\t\texpected := \"a\"\n\t\tactual := strategy.SelectAndPick([]string{\"a\", \"b\", \"c\", \"untested\"})\n\t\tif actual != expected {\n\t\t\tt.Errorf(\"expected: %v, actual: %v\", expected, actual)\n\t\t}\n\t}\n\n\tfunc TestSelectLeastLoadWithCost(t *testing.T) {\n\t\tsettings := &StrategyLeastLoadConfig{\n\t\t\tHealthCheck: &HealthPingConfig{\n\t\t\t\tSamplingCount: 10,\n\t\t\t},\n\t\t\tCosts: []*StrategyWeight{\n\t\t\t\t{Match: \"a\", Value: 9},\n\t\t\t},\n\t\t\tExpected: 1,\n\t\t}\n\t\tstrategy := NewLeastLoadStrategy(settings, nil)\n\t\t// std 40, std+c 120\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(60))\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(140))\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(60))\n\t\tstrategy.PutResult(\"a\", time.Millisecond*time.Duration(140))\n\t\t// std 60\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(40))\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(160))\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(40))\n\t\tstrategy.PutResult(\"b\", time.Millisecond*time.Duration(160))\n\t\texpected := \"b\"\n\t\tactual := strategy.SelectAndPick([]string{\"a\", \"b\", \"untested\"})\n\t\tif actual != expected {\n\t\t\tt.Errorf(\"expected: %v, actual: %v\", expected, actual)\n\t\t}\n\t}\n*/\nfunc TestSelectLeastExpected(t *testing.T) {\n\tstrategy := &LeastLoadStrategy{\n\t\tsettings: &StrategyLeastLoadConfig{\n\t\t\tBaselines: nil,\n\t\t\tExpected:  3,\n\t\t},\n\t}\n\tnodes := []*node{\n\t\t{Tag: \"a\", RTTDeviationCost: 100},\n\t\t{Tag: \"b\", RTTDeviationCost: 200},\n\t\t{Tag: \"c\", RTTDeviationCost: 300},\n\t\t{Tag: \"d\", RTTDeviationCost: 350},\n\t}\n\texpected := 3\n\tns := strategy.selectLeastLoad(nodes)\n\tif len(ns) != expected {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, len(ns))\n\t}\n}\nfunc TestSelectLeastExpected2(t *testing.T) {\n\tstrategy := &LeastLoadStrategy{\n\t\tsettings: &StrategyLeastLoadConfig{\n\t\t\tBaselines: nil,\n\t\t\tExpected:  3,\n\t\t},\n\t}\n\tnodes := []*node{\n\t\t{Tag: \"a\", RTTDeviationCost: 100},\n\t\t{Tag: \"b\", RTTDeviationCost: 200},\n\t}\n\texpected := 2\n\tns := strategy.selectLeastLoad(nodes)\n\tif len(ns) != expected {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, len(ns))\n\t}\n}\nfunc TestSelectLeastExpectedAndBaselines(t *testing.T) {\n\tstrategy := &LeastLoadStrategy{\n\t\tsettings: &StrategyLeastLoadConfig{\n\t\t\tBaselines: []int64{200, 300, 400},\n\t\t\tExpected:  3,\n\t\t},\n\t}\n\tnodes := []*node{\n\t\t{Tag: \"a\", RTTDeviationCost: 100},\n\t\t{Tag: \"b\", RTTDeviationCost: 200},\n\t\t{Tag: \"c\", RTTDeviationCost: 250},\n\t\t{Tag: \"d\", RTTDeviationCost: 300},\n\t\t{Tag: \"e\", RTTDeviationCost: 310},\n\t}\n\texpected := 3\n\tns := strategy.selectLeastLoad(nodes)\n\tif len(ns) != expected {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, len(ns))\n\t}\n}\nfunc TestSelectLeastExpectedAndBaselines2(t *testing.T) {\n\tstrategy := &LeastLoadStrategy{\n\t\tsettings: &StrategyLeastLoadConfig{\n\t\t\tBaselines: []int64{200, 300, 400},\n\t\t\tExpected:  3,\n\t\t},\n\t}\n\tnodes := []*node{\n\t\t{Tag: \"a\", RTTDeviationCost: 500},\n\t\t{Tag: \"b\", RTTDeviationCost: 600},\n\t\t{Tag: \"c\", RTTDeviationCost: 700},\n\t\t{Tag: \"d\", RTTDeviationCost: 800},\n\t\t{Tag: \"e\", RTTDeviationCost: 900},\n\t}\n\texpected := 3\n\tns := strategy.selectLeastLoad(nodes)\n\tif len(ns) != expected {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, len(ns))\n\t}\n}\nfunc TestSelectLeastLoadBaselines(t *testing.T) {\n\tstrategy := &LeastLoadStrategy{\n\t\tsettings: &StrategyLeastLoadConfig{\n\t\t\tBaselines: []int64{200, 400, 600},\n\t\t\tExpected:  0,\n\t\t},\n\t}\n\tnodes := []*node{\n\t\t{Tag: \"a\", RTTDeviationCost: 100},\n\t\t{Tag: \"b\", RTTDeviationCost: 200},\n\t\t{Tag: \"c\", RTTDeviationCost: 300},\n\t}\n\texpected := 1\n\tns := strategy.selectLeastLoad(nodes)\n\tif len(ns) != expected {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, len(ns))\n\t}\n}\nfunc TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {\n\tstrategy := &LeastLoadStrategy{\n\t\tsettings: &StrategyLeastLoadConfig{\n\t\t\tBaselines: []int64{200, 400, 600},\n\t\t\tExpected:  0,\n\t\t},\n\t}\n\tnodes := []*node{\n\t\t{Tag: \"a\", RTTDeviationCost: 800},\n\t\t{Tag: \"b\", RTTDeviationCost: 1000},\n\t}\n\texpected := 0\n\tns := strategy.selectLeastLoad(nodes)\n\tif len(ns) != expected {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, len(ns))\n\t}\n}\n"
  },
  {
    "path": "app/router/strategy_leastping.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n)\n\ntype LeastPingStrategy struct {\n\tctx         context.Context\n\tobservatory extension.Observatory\n}\n\nfunc (l *LeastPingStrategy) GetPrincipleTarget(strings []string) []string {\n\treturn []string{l.PickOutbound(strings)}\n}\n\nfunc (l *LeastPingStrategy) InjectContext(ctx context.Context) {\n\tl.ctx = ctx\n\tcommon.Must(core.RequireFeatures(l.ctx, func(observatory extension.Observatory) error {\n\t\tl.observatory = observatory\n\t\treturn nil\n\t}))\n}\n\nfunc (l *LeastPingStrategy) PickOutbound(strings []string) string {\n\tif l.observatory == nil {\n\t\terrors.LogError(l.ctx, \"observer is nil\")\n\t\treturn \"\"\n\t}\n\tobserveReport, err := l.observatory.GetObservation(l.ctx)\n\tif err != nil {\n\t\terrors.LogInfoInner(l.ctx, err, \"cannot get observer report\")\n\t\treturn \"\"\n\t}\n\toutboundsList := outboundList(strings)\n\tif result, ok := observeReport.(*observatory.ObservationResult); ok {\n\t\tstatus := result.Status\n\t\tleastPing := int64(99999999)\n\t\tselectedOutboundName := \"\"\n\t\tfor _, v := range status {\n\t\t\tif outboundsList.contains(v.OutboundTag) && v.Alive && v.Delay < leastPing {\n\t\t\t\tselectedOutboundName = v.OutboundTag\n\t\t\t\tleastPing = v.Delay\n\t\t\t}\n\t\t}\n\t\treturn selectedOutboundName\n\t}\n\n\t// No way to understand observeReport\n\treturn \"\"\n}\n\ntype outboundList []string\n\nfunc (o outboundList) contains(name string) bool {\n\tfor _, v := range o {\n\t\tif v == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "app/router/strategy_random.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/extension\"\n)\n\n// RandomStrategy represents a random balancing strategy\ntype RandomStrategy struct {\n\tFallbackTag string\n\n\tctx         context.Context\n\tobservatory extension.Observatory\n}\n\nfunc (s *RandomStrategy) InjectContext(ctx context.Context) {\n\ts.ctx = ctx\n\tif len(s.FallbackTag) > 0 {\n\t\tcommon.Must(core.RequireFeatures(s.ctx, func(observatory extension.Observatory) error {\n\t\t\ts.observatory = observatory\n\t\t\treturn nil\n\t\t}))\n\t}\n}\n\nfunc (s *RandomStrategy) GetPrincipleTarget(strings []string) []string {\n\treturn strings\n}\n\nfunc (s *RandomStrategy) PickOutbound(candidates []string) string {\n\tif s.observatory != nil {\n\t\tobserveReport, err := s.observatory.GetObservation(s.ctx)\n\t\tif err == nil {\n\t\t\taliveTags := make([]string, 0)\n\t\t\tif result, ok := observeReport.(*observatory.ObservationResult); ok {\n\t\t\t\tstatus := result.Status\n\t\t\t\tstatusMap := make(map[string]*observatory.OutboundStatus)\n\t\t\t\tfor _, outboundStatus := range status {\n\t\t\t\t\tstatusMap[outboundStatus.OutboundTag] = outboundStatus\n\t\t\t\t}\n\t\t\t\tfor _, candidate := range candidates {\n\t\t\t\t\tif outboundStatus, found := statusMap[candidate]; found {\n\t\t\t\t\t\tif outboundStatus.Alive {\n\t\t\t\t\t\t\taliveTags = append(aliveTags, candidate)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// unfound candidate is considered alive\n\t\t\t\t\t\taliveTags = append(aliveTags, candidate)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcandidates = aliveTags\n\t\t\t}\n\t\t}\n\t}\n\n\tcount := len(candidates)\n\tif count == 0 {\n\t\t// goes to fallbackTag\n\t\treturn \"\"\n\t}\n\treturn candidates[dice.Roll(count)]\n}\n"
  },
  {
    "path": "app/router/webhook.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\trouting_session \"github.com/xtls/xray-core/features/routing/session\"\n)\n\n// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket\n// path. For regular http/https URLs the input is returned unchanged with an\n// empty socketPath. For Unix sockets the format is:\n//\n//\t/path/to/socket.sock:/http/path\n//\t@abstract:/http/path\n//\t@@padded:/http/path\n//\n// The :/ separator after the socket path delimits the HTTP request path.\n// If omitted, \"/\" is used.\nfunc parseURL(raw string) (httpURL, socketPath string) {\n\tif len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {\n\t\treturn raw, \"\"\n\t}\n\tif idx := strings.Index(raw, \":/\"); idx >= 0 {\n\t\treturn \"http://localhost\" + raw[idx+1:], raw[:idx]\n\t}\n\treturn \"http://localhost/\", raw\n}\n\n// resolveSocketPath applies platform-specific transformations to a Unix\n// socket path, matching the behaviour of the listen side in\n// transport/internet/system_listener.go.\n//\n// For abstract sockets (prefix @) on Linux/Android:\n//   - single @ — used as-is (lock-free abstract socket)\n//   - double @@ — stripped to single @ and padded to\n//     syscall.RawSockaddrUnix{}.Path length (HAProxy compat)\nfunc resolveSocketPath(path string) string {\n\tif len(path) == 0 || path[0] != '@' {\n\t\treturn path\n\t}\n\tif runtime.GOOS != \"linux\" && runtime.GOOS != \"android\" {\n\t\treturn path\n\t}\n\tif len(path) > 1 && path[1] == '@' {\n\t\tfullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))\n\t\tcopy(fullAddr, path[1:])\n\t\treturn string(fullAddr)\n\t}\n\treturn path\n}\n\nfunc ptr[T any](v T) *T { return &v }\n\ntype event struct {\n\tEmail          *string `json:\"email\"`\n\tLevel          *uint32 `json:\"level\"`\n\tProtocol       *string `json:\"protocol\"`\n\tNetwork        *string `json:\"network\"`\n\tSource         *string `json:\"source\"`\n\tDestination    *string `json:\"destination\"`\n\tOriginalTarget *string `json:\"originalTarget\"`\n\tRouteTarget    *string `json:\"routeTarget\"`\n\tInboundTag     *string `json:\"inboundTag\"`\n\tInboundName    *string `json:\"inboundName\"`\n\tInboundLocal   *string `json:\"inboundLocal\"`\n\tOutboundTag    *string `json:\"outboundTag\"`\n\tTimestamp      int64   `json:\"ts\"`\n}\n\ntype WebhookNotifier struct {\n\turl           string\n\theaders       map[string]string\n\tdeduplication uint32\n\tclient        *http.Client\n\tseen          sync.Map\n\tdone          chan struct{}\n\twg            sync.WaitGroup\n\tcloseOnce     sync.Once\n}\n\nfunc NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {\n\tif cfg == nil || cfg.Url == \"\" {\n\t\treturn nil, nil\n\t}\n\n\thttpURL, socketPath := parseURL(cfg.Url)\n\th := &WebhookNotifier{\n\t\turl:           httpURL,\n\t\tdeduplication: cfg.Deduplication,\n\t\tclient: &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t},\n\t\tdone: make(chan struct{}),\n\t}\n\n\tif socketPath != \"\" {\n\t\tdialAddr := resolveSocketPath(socketPath)\n\t\th.client.Transport = &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {\n\t\t\t\tvar d net.Dialer\n\t\t\t\treturn d.DialContext(ctx, \"unix\", dialAddr)\n\t\t\t},\n\t\t}\n\t}\n\n\tif len(cfg.Headers) > 0 {\n\t\th.headers = make(map[string]string, len(cfg.Headers))\n\t\tfor k, v := range cfg.Headers {\n\t\t\th.headers[k] = v\n\t\t}\n\t}\n\n\tif h.deduplication > 0 {\n\t\th.wg.Add(1)\n\t\tgo h.cleanupLoop()\n\t}\n\n\treturn h, nil\n}\n\nfunc (h *WebhookNotifier) Fire(ctx routing.Context, outboundTag string) {\n\tev := buildEvent(ctx, outboundTag)\n\n\temail := \"\"\n\tif ev.Email != nil {\n\t\temail = *ev.Email\n\t}\n\tif h.isDuplicate(email) {\n\t\treturn\n\t}\n\n\th.wg.Add(1)\n\tselect {\n\tcase <-h.done:\n\t\th.wg.Done()\n\t\treturn\n\tdefault:\n\t}\n\tgo func() {\n\t\tdefer h.wg.Done()\n\t\th.post(ev)\n\t}()\n}\n\nfunc buildEvent(ctx routing.Context, outboundTag string) *event {\n\tev := &event{\n\t\tTimestamp:   time.Now().Unix(),\n\t\tOutboundTag: ptr(outboundTag),\n\t\tInboundTag:  ptr(ctx.GetInboundTag()),\n\t\tProtocol:    ptr(ctx.GetProtocol()),\n\t\tNetwork:     ptr(ctx.GetNetwork().SystemString()),\n\t}\n\n\tif user := ctx.GetUser(); user != \"\" {\n\t\tev.Email = ptr(user)\n\t}\n\n\tif srcIPs := ctx.GetSourceIPs(); len(srcIPs) > 0 {\n\t\tsrcPort := ctx.GetSourcePort()\n\t\tev.Source = ptr(net.JoinHostPort(srcIPs[0].String(), srcPort.String()))\n\t}\n\n\ttargetPort := ctx.GetTargetPort()\n\tif domain := ctx.GetTargetDomain(); domain != \"\" {\n\t\tev.Destination = ptr(net.JoinHostPort(domain, targetPort.String()))\n\t} else if targetIPs := ctx.GetTargetIPs(); len(targetIPs) > 0 {\n\t\tev.Destination = ptr(net.JoinHostPort(targetIPs[0].String(), targetPort.String()))\n\t}\n\n\tif localIPs := ctx.GetLocalIPs(); len(localIPs) > 0 {\n\t\tlocalPort := ctx.GetLocalPort()\n\t\tev.InboundLocal = ptr(net.JoinHostPort(localIPs[0].String(), localPort.String()))\n\t}\n\n\tif sctx, ok := ctx.(*routing_session.Context); ok {\n\t\tenrichFromSession(ev, sctx)\n\t}\n\n\treturn ev\n}\n\nfunc enrichFromSession(ev *event, sctx *routing_session.Context) {\n\tif sctx.Inbound != nil {\n\t\tev.InboundName = ptr(sctx.Inbound.Name)\n\t\tif sctx.Inbound.User != nil {\n\t\t\tev.Level = ptr(sctx.Inbound.User.Level)\n\t\t}\n\t}\n\tif sctx.Outbound != nil {\n\t\tif sctx.Outbound.OriginalTarget.Address != nil {\n\t\t\tev.OriginalTarget = ptr(sctx.Outbound.OriginalTarget.String())\n\t\t}\n\t\tif sctx.Outbound.RouteTarget.Address != nil {\n\t\t\tev.RouteTarget = ptr(sctx.Outbound.RouteTarget.String())\n\t\t}\n\t}\n}\n\nfunc (h *WebhookNotifier) post(ev *event) {\n\tbody, err := json.Marshal(ev)\n\tif err != nil {\n\t\terrors.LogWarning(context.Background(), \"webhook: marshal failed: \", err)\n\t\treturn\n\t}\n\n\treq, err := http.NewRequestWithContext(context.Background(), http.MethodPost, h.url, bytes.NewReader(body))\n\tif err != nil {\n\t\terrors.LogWarning(context.Background(), \"webhook: request build failed: \", err)\n\t\treturn\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tfor k, v := range h.headers {\n\t\treq.Header.Set(k, v)\n\t}\n\n\tresp, err := h.client.Do(req)\n\tif err != nil {\n\t\terrors.LogInfo(context.Background(), \"webhook: POST failed: \", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tio.Copy(io.Discard, resp.Body)\n\t\tresp.Body.Close()\n\t}()\n\tif resp.StatusCode >= 400 {\n\t\terrors.LogWarning(context.Background(), \"webhook: POST returned status \", resp.StatusCode)\n\t}\n}\n\nfunc (h *WebhookNotifier) isDuplicate(email string) bool {\n\tif h.deduplication == 0 || email == \"\" {\n\t\treturn false\n\t}\n\tttl := time.Duration(h.deduplication) * time.Second\n\tnow := time.Now()\n\tif v, loaded := h.seen.LoadOrStore(email, now); loaded {\n\t\tif now.Sub(v.(time.Time)) < ttl {\n\t\t\treturn true\n\t\t}\n\t\th.seen.Store(email, now)\n\t}\n\treturn false\n}\n\nfunc (h *WebhookNotifier) cleanupLoop() {\n\tdefer h.wg.Done()\n\tttl := time.Duration(h.deduplication) * time.Second\n\tticker := time.NewTicker(ttl)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-h.done:\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tnow := time.Now()\n\t\t\th.seen.Range(func(key, value any) bool {\n\t\t\t\tif now.Sub(value.(time.Time)) >= ttl {\n\t\t\t\t\th.seen.Delete(key)\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc (h *WebhookNotifier) Close() error {\n\th.closeOnce.Do(func() {\n\t\tclose(h.done)\n\t})\n\th.wg.Wait()\n\th.client.CloseIdleConnections()\n\treturn nil\n}\n"
  },
  {
    "path": "app/router/weight.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype weightScaler func(value, weight float64) float64\n\nvar numberFinder = regexp.MustCompile(`\\d+(\\.\\d+)?`)\n\n// NewWeightManager creates a new WeightManager with settings\nfunc NewWeightManager(s []*StrategyWeight, defaultWeight float64, scaler weightScaler) *WeightManager {\n\treturn &WeightManager{\n\t\tsettings:      s,\n\t\tcache:         make(map[string]float64),\n\t\tscaler:        scaler,\n\t\tdefaultWeight: defaultWeight,\n\t}\n}\n\n// WeightManager manages weights for specific settings\ntype WeightManager struct {\n\tsettings      []*StrategyWeight\n\tcache         map[string]float64\n\tscaler        weightScaler\n\tdefaultWeight float64\n\tmu            sync.Mutex\n}\n\n// Get get the weight of specified tag\nfunc (s *WeightManager) Get(tag string) float64 {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tweight, ok := s.cache[tag]\n\tif ok {\n\t\treturn weight\n\t}\n\tweight = s.findValue(tag)\n\ts.cache[tag] = weight\n\treturn weight\n}\n\n// Apply applies weight to the value\nfunc (s *WeightManager) Apply(tag string, value float64) float64 {\n\treturn s.scaler(value, s.Get(tag))\n}\n\nfunc (s *WeightManager) findValue(tag string) float64 {\n\tfor _, w := range s.settings {\n\t\tmatched := s.getMatch(tag, w.Match, w.Regexp)\n\t\tif matched == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif w.Value > 0 {\n\t\t\treturn float64(w.Value)\n\t\t}\n\t\t// auto weight from matched\n\t\tnumStr := numberFinder.FindString(matched)\n\t\tif numStr == \"\" {\n\t\t\treturn s.defaultWeight\n\t\t}\n\t\tweight, err := strconv.ParseFloat(numStr, 64)\n\t\tif err != nil {\n\t\t\terrors.LogError(context.Background(), \"unexpected error from ParseFloat: \", err)\n\t\t\treturn s.defaultWeight\n\t\t}\n\t\treturn weight\n\t}\n\treturn s.defaultWeight\n}\n\nfunc (s *WeightManager) getMatch(tag, find string, isRegexp bool) string {\n\tif !isRegexp {\n\t\tidx := strings.Index(tag, find)\n\t\tif idx < 0 {\n\t\t\treturn \"\"\n\t\t}\n\t\treturn find\n\t}\n\tr, err := regexp.Compile(find)\n\tif err != nil {\n\t\terrors.LogError(context.Background(), \"invalid regexp: \", find, \"err: \", err)\n\t\treturn \"\"\n\t}\n\treturn r.FindString(tag)\n}\n"
  },
  {
    "path": "app/router/weight_test.go",
    "content": "package router_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/router\"\n)\n\nfunc TestWeight(t *testing.T) {\n\tmanager := router.NewWeightManager(\n\t\t[]*router.StrategyWeight{\n\t\t\t{\n\t\t\t\tMatch: \"x5\",\n\t\t\t\tValue: 100,\n\t\t\t},\n\t\t\t{\n\t\t\t\tMatch: \"x8\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tRegexp: true,\n\t\t\t\tMatch:  `\\bx0+(\\.\\d+)?\\b`,\n\t\t\t\tValue:  1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tRegexp: true,\n\t\t\t\tMatch:  `\\bx\\d+(\\.\\d+)?\\b`,\n\t\t\t},\n\t\t},\n\t\t1, func(v, w float64) float64 {\n\t\t\treturn v * w\n\t\t},\n\t)\n\ttags := []string{\n\t\t\"node name, x5, and more\",\n\t\t\"node name, x8\",\n\t\t\"node name, x15\",\n\t\t\"node name, x0100, and more\",\n\t\t\"node name, x10.1\",\n\t\t\"node name, x00.1, and more\",\n\t}\n\t// test weight\n\texpected := []float64{100, 8, 15, 100, 10.1, 1}\n\tactual := make([]float64, 0)\n\tfor _, tag := range tags {\n\t\tactual = append(actual, manager.Get(tag))\n\t}\n\tif !reflect.DeepEqual(expected, actual) {\n\t\tt.Errorf(\"expected: %v, actual: %v\", expected, actual)\n\t}\n\t// test scale\n\texpected2 := []float64{1000, 80, 150, 1000, 101, 10}\n\tactual2 := make([]float64, 0)\n\tfor _, tag := range tags {\n\t\tactual2 = append(actual2, manager.Apply(tag, 10))\n\t}\n\tif !reflect.DeepEqual(expected2, actual2) {\n\t\tt.Errorf(\"expected2: %v, actual2: %v\", expected2, actual2)\n\t}\n}\n"
  },
  {
    "path": "app/stats/channel.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\n// Channel is an implementation of stats.Channel.\ntype Channel struct {\n\tchannel     chan channelMessage\n\tsubscribers []chan interface{}\n\n\t// Synchronization components\n\taccess sync.RWMutex\n\tclosed chan struct{}\n\n\t// Channel options\n\tblocking   bool // Set blocking state if channel buffer reaches limit\n\tbufferSize int  // Set to 0 as no buffering\n\tsubsLimit  int  // Set to 0 as no subscriber limit\n}\n\n// NewChannel creates an instance of Statistics Channel.\nfunc NewChannel(config *ChannelConfig) *Channel {\n\treturn &Channel{\n\t\tchannel:    make(chan channelMessage, config.BufferSize),\n\t\tsubsLimit:  int(config.SubscriberLimit),\n\t\tbufferSize: int(config.BufferSize),\n\t\tblocking:   config.Blocking,\n\t}\n}\n\n// Subscribers implements stats.Channel.\nfunc (c *Channel) Subscribers() []chan interface{} {\n\tc.access.RLock()\n\tdefer c.access.RUnlock()\n\treturn c.subscribers\n}\n\n// Subscribe implements stats.Channel.\nfunc (c *Channel) Subscribe() (chan interface{}, error) {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tif c.subsLimit > 0 && len(c.subscribers) >= c.subsLimit {\n\t\treturn nil, errors.New(\"Number of subscribers has reached limit\")\n\t}\n\tsubscriber := make(chan interface{}, c.bufferSize)\n\tc.subscribers = append(c.subscribers, subscriber)\n\treturn subscriber, nil\n}\n\n// Unsubscribe implements stats.Channel.\nfunc (c *Channel) Unsubscribe(subscriber chan interface{}) error {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tfor i, s := range c.subscribers {\n\t\tif s == subscriber {\n\t\t\t// Copy to new memory block to prevent modifying original data\n\t\t\tsubscribers := make([]chan interface{}, len(c.subscribers)-1)\n\t\t\tcopy(subscribers[:i], c.subscribers[:i])\n\t\t\tcopy(subscribers[i:], c.subscribers[i+1:])\n\t\t\tc.subscribers = subscribers\n\t\t}\n\t}\n\treturn nil\n}\n\n// Publish implements stats.Channel.\nfunc (c *Channel) Publish(ctx context.Context, msg interface{}) {\n\tselect { // Early exit if channel closed\n\tcase <-c.closed:\n\t\treturn\n\tdefault:\n\t\tpub := channelMessage{context: ctx, message: msg}\n\t\tif c.blocking {\n\t\t\tpub.publish(c.channel)\n\t\t} else {\n\t\t\tpub.publishNonBlocking(c.channel)\n\t\t}\n\t}\n}\n\n// Running returns whether the channel is running.\nfunc (c *Channel) Running() bool {\n\tselect {\n\tcase <-c.closed: // Channel closed\n\tdefault: // Channel running or not initialized\n\t\tif c.closed != nil { // Channel initialized\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Start implements common.Runnable.\nfunc (c *Channel) Start() error {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tif !c.Running() {\n\t\tc.closed = make(chan struct{}) // Reset close signal\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase pub := <-c.channel: // Published message received\n\t\t\t\t\tfor _, sub := range c.Subscribers() { // Concurrency-safe subscribers retrievement\n\t\t\t\t\t\tif c.blocking {\n\t\t\t\t\t\t\tpub.broadcast(sub)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpub.broadcastNonBlocking(sub)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase <-c.closed: // Channel closed\n\t\t\t\t\tfor _, sub := range c.Subscribers() { // Remove all subscribers\n\t\t\t\t\t\tcommon.Must(c.Unsubscribe(sub))\n\t\t\t\t\t\tclose(sub)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (c *Channel) Close() error {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tif c.Running() {\n\t\tclose(c.closed) // Send closed signal\n\t}\n\treturn nil\n}\n\n// channelMessage is the published message with guaranteed delivery.\n// message is discarded only when the context is early cancelled.\ntype channelMessage struct {\n\tcontext context.Context\n\tmessage interface{}\n}\n\nfunc (c channelMessage) publish(publisher chan channelMessage) {\n\tselect {\n\tcase publisher <- c:\n\tcase <-c.context.Done():\n\t}\n}\n\nfunc (c channelMessage) publishNonBlocking(publisher chan channelMessage) {\n\tselect {\n\tcase publisher <- c:\n\tdefault: // Create another goroutine to keep sending message\n\t\tgo c.publish(publisher)\n\t}\n}\n\nfunc (c channelMessage) broadcast(subscriber chan interface{}) {\n\tselect {\n\tcase subscriber <- c.message:\n\tcase <-c.context.Done():\n\t}\n}\n\nfunc (c channelMessage) broadcastNonBlocking(subscriber chan interface{}) {\n\tselect {\n\tcase subscriber <- c.message:\n\tdefault: // Create another goroutine to keep sending message\n\t\tgo c.broadcast(subscriber)\n\t}\n}\n"
  },
  {
    "path": "app/stats/channel_test.go",
    "content": "package stats_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\nfunc TestStatsChannel(t *testing.T) {\n\t// At most 2 subscribers could be registered\n\tc := NewChannel(&ChannelConfig{SubscriberLimit: 2, Blocking: true})\n\n\ta, err := stats.SubscribeRunnableChannel(c)\n\tcommon.Must(err)\n\tif !c.Running() {\n\t\tt.Fatal(\"unexpected failure in running channel after first subscription\")\n\t}\n\n\tb, err := c.Subscribe()\n\tcommon.Must(err)\n\n\t// Test that third subscriber is forbidden\n\t_, err = c.Subscribe()\n\tif err == nil {\n\t\tt.Fatal(\"unexpected successful subscription\")\n\t}\n\tt.Log(\"expected error: \", err)\n\n\tstopCh := make(chan struct{})\n\terrCh := make(chan string)\n\n\tgo func() {\n\t\tc.Publish(context.Background(), 1)\n\t\tc.Publish(context.Background(), 2)\n\t\tc.Publish(context.Background(), \"3\")\n\t\tc.Publish(context.Background(), []int{4})\n\t\tstopCh <- struct{}{}\n\t}()\n\n\tgo func() {\n\t\tif v, ok := (<-a).(int); !ok || v != 1 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 1)\n\t\t}\n\t\tif v, ok := (<-a).(int); !ok || v != 2 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 2)\n\t\t}\n\t\tif v, ok := (<-a).(string); !ok || v != \"3\" {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", \"3\")\n\t\t}\n\t\tif v, ok := (<-a).([]int); !ok || v[0] != 4 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", []int{4})\n\t\t}\n\t\tstopCh <- struct{}{}\n\t}()\n\n\tgo func() {\n\t\tif v, ok := (<-b).(int); !ok || v != 1 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 1)\n\t\t}\n\t\tif v, ok := (<-b).(int); !ok || v != 2 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 2)\n\t\t}\n\t\tif v, ok := (<-b).(string); !ok || v != \"3\" {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", \"3\")\n\t\t}\n\t\tif v, ok := (<-b).([]int); !ok || v[0] != 4 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", []int{4})\n\t\t}\n\t\tstopCh <- struct{}{}\n\t}()\n\n\ttimeout := time.After(2 * time.Second)\n\tfor i := 0; i < 3; i++ {\n\t\tselect {\n\t\tcase <-timeout:\n\t\t\tt.Fatal(\"Test timeout after 2s\")\n\t\tcase e := <-errCh:\n\t\t\tt.Fatal(e)\n\t\tcase <-stopCh:\n\t\t}\n\t}\n\n\t// Test the unsubscription of channel\n\tcommon.Must(c.Unsubscribe(b))\n\n\t// Test the last subscriber will close channel with `UnsubscribeClosableChannel`\n\tcommon.Must(stats.UnsubscribeClosableChannel(c, a))\n\tif c.Running() {\n\t\tt.Fatal(\"unexpected running channel after unsubscribing the last subscriber\")\n\t}\n}\n\nfunc TestStatsChannelUnsubscribe(t *testing.T) {\n\tc := NewChannel(&ChannelConfig{Blocking: true})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\ta, err := c.Subscribe()\n\tcommon.Must(err)\n\tdefer c.Unsubscribe(a)\n\n\tb, err := c.Subscribe()\n\tcommon.Must(err)\n\n\tpauseCh := make(chan struct{})\n\tstopCh := make(chan struct{})\n\terrCh := make(chan string)\n\n\t{\n\t\tvar aSet, bSet bool\n\t\tfor _, s := range c.Subscribers() {\n\t\t\tif s == a {\n\t\t\t\taSet = true\n\t\t\t}\n\t\t\tif s == b {\n\t\t\t\tbSet = true\n\t\t\t}\n\t\t}\n\t\tif !(aSet && bSet) {\n\t\t\tt.Fatal(\"unexpected subscribers: \", c.Subscribers())\n\t\t}\n\t}\n\n\tgo func() { // Blocking publish\n\t\tc.Publish(context.Background(), 1)\n\t\t<-pauseCh // Wait for `b` goroutine to resume sending message\n\t\tc.Publish(context.Background(), 2)\n\t}()\n\n\tgo func() {\n\t\tif v, ok := (<-a).(int); !ok || v != 1 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 1)\n\t\t}\n\t\tif v, ok := (<-a).(int); !ok || v != 2 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 2)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tif v, ok := (<-b).(int); !ok || v != 1 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 1)\n\t\t}\n\t\t// Unsubscribe `b` while publishing is paused\n\t\tc.Unsubscribe(b)\n\t\t{ // Test `b` is not in subscribers\n\t\t\tvar aSet, bSet bool\n\t\t\tfor _, s := range c.Subscribers() {\n\t\t\t\tif s == a {\n\t\t\t\t\taSet = true\n\t\t\t\t}\n\t\t\t\tif s == b {\n\t\t\t\t\tbSet = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !(aSet && !bSet) {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected subscribers: \", c.Subscribers())\n\t\t\t}\n\t\t}\n\t\t// Resume publishing progress\n\t\tclose(pauseCh)\n\t\t// Test `b` is neither closed nor able to receive any data\n\t\tselect {\n\t\tcase v, ok := <-b:\n\t\t\tif ok {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected data received: \", v)\n\t\t\t} else {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected closed channel: \", b)\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t\tclose(stopCh)\n\t}()\n\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase e := <-errCh:\n\t\tt.Fatal(e)\n\tcase <-stopCh:\n\t}\n}\n\nfunc TestStatsChannelBlocking(t *testing.T) {\n\t// Do not use buffer so as to create blocking scenario\n\tc := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\ta, err := c.Subscribe()\n\tcommon.Must(err)\n\tdefer c.Unsubscribe(a)\n\n\tpauseCh := make(chan struct{})\n\tstopCh := make(chan struct{})\n\terrCh := make(chan string)\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\t// Test blocking channel publishing\n\tgo func() {\n\t\t// Dummy message with no subscriber receiving, will block broadcasting goroutine\n\t\tc.Publish(context.Background(), nil)\n\n\t\t<-pauseCh\n\n\t\t// Publishing should be blocked here, for last message was not cleared and buffer was full\n\t\tc.Publish(context.Background(), nil)\n\n\t\tpauseCh <- struct{}{}\n\n\t\t// Publishing should still be blocked here\n\t\tc.Publish(ctx, nil)\n\n\t\t// Check publishing is done because context is canceled\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tif ctx.Err() != context.Canceled {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected error: \", ctx.Err())\n\t\t\t}\n\t\tdefault:\n\t\t\terrCh <- \"unexpected non-blocked publishing\"\n\t\t}\n\t\tclose(stopCh)\n\t}()\n\n\tgo func() {\n\t\tpauseCh <- struct{}{}\n\n\t\tselect {\n\t\tcase <-pauseCh:\n\t\t\terrCh <- \"unexpected non-blocked publishing\"\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t}\n\n\t\t// Receive first published message\n\t\t<-a\n\n\t\tselect {\n\t\tcase <-pauseCh:\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\terrCh <- \"unexpected blocking publishing\"\n\t\t}\n\n\t\t// Manually cancel the context to end publishing\n\t\tcancel()\n\t}()\n\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase e := <-errCh:\n\t\tt.Fatal(e)\n\tcase <-stopCh:\n\t}\n}\n\nfunc TestStatsChannelNonBlocking(t *testing.T) {\n\t// Do not use buffer so as to create blocking scenario\n\tc := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: false})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\ta, err := c.Subscribe()\n\tcommon.Must(err)\n\tdefer c.Unsubscribe(a)\n\n\tpauseCh := make(chan struct{})\n\tstopCh := make(chan struct{})\n\terrCh := make(chan string)\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\t// Test blocking channel publishing\n\tgo func() {\n\t\tc.Publish(context.Background(), nil)\n\t\tc.Publish(context.Background(), nil)\n\t\tpauseCh <- struct{}{}\n\t\t<-pauseCh\n\t\tc.Publish(ctx, nil)\n\t\tc.Publish(ctx, nil)\n\t\t// Check publishing is done because context is canceled\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tif ctx.Err() != context.Canceled {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected error: \", ctx.Err())\n\t\t\t}\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\terrCh <- \"unexpected non-cancelled publishing\"\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t// Check publishing won't block even if there is no subscriber receiving message\n\t\tselect {\n\t\tcase <-pauseCh:\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\terrCh <- \"unexpected blocking publishing\"\n\t\t}\n\n\t\t// Receive first and second published message\n\t\t<-a\n\t\t<-a\n\n\t\tpauseCh <- struct{}{}\n\n\t\t// Manually cancel the context to end publishing\n\t\tcancel()\n\n\t\t// Check third and forth published message is cancelled and cannot receive\n\t\t<-time.After(100 * time.Millisecond)\n\t\tselect {\n\t\tcase <-a:\n\t\t\terrCh <- \"unexpected non-cancelled publishing\"\n\t\tdefault:\n\t\t}\n\t\tselect {\n\t\tcase <-a:\n\t\t\terrCh <- \"unexpected non-cancelled publishing\"\n\t\tdefault:\n\t\t}\n\t\tclose(stopCh)\n\t}()\n\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase e := <-errCh:\n\t\tt.Fatal(e)\n\tcase <-stopCh:\n\t}\n}\n\nfunc TestStatsChannelConcurrency(t *testing.T) {\n\t// Do not use buffer so as to create blocking scenario\n\tc := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true})\n\tcommon.Must(c.Start())\n\tdefer c.Close()\n\n\ta, err := c.Subscribe()\n\tcommon.Must(err)\n\tdefer c.Unsubscribe(a)\n\n\tb, err := c.Subscribe()\n\tcommon.Must(err)\n\tdefer c.Unsubscribe(b)\n\n\tstopCh := make(chan struct{})\n\terrCh := make(chan string)\n\n\tgo func() { // Blocking publish\n\t\tc.Publish(context.Background(), 1)\n\t\tc.Publish(context.Background(), 2)\n\t}()\n\n\tgo func() {\n\t\tif v, ok := (<-a).(int); !ok || v != 1 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 1)\n\t\t}\n\t\tif v, ok := (<-a).(int); !ok || v != 2 {\n\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v, \", wanted \", 2)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t// Block `b` for a time so as to ensure source channel is trying to send message to `b`.\n\t\t<-time.After(25 * time.Millisecond)\n\t\t// This causes concurrency scenario: unsubscribe `b` while trying to send message to it\n\t\tc.Unsubscribe(b)\n\t\t// Test `b` is not closed and can still receive data 1:\n\t\t// Because unsubscribe won't affect the ongoing process of sending message.\n\t\tselect {\n\t\tcase v, ok := <-b:\n\t\t\tif v1, ok1 := v.(int); !(ok && ok1 && v1 == 1) {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected failure in receiving data: \", 1)\n\t\t\t}\n\t\tdefault:\n\t\t\terrCh <- fmt.Sprint(\"unexpected block from receiving data: \", 1)\n\t\t}\n\t\t// Test `b` is not closed but cannot receive data 2:\n\t\t// Because in a new round of messaging, `b` has been unsubscribed.\n\t\tselect {\n\t\tcase v, ok := <-b:\n\t\t\tif ok {\n\t\t\t\terrCh <- fmt.Sprint(\"unexpected receiving: \", v)\n\t\t\t} else {\n\t\t\t\terrCh <- \"unexpected closing of channel\"\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t\tclose(stopCh)\n\t}()\n\n\tselect {\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"Test timeout after 2s\")\n\tcase e := <-errCh:\n\t\tt.Fatal(e)\n\tcase <-stopCh:\n\t}\n}\n"
  },
  {
    "path": "app/stats/command/command.go",
    "content": "package command\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/strmatcher\"\n\t\"github.com/xtls/xray-core/core\"\n\tfeature_stats \"github.com/xtls/xray-core/features/stats\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// statsServer is an implementation of StatsService.\ntype statsServer struct {\n\tstats     feature_stats.Manager\n\tstartTime time.Time\n}\n\nfunc NewStatsServer(manager feature_stats.Manager) StatsServiceServer {\n\treturn &statsServer{\n\t\tstats:     manager,\n\t\tstartTime: time.Now(),\n\t}\n}\n\nfunc (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {\n\tc := s.stats.GetCounter(request.Name)\n\tif c == nil {\n\t\treturn nil, status.Error(codes.NotFound, request.Name+\" not found.\")\n\t}\n\tvar value int64\n\tif request.Reset_ {\n\t\tvalue = c.Set(0)\n\t} else {\n\t\tvalue = c.Value()\n\t}\n\treturn &GetStatsResponse{\n\t\tStat: &Stat{\n\t\t\tName:  request.Name,\n\t\t\tValue: value,\n\t\t},\n\t}, nil\n}\n\nfunc (s *statsServer) GetStatsOnline(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {\n\tc := s.stats.GetOnlineMap(request.Name)\n\tif c == nil {\n\t\treturn nil, status.Error(codes.NotFound, request.Name+\" not found.\")\n\t}\n\tvalue := int64(c.Count())\n\treturn &GetStatsResponse{\n\t\tStat: &Stat{\n\t\t\tName:  request.Name,\n\t\t\tValue: value,\n\t\t},\n\t}, nil\n}\n\nfunc (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) {\n\tc := s.stats.GetOnlineMap(request.Name)\n\n\tif c == nil {\n\t\treturn nil, status.Error(codes.NotFound, request.Name+\" not found.\")\n\t}\n\n\tips := make(map[string]int64)\n\tfor ip, t := range c.IPTimeMap() {\n\t\tips[ip] = t.Unix()\n\t}\n\n\treturn &GetStatsOnlineIpListResponse{\n\t\tName: request.Name,\n\t\tIps:  ips,\n\t}, nil\n}\n\nfunc (s *statsServer) GetAllOnlineUsers(ctx context.Context, request *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error) {\n\treturn &GetAllOnlineUsersResponse{\n\t\tUsers: s.stats.GetAllOnlineUsers(),\n\t}, nil\n}\n\nfunc (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {\n\tmatcher, err := strmatcher.Substr.New(request.Pattern)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresponse := &QueryStatsResponse{}\n\n\tmanager, ok := s.stats.(*stats.Manager)\n\tif !ok {\n\t\treturn nil, errors.New(\"QueryStats only works its own stats.Manager.\")\n\t}\n\n\tmanager.VisitCounters(func(name string, c feature_stats.Counter) bool {\n\t\tif matcher.Match(name) {\n\t\t\tvar value int64\n\t\t\tif request.Reset_ {\n\t\t\t\tvalue = c.Set(0)\n\t\t\t} else {\n\t\t\t\tvalue = c.Value()\n\t\t\t}\n\t\t\tresponse.Stat = append(response.Stat, &Stat{\n\t\t\t\tName:  name,\n\t\t\t\tValue: value,\n\t\t\t})\n\t\t}\n\t\treturn true\n\t})\n\n\treturn response, nil\n}\n\nfunc (s *statsServer) GetSysStats(ctx context.Context, request *SysStatsRequest) (*SysStatsResponse, error) {\n\tvar rtm runtime.MemStats\n\truntime.ReadMemStats(&rtm)\n\n\tuptime := time.Since(s.startTime)\n\n\tresponse := &SysStatsResponse{\n\t\tUptime:       uint32(uptime.Seconds()),\n\t\tNumGoroutine: uint32(runtime.NumGoroutine()),\n\t\tAlloc:        rtm.Alloc,\n\t\tTotalAlloc:   rtm.TotalAlloc,\n\t\tSys:          rtm.Sys,\n\t\tMallocs:      rtm.Mallocs,\n\t\tFrees:        rtm.Frees,\n\t\tLiveObjects:  rtm.Mallocs - rtm.Frees,\n\t\tNumGC:        rtm.NumGC,\n\t\tPauseTotalNs: rtm.PauseTotalNs,\n\t}\n\n\treturn response, nil\n}\n\nfunc (s *statsServer) mustEmbedUnimplementedStatsServiceServer() {}\n\ntype service struct {\n\tstatsManager feature_stats.Manager\n}\n\nfunc (s *service) Register(server *grpc.Server) {\n\tss := NewStatsServer(s.statsManager)\n\tRegisterStatsServiceServer(server, ss)\n\n\t// For compatibility purposes\n\tvCoreDesc := StatsService_ServiceDesc\n\tvCoreDesc.ServiceName = \"v2ray.core.app.stats.command.StatsService\"\n\tserver.RegisterService(&vCoreDesc, ss)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {\n\t\ts := new(service)\n\n\t\tcore.RequireFeatures(ctx, func(sm feature_stats.Manager) {\n\t\t\ts.statsManager = sm\n\t\t})\n\n\t\treturn s, nil\n\t}))\n}\n"
  },
  {
    "path": "app/stats/command/command.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/stats/command/command.proto\n\npackage command\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype GetStatsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Name of the stat counter.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Whether or not to reset the counter to fetching its value.\n\tReset_        bool `protobuf:\"varint,2,opt,name=reset,proto3\" json:\"reset,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetStatsRequest) Reset() {\n\t*x = GetStatsRequest{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetStatsRequest) ProtoMessage() {}\n\nfunc (x *GetStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*GetStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *GetStatsRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetStatsRequest) GetReset_() bool {\n\tif x != nil {\n\t\treturn x.Reset_\n\t}\n\treturn false\n}\n\ntype Stat struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tValue         int64                  `protobuf:\"varint,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Stat) Reset() {\n\t*x = Stat{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Stat) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Stat) ProtoMessage() {}\n\nfunc (x *Stat) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Stat.ProtoReflect.Descriptor instead.\nfunc (*Stat) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Stat) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Stat) GetValue() int64 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype GetStatsResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStat          *Stat                  `protobuf:\"bytes,1,opt,name=stat,proto3\" json:\"stat,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetStatsResponse) Reset() {\n\t*x = GetStatsResponse{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetStatsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetStatsResponse) ProtoMessage() {}\n\nfunc (x *GetStatsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead.\nfunc (*GetStatsResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *GetStatsResponse) GetStat() *Stat {\n\tif x != nil {\n\t\treturn x.Stat\n\t}\n\treturn nil\n}\n\ntype QueryStatsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPattern       string                 `protobuf:\"bytes,1,opt,name=pattern,proto3\" json:\"pattern,omitempty\"`\n\tReset_        bool                   `protobuf:\"varint,2,opt,name=reset,proto3\" json:\"reset,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *QueryStatsRequest) Reset() {\n\t*x = QueryStatsRequest{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *QueryStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryStatsRequest) ProtoMessage() {}\n\nfunc (x *QueryStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*QueryStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *QueryStatsRequest) GetPattern() string {\n\tif x != nil {\n\t\treturn x.Pattern\n\t}\n\treturn \"\"\n}\n\nfunc (x *QueryStatsRequest) GetReset_() bool {\n\tif x != nil {\n\t\treturn x.Reset_\n\t}\n\treturn false\n}\n\ntype QueryStatsResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tStat          []*Stat                `protobuf:\"bytes,1,rep,name=stat,proto3\" json:\"stat,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *QueryStatsResponse) Reset() {\n\t*x = QueryStatsResponse{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *QueryStatsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QueryStatsResponse) ProtoMessage() {}\n\nfunc (x *QueryStatsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead.\nfunc (*QueryStatsResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *QueryStatsResponse) GetStat() []*Stat {\n\tif x != nil {\n\t\treturn x.Stat\n\t}\n\treturn nil\n}\n\ntype SysStatsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SysStatsRequest) Reset() {\n\t*x = SysStatsRequest{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SysStatsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SysStatsRequest) ProtoMessage() {}\n\nfunc (x *SysStatsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead.\nfunc (*SysStatsRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{5}\n}\n\ntype SysStatsResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNumGoroutine  uint32                 `protobuf:\"varint,1,opt,name=NumGoroutine,proto3\" json:\"NumGoroutine,omitempty\"`\n\tNumGC         uint32                 `protobuf:\"varint,2,opt,name=NumGC,proto3\" json:\"NumGC,omitempty\"`\n\tAlloc         uint64                 `protobuf:\"varint,3,opt,name=Alloc,proto3\" json:\"Alloc,omitempty\"`\n\tTotalAlloc    uint64                 `protobuf:\"varint,4,opt,name=TotalAlloc,proto3\" json:\"TotalAlloc,omitempty\"`\n\tSys           uint64                 `protobuf:\"varint,5,opt,name=Sys,proto3\" json:\"Sys,omitempty\"`\n\tMallocs       uint64                 `protobuf:\"varint,6,opt,name=Mallocs,proto3\" json:\"Mallocs,omitempty\"`\n\tFrees         uint64                 `protobuf:\"varint,7,opt,name=Frees,proto3\" json:\"Frees,omitempty\"`\n\tLiveObjects   uint64                 `protobuf:\"varint,8,opt,name=LiveObjects,proto3\" json:\"LiveObjects,omitempty\"`\n\tPauseTotalNs  uint64                 `protobuf:\"varint,9,opt,name=PauseTotalNs,proto3\" json:\"PauseTotalNs,omitempty\"`\n\tUptime        uint32                 `protobuf:\"varint,10,opt,name=Uptime,proto3\" json:\"Uptime,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SysStatsResponse) Reset() {\n\t*x = SysStatsResponse{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SysStatsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SysStatsResponse) ProtoMessage() {}\n\nfunc (x *SysStatsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead.\nfunc (*SysStatsResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *SysStatsResponse) GetNumGoroutine() uint32 {\n\tif x != nil {\n\t\treturn x.NumGoroutine\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetNumGC() uint32 {\n\tif x != nil {\n\t\treturn x.NumGC\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetAlloc() uint64 {\n\tif x != nil {\n\t\treturn x.Alloc\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetTotalAlloc() uint64 {\n\tif x != nil {\n\t\treturn x.TotalAlloc\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetSys() uint64 {\n\tif x != nil {\n\t\treturn x.Sys\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetMallocs() uint64 {\n\tif x != nil {\n\t\treturn x.Mallocs\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetFrees() uint64 {\n\tif x != nil {\n\t\treturn x.Frees\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetLiveObjects() uint64 {\n\tif x != nil {\n\t\treturn x.LiveObjects\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetPauseTotalNs() uint64 {\n\tif x != nil {\n\t\treturn x.PauseTotalNs\n\t}\n\treturn 0\n}\n\nfunc (x *SysStatsResponse) GetUptime() uint32 {\n\tif x != nil {\n\t\treturn x.Uptime\n\t}\n\treturn 0\n}\n\ntype GetStatsOnlineIpListResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tIps           map[string]int64       `protobuf:\"bytes,2,rep,name=ips,proto3\" json:\"ips,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"varint,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetStatsOnlineIpListResponse) Reset() {\n\t*x = GetStatsOnlineIpListResponse{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetStatsOnlineIpListResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetStatsOnlineIpListResponse) ProtoMessage() {}\n\nfunc (x *GetStatsOnlineIpListResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetStatsOnlineIpListResponse.ProtoReflect.Descriptor instead.\nfunc (*GetStatsOnlineIpListResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *GetStatsOnlineIpListResponse) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetStatsOnlineIpListResponse) GetIps() map[string]int64 {\n\tif x != nil {\n\t\treturn x.Ips\n\t}\n\treturn nil\n}\n\ntype GetAllOnlineUsersRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetAllOnlineUsersRequest) Reset() {\n\t*x = GetAllOnlineUsersRequest{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetAllOnlineUsersRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetAllOnlineUsersRequest) ProtoMessage() {}\n\nfunc (x *GetAllOnlineUsersRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetAllOnlineUsersRequest.ProtoReflect.Descriptor instead.\nfunc (*GetAllOnlineUsersRequest) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{8}\n}\n\ntype GetAllOnlineUsersResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsers         []string               `protobuf:\"bytes,1,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetAllOnlineUsersResponse) Reset() {\n\t*x = GetAllOnlineUsersResponse{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetAllOnlineUsersResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetAllOnlineUsersResponse) ProtoMessage() {}\n\nfunc (x *GetAllOnlineUsersResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetAllOnlineUsersResponse.ProtoReflect.Descriptor instead.\nfunc (*GetAllOnlineUsersResponse) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *GetAllOnlineUsersResponse) GetUsers() []string {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_stats_command_command_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_command_command_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_command_command_proto_rawDescGZIP(), []int{10}\n}\n\nvar File_app_stats_command_command_proto protoreflect.FileDescriptor\n\nconst file_app_stats_command_command_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1fapp/stats/command/command.proto\\x12\\x16xray.app.stats.command\\\";\\n\" +\n\t\"\\x0fGetStatsRequest\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x14\\n\" +\n\t\"\\x05reset\\x18\\x02 \\x01(\\bR\\x05reset\\\"0\\n\" +\n\t\"\\x04Stat\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x03R\\x05value\\\"D\\n\" +\n\t\"\\x10GetStatsResponse\\x120\\n\" +\n\t\"\\x04stat\\x18\\x01 \\x01(\\v2\\x1c.xray.app.stats.command.StatR\\x04stat\\\"C\\n\" +\n\t\"\\x11QueryStatsRequest\\x12\\x18\\n\" +\n\t\"\\apattern\\x18\\x01 \\x01(\\tR\\apattern\\x12\\x14\\n\" +\n\t\"\\x05reset\\x18\\x02 \\x01(\\bR\\x05reset\\\"F\\n\" +\n\t\"\\x12QueryStatsResponse\\x120\\n\" +\n\t\"\\x04stat\\x18\\x01 \\x03(\\v2\\x1c.xray.app.stats.command.StatR\\x04stat\\\"\\x11\\n\" +\n\t\"\\x0fSysStatsRequest\\\"\\xa2\\x02\\n\" +\n\t\"\\x10SysStatsResponse\\x12\\\"\\n\" +\n\t\"\\fNumGoroutine\\x18\\x01 \\x01(\\rR\\fNumGoroutine\\x12\\x14\\n\" +\n\t\"\\x05NumGC\\x18\\x02 \\x01(\\rR\\x05NumGC\\x12\\x14\\n\" +\n\t\"\\x05Alloc\\x18\\x03 \\x01(\\x04R\\x05Alloc\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"TotalAlloc\\x18\\x04 \\x01(\\x04R\\n\" +\n\t\"TotalAlloc\\x12\\x10\\n\" +\n\t\"\\x03Sys\\x18\\x05 \\x01(\\x04R\\x03Sys\\x12\\x18\\n\" +\n\t\"\\aMallocs\\x18\\x06 \\x01(\\x04R\\aMallocs\\x12\\x14\\n\" +\n\t\"\\x05Frees\\x18\\a \\x01(\\x04R\\x05Frees\\x12 \\n\" +\n\t\"\\vLiveObjects\\x18\\b \\x01(\\x04R\\vLiveObjects\\x12\\\"\\n\" +\n\t\"\\fPauseTotalNs\\x18\\t \\x01(\\x04R\\fPauseTotalNs\\x12\\x16\\n\" +\n\t\"\\x06Uptime\\x18\\n\" +\n\t\" \\x01(\\rR\\x06Uptime\\\"\\xbb\\x01\\n\" +\n\t\"\\x1cGetStatsOnlineIpListResponse\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12O\\n\" +\n\t\"\\x03ips\\x18\\x02 \\x03(\\v2=.xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntryR\\x03ips\\x1a6\\n\" +\n\t\"\\bIpsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x03R\\x05value:\\x028\\x01\\\"\\x1a\\n\" +\n\t\"\\x18GetAllOnlineUsersRequest\\\"1\\n\" +\n\t\"\\x19GetAllOnlineUsersResponse\\x12\\x14\\n\" +\n\t\"\\x05users\\x18\\x01 \\x03(\\tR\\x05users\\\"\\b\\n\" +\n\t\"\\x06Config2\\x96\\x05\\n\" +\n\t\"\\fStatsService\\x12_\\n\" +\n\t\"\\bGetStats\\x12'.xray.app.stats.command.GetStatsRequest\\x1a(.xray.app.stats.command.GetStatsResponse\\\"\\x00\\x12e\\n\" +\n\t\"\\x0eGetStatsOnline\\x12'.xray.app.stats.command.GetStatsRequest\\x1a(.xray.app.stats.command.GetStatsResponse\\\"\\x00\\x12e\\n\" +\n\t\"\\n\" +\n\t\"QueryStats\\x12).xray.app.stats.command.QueryStatsRequest\\x1a*.xray.app.stats.command.QueryStatsResponse\\\"\\x00\\x12b\\n\" +\n\t\"\\vGetSysStats\\x12'.xray.app.stats.command.SysStatsRequest\\x1a(.xray.app.stats.command.SysStatsResponse\\\"\\x00\\x12w\\n\" +\n\t\"\\x14GetStatsOnlineIpList\\x12'.xray.app.stats.command.GetStatsRequest\\x1a4.xray.app.stats.command.GetStatsOnlineIpListResponse\\\"\\x00\\x12z\\n\" +\n\t\"\\x11GetAllOnlineUsers\\x120.xray.app.stats.command.GetAllOnlineUsersRequest\\x1a1.xray.app.stats.command.GetAllOnlineUsersResponse\\\"\\x00Bd\\n\" +\n\t\"\\x1acom.xray.app.stats.commandP\\x01Z+github.com/xtls/xray-core/app/stats/command\\xaa\\x02\\x16Xray.App.Stats.Commandb\\x06proto3\"\n\nvar (\n\tfile_app_stats_command_command_proto_rawDescOnce sync.Once\n\tfile_app_stats_command_command_proto_rawDescData []byte\n)\n\nfunc file_app_stats_command_command_proto_rawDescGZIP() []byte {\n\tfile_app_stats_command_command_proto_rawDescOnce.Do(func() {\n\t\tfile_app_stats_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_stats_command_command_proto_rawDesc), len(file_app_stats_command_command_proto_rawDesc)))\n\t})\n\treturn file_app_stats_command_command_proto_rawDescData\n}\n\nvar file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 12)\nvar file_app_stats_command_command_proto_goTypes = []any{\n\t(*GetStatsRequest)(nil),              // 0: xray.app.stats.command.GetStatsRequest\n\t(*Stat)(nil),                         // 1: xray.app.stats.command.Stat\n\t(*GetStatsResponse)(nil),             // 2: xray.app.stats.command.GetStatsResponse\n\t(*QueryStatsRequest)(nil),            // 3: xray.app.stats.command.QueryStatsRequest\n\t(*QueryStatsResponse)(nil),           // 4: xray.app.stats.command.QueryStatsResponse\n\t(*SysStatsRequest)(nil),              // 5: xray.app.stats.command.SysStatsRequest\n\t(*SysStatsResponse)(nil),             // 6: xray.app.stats.command.SysStatsResponse\n\t(*GetStatsOnlineIpListResponse)(nil), // 7: xray.app.stats.command.GetStatsOnlineIpListResponse\n\t(*GetAllOnlineUsersRequest)(nil),     // 8: xray.app.stats.command.GetAllOnlineUsersRequest\n\t(*GetAllOnlineUsersResponse)(nil),    // 9: xray.app.stats.command.GetAllOnlineUsersResponse\n\t(*Config)(nil),                       // 10: xray.app.stats.command.Config\n\tnil,                                  // 11: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry\n}\nvar file_app_stats_command_command_proto_depIdxs = []int32{\n\t1,  // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat\n\t1,  // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat\n\t11, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry\n\t0,  // 3: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest\n\t0,  // 4: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest\n\t3,  // 5: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest\n\t5,  // 6: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest\n\t0,  // 7: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest\n\t8,  // 8: xray.app.stats.command.StatsService.GetAllOnlineUsers:input_type -> xray.app.stats.command.GetAllOnlineUsersRequest\n\t2,  // 9: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse\n\t2,  // 10: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse\n\t4,  // 11: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse\n\t6,  // 12: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse\n\t7,  // 13: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse\n\t9,  // 14: xray.app.stats.command.StatsService.GetAllOnlineUsers:output_type -> xray.app.stats.command.GetAllOnlineUsersResponse\n\t9,  // [9:15] is the sub-list for method output_type\n\t3,  // [3:9] is the sub-list for method input_type\n\t3,  // [3:3] is the sub-list for extension type_name\n\t3,  // [3:3] is the sub-list for extension extendee\n\t0,  // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_app_stats_command_command_proto_init() }\nfunc file_app_stats_command_command_proto_init() {\n\tif File_app_stats_command_command_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_stats_command_command_proto_rawDesc), len(file_app_stats_command_command_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   12,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_app_stats_command_command_proto_goTypes,\n\t\tDependencyIndexes: file_app_stats_command_command_proto_depIdxs,\n\t\tMessageInfos:      file_app_stats_command_command_proto_msgTypes,\n\t}.Build()\n\tFile_app_stats_command_command_proto = out.File\n\tfile_app_stats_command_command_proto_goTypes = nil\n\tfile_app_stats_command_command_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/stats/command/command.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.stats.command;\noption csharp_namespace = \"Xray.App.Stats.Command\";\noption go_package = \"github.com/xtls/xray-core/app/stats/command\";\noption java_package = \"com.xray.app.stats.command\";\noption java_multiple_files = true;\n\nmessage GetStatsRequest {\n  // Name of the stat counter.\n  string name = 1;\n  // Whether or not to reset the counter to fetching its value.\n  bool reset = 2;\n}\n\nmessage Stat {\n  string name = 1;\n  int64 value = 2;\n}\n\nmessage GetStatsResponse {\n  Stat stat = 1;\n}\n\nmessage QueryStatsRequest {\n  string pattern = 1;\n  bool reset = 2;\n}\n\nmessage QueryStatsResponse {\n  repeated Stat stat = 1;\n}\n\nmessage SysStatsRequest {}\n\nmessage SysStatsResponse {\n  uint32 NumGoroutine = 1;\n  uint32 NumGC = 2;\n  uint64 Alloc = 3;\n  uint64 TotalAlloc = 4;\n  uint64 Sys = 5;\n  uint64 Mallocs = 6;\n  uint64 Frees = 7;\n  uint64 LiveObjects = 8;\n  uint64 PauseTotalNs = 9;\n  uint32 Uptime = 10;\n}\n\nmessage GetStatsOnlineIpListResponse {\n  string name = 1;\n  map<string, int64> ips = 2;\n}\n\nmessage GetAllOnlineUsersRequest {}\n\nmessage GetAllOnlineUsersResponse {\n  repeated string users = 1;\n}\n\nservice StatsService {\n  rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}\n  rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {}\n  rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}\n  rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}\n  rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {}\n  rpc GetAllOnlineUsers(GetAllOnlineUsersRequest) returns (GetAllOnlineUsersResponse) {}\n}\n\nmessage Config {}\n"
  },
  {
    "path": "app/stats/command/command_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.0\n// - protoc             v6.33.5\n// source: app/stats/command/command.proto\n\npackage command\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tStatsService_GetStats_FullMethodName             = \"/xray.app.stats.command.StatsService/GetStats\"\n\tStatsService_GetStatsOnline_FullMethodName       = \"/xray.app.stats.command.StatsService/GetStatsOnline\"\n\tStatsService_QueryStats_FullMethodName           = \"/xray.app.stats.command.StatsService/QueryStats\"\n\tStatsService_GetSysStats_FullMethodName          = \"/xray.app.stats.command.StatsService/GetSysStats\"\n\tStatsService_GetStatsOnlineIpList_FullMethodName = \"/xray.app.stats.command.StatsService/GetStatsOnlineIpList\"\n\tStatsService_GetAllOnlineUsers_FullMethodName    = \"/xray.app.stats.command.StatsService/GetAllOnlineUsers\"\n)\n\n// StatsServiceClient is the client API for StatsService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype StatsServiceClient interface {\n\tGetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)\n\tGetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)\n\tQueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error)\n\tGetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error)\n\tGetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error)\n\tGetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error)\n}\n\ntype statsServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient {\n\treturn &statsServiceClient{cc}\n}\n\nfunc (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) GetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetStatsOnline_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(QueryStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SysStatsResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetStatsOnlineIpListResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetStatsOnlineIpList_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *statsServiceClient) GetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetAllOnlineUsersResponse)\n\terr := c.cc.Invoke(ctx, StatsService_GetAllOnlineUsers_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// StatsServiceServer is the server API for StatsService service.\n// All implementations must embed UnimplementedStatsServiceServer\n// for forward compatibility.\ntype StatsServiceServer interface {\n\tGetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error)\n\tGetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error)\n\tQueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error)\n\tGetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error)\n\tGetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error)\n\tGetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error)\n\tmustEmbedUnimplementedStatsServiceServer()\n}\n\n// UnimplementedStatsServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedStatsServiceServer struct{}\n\nfunc (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetStats not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) GetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetStatsOnline not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method QueryStats not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetSysStats not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetStatsOnlineIpList not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetAllOnlineUsers not implemented\")\n}\nfunc (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {}\nfunc (UnimplementedStatsServiceServer) testEmbeddedByValue()                      {}\n\n// UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to StatsServiceServer will\n// result in compilation errors.\ntype UnsafeStatsServiceServer interface {\n\tmustEmbedUnimplementedStatsServiceServer()\n}\n\nfunc RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) {\n\t// If the following call panics, it indicates UnimplementedStatsServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&StatsService_ServiceDesc, srv)\n}\n\nfunc _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetStats_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_GetStatsOnline_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetStatsOnline(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetStatsOnline_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetStatsOnline(ctx, req.(*GetStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(QueryStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).QueryStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_QueryStats_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SysStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetSysStats(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetSysStats_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_GetStatsOnlineIpList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetStatsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetStatsOnlineIpList_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, req.(*GetStatsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _StatsService_GetAllOnlineUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetAllOnlineUsersRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(StatsServiceServer).GetAllOnlineUsers(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: StatsService_GetAllOnlineUsers_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(StatsServiceServer).GetAllOnlineUsers(ctx, req.(*GetAllOnlineUsersRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar StatsService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"xray.app.stats.command.StatsService\",\n\tHandlerType: (*StatsServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetStats\",\n\t\t\tHandler:    _StatsService_GetStats_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetStatsOnline\",\n\t\t\tHandler:    _StatsService_GetStatsOnline_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"QueryStats\",\n\t\t\tHandler:    _StatsService_QueryStats_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetSysStats\",\n\t\t\tHandler:    _StatsService_GetSysStats_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetStatsOnlineIpList\",\n\t\t\tHandler:    _StatsService_GetStatsOnlineIpList_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetAllOnlineUsers\",\n\t\t\tHandler:    _StatsService_GetAllOnlineUsers_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"app/stats/command/command.proto\",\n}\n"
  },
  {
    "path": "app/stats/command/command_test.go",
    "content": "package command_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/xtls/xray-core/app/stats\"\n\t. \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/common\"\n)\n\nfunc TestGetStats(t *testing.T) {\n\tm, err := stats.NewManager(context.Background(), &stats.Config{})\n\tcommon.Must(err)\n\n\tsc, err := m.RegisterCounter(\"test_counter\")\n\tcommon.Must(err)\n\n\tsc.Set(1)\n\n\ts := NewStatsServer(m)\n\n\ttestCases := []struct {\n\t\tname  string\n\t\treset bool\n\t\tvalue int64\n\t\terr   bool\n\t}{\n\t\t{\n\t\t\tname: \"counterNotExist\",\n\t\t\terr:  true,\n\t\t},\n\t\t{\n\t\t\tname:  \"test_counter\",\n\t\t\treset: true,\n\t\t\tvalue: 1,\n\t\t},\n\t\t{\n\t\t\tname:  \"test_counter\",\n\t\t\tvalue: 0,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tresp, err := s.GetStats(context.Background(), &GetStatsRequest{\n\t\t\tName:   tc.name,\n\t\t\tReset_: tc.reset,\n\t\t})\n\t\tif tc.err {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"nil error: \", tc.name)\n\t\t\t}\n\t\t} else {\n\t\t\tcommon.Must(err)\n\t\t\tif r := cmp.Diff(resp.Stat, &Stat{Name: tc.name, Value: tc.value}, cmpopts.IgnoreUnexported(Stat{})); r != \"\" {\n\t\t\t\tt.Error(r)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestQueryStats(t *testing.T) {\n\tm, err := stats.NewManager(context.Background(), &stats.Config{})\n\tcommon.Must(err)\n\n\tsc1, err := m.RegisterCounter(\"test_counter\")\n\tcommon.Must(err)\n\tsc1.Set(1)\n\n\tsc2, err := m.RegisterCounter(\"test_counter_2\")\n\tcommon.Must(err)\n\tsc2.Set(2)\n\n\tsc3, err := m.RegisterCounter(\"test_counter_3\")\n\tcommon.Must(err)\n\tsc3.Set(3)\n\n\ts := NewStatsServer(m)\n\tresp, err := s.QueryStats(context.Background(), &QueryStatsRequest{\n\t\tPattern: \"counter_\",\n\t})\n\tcommon.Must(err)\n\tif r := cmp.Diff(resp.Stat, []*Stat{\n\t\t{Name: \"test_counter_2\", Value: 2},\n\t\t{Name: \"test_counter_3\", Value: 3},\n\t}, cmpopts.SortSlices(func(s1, s2 *Stat) bool { return s1.Name < s2.Name }),\n\t\tcmpopts.IgnoreUnexported(Stat{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n"
  },
  {
    "path": "app/stats/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/stats/config.proto\n\npackage stats\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_stats_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype ChannelConfig struct {\n\tstate           protoimpl.MessageState `protogen:\"open.v1\"`\n\tBlocking        bool                   `protobuf:\"varint,1,opt,name=Blocking,proto3\" json:\"Blocking,omitempty\"`\n\tSubscriberLimit int32                  `protobuf:\"varint,2,opt,name=SubscriberLimit,proto3\" json:\"SubscriberLimit,omitempty\"`\n\tBufferSize      int32                  `protobuf:\"varint,3,opt,name=BufferSize,proto3\" json:\"BufferSize,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *ChannelConfig) Reset() {\n\t*x = ChannelConfig{}\n\tmi := &file_app_stats_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ChannelConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ChannelConfig) ProtoMessage() {}\n\nfunc (x *ChannelConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_stats_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ChannelConfig.ProtoReflect.Descriptor instead.\nfunc (*ChannelConfig) Descriptor() ([]byte, []int) {\n\treturn file_app_stats_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ChannelConfig) GetBlocking() bool {\n\tif x != nil {\n\t\treturn x.Blocking\n\t}\n\treturn false\n}\n\nfunc (x *ChannelConfig) GetSubscriberLimit() int32 {\n\tif x != nil {\n\t\treturn x.SubscriberLimit\n\t}\n\treturn 0\n}\n\nfunc (x *ChannelConfig) GetBufferSize() int32 {\n\tif x != nil {\n\t\treturn x.BufferSize\n\t}\n\treturn 0\n}\n\nvar File_app_stats_config_proto protoreflect.FileDescriptor\n\nconst file_app_stats_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x16app/stats/config.proto\\x12\\x0exray.app.stats\\\"\\b\\n\" +\n\t\"\\x06Config\\\"u\\n\" +\n\t\"\\rChannelConfig\\x12\\x1a\\n\" +\n\t\"\\bBlocking\\x18\\x01 \\x01(\\bR\\bBlocking\\x12(\\n\" +\n\t\"\\x0fSubscriberLimit\\x18\\x02 \\x01(\\x05R\\x0fSubscriberLimit\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"BufferSize\\x18\\x03 \\x01(\\x05R\\n\" +\n\t\"BufferSizeBL\\n\" +\n\t\"\\x12com.xray.app.statsP\\x01Z#github.com/xtls/xray-core/app/stats\\xaa\\x02\\x0eXray.App.Statsb\\x06proto3\"\n\nvar (\n\tfile_app_stats_config_proto_rawDescOnce sync.Once\n\tfile_app_stats_config_proto_rawDescData []byte\n)\n\nfunc file_app_stats_config_proto_rawDescGZIP() []byte {\n\tfile_app_stats_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_stats_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_stats_config_proto_rawDesc), len(file_app_stats_config_proto_rawDesc)))\n\t})\n\treturn file_app_stats_config_proto_rawDescData\n}\n\nvar file_app_stats_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_app_stats_config_proto_goTypes = []any{\n\t(*Config)(nil),        // 0: xray.app.stats.Config\n\t(*ChannelConfig)(nil), // 1: xray.app.stats.ChannelConfig\n}\nvar file_app_stats_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_app_stats_config_proto_init() }\nfunc file_app_stats_config_proto_init() {\n\tif File_app_stats_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_stats_config_proto_rawDesc), len(file_app_stats_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_stats_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_stats_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_stats_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_stats_config_proto = out.File\n\tfile_app_stats_config_proto_goTypes = nil\n\tfile_app_stats_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/stats/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.stats;\noption csharp_namespace = \"Xray.App.Stats\";\noption go_package = \"github.com/xtls/xray-core/app/stats\";\noption java_package = \"com.xray.app.stats\";\noption java_multiple_files = true;\n\nmessage Config {}\n\nmessage ChannelConfig {\n  bool Blocking = 1;\n  int32 SubscriberLimit = 2;\n  int32 BufferSize = 3;\n}\n"
  },
  {
    "path": "app/stats/counter.go",
    "content": "package stats\n\nimport \"sync/atomic\"\n\n// Counter is an implementation of stats.Counter.\ntype Counter struct {\n\tvalue int64\n}\n\n// Value implements stats.Counter.\nfunc (c *Counter) Value() int64 {\n\treturn atomic.LoadInt64(&c.value)\n}\n\n// Set implements stats.Counter.\nfunc (c *Counter) Set(newValue int64) int64 {\n\treturn atomic.SwapInt64(&c.value, newValue)\n}\n\n// Add implements stats.Counter.\nfunc (c *Counter) Add(delta int64) int64 {\n\treturn atomic.AddInt64(&c.value, delta)\n}\n"
  },
  {
    "path": "app/stats/counter_test.go",
    "content": "package stats_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\nfunc TestStatsCounter(t *testing.T) {\n\traw, err := common.CreateObject(context.Background(), &Config{})\n\tcommon.Must(err)\n\n\tm := raw.(stats.Manager)\n\tc, err := m.RegisterCounter(\"test.counter\")\n\tcommon.Must(err)\n\n\tif v := c.Add(1); v != 1 {\n\t\tt.Fatal(\"unexpected Add(1) return: \", v, \", wanted \", 1)\n\t}\n\n\tif v := c.Set(0); v != 1 {\n\t\tt.Fatal(\"unexpected Set(0) return: \", v, \", wanted \", 1)\n\t}\n\n\tif v := c.Value(); v != 0 {\n\t\tt.Fatal(\"unexpected Value() return: \", v, \", wanted \", 0)\n\t}\n}\n"
  },
  {
    "path": "app/stats/online_map.go",
    "content": "package stats\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nconst (\n\tlocalhostIPv4 = \"127.0.0.1\"\n\tlocalhostIPv6 = \"[::1]\"\n)\n\ntype ipEntry struct {\n\trefCount int\n\tlastSeen time.Time\n}\n\n// OnlineMap is a refcount-based implementation of stats.OnlineMap.\n// IPs are tracked by reference counting: AddIP increments, RemoveIP decrements.\n// An IP is removed from the map when its reference count reaches zero.\ntype OnlineMap struct {\n\tentries map[string]*ipEntry\n\taccess  sync.Mutex\n\tcount   atomic.Int64\n}\n\n// NewOnlineMap creates a new OnlineMap instance.\nfunc NewOnlineMap() *OnlineMap {\n\treturn &OnlineMap{\n\t\tentries: make(map[string]*ipEntry),\n\t}\n}\n\n// AddIP implements stats.OnlineMap.\nfunc (om *OnlineMap) AddIP(ip string) {\n\tif ip == localhostIPv4 || ip == localhostIPv6 {\n\t\treturn\n\t}\n\n\tom.access.Lock()\n\tdefer om.access.Unlock()\n\n\tif e, ok := om.entries[ip]; ok {\n\t\te.refCount++\n\t\te.lastSeen = time.Now()\n\t} else {\n\t\tom.entries[ip] = &ipEntry{\n\t\t\trefCount: 1,\n\t\t\tlastSeen: time.Now(),\n\t\t}\n\t\tom.count.Add(1)\n\t}\n}\n\n// RemoveIP implements stats.OnlineMap.\nfunc (om *OnlineMap) RemoveIP(ip string) {\n\tom.access.Lock()\n\tdefer om.access.Unlock()\n\n\te, ok := om.entries[ip]\n\tif !ok {\n\t\treturn\n\t}\n\te.refCount--\n\tif e.refCount <= 0 {\n\t\tdelete(om.entries, ip)\n\t\tom.count.Add(-1)\n\t}\n}\n\n// Count implements stats.OnlineMap.\nfunc (om *OnlineMap) Count() int {\n\treturn int(om.count.Load())\n}\n\n// List implements stats.OnlineMap.\nfunc (om *OnlineMap) List() []string {\n\tom.access.Lock()\n\tdefer om.access.Unlock()\n\n\tkeys := make([]string, 0, len(om.entries))\n\tfor ip := range om.entries {\n\t\tkeys = append(keys, ip)\n\t}\n\treturn keys\n}\n\n// IPTimeMap implements stats.OnlineMap.\nfunc (om *OnlineMap) IPTimeMap() map[string]time.Time {\n\tom.access.Lock()\n\tdefer om.access.Unlock()\n\n\tresult := make(map[string]time.Time, len(om.entries))\n\tfor ip, e := range om.entries {\n\t\tresult[ip] = e.lastSeen\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "app/stats/stats.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\n// Manager is an implementation of stats.Manager.\ntype Manager struct {\n\taccess    sync.RWMutex\n\tcounters  map[string]*Counter\n\tonlineMap map[string]*OnlineMap\n\tchannels  map[string]*Channel\n\trunning   bool\n}\n\n// NewManager creates an instance of Statistics Manager.\nfunc NewManager(ctx context.Context, config *Config) (*Manager, error) {\n\tm := &Manager{\n\t\tcounters:  make(map[string]*Counter),\n\t\tonlineMap: make(map[string]*OnlineMap),\n\t\tchannels:  make(map[string]*Channel),\n\t}\n\n\treturn m, nil\n}\n\n// Type implements common.HasType.\nfunc (*Manager) Type() interface{} {\n\treturn stats.ManagerType()\n}\n\n// RegisterCounter implements stats.Manager.\nfunc (m *Manager) RegisterCounter(name string) (stats.Counter, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.counters[name]; found {\n\t\treturn nil, errors.New(\"Counter \", name, \" already registered.\")\n\t}\n\terrors.LogDebug(context.Background(), \"create new counter \", name)\n\tc := new(Counter)\n\tm.counters[name] = c\n\treturn c, nil\n}\n\n// UnregisterCounter implements stats.Manager.\nfunc (m *Manager) UnregisterCounter(name string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.counters[name]; found {\n\t\terrors.LogDebug(context.Background(), \"remove counter \", name)\n\t\tdelete(m.counters, name)\n\t}\n\treturn nil\n}\n\n// GetCounter implements stats.Manager.\nfunc (m *Manager) GetCounter(name string) stats.Counter {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tif c, found := m.counters[name]; found {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// VisitCounters calls visitor function on all managed counters.\nfunc (m *Manager) VisitCounters(visitor func(string, stats.Counter) bool) {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tfor name, c := range m.counters {\n\t\tif !visitor(name, c) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// RegisterOnlineMap implements stats.Manager.\nfunc (m *Manager) RegisterOnlineMap(name string) (stats.OnlineMap, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.onlineMap[name]; found {\n\t\treturn nil, errors.New(\"onlineMap \", name, \" already registered.\")\n\t}\n\terrors.LogDebug(context.Background(), \"create new onlineMap \", name)\n\tom := NewOnlineMap()\n\tm.onlineMap[name] = om\n\treturn om, nil\n}\n\n// UnregisterOnlineMap implements stats.Manager.\nfunc (m *Manager) UnregisterOnlineMap(name string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.onlineMap[name]; found {\n\t\terrors.LogDebug(context.Background(), \"remove onlineMap \", name)\n\t\tdelete(m.onlineMap, name)\n\t}\n\treturn nil\n}\n\n// GetOnlineMap implements stats.Manager.\nfunc (m *Manager) GetOnlineMap(name string) stats.OnlineMap {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tif om, found := m.onlineMap[name]; found {\n\t\treturn om\n\t}\n\treturn nil\n}\n\n// RegisterChannel implements stats.Manager.\nfunc (m *Manager) RegisterChannel(name string) (stats.Channel, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.channels[name]; found {\n\t\treturn nil, errors.New(\"Channel \", name, \" already registered.\")\n\t}\n\terrors.LogDebug(context.Background(), \"create new channel \", name)\n\tc := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false})\n\tm.channels[name] = c\n\tif m.running {\n\t\treturn c, c.Start()\n\t}\n\treturn c, nil\n}\n\n// UnregisterChannel implements stats.Manager.\nfunc (m *Manager) UnregisterChannel(name string) error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif c, found := m.channels[name]; found {\n\t\terrors.LogDebug(context.Background(), \"remove channel \", name)\n\t\tdelete(m.channels, name)\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n\n// GetChannel implements stats.Manager.\nfunc (m *Manager) GetChannel(name string) stats.Channel {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tif c, found := m.channels[name]; found {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// GetAllOnlineUsers implements stats.Manager.\nfunc (m *Manager) GetAllOnlineUsers() []string {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\n\tusersOnline := make([]string, 0, len(m.onlineMap))\n\tfor user, onlineMap := range m.onlineMap {\n\t\tif onlineMap.Count() > 0 {\n\t\t\tusersOnline = append(usersOnline, user)\n\t\t}\n\t}\n\n\treturn usersOnline\n}\n\n// Start implements common.Runnable.\nfunc (m *Manager) Start() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.running = true\n\terrs := []error{}\n\tfor _, channel := range m.channels {\n\t\tif err := channel.Start(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) != 0 {\n\t\treturn errors.Combine(errs...)\n\t}\n\treturn nil\n}\n\n// Close implement common.Closable.\nfunc (m *Manager) Close() error {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\tm.running = false\n\terrs := []error{}\n\tfor name, channel := range m.channels {\n\t\terrors.LogDebug(context.Background(), \"remove channel \", name)\n\t\tdelete(m.channels, name)\n\t\tif err := channel.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) != 0 {\n\t\treturn errors.Combine(errs...)\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewManager(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "app/stats/stats_test.go",
    "content": "package stats_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\nfunc TestInterface(t *testing.T) {\n\t_ = (stats.Manager)(new(Manager))\n}\n\nfunc TestStatsChannelRunnable(t *testing.T) {\n\traw, err := common.CreateObject(context.Background(), &Config{})\n\tcommon.Must(err)\n\n\tm := raw.(stats.Manager)\n\n\tch1, err := m.RegisterChannel(\"test.channel.1\")\n\tc1 := ch1.(*Channel)\n\tcommon.Must(err)\n\n\tif c1.Running() {\n\t\tt.Fatalf(\"unexpected running channel: test.channel.%d\", 1)\n\t}\n\n\tcommon.Must(m.Start())\n\n\tif !c1.Running() {\n\t\tt.Fatalf(\"unexpected non-running channel: test.channel.%d\", 1)\n\t}\n\n\tch2, err := m.RegisterChannel(\"test.channel.2\")\n\tc2 := ch2.(*Channel)\n\tcommon.Must(err)\n\n\tif !c2.Running() {\n\t\tt.Fatalf(\"unexpected non-running channel: test.channel.%d\", 2)\n\t}\n\n\ts1, err := c1.Subscribe()\n\tcommon.Must(err)\n\tcommon.Must(c1.Close())\n\n\tif c1.Running() {\n\t\tt.Fatalf(\"unexpected running channel: test.channel.%d\", 1)\n\t}\n\n\tselect { // Check all subscribers in closed channel are closed\n\tcase _, ok := <-s1:\n\t\tif ok {\n\t\t\tt.Fatalf(\"unexpected non-closed subscriber in channel: test.channel.%d\", 1)\n\t\t}\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatalf(\"unexpected non-closed subscriber in channel: test.channel.%d\", 1)\n\t}\n\n\tif len(c1.Subscribers()) != 0 { // Check subscribers in closed channel are emptied\n\t\tt.Fatalf(\"unexpected non-empty subscribers in channel: test.channel.%d\", 1)\n\t}\n\n\tcommon.Must(m.Close())\n\n\tif c2.Running() {\n\t\tt.Fatalf(\"unexpected running channel: test.channel.%d\", 2)\n\t}\n\n\tch3, err := m.RegisterChannel(\"test.channel.3\")\n\tc3 := ch3.(*Channel)\n\tcommon.Must(err)\n\n\tif c3.Running() {\n\t\tt.Fatalf(\"unexpected running channel: test.channel.%d\", 3)\n\t}\n\n\tcommon.Must(c3.Start())\n\tcommon.Must(m.UnregisterChannel(\"test.channel.3\"))\n\n\tif c3.Running() { // Test that unregistering will close the channel.\n\t\tt.Fatalf(\"unexpected running channel: test.channel.%d\", 3)\n\t}\n}\n"
  },
  {
    "path": "app/version/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: app/version/config.proto\n\npackage version\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tCoreVersion   string                 `protobuf:\"bytes,1,opt,name=core_version,json=coreVersion,proto3\" json:\"core_version,omitempty\"`\n\tMinVersion    string                 `protobuf:\"bytes,2,opt,name=min_version,json=minVersion,proto3\" json:\"min_version,omitempty\"`\n\tMaxVersion    string                 `protobuf:\"bytes,3,opt,name=max_version,json=maxVersion,proto3\" json:\"max_version,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_app_version_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_app_version_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_app_version_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetCoreVersion() string {\n\tif x != nil {\n\t\treturn x.CoreVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMinVersion() string {\n\tif x != nil {\n\t\treturn x.MinVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMaxVersion() string {\n\tif x != nil {\n\t\treturn x.MaxVersion\n\t}\n\treturn \"\"\n}\n\nvar File_app_version_config_proto protoreflect.FileDescriptor\n\nconst file_app_version_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x18app/version/config.proto\\x12\\x10xray.app.version\\\"m\\n\" +\n\t\"\\x06Config\\x12!\\n\" +\n\t\"\\fcore_version\\x18\\x01 \\x01(\\tR\\vcoreVersion\\x12\\x1f\\n\" +\n\t\"\\vmin_version\\x18\\x02 \\x01(\\tR\\n\" +\n\t\"minVersion\\x12\\x1f\\n\" +\n\t\"\\vmax_version\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"maxVersionBR\\n\" +\n\t\"\\x14com.xray.app.versionP\\x01Z%github.com/xtls/xray-core/app/version\\xaa\\x02\\x10Xray.App.Versionb\\x06proto3\"\n\nvar (\n\tfile_app_version_config_proto_rawDescOnce sync.Once\n\tfile_app_version_config_proto_rawDescData []byte\n)\n\nfunc file_app_version_config_proto_rawDescGZIP() []byte {\n\tfile_app_version_config_proto_rawDescOnce.Do(func() {\n\t\tfile_app_version_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_version_config_proto_rawDesc), len(file_app_version_config_proto_rawDesc)))\n\t})\n\treturn file_app_version_config_proto_rawDescData\n}\n\nvar file_app_version_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_app_version_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.app.version.Config\n}\nvar file_app_version_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_app_version_config_proto_init() }\nfunc file_app_version_config_proto_init() {\n\tif File_app_version_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_app_version_config_proto_rawDesc), len(file_app_version_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_app_version_config_proto_goTypes,\n\t\tDependencyIndexes: file_app_version_config_proto_depIdxs,\n\t\tMessageInfos:      file_app_version_config_proto_msgTypes,\n\t}.Build()\n\tFile_app_version_config_proto = out.File\n\tfile_app_version_config_proto_goTypes = nil\n\tfile_app_version_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "app/version/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.app.version;\noption csharp_namespace = \"Xray.App.Version\";\noption go_package = \"github.com/xtls/xray-core/app/version\";\noption java_package = \"com.xray.app.version\";\noption java_multiple_files = true;\n\n\nmessage Config {\n  string core_version = 1;\n  string min_version = 2;\n  string max_version = 3;\n}\n"
  },
  {
    "path": "app/version/version.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Version struct {\n\tconfig *Config\n\tctx    context.Context\n}\n\nfunc New(ctx context.Context, config *Config) (*Version, error) {\n\tif config.MinVersion != \"\" {\n\t\tresult, err := compareVersions(config.MinVersion, config.CoreVersion)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif result > 0 {\n\t\t\treturn nil, errors.New(\"this config must be run on version \", config.MinVersion, \" or higher\")\n\t\t}\n\t}\n\tif config.MaxVersion != \"\" {\n\t\tresult, err := compareVersions(config.MaxVersion, config.CoreVersion)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif result < 0 {\n\t\t\treturn nil, errors.New(\"this config should be run on version \", config.MaxVersion, \" or lower\")\n\t\t}\n\t}\n\treturn &Version{config: config, ctx: ctx}, nil\n}\n\nfunc compareVersions(v1, v2 string) (int, error) {\n\t// Split version strings into components\n\tv1Parts := strings.Split(v1, \".\")\n\tv2Parts := strings.Split(v2, \".\")\n\n\t// Pad shorter versions with zeros\n\tfor len(v1Parts) < len(v2Parts) {\n\t\tv1Parts = append(v1Parts, \"0\")\n\t}\n\tfor len(v2Parts) < len(v1Parts) {\n\t\tv2Parts = append(v2Parts, \"0\")\n\t}\n\n\t// Compare each part\n\tfor i := 0; i < len(v1Parts); i++ {\n\t\t// Convert parts to integers\n\t\tn1, err := strconv.Atoi(v1Parts[i])\n\t\tif err != nil {\n\t\t\treturn 0, errors.New(\"invalid version component \", v1Parts[i], \" in \", v1)\n\t\t}\n\t\tn2, err := strconv.Atoi(v2Parts[i])\n\t\tif err != nil {\n\t\t\treturn 0, errors.New(\"invalid version component \", v2Parts[i], \" in \", v2)\n\t\t}\n\n\t\tif n1 < n2 {\n\t\t\treturn -1, nil // v1 < v2\n\t\t}\n\t\tif n1 > n2 {\n\t\t\treturn 1, nil // v1 > v2\n\t\t}\n\t}\n\treturn 0, nil // v1 == v2\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "common/antireplay/antireplay_test.go",
    "content": "package antireplay\n\nimport (\n\t\"bufio\"\n\t\"crypto/rand\"\n\t\"testing\"\n)\n\nfunc BenchmarkMapFilter(b *testing.B) {\n\tfilter := NewMapFilter[[16]byte](120)\n\tvar sample [16]byte\n\treader := bufio.NewReader(rand.Reader)\n\treader.Read(sample[:])\n\tb.ResetTimer()\n\tfor range b.N {\n\t\treader.Read(sample[:])\n\t\tfilter.Check(sample)\n\t}\n}\n\nfunc TestMapFilter(t *testing.T) {\n\tfilter := NewMapFilter[[16]byte](120)\n\tvar sample [16]byte\n\trand.Read(sample[:])\n\tfilter.Check(sample)\n\tif filter.Check(sample) {\n\t\tt.Error(\"Unexpected true negative\")\n\t}\n\tsample[0]++\n\tif !filter.Check(sample) {\n\t\tt.Error(\"Unexpected false positive\")\n\t}\n}\n"
  },
  {
    "path": "common/antireplay/mapfilter.go",
    "content": "package antireplay\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// ReplayFilter checks for replay attacks.\ntype ReplayFilter[T comparable] struct {\n\tlock      sync.Mutex\n\tpoolA     map[T]struct{}\n\tpoolB     map[T]struct{}\n\tinterval  time.Duration\n\tlastClean time.Time\n}\n\n// NewMapFilter create a new filter with specifying the expiration time interval in seconds.\nfunc NewMapFilter[T comparable](interval int64) *ReplayFilter[T] {\n\tfilter := &ReplayFilter[T]{\n\t\tpoolA:     make(map[T]struct{}),\n\t\tpoolB:     make(map[T]struct{}),\n\t\tinterval:  time.Duration(interval) * time.Second,\n\t\tlastClean: time.Now(),\n\t}\n\treturn filter\n}\n\n// Check determines if there are duplicate records.\nfunc (filter *ReplayFilter[T]) Check(sum T) bool {\n\tfilter.lock.Lock()\n\tdefer filter.lock.Unlock()\n\n\tnow := time.Now()\n\tif now.Sub(filter.lastClean) >= filter.interval {\n\t\tfilter.poolB = filter.poolA\n\t\tfilter.poolA = make(map[T]struct{})\n\t\tfilter.lastClean = now\n\t}\n\n\t_, existsA := filter.poolA[sum]\n\t_, existsB := filter.poolB[sum]\n\tif !existsA && !existsB {\n\t\tfilter.poolA[sum] = struct{}{}\n\t}\n\treturn !(existsA || existsB)\n}\n"
  },
  {
    "path": "common/bitmask/byte.go",
    "content": "package bitmask\n\n// Byte is a bitmask in byte.\ntype Byte byte\n\n// Has returns true if this bitmask contains another bitmask.\nfunc (b Byte) Has(bb Byte) bool {\n\treturn (b & bb) != 0\n}\n\nfunc (b *Byte) Set(bb Byte) {\n\t*b |= bb\n}\n\nfunc (b *Byte) Clear(bb Byte) {\n\t*b &= ^bb\n}\n\nfunc (b *Byte) Toggle(bb Byte) {\n\t*b ^= bb\n}\n"
  },
  {
    "path": "common/bitmask/byte_test.go",
    "content": "package bitmask_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/bitmask\"\n)\n\nfunc TestBitmaskByte(t *testing.T) {\n\tb := Byte(0)\n\tb.Set(Byte(1))\n\tif !b.Has(1) {\n\t\tt.Fatal(\"expected \", b, \" to contain 1, but actually not\")\n\t}\n\n\tb.Set(Byte(2))\n\tif !b.Has(2) {\n\t\tt.Fatal(\"expected \", b, \" to contain 2, but actually not\")\n\t}\n\tif !b.Has(1) {\n\t\tt.Fatal(\"expected \", b, \" to contain 1, but actually not\")\n\t}\n\n\tb.Clear(Byte(1))\n\tif !b.Has(2) {\n\t\tt.Fatal(\"expected \", b, \" to contain 2, but actually not\")\n\t}\n\tif b.Has(1) {\n\t\tt.Fatal(\"expected \", b, \" to not contain 1, but actually did\")\n\t}\n\n\tb.Toggle(Byte(2))\n\tif b.Has(2) {\n\t\tt.Fatal(\"expected \", b, \" to not contain 2, but actually did\")\n\t}\n}\n"
  },
  {
    "path": "common/buf/buf.go",
    "content": "// Package buf provides a light-weight memory allocation mechanism.\npackage buf // import \"github.com/xtls/xray-core/common/buf\"\n"
  },
  {
    "path": "common/buf/buffer.go",
    "content": "package buf\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/bytespool\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nconst (\n\t// Size of a regular buffer.\n\tSize = 8192\n)\n\nvar ErrBufferFull = errors.New(\"buffer is full\")\n\nvar pool = bytespool.GetPool(Size)\n\n// ownership represents the data owner of the buffer.\ntype ownership uint8\n\nconst (\n\tmanaged ownership = iota\n\tunmanaged\n\tbytespools\n)\n\n// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles\n// the buffer into an internal buffer pool, in order to recreate a buffer more\n// quickly.\ntype Buffer struct {\n\tv         []byte\n\tstart     int32\n\tend       int32\n\townership ownership\n\tUDP       *net.Destination\n}\n\n// New creates a Buffer with 0 length and 8K capacity, managed.\nfunc New() *Buffer {\n\tbuf := pool.Get().([]byte)\n\tif cap(buf) >= Size {\n\t\tbuf = buf[:Size]\n\t} else {\n\t\tbuf = make([]byte, Size)\n\t}\n\n\treturn &Buffer{\n\t\tv: buf,\n\t}\n}\n\n// NewExisted creates a standard size Buffer with an existed bytearray, managed.\nfunc NewExisted(b []byte) *Buffer {\n\tif cap(b) < Size {\n\t\tpanic(\"Invalid buffer\")\n\t}\n\n\toLen := len(b)\n\tif oLen < Size {\n\t\tb = b[:Size]\n\t}\n\n\treturn &Buffer{\n\t\tv:   b,\n\t\tend: int32(oLen),\n\t}\n}\n\n// FromBytes creates a Buffer with an existed bytearray, unmanaged.\nfunc FromBytes(b []byte) *Buffer {\n\treturn &Buffer{\n\t\tv:         b,\n\t\tend:       int32(len(b)),\n\t\townership: unmanaged,\n\t}\n}\n\n// StackNew creates a new Buffer object on stack, managed.\n// This method is for buffers that is released in the same function.\nfunc StackNew() Buffer {\n\tbuf := pool.Get().([]byte)\n\tif cap(buf) >= Size {\n\t\tbuf = buf[:Size]\n\t} else {\n\t\tbuf = make([]byte, Size)\n\t}\n\n\treturn Buffer{\n\t\tv: buf,\n\t}\n}\n\n// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.\nfunc NewWithSize(size int32) *Buffer {\n\treturn &Buffer{\n\t\tv:         bytespool.Alloc(size),\n\t\townership: bytespools,\n\t}\n}\n\n// Release recycles the buffer into an internal buffer pool.\nfunc (b *Buffer) Release() {\n\tif b == nil || b.v == nil || b.ownership == unmanaged {\n\t\treturn\n\t}\n\n\tp := b.v\n\tb.v = nil\n\tb.Clear()\n\n\tswitch b.ownership {\n\tcase managed:\n\t\tif cap(p) == Size {\n\t\t\tpool.Put(p)\n\t\t}\n\tcase bytespools:\n\t\tbytespool.Free(p)\n\t}\n\tb.UDP = nil\n}\n\n// Clear clears the content of the buffer, results an empty buffer with\n// Len() = 0.\nfunc (b *Buffer) Clear() {\n\tb.start = 0\n\tb.end = 0\n}\n\n// Byte returns the bytes at index.\nfunc (b *Buffer) Byte(index int32) byte {\n\treturn b.v[b.start+index]\n}\n\n// SetByte sets the byte value at index.\nfunc (b *Buffer) SetByte(index int32, value byte) {\n\tb.v[b.start+index] = value\n}\n\n// Bytes returns the content bytes of this Buffer.\nfunc (b *Buffer) Bytes() []byte {\n\treturn b.v[b.start:b.end]\n}\n\n// Extend increases the buffer size by n bytes, and returns the extended part.\n// It panics if result size is larger than size of this buffer.\nfunc (b *Buffer) Extend(n int32) []byte {\n\tend := b.end + n\n\tif end > int32(len(b.v)) {\n\t\tpanic(\"extending out of bound\")\n\t}\n\text := b.v[b.end:end]\n\tb.end = end\n\tclear(ext)\n\treturn ext\n}\n\n// BytesRange returns a slice of this buffer with given from and to boundary.\nfunc (b *Buffer) BytesRange(from, to int32) []byte {\n\tif from < 0 {\n\t\tfrom += b.Len()\n\t}\n\tif to < 0 {\n\t\tto += b.Len()\n\t}\n\treturn b.v[b.start+from : b.start+to]\n}\n\n// BytesFrom returns a slice of this Buffer starting from the given position.\nfunc (b *Buffer) BytesFrom(from int32) []byte {\n\tif from < 0 {\n\t\tfrom += b.Len()\n\t}\n\treturn b.v[b.start+from : b.end]\n}\n\n// BytesTo returns a slice of this Buffer from start to the given position.\nfunc (b *Buffer) BytesTo(to int32) []byte {\n\tif to < 0 {\n\t\tto += b.Len()\n\t}\n\tif to < 0 {\n\t\tto = 0\n\t}\n\treturn b.v[b.start : b.start+to]\n}\n\n// Check makes sure that 0 <= b.start <= b.end.\nfunc (b *Buffer) Check() {\n\tif b.start < 0 {\n\t\tb.start = 0\n\t}\n\tif b.end < 0 {\n\t\tb.end = 0\n\t}\n\tif b.start > b.end {\n\t\tb.start = b.end\n\t}\n}\n\n// Resize cuts the buffer at the given position.\nfunc (b *Buffer) Resize(from, to int32) {\n\toldEnd := b.end\n\tif from < 0 {\n\t\tfrom += b.Len()\n\t}\n\tif to < 0 {\n\t\tto += b.Len()\n\t}\n\tif to < from {\n\t\tpanic(\"Invalid slice\")\n\t}\n\tb.end = b.start + to\n\tb.start += from\n\tb.Check()\n\tif b.end > oldEnd {\n\t\tclear(b.v[oldEnd:b.end])\n\t}\n}\n\n// Advance cuts the buffer at the given position.\nfunc (b *Buffer) Advance(from int32) {\n\tif from < 0 {\n\t\tfrom += b.Len()\n\t}\n\tb.start += from\n\tb.Check()\n}\n\n// Len returns the length of the buffer content.\nfunc (b *Buffer) Len() int32 {\n\tif b == nil {\n\t\treturn 0\n\t}\n\treturn b.end - b.start\n}\n\n// Cap returns the capacity of the buffer content.\nfunc (b *Buffer) Cap() int32 {\n\tif b == nil {\n\t\treturn 0\n\t}\n\treturn int32(len(b.v))\n}\n\n// Available returns the available capacity of the buffer content.\nfunc (b *Buffer) Available() int32 {\n\tif b == nil {\n\t\treturn 0\n\t}\n\treturn int32(len(b.v)) - b.end\n}\n\n// IsEmpty returns true if the buffer is empty.\nfunc (b *Buffer) IsEmpty() bool {\n\treturn b.Len() == 0\n}\n\n// IsFull returns true if the buffer has no more room to grow.\nfunc (b *Buffer) IsFull() bool {\n\treturn b != nil && b.end == int32(len(b.v))\n}\n\n// Write implements Write method in io.Writer.\nfunc (b *Buffer) Write(data []byte) (int, error) {\n\tnBytes := copy(b.v[b.end:], data)\n\tb.end += int32(nBytes)\n\tif nBytes < len(data) {\n\t\treturn nBytes, ErrBufferFull\n\t}\n\treturn nBytes, nil\n}\n\n// WriteByte writes a single byte into the buffer.\nfunc (b *Buffer) WriteByte(v byte) error {\n\tif b.IsFull() {\n\t\treturn ErrBufferFull\n\t}\n\tb.v[b.end] = v\n\tb.end++\n\treturn nil\n}\n\n// WriteString implements io.StringWriter.\nfunc (b *Buffer) WriteString(s string) (int, error) {\n\treturn b.Write([]byte(s))\n}\n\n// ReadByte implements io.ByteReader\nfunc (b *Buffer) ReadByte() (byte, error) {\n\tif b.start == b.end {\n\t\treturn 0, io.EOF\n\t}\n\n\tnb := b.v[b.start]\n\tb.start++\n\treturn nb, nil\n}\n\n// ReadBytes implements bufio.Reader.ReadBytes\nfunc (b *Buffer) ReadBytes(length int32) ([]byte, error) {\n\tif b.end-b.start < length {\n\t\treturn nil, io.EOF\n\t}\n\n\tnb := b.v[b.start : b.start+length]\n\tb.start += length\n\treturn nb, nil\n}\n\n// Read implements io.Reader.Read().\nfunc (b *Buffer) Read(data []byte) (int, error) {\n\tif b.Len() == 0 {\n\t\treturn 0, io.EOF\n\t}\n\tnBytes := copy(data, b.v[b.start:b.end])\n\tif int32(nBytes) == b.Len() {\n\t\tb.Clear()\n\t} else {\n\t\tb.start += int32(nBytes)\n\t}\n\treturn nBytes, nil\n}\n\n// ReadFrom implements io.ReaderFrom.\nfunc (b *Buffer) ReadFrom(reader io.Reader) (int64, error) {\n\tn, err := reader.Read(b.v[b.end:])\n\tb.end += int32(n)\n\treturn int64(n), err\n}\n\n// ReadFullFrom reads exact size of bytes from given reader, or until error occurs.\nfunc (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) {\n\tend := b.end + size\n\tif end > int32(len(b.v)) {\n\t\tv := end\n\t\treturn 0, errors.New(\"out of bound: \", v)\n\t}\n\tn, err := io.ReadFull(reader, b.v[b.end:end])\n\tb.end += int32(n)\n\treturn int64(n), err\n}\n\n// String returns the string form of this Buffer.\nfunc (b *Buffer) String() string {\n\treturn string(b.Bytes())\n}\n"
  },
  {
    "path": "common/buf/buffer_test.go",
    "content": "package buf_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/buf\"\n)\n\nfunc TestBufferClear(t *testing.T) {\n\tbuffer := New()\n\tdefer buffer.Release()\n\n\tpayload := \"Bytes\"\n\tbuffer.Write([]byte(payload))\n\tif diff := cmp.Diff(buffer.Bytes(), []byte(payload)); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n\n\tbuffer.Clear()\n\tif buffer.Len() != 0 {\n\t\tt.Error(\"expect 0 length, but got \", buffer.Len())\n\t}\n}\n\nfunc TestBufferIsEmpty(t *testing.T) {\n\tbuffer := New()\n\tdefer buffer.Release()\n\n\tif buffer.IsEmpty() != true {\n\t\tt.Error(\"expect empty buffer, but not\")\n\t}\n}\n\nfunc TestBufferString(t *testing.T) {\n\tbuffer := New()\n\tdefer buffer.Release()\n\n\tconst payload = \"Test String\"\n\tcommon.Must2(buffer.WriteString(payload))\n\tif buffer.String() != payload {\n\t\tt.Error(\"expect buffer content as \", payload, \" but actually \", buffer.String())\n\t}\n}\n\nfunc TestBufferByte(t *testing.T) {\n\t{\n\t\tbuffer := New()\n\t\tcommon.Must(buffer.WriteByte('m'))\n\t\tif buffer.String() != \"m\" {\n\t\t\tt.Error(\"expect buffer content as \", \"m\", \" but actually \", buffer.String())\n\t\t}\n\t\tbuffer.Release()\n\t}\n\t{\n\t\tbuffer := StackNew()\n\t\tcommon.Must(buffer.WriteByte('n'))\n\t\tif buffer.String() != \"n\" {\n\t\t\tt.Error(\"expect buffer content as \", \"n\", \" but actually \", buffer.String())\n\t\t}\n\t\tbuffer.Release()\n\t}\n\t{\n\t\tbuffer := StackNew()\n\t\tcommon.Must2(buffer.WriteString(\"HELLOWORLD\"))\n\t\tif b := buffer.Byte(5); b != 'W' {\n\t\t\tt.Error(\"unexpected byte \", b)\n\t\t}\n\n\t\tbuffer.SetByte(5, 'M')\n\t\tif buffer.String() != \"HELLOMORLD\" {\n\t\t\tt.Error(\"expect buffer content as \", \"n\", \" but actually \", buffer.String())\n\t\t}\n\t\tbuffer.Release()\n\t}\n}\n\nfunc TestBufferResize(t *testing.T) {\n\tbuffer := New()\n\tdefer buffer.Release()\n\n\tconst payload = \"Test String\"\n\tcommon.Must2(buffer.WriteString(payload))\n\tif buffer.String() != payload {\n\t\tt.Error(\"expect buffer content as \", payload, \" but actually \", buffer.String())\n\t}\n\n\tbuffer.Resize(-6, -3)\n\tif l := buffer.Len(); int(l) != 3 {\n\t\tt.Error(\"len error \", l)\n\t}\n\n\tif s := buffer.String(); s != \"Str\" {\n\t\tt.Error(\"unexpect buffer \", s)\n\t}\n\n\tbuffer.Resize(int32(len(payload)), 200)\n\tif l := buffer.Len(); int(l) != 200-len(payload) {\n\t\tt.Error(\"len error \", l)\n\t}\n}\n\nfunc TestBufferSlice(t *testing.T) {\n\t{\n\t\tb := New()\n\t\tcommon.Must2(b.Write([]byte(\"abcd\")))\n\t\tbytes := b.BytesFrom(-2)\n\t\tif diff := cmp.Diff(bytes, []byte{'c', 'd'}); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n\n\t{\n\t\tb := New()\n\t\tcommon.Must2(b.Write([]byte(\"abcd\")))\n\t\tbytes := b.BytesTo(-2)\n\t\tif diff := cmp.Diff(bytes, []byte{'a', 'b'}); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n\n\t{\n\t\tb := New()\n\t\tcommon.Must2(b.Write([]byte(\"abcd\")))\n\t\tbytes := b.BytesRange(-3, -1)\n\t\tif diff := cmp.Diff(bytes, []byte{'b', 'c'}); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n}\n\nfunc TestBufferReadFullFrom(t *testing.T) {\n\tpayload := make([]byte, 1024)\n\tcommon.Must2(rand.Read(payload))\n\n\treader := bytes.NewReader(payload)\n\tb := New()\n\tn, err := b.ReadFullFrom(reader, 1024)\n\tcommon.Must(err)\n\tif n != 1024 {\n\t\tt.Error(\"expect reading 1024 bytes, but actually \", n)\n\t}\n\n\tif diff := cmp.Diff(payload, b.Bytes()); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n}\n\nfunc BenchmarkNewBuffer(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tbuffer := New()\n\t\tbuffer.Release()\n\t}\n}\n\nfunc BenchmarkNewBufferStack(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tbuffer := StackNew()\n\t\tbuffer.Release()\n\t}\n}\n\nfunc BenchmarkWrite2(b *testing.B) {\n\tbuffer := New()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = buffer.Write([]byte{'a', 'b'})\n\t\tbuffer.Clear()\n\t}\n}\n\nfunc BenchmarkWrite8(b *testing.B) {\n\tbuffer := New()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = buffer.Write([]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'})\n\t\tbuffer.Clear()\n\t}\n}\n\nfunc BenchmarkWrite32(b *testing.B) {\n\tbuffer := New()\n\tpayload := make([]byte, 32)\n\trand.Read(payload)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = buffer.Write(payload)\n\t\tbuffer.Clear()\n\t}\n}\n\nfunc BenchmarkWriteByte2(b *testing.B) {\n\tbuffer := New()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = buffer.WriteByte('a')\n\t\t_ = buffer.WriteByte('b')\n\t\tbuffer.Clear()\n\t}\n}\n\nfunc BenchmarkWriteByte8(b *testing.B) {\n\tbuffer := New()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = buffer.WriteByte('a')\n\t\t_ = buffer.WriteByte('b')\n\t\t_ = buffer.WriteByte('c')\n\t\t_ = buffer.WriteByte('d')\n\t\t_ = buffer.WriteByte('e')\n\t\t_ = buffer.WriteByte('f')\n\t\t_ = buffer.WriteByte('g')\n\t\t_ = buffer.WriteByte('h')\n\t\tbuffer.Clear()\n\t}\n}\n"
  },
  {
    "path": "common/buf/copy.go",
    "content": "package buf\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\ntype dataHandler func(MultiBuffer)\n\ntype copyHandler struct {\n\tonData []dataHandler\n}\n\n// SizeCounter is for counting bytes copied by Copy().\ntype SizeCounter struct {\n\tSize int64\n}\n\n// CopyOption is an option for copying data.\ntype CopyOption func(*copyHandler)\n\n// UpdateActivity is a CopyOption to update activity on each data copy operation.\nfunc UpdateActivity(timer signal.ActivityUpdater) CopyOption {\n\treturn func(handler *copyHandler) {\n\t\thandler.onData = append(handler.onData, func(MultiBuffer) {\n\t\t\ttimer.Update()\n\t\t})\n\t}\n}\n\n// CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter.\nfunc CountSize(sc *SizeCounter) CopyOption {\n\treturn func(handler *copyHandler) {\n\t\thandler.onData = append(handler.onData, func(b MultiBuffer) {\n\t\t\tsc.Size += int64(b.Len())\n\t\t})\n\t}\n}\n\n// AddToStatCounter a CopyOption add to stat counter\nfunc AddToStatCounter(sc stats.Counter) CopyOption {\n\treturn func(handler *copyHandler) {\n\t\thandler.onData = append(handler.onData, func(b MultiBuffer) {\n\t\t\tif sc != nil {\n\t\t\t\tsc.Add(int64(b.Len()))\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype readError struct {\n\terror\n}\n\nfunc (e readError) Error() string {\n\treturn e.error.Error()\n}\n\nfunc (e readError) Unwrap() error {\n\treturn e.error\n}\n\n// IsReadError returns true if the error in Copy() comes from reading.\nfunc IsReadError(err error) bool {\n\t_, ok := err.(readError)\n\treturn ok\n}\n\ntype writeError struct {\n\terror\n}\n\nfunc (e writeError) Error() string {\n\treturn e.error.Error()\n}\n\nfunc (e writeError) Unwrap() error {\n\treturn e.error\n}\n\n// IsWriteError returns true if the error in Copy() comes from writing.\nfunc IsWriteError(err error) bool {\n\t_, ok := err.(writeError)\n\treturn ok\n}\n\nfunc copyInternal(reader Reader, writer Writer, handler *copyHandler) error {\n\tfor {\n\t\tbuffer, err := reader.ReadMultiBuffer()\n\t\tif !buffer.IsEmpty() {\n\t\t\tfor _, handler := range handler.onData {\n\t\t\t\thandler(buffer)\n\t\t\t}\n\n\t\t\tif werr := writer.WriteMultiBuffer(buffer); werr != nil {\n\t\t\t\treturn writeError{werr}\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn readError{err}\n\t\t}\n\t}\n}\n\n// Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF.\nfunc Copy(reader Reader, writer Writer, options ...CopyOption) error {\n\tvar handler copyHandler\n\tfor _, option := range options {\n\t\toption(&handler)\n\t}\n\terr := copyInternal(reader, writer, &handler)\n\tif err != nil && errors.Cause(err) != io.EOF {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nvar ErrNotTimeoutReader = errors.New(\"not a TimeoutReader\")\n\nfunc CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error {\n\ttimeoutReader, ok := reader.(TimeoutReader)\n\tif !ok {\n\t\treturn ErrNotTimeoutReader\n\t}\n\tmb, err := timeoutReader.ReadMultiBufferTimeout(timeout)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn writer.WriteMultiBuffer(mb)\n}\n"
  },
  {
    "path": "common/buf/copy_test.go",
    "content": "package buf_test\n\nimport (\n\t\"crypto/rand\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/testing/mocks\"\n)\n\nfunc TestReadError(t *testing.T) {\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockReader := mocks.NewReader(mockCtl)\n\tmockReader.EXPECT().Read(gomock.Any()).Return(0, errors.New(\"error\"))\n\n\terr := buf.Copy(buf.NewReader(mockReader), buf.Discard)\n\tif err == nil {\n\t\tt.Fatal(\"expected error, but nil\")\n\t}\n\n\tif !buf.IsReadError(err) {\n\t\tt.Error(\"expected to be ReadError, but not\")\n\t}\n\n\tif err.Error() != \"common/buf_test: error\" {\n\t\tt.Fatal(\"unexpected error message: \", err.Error())\n\t}\n}\n\nfunc TestWriteError(t *testing.T) {\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockWriter := mocks.NewWriter(mockCtl)\n\tmockWriter.EXPECT().Write(gomock.Any()).Return(0, errors.New(\"error\"))\n\n\terr := buf.Copy(buf.NewReader(rand.Reader), buf.NewWriter(mockWriter))\n\tif err == nil {\n\t\tt.Fatal(\"expected error, but nil\")\n\t}\n\n\tif !buf.IsWriteError(err) {\n\t\tt.Error(\"expected to be WriteError, but not\")\n\t}\n\n\tif err.Error() != \"common/buf_test: error\" {\n\t\tt.Fatal(\"unexpected error message: \", err.Error())\n\t}\n}\n\ntype TestReader struct{}\n\nfunc (TestReader) Read(b []byte) (int, error) {\n\treturn len(b), nil\n}\n\nfunc BenchmarkCopy(b *testing.B) {\n\treader := buf.NewReader(io.LimitReader(TestReader{}, 10240))\n\twriter := buf.Discard\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = buf.Copy(reader, writer)\n\t}\n}\n"
  },
  {
    "path": "common/buf/io.go",
    "content": "package buf\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Reader extends io.Reader with MultiBuffer.\ntype Reader interface {\n\t// ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer.\n\tReadMultiBuffer() (MultiBuffer, error)\n}\n\n// ErrReadTimeout is an error that happens with IO timeout.\nvar ErrReadTimeout = errors.New(\"IO timeout\")\n\n// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout.\ntype TimeoutReader interface {\n\tReader\n\tReadMultiBufferTimeout(time.Duration) (MultiBuffer, error)\n}\n\ntype TimeoutWrapperReader struct {\n\tReader\n\tstats.Counter\n\tmb   MultiBuffer\n\terr  error\n\tdone chan struct{}\n}\n\nfunc (r *TimeoutWrapperReader) ReadMultiBuffer() (MultiBuffer, error) {\n\tif r.done != nil {\n\t\t<-r.done\n\t\tr.done = nil\n\t\tif r.Counter != nil {\n\t\t\tr.Counter.Add(int64(r.mb.Len()))\n\t\t}\n\t\treturn r.mb, r.err\n\t}\n\tr.mb, r.err = r.Reader.ReadMultiBuffer()\n\tif r.Counter != nil {\n\t\tr.Counter.Add(int64(r.mb.Len()))\n\t}\n\treturn r.mb, r.err\n}\n\nfunc (r *TimeoutWrapperReader) ReadMultiBufferTimeout(duration time.Duration) (MultiBuffer, error) {\n\tif r.done == nil {\n\t\tr.done = make(chan struct{})\n\t\tgo func() {\n\t\t\tr.mb, r.err = r.Reader.ReadMultiBuffer()\n\t\t\tclose(r.done)\n\t\t}()\n\t}\n\ttimeout := make(chan struct{})\n\tgo func() {\n\t\ttime.Sleep(duration)\n\t\tclose(timeout)\n\t}()\n\tselect {\n\tcase <-r.done:\n\t\tr.done = nil\n\t\tif r.Counter != nil {\n\t\t\tr.Counter.Add(int64(r.mb.Len()))\n\t\t}\n\t\treturn r.mb, r.err\n\tcase <-timeout:\n\t\treturn nil, nil\n\t}\n}\n\n// Writer extends io.Writer with MultiBuffer.\ntype Writer interface {\n\t// WriteMultiBuffer writes a MultiBuffer into underlying writer.\n\tWriteMultiBuffer(MultiBuffer) error\n}\n\n// WriteAllBytes ensures all bytes are written into the given writer.\nfunc WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error {\n\twc := 0\n\tdefer func() {\n\t\tif c != nil {\n\t\t\tc.Add(int64(wc))\n\t\t}\n\t}()\n\n\tfor len(payload) > 0 {\n\t\tn, err := writer.Write(payload)\n\t\twc += n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpayload = payload[n:]\n\t}\n\treturn nil\n}\n\nfunc isPacketReader(reader io.Reader) bool {\n\t_, ok := reader.(net.PacketConn)\n\treturn ok\n}\n\n// NewReader creates a new Reader.\n// The Reader instance doesn't take the ownership of reader.\nfunc NewReader(reader io.Reader) Reader {\n\tif mr, ok := reader.(Reader); ok {\n\t\treturn mr\n\t}\n\n\tif isPacketReader(reader) {\n\t\treturn &PacketReader{\n\t\t\tReader: reader,\n\t\t}\n\t}\n\n\t_, isFile := reader.(*os.File)\n\tif !isFile && useReadv {\n\t\tif sc, ok := reader.(syscall.Conn); ok {\n\t\t\trawConn, err := sc.SyscallConn()\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to get sysconn\")\n\t\t\t} else {\n\t\t\t\tvar counter stats.Counter\n\n\t\t\t\tif statConn, ok := reader.(*stat.CounterConnection); ok {\n\t\t\t\t\treader = statConn.Connection\n\t\t\t\t\tcounter = statConn.ReadCounter\n\t\t\t\t}\n\t\t\t\treturn NewReadVReader(reader, rawConn, counter)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &SingleReader{\n\t\tReader: reader,\n\t}\n}\n\n// NewPacketReader creates a new PacketReader based on the given reader.\nfunc NewPacketReader(reader io.Reader) Reader {\n\tif mr, ok := reader.(Reader); ok {\n\t\treturn mr\n\t}\n\n\treturn &PacketReader{\n\t\tReader: reader,\n\t}\n}\n\nfunc isPacketWriter(writer io.Writer) bool {\n\tif _, ok := writer.(net.PacketConn); ok {\n\t\treturn true\n\t}\n\n\t// If the writer doesn't implement syscall.Conn, it is probably not a TCP connection.\n\tif _, ok := writer.(syscall.Conn); !ok {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// NewWriter creates a new Writer.\nfunc NewWriter(writer io.Writer) Writer {\n\tif mw, ok := writer.(Writer); ok {\n\t\treturn mw\n\t}\n\n\tiConn := writer\n\tif statConn, ok := writer.(*stat.CounterConnection); ok {\n\t\tiConn = statConn.Connection\n\t}\n\n\tif isPacketWriter(iConn) {\n\t\treturn &SequentialWriter{\n\t\t\tWriter: writer,\n\t\t}\n\t}\n\n\tvar counter stats.Counter\n\n\tif statConn, ok := writer.(*stat.CounterConnection); ok {\n\t\tcounter = statConn.WriteCounter\n\t}\n\treturn &BufferToBytesWriter{\n\t\tWriter:  iConn,\n\t\tcounter: counter,\n\t}\n}\n"
  },
  {
    "path": "common/buf/io_test.go",
    "content": "package buf_test\n\nimport (\n\t\"crypto/tls\"\n\t\"io\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n)\n\nfunc TestWriterCreation(t *testing.T) {\n\ttcpServer := tcp.Server{}\n\tdest, err := tcpServer.Start()\n\tif err != nil {\n\t\tt.Fatal(\"failed to start tcp server: \", err)\n\t}\n\tdefer tcpServer.Close()\n\n\tconn, err := net.Dial(\"tcp\", dest.NetAddr())\n\tif err != nil {\n\t\tt.Fatal(\"failed to dial a TCP connection: \", err)\n\t}\n\tdefer conn.Close()\n\n\t{\n\t\twriter := NewWriter(conn)\n\t\tif _, ok := writer.(*BufferToBytesWriter); !ok {\n\t\t\tt.Fatal(\"writer is not a BufferToBytesWriter\")\n\t\t}\n\n\t\twriter2 := NewWriter(writer.(io.Writer))\n\t\tif writer2 != writer {\n\t\t\tt.Fatal(\"writer is not reused\")\n\t\t}\n\t}\n\n\ttlsConn := tls.Client(conn, &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t})\n\tdefer tlsConn.Close()\n\n\t{\n\t\twriter := NewWriter(tlsConn)\n\t\tif _, ok := writer.(*SequentialWriter); !ok {\n\t\t\tt.Fatal(\"writer is not a SequentialWriter\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/buf/multi_buffer.go",
    "content": "package buf\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\n// ReadAllToBytes reads all content from the reader into a byte array, until EOF.\nfunc ReadAllToBytes(reader io.Reader) ([]byte, error) {\n\tmb, err := ReadFrom(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif mb.Len() == 0 {\n\t\treturn nil, nil\n\t}\n\tb := make([]byte, mb.Len())\n\tmb, _ = SplitBytes(mb, b)\n\tReleaseMulti(mb)\n\treturn b, nil\n}\n\n// MultiBuffer is a list of Buffers. The order of Buffer matters.\ntype MultiBuffer []*Buffer\n\n// MergeMulti merges content from src to dest, and returns the new address of dest and src\nfunc MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {\n\tdest = append(dest, src...)\n\tfor idx := range src {\n\t\tsrc[idx] = nil\n\t}\n\treturn dest, src[:0]\n}\n\n// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.\nfunc MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {\n\tn := len(dest)\n\tif n > 0 && !(dest)[n-1].IsFull() {\n\t\tnBytes, _ := (dest)[n-1].Write(src)\n\t\tsrc = src[nBytes:]\n\t}\n\n\tfor len(src) > 0 {\n\t\tb := New()\n\t\tnBytes, _ := b.Write(src)\n\t\tsrc = src[nBytes:]\n\t\tdest = append(dest, b)\n\t}\n\n\treturn dest\n}\n\n// ReleaseMulti releases all content of the MultiBuffer, and returns an empty MultiBuffer.\nfunc ReleaseMulti(mb MultiBuffer) MultiBuffer {\n\tfor i := range mb {\n\t\tmb[i].Release()\n\t\tmb[i] = nil\n\t}\n\treturn mb[:0]\n}\n\n// Copy copied the beginning part of the MultiBuffer into the given byte array.\nfunc (mb MultiBuffer) Copy(b []byte) int {\n\ttotal := 0\n\tfor _, bb := range mb {\n\t\tnBytes := copy(b[total:], bb.Bytes())\n\t\ttotal += nBytes\n\t\tif int32(nBytes) < bb.Len() {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn total\n}\n\n// ReadFrom reads all content from reader until EOF.\nfunc ReadFrom(reader io.Reader) (MultiBuffer, error) {\n\tmb := make(MultiBuffer, 0, 16)\n\tfor {\n\t\tb := New()\n\t\t_, err := b.ReadFullFrom(reader, Size)\n\t\tif b.IsEmpty() {\n\t\t\tb.Release()\n\t\t} else {\n\t\t\tmb = append(mb, b)\n\t\t}\n\t\tif err != nil {\n\t\t\tif errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF {\n\t\t\t\treturn mb, nil\n\t\t\t}\n\t\t\treturn mb, err\n\t\t}\n\t}\n}\n\n// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer.\n// It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice.\nfunc SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) {\n\ttotalBytes := 0\n\tendIndex := -1\n\tfor i := range mb {\n\t\tpBuffer := mb[i]\n\t\tnBytes, _ := pBuffer.Read(b)\n\t\ttotalBytes += nBytes\n\t\tb = b[nBytes:]\n\t\tif !pBuffer.IsEmpty() {\n\t\t\tendIndex = i\n\t\t\tbreak\n\t\t}\n\t\tpBuffer.Release()\n\t\tmb[i] = nil\n\t}\n\n\tif endIndex == -1 {\n\t\tmb = mb[:0]\n\t} else {\n\t\tmb = mb[endIndex:]\n\t}\n\n\treturn mb, totalBytes\n}\n\n// SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice.\nfunc SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) {\n\tmb, b := SplitFirst(mb)\n\tif b == nil {\n\t\treturn mb, 0\n\t}\n\tn := copy(p, b.Bytes())\n\tb.Release()\n\treturn mb, n\n}\n\n// Compact returns another MultiBuffer by merging all content of the given one together.\nfunc Compact(mb MultiBuffer) MultiBuffer {\n\tif len(mb) == 0 {\n\t\treturn mb\n\t}\n\n\tmb2 := make(MultiBuffer, 0, len(mb))\n\tlast := mb[0]\n\n\tfor i := 1; i < len(mb); i++ {\n\t\tcurr := mb[i]\n\t\tif curr.Len() > last.Available() {\n\t\t\tmb2 = append(mb2, last)\n\t\t\tlast = curr\n\t\t} else {\n\t\t\tcommon.Must2(last.ReadFrom(curr))\n\t\t\tcurr.Release()\n\t\t}\n\t}\n\n\tmb2 = append(mb2, last)\n\treturn mb2\n}\n\n// SplitFirst splits the first Buffer from the beginning of the MultiBuffer.\nfunc SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) {\n\tif len(mb) == 0 {\n\t\treturn mb, nil\n\t}\n\n\tb := mb[0]\n\tmb[0] = nil\n\tmb = mb[1:]\n\treturn mb, b\n}\n\n// SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes.\nfunc SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) {\n\tif len(mb) == 0 {\n\t\treturn mb, nil\n\t}\n\n\tif mb[0].Len() > size {\n\t\tb := New()\n\t\tcopy(b.Extend(size), mb[0].BytesTo(size))\n\t\tmb[0].Advance(size)\n\t\treturn mb, MultiBuffer{b}\n\t}\n\n\ttotalBytes := int32(0)\n\tvar r MultiBuffer\n\tendIndex := -1\n\tfor i := range mb {\n\t\tif totalBytes+mb[i].Len() > size {\n\t\t\tendIndex = i\n\t\t\tbreak\n\t\t}\n\t\ttotalBytes += mb[i].Len()\n\t\tr = append(r, mb[i])\n\t\tmb[i] = nil\n\t}\n\tif endIndex == -1 {\n\t\t// To reuse mb array\n\t\tmb = mb[:0]\n\t} else {\n\t\tmb = mb[endIndex:]\n\t}\n\treturn mb, r\n}\n\n// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one\nfunc SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) {\n\tmb2 := make(MultiBuffer, 0, len(mb))\n\tif i < len(mb) && i >= 0 {\n\t\tmb2 = append(mb2, mb[i:]...)\n\t\tfor j := i; j < len(mb); j++ {\n\t\t\tmb[j] = nil\n\t\t}\n\t\tmb = mb[:i]\n\t}\n\treturn mb, mb2\n}\n\n// WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer.\nfunc WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) {\n\tfor {\n\t\tmb2, b := SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\n\t\t_, err := writer.Write(b.Bytes())\n\t\tb.Release()\n\t\tif err != nil {\n\t\t\treturn mb, err\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n\n// Len returns the total number of bytes in the MultiBuffer.\nfunc (mb MultiBuffer) Len() int32 {\n\tif mb == nil {\n\t\treturn 0\n\t}\n\n\tsize := int32(0)\n\tfor _, b := range mb {\n\t\tsize += b.Len()\n\t}\n\treturn size\n}\n\n// IsEmpty returns true if the MultiBuffer has no content.\nfunc (mb MultiBuffer) IsEmpty() bool {\n\tfor _, b := range mb {\n\t\tif !b.IsEmpty() {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// String returns the content of the MultiBuffer in string.\nfunc (mb MultiBuffer) String() string {\n\tv := make([]interface{}, len(mb))\n\tfor i, b := range mb {\n\t\tv[i] = b\n\t}\n\treturn serial.Concat(v...)\n}\n\n// MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer.\ntype MultiBufferContainer struct {\n\tMultiBuffer\n}\n\n// Read implements io.Reader.\nfunc (c *MultiBufferContainer) Read(b []byte) (int, error) {\n\tif c.MultiBuffer.IsEmpty() {\n\t\treturn 0, io.EOF\n\t}\n\n\tmb, nBytes := SplitBytes(c.MultiBuffer, b)\n\tc.MultiBuffer = mb\n\treturn nBytes, nil\n}\n\n// ReadMultiBuffer implements Reader.\nfunc (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) {\n\tmb := c.MultiBuffer\n\tc.MultiBuffer = nil\n\treturn mb, nil\n}\n\n// Write implements io.Writer.\nfunc (c *MultiBufferContainer) Write(b []byte) (int, error) {\n\tc.MultiBuffer = MergeBytes(c.MultiBuffer, b)\n\treturn len(b), nil\n}\n\n// WriteMultiBuffer implements Writer.\nfunc (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error {\n\tmb, _ := MergeMulti(c.MultiBuffer, b)\n\tc.MultiBuffer = mb\n\treturn nil\n}\n\n// Close implements io.Closer.\nfunc (c *MultiBufferContainer) Close() error {\n\tc.MultiBuffer = ReleaseMulti(c.MultiBuffer)\n\treturn nil\n}\n"
  },
  {
    "path": "common/buf/multi_buffer_test.go",
    "content": "package buf_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/buf\"\n)\n\nfunc TestMultiBufferRead(t *testing.T) {\n\tb1 := New()\n\tcommon.Must2(b1.WriteString(\"ab\"))\n\n\tb2 := New()\n\tcommon.Must2(b2.WriteString(\"cd\"))\n\tmb := MultiBuffer{b1, b2}\n\n\tbs := make([]byte, 32)\n\t_, nBytes := SplitBytes(mb, bs)\n\tif nBytes != 4 {\n\t\tt.Error(\"expect 4 bytes split, but got \", nBytes)\n\t}\n\tif r := cmp.Diff(bs[:nBytes], []byte(\"abcd\")); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestMultiBufferAppend(t *testing.T) {\n\tvar mb MultiBuffer\n\tb := New()\n\tcommon.Must2(b.WriteString(\"ab\"))\n\tmb = append(mb, b)\n\tif mb.Len() != 2 {\n\t\tt.Error(\"expected length 2, but got \", mb.Len())\n\t}\n}\n\nfunc TestMultiBufferSliceBySizeLarge(t *testing.T) {\n\tlb := make([]byte, 8*1024)\n\tcommon.Must2(io.ReadFull(rand.Reader, lb))\n\n\tmb := MergeBytes(nil, lb)\n\n\tmb, mb2 := SplitSize(mb, 1024)\n\tif mb2.Len() != 1024 {\n\t\tt.Error(\"expect length 1024, but got \", mb2.Len())\n\t}\n\tif mb.Len() != 7*1024 {\n\t\tt.Error(\"expect length 7*1024, but got \", mb.Len())\n\t}\n\n\tmb, mb3 := SplitSize(mb, 7*1024)\n\tif mb3.Len() != 7*1024 {\n\t\tt.Error(\"expect length 7*1024, but got\", mb.Len())\n\t}\n\n\tif !mb.IsEmpty() {\n\t\tt.Error(\"expect empty buffer, but got \", mb.Len())\n\t}\n}\n\nfunc TestMultiBufferSplitFirst(t *testing.T) {\n\tb1 := New()\n\tb1.WriteString(\"b1\")\n\n\tb2 := New()\n\tb2.WriteString(\"b2\")\n\n\tb3 := New()\n\tb3.WriteString(\"b3\")\n\n\tvar mb MultiBuffer\n\tmb = append(mb, b1, b2, b3)\n\n\tmb, c1 := SplitFirst(mb)\n\tif diff := cmp.Diff(b1.String(), c1.String()); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n\n\tmb, c2 := SplitFirst(mb)\n\tif diff := cmp.Diff(b2.String(), c2.String()); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n\n\tmb, c3 := SplitFirst(mb)\n\tif diff := cmp.Diff(b3.String(), c3.String()); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n\n\tif !mb.IsEmpty() {\n\t\tt.Error(\"expect empty buffer, but got \", mb.String())\n\t}\n}\n\nfunc TestMultiBufferReadAllToByte(t *testing.T) {\n\t{\n\t\tlb := make([]byte, 8*1024)\n\t\tcommon.Must2(io.ReadFull(rand.Reader, lb))\n\t\trd := bytes.NewBuffer(lb)\n\t\tb, err := ReadAllToBytes(rd)\n\t\tcommon.Must(err)\n\n\t\tif l := len(b); l != 8*1024 {\n\t\t\tt.Error(\"unexpected length from ReadAllToBytes\", l)\n\t\t}\n\t}\n\t{\n\t\tconst dat = \"data/test_MultiBufferReadAllToByte.dat\"\n\t\tf, err := os.Open(dat)\n\t\tcommon.Must(err)\n\n\t\tbuf2, err := ReadAllToBytes(f)\n\t\tcommon.Must(err)\n\t\tf.Close()\n\n\t\tcnt, err := os.ReadFile(dat)\n\t\tcommon.Must(err)\n\n\t\tif d := cmp.Diff(buf2, cnt); d != \"\" {\n\t\t\tt.Error(\"fail to read from file: \", d)\n\t\t}\n\t}\n}\n\nfunc TestMultiBufferCopy(t *testing.T) {\n\tlb := make([]byte, 8*1024)\n\tcommon.Must2(io.ReadFull(rand.Reader, lb))\n\treader := bytes.NewBuffer(lb)\n\n\tmb, err := ReadFrom(reader)\n\tcommon.Must(err)\n\n\tlbdst := make([]byte, 8*1024)\n\tmb.Copy(lbdst)\n\n\tif d := cmp.Diff(lb, lbdst); d != \"\" {\n\t\tt.Error(\"unexpected different from MultiBufferCopy \", d)\n\t}\n}\n\nfunc TestSplitFirstBytes(t *testing.T) {\n\ta := New()\n\tcommon.Must2(a.WriteString(\"ab\"))\n\tb := New()\n\tcommon.Must2(b.WriteString(\"bc\"))\n\n\tmb := MultiBuffer{a, b}\n\n\to := make([]byte, 2)\n\t_, cnt := SplitFirstBytes(mb, o)\n\tif cnt != 2 {\n\t\tt.Error(\"unexpected cnt from SplitFirstBytes \", cnt)\n\t}\n\tif d := cmp.Diff(string(o), \"ab\"); d != \"\" {\n\t\tt.Error(\"unexpected splited result from SplitFirstBytes \", d)\n\t}\n}\n\nfunc TestCompact(t *testing.T) {\n\ta := New()\n\tcommon.Must2(a.WriteString(\"ab\"))\n\tb := New()\n\tcommon.Must2(b.WriteString(\"bc\"))\n\n\tmb := MultiBuffer{a, b}\n\tcmb := Compact(mb)\n\n\tif w := cmb.String(); w != \"abbc\" {\n\t\tt.Error(\"unexpected Compact result \", w)\n\t}\n}\n\nfunc TestCompactWithConsumed(t *testing.T) {\n\t// make a consumed buffer (a.Start != 0)\n\ta := New()\n\tfor range 8192 {\n\t\tcommon.Must2(a.WriteString(\"a\"))\n\t}\n\ta.Read(make([]byte, 2))\n\n\tb := New()\n\tfor range 2 {\n\t\tcommon.Must2(b.WriteString(\"b\"))\n\t}\n\n\tmb := MultiBuffer{a, b}\n\tcmb := Compact(mb)\n\tmbc := &MultiBufferContainer{mb}\n\tmbc.Read(make([]byte, 8190))\n\n\tif w := cmb.String(); w != \"bb\" {\n\t\tt.Error(\"unexpected Compact result \", w)\n\t}\n}\n\nfunc BenchmarkSplitBytes(b *testing.B) {\n\tvar mb MultiBuffer\n\traw := make([]byte, Size)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tbuffer := StackNew()\n\t\tbuffer.Extend(Size)\n\t\tmb = append(mb, &buffer)\n\t\tmb, _ = SplitBytes(mb, raw)\n\t}\n}\n"
  },
  {
    "path": "common/buf/override.go",
    "content": "package buf\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\ntype EndpointOverrideReader struct {\n\tReader\n\tDest         net.Address\n\tOriginalDest net.Address\n}\n\nfunc (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) {\n\tmb, err := r.Reader.ReadMultiBuffer()\n\tif err == nil {\n\t\tfor _, b := range mb {\n\t\t\tif b.UDP != nil && b.UDP.Address == r.OriginalDest {\n\t\t\t\tb.UDP.Address = r.Dest\n\t\t\t}\n\t\t}\n\t}\n\treturn mb, err\n}\n\ntype EndpointOverrideWriter struct {\n\tWriter\n\tDest         net.Address\n\tOriginalDest net.Address\n}\n\nfunc (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error {\n\tfor _, b := range mb {\n\t\tif b.UDP != nil && b.UDP.Address == w.Dest {\n\t\t\tb.UDP.Address = w.OriginalDest\n\t\t}\n\t}\n\treturn w.Writer.WriteMultiBuffer(mb)\n}\n"
  },
  {
    "path": "common/buf/reader.go",
    "content": "package buf\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nfunc readOneUDP(r io.Reader) (*Buffer, error) {\n\tb := New()\n\tfor i := 0; i < 64; i++ {\n\t\t_, err := b.ReadFrom(r)\n\t\tif !b.IsEmpty() {\n\t\t\treturn b, nil\n\t\t}\n\t\tif err != nil {\n\t\t\tb.Release()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tb.Release()\n\treturn nil, errors.New(\"Reader returns too many empty payloads.\")\n}\n\n// ReadBuffer reads a Buffer from the given reader.\nfunc ReadBuffer(r io.Reader) (*Buffer, error) {\n\tb := New()\n\tn, err := b.ReadFrom(r)\n\tif n > 0 {\n\t\treturn b, err\n\t}\n\tb.Release()\n\treturn nil, err\n}\n\n// BufferedReader is a Reader that keeps its internal buffer.\ntype BufferedReader struct {\n\t// Reader is the underlying reader to be read from\n\tReader Reader\n\t// Buffer is the internal buffer to be read from first\n\tBuffer MultiBuffer\n\t// Splitter is a function to read bytes from MultiBuffer\n\tSplitter func(MultiBuffer, []byte) (MultiBuffer, int)\n}\n\n// BufferedBytes returns the number of bytes that is cached in this reader.\nfunc (r *BufferedReader) BufferedBytes() int32 {\n\treturn r.Buffer.Len()\n}\n\n// ReadByte implements io.ByteReader.\nfunc (r *BufferedReader) ReadByte() (byte, error) {\n\tvar b [1]byte\n\t_, err := r.Read(b[:])\n\treturn b[0], err\n}\n\n// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader.\nfunc (r *BufferedReader) Read(b []byte) (int, error) {\n\tspliter := r.Splitter\n\tif spliter == nil {\n\t\tspliter = SplitBytes\n\t}\n\n\tif !r.Buffer.IsEmpty() {\n\t\tbuffer, nBytes := spliter(r.Buffer, b)\n\t\tr.Buffer = buffer\n\t\tif r.Buffer.IsEmpty() {\n\t\t\tr.Buffer = nil\n\t\t}\n\t\treturn nBytes, nil\n\t}\n\n\tmb, err := r.Reader.ReadMultiBuffer()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tmb, nBytes := spliter(mb, b)\n\tif !mb.IsEmpty() {\n\t\tr.Buffer = mb\n\t}\n\treturn nBytes, nil\n}\n\n// ReadMultiBuffer implements Reader.\nfunc (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) {\n\tif !r.Buffer.IsEmpty() {\n\t\tmb := r.Buffer\n\t\tr.Buffer = nil\n\t\treturn mb, nil\n\t}\n\n\treturn r.Reader.ReadMultiBuffer()\n}\n\n// ReadAtMost returns a MultiBuffer with at most size.\nfunc (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) {\n\tif r.Buffer.IsEmpty() {\n\t\tmb, err := r.Reader.ReadMultiBuffer()\n\t\tif mb.IsEmpty() && err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tr.Buffer = mb\n\t}\n\n\trb, mb := SplitSize(r.Buffer, size)\n\tr.Buffer = rb\n\tif r.Buffer.IsEmpty() {\n\t\tr.Buffer = nil\n\t}\n\treturn mb, nil\n}\n\nfunc (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) {\n\tmbWriter := NewWriter(writer)\n\tvar sc SizeCounter\n\tif r.Buffer != nil {\n\t\tsc.Size = int64(r.Buffer.Len())\n\t\tif err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tr.Buffer = nil\n\t}\n\n\terr := Copy(r.Reader, mbWriter, CountSize(&sc))\n\treturn sc.Size, err\n}\n\n// WriteTo implements io.WriterTo.\nfunc (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) {\n\tnBytes, err := r.writeToInternal(writer)\n\tif errors.Cause(err) == io.EOF {\n\t\treturn nBytes, nil\n\t}\n\treturn nBytes, err\n}\n\n// Interrupt implements common.Interruptible.\nfunc (r *BufferedReader) Interrupt() {\n\tcommon.Interrupt(r.Reader)\n}\n\n// Close implements io.Closer.\nfunc (r *BufferedReader) Close() error {\n\treturn common.Close(r.Reader)\n}\n\n// SingleReader is a Reader that read one Buffer every time.\ntype SingleReader struct {\n\tio.Reader\n}\n\n// ReadMultiBuffer implements Reader.\nfunc (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) {\n\tb, err := ReadBuffer(r.Reader)\n\treturn MultiBuffer{b}, err\n}\n\n// PacketReader is a Reader that read one Buffer every time.\ntype PacketReader struct {\n\tio.Reader\n}\n\n// ReadMultiBuffer implements Reader.\nfunc (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) {\n\tb, err := readOneUDP(r.Reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn MultiBuffer{b}, nil\n}\n"
  },
  {
    "path": "common/buf/reader_test.go",
    "content": "package buf_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc TestBytesReaderWriteTo(t *testing.T) {\n\tpReader, pWriter := pipe.New(pipe.WithSizeLimit(1024))\n\treader := &BufferedReader{Reader: pReader}\n\tb1 := New()\n\tb1.WriteString(\"abc\")\n\tb2 := New()\n\tb2.WriteString(\"efg\")\n\tcommon.Must(pWriter.WriteMultiBuffer(MultiBuffer{b1, b2}))\n\tpWriter.Close()\n\n\tpReader2, pWriter2 := pipe.New(pipe.WithSizeLimit(1024))\n\twriter := NewBufferedWriter(pWriter2)\n\twriter.SetBuffered(false)\n\n\tnBytes, err := io.Copy(writer, reader)\n\tcommon.Must(err)\n\tif nBytes != 6 {\n\t\tt.Error(\"copy: \", nBytes)\n\t}\n\n\tmb, err := pReader2.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif s := mb.String(); s != \"abcefg\" {\n\t\tt.Error(\"content: \", s)\n\t}\n}\n\nfunc TestBytesReaderMultiBuffer(t *testing.T) {\n\tpReader, pWriter := pipe.New(pipe.WithSizeLimit(1024))\n\treader := &BufferedReader{Reader: pReader}\n\tb1 := New()\n\tb1.WriteString(\"abc\")\n\tb2 := New()\n\tb2.WriteString(\"efg\")\n\tcommon.Must(pWriter.WriteMultiBuffer(MultiBuffer{b1, b2}))\n\tpWriter.Close()\n\n\tmbReader := NewReader(reader)\n\tmb, err := mbReader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif s := mb.String(); s != \"abcefg\" {\n\t\tt.Error(\"content: \", s)\n\t}\n}\n\nfunc TestReadByte(t *testing.T) {\n\tsr := strings.NewReader(\"abcd\")\n\treader := &BufferedReader{\n\t\tReader: NewReader(sr),\n\t}\n\tb, err := reader.ReadByte()\n\tcommon.Must(err)\n\tif b != 'a' {\n\t\tt.Error(\"unexpected byte: \", b, \" want a\")\n\t}\n\tif reader.BufferedBytes() != 3 { // 3 bytes left in buffer\n\t\tt.Error(\"unexpected buffered Bytes: \", reader.BufferedBytes())\n\t}\n\n\tnBytes, err := reader.WriteTo(DiscardBytes)\n\tcommon.Must(err)\n\tif nBytes != 3 {\n\t\tt.Error(\"unexpect bytes written: \", nBytes)\n\t}\n}\n\nfunc TestReadBuffer(t *testing.T) {\n\t{\n\t\tsr := strings.NewReader(\"abcd\")\n\t\tbuf, err := ReadBuffer(sr)\n\t\tcommon.Must(err)\n\n\t\tif s := buf.String(); s != \"abcd\" {\n\t\t\tt.Error(\"unexpected str: \", s, \" want abcd\")\n\t\t}\n\t\tbuf.Release()\n\t}\n}\n\nfunc TestReadAtMost(t *testing.T) {\n\tsr := strings.NewReader(\"abcd\")\n\treader := &BufferedReader{\n\t\tReader: NewReader(sr),\n\t}\n\n\tmb, err := reader.ReadAtMost(3)\n\tcommon.Must(err)\n\tif s := mb.String(); s != \"abc\" {\n\t\tt.Error(\"unexpected read result: \", s)\n\t}\n\n\tnBytes, err := reader.WriteTo(DiscardBytes)\n\tcommon.Must(err)\n\tif nBytes != 1 {\n\t\tt.Error(\"unexpect bytes written: \", nBytes)\n\t}\n}\n\nfunc TestPacketReader_ReadMultiBuffer(t *testing.T) {\n\tconst alpha = \"abcefg\"\n\tbuf := bytes.NewBufferString(alpha)\n\treader := &PacketReader{buf}\n\tmb, err := reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif s := mb.String(); s != alpha {\n\t\tt.Error(\"content: \", s)\n\t}\n}\n\nfunc TestReaderInterface(t *testing.T) {\n\t_ = (io.Reader)(new(ReadVReader))\n\t_ = (Reader)(new(ReadVReader))\n\n\t_ = (Reader)(new(BufferedReader))\n\t_ = (io.Reader)(new(BufferedReader))\n\t_ = (io.ByteReader)(new(BufferedReader))\n\t_ = (io.WriterTo)(new(BufferedReader))\n}\n"
  },
  {
    "path": "common/buf/readv_posix.go",
    "content": "//go:build !windows && !wasm && !illumos\n// +build !windows,!wasm,!illumos\n\npackage buf\n\nimport (\n\t\"syscall\"\n\t\"unsafe\"\n)\n\ntype posixReader struct {\n\tiovecs []syscall.Iovec\n}\n\nfunc (r *posixReader) Init(bs []*Buffer) {\n\tiovecs := r.iovecs\n\tif iovecs == nil {\n\t\tiovecs = make([]syscall.Iovec, 0, len(bs))\n\t}\n\tfor idx, b := range bs {\n\t\tiovecs = append(iovecs, syscall.Iovec{\n\t\t\tBase: &(b.v[0]),\n\t\t})\n\t\tiovecs[idx].SetLen(int(Size))\n\t}\n\tr.iovecs = iovecs\n}\n\nfunc (r *posixReader) Read(fd uintptr) int32 {\n\tn, _, e := syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&r.iovecs[0])), uintptr(len(r.iovecs)))\n\tif e != 0 {\n\t\treturn -1\n\t}\n\treturn int32(n)\n}\n\nfunc (r *posixReader) Clear() {\n\tfor idx := range r.iovecs {\n\t\tr.iovecs[idx].Base = nil\n\t}\n\tr.iovecs = r.iovecs[:0]\n}\n\nfunc newMultiReader() multiReader {\n\treturn &posixReader{}\n}\n"
  },
  {
    "path": "common/buf/readv_reader.go",
    "content": "//go:build !wasm\n// +build !wasm\n\npackage buf\n\nimport (\n\t\"io\"\n\t\"syscall\"\n\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\ntype allocStrategy struct {\n\tcurrent uint32\n}\n\nfunc (s *allocStrategy) Current() uint32 {\n\treturn s.current\n}\n\nfunc (s *allocStrategy) Adjust(n uint32) {\n\tif n >= s.current {\n\t\ts.current *= 2\n\t} else {\n\t\ts.current = n\n\t}\n\n\tif s.current > 8 {\n\t\ts.current = 8\n\t}\n\n\tif s.current == 0 {\n\t\ts.current = 1\n\t}\n}\n\nfunc (s *allocStrategy) Alloc() []*Buffer {\n\tbs := make([]*Buffer, s.current)\n\tfor i := range bs {\n\t\tbs[i] = New()\n\t}\n\treturn bs\n}\n\ntype multiReader interface {\n\tInit([]*Buffer)\n\tRead(fd uintptr) int32\n\tClear()\n}\n\n// ReadVReader is a Reader that uses readv(2) syscall to read data.\ntype ReadVReader struct {\n\tio.Reader\n\trawConn syscall.RawConn\n\tmr      multiReader\n\talloc   allocStrategy\n\tcounter stats.Counter\n}\n\n// NewReadVReader creates a new ReadVReader.\nfunc NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) *ReadVReader {\n\treturn &ReadVReader{\n\t\tReader:  reader,\n\t\trawConn: rawConn,\n\t\talloc: allocStrategy{\n\t\t\tcurrent: 1,\n\t\t},\n\t\tmr:      newMultiReader(),\n\t\tcounter: counter,\n\t}\n}\n\nfunc (r *ReadVReader) readMulti() (MultiBuffer, error) {\n\tbs := r.alloc.Alloc()\n\n\tr.mr.Init(bs)\n\tvar nBytes int32\n\terr := r.rawConn.Read(func(fd uintptr) bool {\n\t\tn := r.mr.Read(fd)\n\t\tif n < 0 {\n\t\t\treturn false\n\t\t}\n\n\t\tnBytes = n\n\t\treturn true\n\t})\n\tr.mr.Clear()\n\n\tif err != nil {\n\t\tReleaseMulti(MultiBuffer(bs))\n\t\treturn nil, err\n\t}\n\n\tif nBytes == 0 {\n\t\tReleaseMulti(MultiBuffer(bs))\n\t\treturn nil, io.EOF\n\t}\n\n\tnBuf := 0\n\tfor nBuf < len(bs) {\n\t\tif nBytes <= 0 {\n\t\t\tbreak\n\t\t}\n\t\tend := nBytes\n\t\tif end > Size {\n\t\t\tend = Size\n\t\t}\n\t\tbs[nBuf].end = end\n\t\tnBytes -= end\n\t\tnBuf++\n\t}\n\n\tfor i := nBuf; i < len(bs); i++ {\n\t\tbs[i].Release()\n\t\tbs[i] = nil\n\t}\n\n\treturn MultiBuffer(bs[:nBuf]), nil\n}\n\n// ReadMultiBuffer implements Reader.\nfunc (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {\n\tif r.alloc.Current() == 1 {\n\t\tb, err := ReadBuffer(r.Reader)\n\t\tif b.IsFull() {\n\t\t\tr.alloc.Adjust(1)\n\t\t}\n\t\tif r.counter != nil && b != nil {\n\t\t\tr.counter.Add(int64(b.Len()))\n\t\t}\n\t\treturn MultiBuffer{b}, err\n\t}\n\n\tmb, err := r.readMulti()\n\tif r.counter != nil && mb != nil {\n\t\tr.counter.Add(int64(mb.Len()))\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.alloc.Adjust(uint32(len(mb)))\n\treturn mb, nil\n}\n\nvar useReadv bool\n\nfunc init() {\n\tconst defaultFlagValue = \"NOT_DEFINED_AT_ALL\"\n\tvalue := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })\n\tswitch value {\n\tcase defaultFlagValue, \"auto\", \"enable\":\n\t\tuseReadv = true\n\t}\n}\n"
  },
  {
    "path": "common/buf/readv_reader_wasm.go",
    "content": "//go:build wasm\n// +build wasm\n\npackage buf\n\nimport (\n\t\"io\"\n\t\"syscall\"\n)\n\nconst useReadv = false\n\nfunc NewReadVReader(reader io.Reader, rawConn syscall.RawConn) Reader {\n\tpanic(\"not implemented\")\n}\n"
  },
  {
    "path": "common/buf/readv_test.go",
    "content": "//go:build !wasm\n// +build !wasm\n\npackage buf_test\n\nimport (\n\t\"crypto/rand\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestReadvReader(t *testing.T) {\n\ttcpServer := &tcp.Server{\n\t\tMsgProcessor: func(b []byte) []byte {\n\t\t\treturn b\n\t\t},\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tconn, err := net.Dial(\"tcp\", dest.NetAddr())\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\tconst size = 8192\n\tdata := make([]byte, 8192)\n\tcommon.Must2(rand.Read(data))\n\n\tvar errg errgroup.Group\n\terrg.Go(func() error {\n\t\twriter := NewWriter(conn)\n\t\tmb := MergeBytes(nil, data)\n\n\t\treturn writer.WriteMultiBuffer(mb)\n\t})\n\n\tdefer func() {\n\t\tif err := errg.Wait(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\trawConn, err := conn.(*net.TCPConn).SyscallConn()\n\tcommon.Must(err)\n\n\treader := NewReadVReader(conn, rawConn, nil)\n\tvar rmb MultiBuffer\n\tfor {\n\t\tmb, err := reader.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unexpected error: \", err)\n\t\t}\n\t\trmb, _ = MergeMulti(rmb, mb)\n\t\tif rmb.Len() == size {\n\t\t\tbreak\n\t\t}\n\t}\n\n\trdata := make([]byte, size)\n\tSplitBytes(rmb, rdata)\n\n\tif r := cmp.Diff(data, rdata); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n"
  },
  {
    "path": "common/buf/readv_unix.go",
    "content": "//go:build illumos\n// +build illumos\n\npackage buf\n\nimport \"golang.org/x/sys/unix\"\n\ntype unixReader struct {\n\tiovs [][]byte\n}\n\nfunc (r *unixReader) Init(bs []*Buffer) {\n\tiovs := r.iovs\n\tif iovs == nil {\n\t\tiovs = make([][]byte, 0, len(bs))\n\t}\n\tfor _, b := range bs {\n\t\tiovs = append(iovs, b.v)\n\t}\n\tr.iovs = iovs\n}\n\nfunc (r *unixReader) Read(fd uintptr) int32 {\n\tn, e := unix.Readv(int(fd), r.iovs)\n\tif e != nil {\n\t\treturn -1\n\t}\n\treturn int32(n)\n}\n\nfunc (r *unixReader) Clear() {\n\tr.iovs = r.iovs[:0]\n}\n\nfunc newMultiReader() multiReader {\n\treturn &unixReader{}\n}\n"
  },
  {
    "path": "common/buf/readv_windows.go",
    "content": "package buf\n\nimport (\n\t\"syscall\"\n)\n\ntype windowsReader struct {\n\tbufs []syscall.WSABuf\n}\n\nfunc (r *windowsReader) Init(bs []*Buffer) {\n\tif r.bufs == nil {\n\t\tr.bufs = make([]syscall.WSABuf, 0, len(bs))\n\t}\n\tfor _, b := range bs {\n\t\tr.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})\n\t}\n}\n\nfunc (r *windowsReader) Clear() {\n\tfor idx := range r.bufs {\n\t\tr.bufs[idx].Buf = nil\n\t}\n\tr.bufs = r.bufs[:0]\n}\n\nfunc (r *windowsReader) Read(fd uintptr) int32 {\n\tvar nBytes uint32\n\tvar flags uint32\n\terr := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)\n\tif err != nil {\n\t\treturn -1\n\t}\n\treturn int32(nBytes)\n}\n\nfunc newMultiReader() multiReader {\n\treturn new(windowsReader)\n}\n"
  },
  {
    "path": "common/buf/writer.go",
    "content": "package buf\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\n// BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer.\ntype BufferToBytesWriter struct {\n\tio.Writer\n\n\tcounter stats.Counter\n\tcache   [][]byte\n}\n\n// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer.\nfunc (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {\n\tdefer ReleaseMulti(mb)\n\n\tsize := mb.Len()\n\tif size == 0 {\n\t\treturn nil\n\t}\n\n\tif len(mb) == 1 {\n\t\treturn WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter)\n\t}\n\n\tif cap(w.cache) < len(mb) {\n\t\tw.cache = make([][]byte, 0, len(mb))\n\t}\n\n\tbs := w.cache\n\tfor _, b := range mb {\n\t\tbs = append(bs, b.Bytes())\n\t}\n\n\tdefer func() {\n\t\tfor idx := range bs {\n\t\t\tbs[idx] = nil\n\t\t}\n\t}()\n\n\tnb := net.Buffers(bs)\n\twc := int64(0)\n\tdefer func() {\n\t\tif w.counter != nil {\n\t\t\tw.counter.Add(wc)\n\t\t}\n\t}()\n\tfor size > 0 {\n\t\tn, err := nb.WriteTo(w.Writer)\n\t\twc += n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsize -= int32(n)\n\t}\n\n\treturn nil\n}\n\n// ReadFrom implements io.ReaderFrom.\nfunc (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) {\n\tvar sc SizeCounter\n\terr := Copy(NewReader(reader), w, CountSize(&sc))\n\treturn sc.Size, err\n}\n\n// BufferedWriter is a Writer with internal buffer.\ntype BufferedWriter struct {\n\tsync.Mutex\n\twriter    Writer\n\tbuffer    *Buffer\n\tbuffered  bool\n\tflushNext bool\n}\n\n// NewBufferedWriter creates a new BufferedWriter.\nfunc NewBufferedWriter(writer Writer) *BufferedWriter {\n\treturn &BufferedWriter{\n\t\twriter:   writer,\n\t\tbuffer:   New(),\n\t\tbuffered: true,\n\t}\n}\n\n// WriteByte implements io.ByteWriter.\nfunc (w *BufferedWriter) WriteByte(c byte) error {\n\treturn common.Error2(w.Write([]byte{c}))\n}\n\n// Write implements io.Writer.\nfunc (w *BufferedWriter) Write(b []byte) (int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tif !w.buffered {\n\t\tif writer, ok := w.writer.(io.Writer); ok {\n\t\t\treturn writer.Write(b)\n\t\t}\n\t}\n\n\ttotalBytes := 0\n\tfor len(b) > 0 {\n\t\tif w.buffer == nil {\n\t\t\tw.buffer = New()\n\t\t}\n\n\t\tnBytes, err := w.buffer.Write(b)\n\t\ttotalBytes += nBytes\n\t\tif err != nil {\n\t\t\treturn totalBytes, err\n\t\t}\n\t\tif !w.buffered || w.buffer.IsFull() {\n\t\t\tif err := w.flushInternal(); err != nil {\n\t\t\t\treturn totalBytes, err\n\t\t\t}\n\t\t}\n\t\tb = b[nBytes:]\n\t}\n\n\treturn totalBytes, nil\n}\n\n// WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer.\nfunc (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error {\n\tif b.IsEmpty() {\n\t\treturn nil\n\t}\n\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tif !w.buffered {\n\t\treturn w.writer.WriteMultiBuffer(b)\n\t}\n\n\treader := MultiBufferContainer{\n\t\tMultiBuffer: b,\n\t}\n\tdefer reader.Close()\n\n\tfor !reader.MultiBuffer.IsEmpty() {\n\t\tif w.buffer == nil {\n\t\t\tw.buffer = New()\n\t\t}\n\t\tcommon.Must2(w.buffer.ReadFrom(&reader))\n\t\tif w.buffer.IsFull() {\n\t\t\tif err := w.flushInternal(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif w.flushNext {\n\t\tw.buffered = false\n\t\tw.flushNext = false\n\t\treturn w.flushInternal()\n\t}\n\n\treturn nil\n}\n\n// Flush flushes buffered content into underlying writer.\nfunc (w *BufferedWriter) Flush() error {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\treturn w.flushInternal()\n}\n\nfunc (w *BufferedWriter) flushInternal() error {\n\tif w.buffer.IsEmpty() {\n\t\treturn nil\n\t}\n\n\tb := w.buffer\n\tw.buffer = nil\n\n\tif writer, ok := w.writer.(io.Writer); ok {\n\t\terr := WriteAllBytes(writer, b.Bytes(), nil)\n\t\tb.Release()\n\t\treturn err\n\t}\n\n\treturn w.writer.WriteMultiBuffer(MultiBuffer{b})\n}\n\n// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer.\nfunc (w *BufferedWriter) SetBuffered(f bool) error {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tw.buffered = f\n\tif !f {\n\t\treturn w.flushInternal()\n\t}\n\treturn nil\n}\n\n// SetFlushNext will wait the next WriteMultiBuffer to flush and set buffered = false\nfunc (w *BufferedWriter) SetFlushNext() {\n\tw.Lock()\n\tdefer w.Unlock()\n\tw.flushNext = true\n}\n\n// ReadFrom implements io.ReaderFrom.\nfunc (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) {\n\tif err := w.SetBuffered(false); err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar sc SizeCounter\n\terr := Copy(NewReader(reader), w, CountSize(&sc))\n\treturn sc.Size, err\n}\n\n// Close implements io.Closable.\nfunc (w *BufferedWriter) Close() error {\n\tif err := w.Flush(); err != nil {\n\t\treturn err\n\t}\n\treturn common.Close(w.writer)\n}\n\n// SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer.\ntype SequentialWriter struct {\n\tio.Writer\n}\n\n// WriteMultiBuffer implements Writer.\nfunc (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error {\n\tmb, err := WriteMultiBuffer(w.Writer, mb)\n\tReleaseMulti(mb)\n\treturn err\n}\n\ntype noOpWriter byte\n\nfunc (noOpWriter) WriteMultiBuffer(b MultiBuffer) error {\n\tReleaseMulti(b)\n\treturn nil\n}\n\nfunc (noOpWriter) Write(b []byte) (int, error) {\n\treturn len(b), nil\n}\n\nfunc (noOpWriter) ReadFrom(reader io.Reader) (int64, error) {\n\tb := New()\n\tdefer b.Release()\n\n\ttotalBytes := int64(0)\n\tfor {\n\t\tb.Clear()\n\t\t_, err := b.ReadFrom(reader)\n\t\ttotalBytes += int64(b.Len())\n\t\tif err != nil {\n\t\t\tif errors.Cause(err) == io.EOF {\n\t\t\t\treturn totalBytes, nil\n\t\t\t}\n\t\t\treturn totalBytes, err\n\t\t}\n\t}\n}\n\nvar (\n\t// Discard is a Writer that swallows all contents written in.\n\tDiscard Writer = noOpWriter(0)\n\n\t// DiscardBytes is an io.Writer that swallows all contents written in.\n\tDiscardBytes io.Writer = noOpWriter(0)\n)\n"
  },
  {
    "path": "common/buf/writer_test.go",
    "content": "package buf_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc TestWriter(t *testing.T) {\n\tlb := New()\n\tcommon.Must2(lb.ReadFrom(rand.Reader))\n\n\texpectedBytes := append([]byte(nil), lb.Bytes()...)\n\n\twriteBuffer := bytes.NewBuffer(make([]byte, 0, 1024*1024))\n\n\twriter := NewBufferedWriter(NewWriter(writeBuffer))\n\twriter.SetBuffered(false)\n\tcommon.Must(writer.WriteMultiBuffer(MultiBuffer{lb}))\n\tcommon.Must(writer.Flush())\n\n\tif r := cmp.Diff(expectedBytes, writeBuffer.Bytes()); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestBytesWriterReadFrom(t *testing.T) {\n\tconst size = 50000\n\tpReader, pWriter := pipe.New(pipe.WithSizeLimit(size))\n\treader := bufio.NewReader(io.LimitReader(rand.Reader, size))\n\twriter := NewBufferedWriter(pWriter)\n\twriter.SetBuffered(false)\n\tnBytes, err := reader.WriteTo(writer)\n\tif nBytes != size {\n\t\tt.Fatal(\"unexpected size of bytes written: \", nBytes)\n\t}\n\tif err != nil {\n\t\tt.Fatal(\"expect success, but actually error: \", err.Error())\n\t}\n\n\tmb, err := pReader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif mb.Len() != size {\n\t\tt.Fatal(\"unexpected size read: \", mb.Len())\n\t}\n}\n\nfunc TestDiscardBytes(t *testing.T) {\n\tb := New()\n\tcommon.Must2(b.ReadFullFrom(rand.Reader, Size))\n\n\tnBytes, err := io.Copy(DiscardBytes, b)\n\tcommon.Must(err)\n\tif nBytes != Size {\n\t\tt.Error(\"copy size: \", nBytes)\n\t}\n}\n\nfunc TestDiscardBytesMultiBuffer(t *testing.T) {\n\tconst size = 10240*1024 + 1\n\tbuffer := bytes.NewBuffer(make([]byte, 0, size))\n\tcommon.Must2(buffer.ReadFrom(io.LimitReader(rand.Reader, size)))\n\n\tr := NewReader(buffer)\n\tnBytes, err := io.Copy(DiscardBytes, &BufferedReader{Reader: r})\n\tcommon.Must(err)\n\tif nBytes != size {\n\t\tt.Error(\"copy size: \", nBytes)\n\t}\n}\n\nfunc TestWriterInterface(t *testing.T) {\n\t{\n\t\tvar writer interface{} = (*BufferToBytesWriter)(nil)\n\t\tswitch writer.(type) {\n\t\tcase Writer, io.Writer, io.ReaderFrom:\n\t\tdefault:\n\t\t\tt.Error(\"BufferToBytesWriter is not Writer, io.Writer or io.ReaderFrom\")\n\t\t}\n\t}\n\n\t{\n\t\tvar writer interface{} = (*BufferedWriter)(nil)\n\t\tswitch writer.(type) {\n\t\tcase Writer, io.Writer, io.ReaderFrom, io.ByteWriter:\n\t\tdefault:\n\t\t\tt.Error(\"BufferedWriter is not Writer, io.Writer, io.ReaderFrom or io.ByteWriter\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/bytespool/pool.go",
    "content": "package bytespool\n\nimport \"sync\"\n\nfunc createAllocFunc(size int32) func() interface{} {\n\treturn func() interface{} {\n\t\treturn make([]byte, size)\n\t}\n}\n\n// The following parameters controls the size of buffer pools.\n// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one.\n// Package buf is guaranteed to not use buffers larger than the largest pool.\n// Other packets may use larger buffers.\nconst (\n\tnumPools  = 4\n\tsizeMulti = 4\n)\n\nvar (\n\tpool     [numPools]sync.Pool\n\tpoolSize [numPools]int32\n)\n\nfunc init() {\n\tsize := int32(2048)\n\tfor i := 0; i < numPools; i++ {\n\t\tpool[i] = sync.Pool{\n\t\t\tNew: createAllocFunc(size),\n\t\t}\n\t\tpoolSize[i] = size\n\t\tsize *= sizeMulti\n\t}\n}\n\n// GetPool returns a sync.Pool that generates bytes array with at least the given size.\n// It may return nil if no such pool exists.\n//\n// xray:api:stable\nfunc GetPool(size int32) *sync.Pool {\n\tfor idx, ps := range poolSize {\n\t\tif size <= ps {\n\t\t\treturn &pool[idx]\n\t\t}\n\t}\n\treturn nil\n}\n\n// Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048.\n//\n// xray:api:stable\nfunc Alloc(size int32) []byte {\n\tpool := GetPool(size)\n\tif pool != nil {\n\t\treturn pool.Get().([]byte)\n\t}\n\treturn make([]byte, size)\n}\n\n// Free puts a byte slice into the internal pool.\n//\n// xray:api:stable\nfunc Free(b []byte) {\n\tsize := int32(cap(b))\n\tb = b[0:cap(b)]\n\tfor i := numPools - 1; i >= 0; i-- {\n\t\tif size >= poolSize[i] {\n\t\t\tpool[i].Put(b)\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/cache/lru.go",
    "content": "package cache\n\nimport (\n\t\"container/list\"\n\t\"sync\"\n)\n\n// Lru simple, fast lru cache implementation\ntype Lru interface {\n\tGet(key interface{}) (value interface{}, ok bool)\n\tGetKeyFromValue(value interface{}) (key interface{}, ok bool)\n\tPeekKeyFromValue(value interface{}) (key interface{}, ok bool) // Peek means check but NOT bring to top\n\tPut(key, value interface{})\n}\n\ntype lru struct {\n\tcapacity         int\n\tdoubleLinkedlist *list.List\n\tkeyToElement     *sync.Map\n\tvalueToElement   *sync.Map\n\tmu               *sync.Mutex\n}\n\ntype lruElement struct {\n\tkey   interface{}\n\tvalue interface{}\n}\n\n// NewLru initializes a lru cache\nfunc NewLru(cap int) Lru {\n\treturn &lru{\n\t\tcapacity:         cap,\n\t\tdoubleLinkedlist: list.New(),\n\t\tkeyToElement:     new(sync.Map),\n\t\tvalueToElement:   new(sync.Map),\n\t\tmu:               new(sync.Mutex),\n\t}\n}\n\nfunc (l *lru) Get(key interface{}) (value interface{}, ok bool) {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tif v, ok := l.keyToElement.Load(key); ok {\n\t\telement := v.(*list.Element)\n\t\tl.doubleLinkedlist.MoveToFront(element)\n\t\treturn element.Value.(*lruElement).value, true\n\t}\n\treturn nil, false\n}\n\nfunc (l *lru) GetKeyFromValue(value interface{}) (key interface{}, ok bool) {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tif k, ok := l.valueToElement.Load(value); ok {\n\t\telement := k.(*list.Element)\n\t\tl.doubleLinkedlist.MoveToFront(element)\n\t\treturn element.Value.(*lruElement).key, true\n\t}\n\treturn nil, false\n}\n\nfunc (l *lru) PeekKeyFromValue(value interface{}) (key interface{}, ok bool) {\n\tif k, ok := l.valueToElement.Load(value); ok {\n\t\telement := k.(*list.Element)\n\t\treturn element.Value.(*lruElement).key, true\n\t}\n\treturn nil, false\n}\n\nfunc (l *lru) Put(key, value interface{}) {\n\tl.mu.Lock()\n\te := &lruElement{key, value}\n\tif v, ok := l.keyToElement.Load(key); ok {\n\t\telement := v.(*list.Element)\n\t\telement.Value = e\n\t\tl.doubleLinkedlist.MoveToFront(element)\n\t} else {\n\t\telement := l.doubleLinkedlist.PushFront(e)\n\t\tl.keyToElement.Store(key, element)\n\t\tl.valueToElement.Store(value, element)\n\t\tif l.doubleLinkedlist.Len() > l.capacity {\n\t\t\ttoBeRemove := l.doubleLinkedlist.Back()\n\t\t\tl.doubleLinkedlist.Remove(toBeRemove)\n\t\t\tl.keyToElement.Delete(toBeRemove.Value.(*lruElement).key)\n\t\t\tl.valueToElement.Delete(toBeRemove.Value.(*lruElement).value)\n\t\t}\n\t}\n\tl.mu.Unlock()\n}\n"
  },
  {
    "path": "common/cache/lru_test.go",
    "content": "package cache_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/cache\"\n)\n\nfunc TestLruReplaceValue(t *testing.T) {\n\tlru := NewLru(2)\n\tlru.Put(2, 6)\n\tlru.Put(1, 5)\n\tlru.Put(1, 2)\n\tv, _ := lru.Get(1)\n\tif v != 2 {\n\t\tt.Error(\"should get 2\", v)\n\t}\n\tv, _ = lru.Get(2)\n\tif v != 6 {\n\t\tt.Error(\"should get 6\", v)\n\t}\n}\n\nfunc TestLruRemoveOld(t *testing.T) {\n\tlru := NewLru(2)\n\tv, ok := lru.Get(2)\n\tif ok {\n\t\tt.Error(\"should get nil\", v)\n\t}\n\tlru.Put(1, 1)\n\tlru.Put(2, 2)\n\tv, _ = lru.Get(1)\n\tif v != 1 {\n\t\tt.Error(\"should get 1\", v)\n\t}\n\tlru.Put(3, 3)\n\tv, ok = lru.Get(2)\n\tif ok {\n\t\tt.Error(\"should get nil\", v)\n\t}\n\tlru.Put(4, 4)\n\tv, ok = lru.Get(1)\n\tif ok {\n\t\tt.Error(\"should get nil\", v)\n\t}\n\tv, _ = lru.Get(3)\n\tif v != 3 {\n\t\tt.Error(\"should get 3\", v)\n\t}\n\tv, _ = lru.Get(4)\n\tif v != 4 {\n\t\tt.Error(\"should get 4\", v)\n\t}\n}\n\nfunc TestGetKeyFromValue(t *testing.T) {\n\tlru := NewLru(2)\n\tlru.Put(3, 3)\n\tlru.Put(2, 2)\n\tlru.GetKeyFromValue(3)\n\tlru.Put(1, 1)\n\tv, ok := lru.GetKeyFromValue(2)\n\tif ok {\n\t\tt.Error(\"should get nil\", v)\n\t}\n\tv, _ = lru.GetKeyFromValue(3)\n\tif v != 3 {\n\t\tt.Error(\"should get 3\", v)\n\t}\n}\n\nfunc TestPeekKeyFromValue(t *testing.T) {\n\tlru := NewLru(2)\n\tlru.Put(3, 3)\n\tlru.Put(2, 2)\n\tlru.PeekKeyFromValue(3)\n\tlru.Put(1, 1)\n\tv, ok := lru.PeekKeyFromValue(3)\n\tif ok {\n\t\tt.Error(\"should get nil\", v)\n\t}\n\tv, _ = lru.PeekKeyFromValue(2)\n\tif v != 2 {\n\t\tt.Error(\"should get 2\", v)\n\t}\n}\n"
  },
  {
    "path": "common/cmdarg/cmdarg.go",
    "content": "package cmdarg\n\nimport \"strings\"\n\n// Arg is used by flag to accept multiple argument.\ntype Arg []string\n\nfunc (c *Arg) String() string {\n\treturn strings.Join([]string(*c), \" \")\n}\n\n// Set is the method flag package calls\nfunc (c *Arg) Set(value string) error {\n\t*c = append(*c, value)\n\treturn nil\n}\n"
  },
  {
    "path": "common/common.go",
    "content": "// Package common contains common utilities that are shared among other packages.\n// See each sub-package for detail.\npackage common\n\nimport (\n\t\"fmt\"\n\t\"go/build\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\n// ErrNoClue is for the situation that existing information is not enough to make a decision. For example, Router may return this error when there is no suitable route.\nvar ErrNoClue = errors.New(\"not enough information for making a decision\")\n\n// Must panics if err is not nil.\nfunc Must(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Must2 panics if the second parameter is not nil, otherwise returns the first parameter.\n// This is useful when function returned \"sth, err\" and avoid many \"if err != nil\"\n// Internal usage only, if user input can cause err, it must be handled\nfunc Must2[T any](v T, err error) T {\n\tMust(err)\n\treturn v\n}\n\n// Error2 returns the err from the 2nd parameter.\nfunc Error2(v interface{}, err error) error {\n\treturn err\n}\n\n// envFile returns the name of the Go environment configuration file.\n// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166\nfunc envFile() (string, error) {\n\tif file := os.Getenv(\"GOENV\"); file != \"\" {\n\t\tif file == \"off\" {\n\t\t\treturn \"\", errors.New(\"GOENV=off\")\n\t\t}\n\t\treturn file, nil\n\t}\n\tdir, err := os.UserConfigDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif dir == \"\" {\n\t\treturn \"\", errors.New(\"missing user-config dir\")\n\t}\n\treturn filepath.Join(dir, \"go\", \"env\"), nil\n}\n\n// GetRuntimeEnv returns the value of runtime environment variable,\n// that is set by running following command: `go env -w key=value`.\nfunc GetRuntimeEnv(key string) (string, error) {\n\tfile, err := envFile()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif file == \"\" {\n\t\treturn \"\", errors.New(\"missing runtime env file\")\n\t}\n\tvar data []byte\n\tvar runtimeEnv string\n\tdata, readErr := os.ReadFile(file)\n\tif readErr != nil {\n\t\treturn \"\", readErr\n\t}\n\tenvStrings := strings.Split(string(data), \"\\n\")\n\tfor _, envItem := range envStrings {\n\t\tenvItem = strings.TrimSuffix(envItem, \"\\r\")\n\t\tenvKeyValue := strings.Split(envItem, \"=\")\n\t\tif strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {\n\t\t\truntimeEnv = strings.TrimSpace(envKeyValue[1])\n\t\t}\n\t}\n\treturn runtimeEnv, nil\n}\n\n// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.\nfunc GetGOBIN() string {\n\t// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`\n\tGOBIN := os.Getenv(\"GOBIN\")\n\tif GOBIN == \"\" {\n\t\tvar err error\n\t\t// The one set by user by running `go env -w GOBIN=/path`\n\t\tGOBIN, err = GetRuntimeEnv(\"GOBIN\")\n\t\tif err != nil {\n\t\t\t// The default one that Golang uses\n\t\t\treturn filepath.Join(build.Default.GOPATH, \"bin\")\n\t\t}\n\t\tif GOBIN == \"\" {\n\t\t\treturn filepath.Join(build.Default.GOPATH, \"bin\")\n\t\t}\n\t\treturn GOBIN\n\t}\n\treturn GOBIN\n}\n\n// GetGOPATH returns GOPATH environment variable as a string. It will NOT be empty.\nfunc GetGOPATH() string {\n\t// The one set by user explicitly by `export GOPATH=/path` or `env GOPATH=/path command`\n\tGOPATH := os.Getenv(\"GOPATH\")\n\tif GOPATH == \"\" {\n\t\tvar err error\n\t\t// The one set by user by running `go env -w GOPATH=/path`\n\t\tGOPATH, err = GetRuntimeEnv(\"GOPATH\")\n\t\tif err != nil {\n\t\t\t// The default one that Golang uses\n\t\t\treturn build.Default.GOPATH\n\t\t}\n\t\tif GOPATH == \"\" {\n\t\t\treturn build.Default.GOPATH\n\t\t}\n\t\treturn GOPATH\n\t}\n\treturn GOPATH\n}\n\n// GetModuleName returns the value of module in `go.mod` file.\nfunc GetModuleName(pathToProjectRoot string) (string, error) {\n\tvar moduleName string\n\tloopPath := pathToProjectRoot\n\tfor {\n\t\tif idx := strings.LastIndex(loopPath, string(filepath.Separator)); idx >= 0 {\n\t\t\tgomodPath := filepath.Join(loopPath, \"go.mod\")\n\t\t\tgomodBytes, err := os.ReadFile(gomodPath)\n\t\t\tif err != nil {\n\t\t\t\tloopPath = loopPath[:idx]\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tgomodContent := string(gomodBytes)\n\t\t\tmoduleIdx := strings.Index(gomodContent, \"module \")\n\t\t\tnewLineIdx := strings.Index(gomodContent, \"\\n\")\n\n\t\t\tif moduleIdx >= 0 {\n\t\t\t\tif newLineIdx >= 0 {\n\t\t\t\t\tmoduleName = strings.TrimSpace(gomodContent[moduleIdx+6 : newLineIdx])\n\t\t\t\t\tmoduleName = strings.TrimSuffix(moduleName, \"\\r\")\n\t\t\t\t} else {\n\t\t\t\t\tmoduleName = strings.TrimSpace(gomodContent[moduleIdx+6:])\n\t\t\t\t}\n\t\t\t\treturn moduleName, nil\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"can not get module path in `%s`\", gomodPath)\n\t\t}\n\t\tbreak\n\t}\n\treturn moduleName, fmt.Errorf(\"no `go.mod` file in every parent directory of `%s`\", pathToProjectRoot)\n}\n\n// CloseIfExists call obj.Close() if obj is not nil.\nfunc CloseIfExists(obj any) error {\n\tif obj != nil {\n\t\tv := reflect.ValueOf(obj)\n\t\tif !v.IsNil() {\n\t\t\treturn Close(obj)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/common_test.go",
    "content": "package common_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common\"\n)\n\nfunc TestMust(t *testing.T) {\n\thasPanic := func(f func()) (ret bool) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tret = true\n\t\t\t}\n\t\t}()\n\t\tf()\n\t\treturn false\n\t}\n\n\ttestCases := []struct {\n\t\tInput func()\n\t\tPanic bool\n\t}{\n\t\t{\n\t\t\tPanic: true,\n\t\t\tInput: func() { Must(func() error { return errors.New(\"test error\") }()) },\n\t\t},\n\t\t{\n\t\t\tPanic: true,\n\t\t\tInput: func() { Must2(func() (int, error) { return 0, errors.New(\"test error\") }()) },\n\t\t},\n\t\t{\n\t\t\tPanic: false,\n\t\t\tInput: func() { Must(func() error { return nil }()) },\n\t\t},\n\t}\n\n\tfor idx, test := range testCases {\n\t\tif hasPanic(test.Input) != test.Panic {\n\t\t\tt.Error(\"test case #\", idx, \" expect panic \", test.Panic, \" but actually not\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/crypto/aes.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\n\t\"github.com/xtls/xray-core/common\"\n)\n\n// NewAesDecryptionStream creates a new AES encryption stream based on given key and IV.\n// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.\nfunc NewAesDecryptionStream(key []byte, iv []byte) cipher.Stream {\n\treturn NewAesStreamMethod(key, iv, cipher.NewCFBDecrypter)\n}\n\n// NewAesEncryptionStream creates a new AES description stream based on given key and IV.\n// Caller must ensure the length of key and IV is either 16, 24 or 32 bytes.\nfunc NewAesEncryptionStream(key []byte, iv []byte) cipher.Stream {\n\treturn NewAesStreamMethod(key, iv, cipher.NewCFBEncrypter)\n}\n\nfunc NewAesStreamMethod(key []byte, iv []byte, f func(cipher.Block, []byte) cipher.Stream) cipher.Stream {\n\taesBlock, err := aes.NewCipher(key)\n\tcommon.Must(err)\n\treturn f(aesBlock, iv)\n}\n\n// NewAesCTRStream creates a stream cipher based on AES-CTR.\nfunc NewAesCTRStream(key []byte, iv []byte) cipher.Stream {\n\treturn NewAesStreamMethod(key, iv, cipher.NewCTR)\n}\n\n// NewAesGcm creates a AEAD cipher based on AES-GCM.\nfunc NewAesGcm(key []byte) cipher.AEAD {\n\tblock := common.Must2(aes.NewCipher(key))\n\taead := common.Must2(cipher.NewGCM(block))\n\treturn aead\n}\n"
  },
  {
    "path": "common/crypto/auth.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/cipher\"\n\t\"crypto/rand\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/bytespool\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\ntype BytesGenerator func() []byte\n\nfunc GenerateEmptyBytes() BytesGenerator {\n\tvar b [1]byte\n\treturn func() []byte {\n\t\treturn b[:0]\n\t}\n}\n\nfunc GenerateStaticBytes(content []byte) BytesGenerator {\n\treturn func() []byte {\n\t\treturn content\n\t}\n}\n\nfunc GenerateIncreasingNonce(nonce []byte) BytesGenerator {\n\tc := append([]byte(nil), nonce...)\n\treturn func() []byte {\n\t\tfor i := range c {\n\t\t\tc[i]++\n\t\t\tif c[i] != 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn c\n\t}\n}\n\nfunc GenerateAEADNonceWithSize(nonceSize int) BytesGenerator {\n\tc := make([]byte, nonceSize)\n\tfor i := 0; i < nonceSize; i++ {\n\t\tc[i] = 0xFF\n\t}\n\treturn GenerateIncreasingNonce(c)\n}\n\ntype Authenticator interface {\n\tNonceSize() int\n\tOverhead() int\n\tOpen(dst, cipherText []byte) ([]byte, error)\n\tSeal(dst, plainText []byte) ([]byte, error)\n}\n\ntype AEADAuthenticator struct {\n\tcipher.AEAD\n\tNonceGenerator          BytesGenerator\n\tAdditionalDataGenerator BytesGenerator\n}\n\nfunc (v *AEADAuthenticator) Open(dst, cipherText []byte) ([]byte, error) {\n\tiv := v.NonceGenerator()\n\tif len(iv) != v.AEAD.NonceSize() {\n\t\treturn nil, errors.New(\"invalid AEAD nonce size: \", len(iv))\n\t}\n\n\tvar additionalData []byte\n\tif v.AdditionalDataGenerator != nil {\n\t\tadditionalData = v.AdditionalDataGenerator()\n\t}\n\treturn v.AEAD.Open(dst, iv, cipherText, additionalData)\n}\n\nfunc (v *AEADAuthenticator) Seal(dst, plainText []byte) ([]byte, error) {\n\tiv := v.NonceGenerator()\n\tif len(iv) != v.AEAD.NonceSize() {\n\t\treturn nil, errors.New(\"invalid AEAD nonce size: \", len(iv))\n\t}\n\n\tvar additionalData []byte\n\tif v.AdditionalDataGenerator != nil {\n\t\tadditionalData = v.AdditionalDataGenerator()\n\t}\n\treturn v.AEAD.Seal(dst, iv, plainText, additionalData), nil\n}\n\ntype AuthenticationReader struct {\n\tauth         Authenticator\n\treader       *buf.BufferedReader\n\tsizeParser   ChunkSizeDecoder\n\tsizeBytes    []byte\n\ttransferType protocol.TransferType\n\tpadding      PaddingLengthGenerator\n\tsize         uint16\n\tpaddingLen   uint16\n\thasSize      bool\n\tdone         bool\n}\n\nfunc NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, reader io.Reader, transferType protocol.TransferType, paddingLen PaddingLengthGenerator) *AuthenticationReader {\n\tr := &AuthenticationReader{\n\t\tauth:         auth,\n\t\tsizeParser:   sizeParser,\n\t\ttransferType: transferType,\n\t\tpadding:      paddingLen,\n\t\tsizeBytes:    make([]byte, sizeParser.SizeBytes()),\n\t}\n\tif breader, ok := reader.(*buf.BufferedReader); ok {\n\t\tr.reader = breader\n\t} else {\n\t\tr.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}\n\t}\n\treturn r\n}\n\nfunc (r *AuthenticationReader) readSize() (uint16, uint16, error) {\n\tif r.hasSize {\n\t\tr.hasSize = false\n\t\treturn r.size, r.paddingLen, nil\n\t}\n\tif _, err := io.ReadFull(r.reader, r.sizeBytes); err != nil {\n\t\treturn 0, 0, err\n\t}\n\tvar padding uint16\n\tif r.padding != nil {\n\t\tpadding = r.padding.NextPaddingLen()\n\t}\n\tsize, err := r.sizeParser.Decode(r.sizeBytes)\n\treturn size, padding, err\n}\n\nvar errSoft = errors.New(\"waiting for more data\")\n\nfunc (r *AuthenticationReader) readBuffer(size int32, padding int32) (*buf.Buffer, error) {\n\tb := buf.New()\n\tif _, err := b.ReadFullFrom(r.reader, size); err != nil {\n\t\tb.Release()\n\t\treturn nil, err\n\t}\n\tsize -= padding\n\trb, err := r.auth.Open(b.BytesTo(0), b.BytesTo(size))\n\tif err != nil {\n\t\tb.Release()\n\t\treturn nil, err\n\t}\n\tb.Resize(0, int32(len(rb)))\n\treturn b, nil\n}\n\nfunc (r *AuthenticationReader) readInternal(soft bool, mb *buf.MultiBuffer) error {\n\tif soft && r.reader.BufferedBytes() < r.sizeParser.SizeBytes() {\n\t\treturn errSoft\n\t}\n\n\tif r.done {\n\t\treturn io.EOF\n\t}\n\n\tsize, padding, err := r.readSize()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif size == uint16(r.auth.Overhead())+padding {\n\t\tr.done = true\n\t\treturn io.EOF\n\t}\n\n\tif soft && int32(size) > r.reader.BufferedBytes() {\n\t\tr.size = size\n\t\tr.paddingLen = padding\n\t\tr.hasSize = true\n\t\treturn errSoft\n\t}\n\n\tif size <= buf.Size {\n\t\tb, err := r.readBuffer(int32(size), int32(padding))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*mb = append(*mb, b)\n\t\treturn nil\n\t}\n\n\tpayload := bytespool.Alloc(int32(size))\n\tdefer bytespool.Free(payload)\n\n\tif _, err := io.ReadFull(r.reader, payload[:size]); err != nil {\n\t\treturn err\n\t}\n\n\tsize -= padding\n\n\trb, err := r.auth.Open(payload[:0], payload[:size])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*mb = buf.MergeBytes(*mb, rb)\n\treturn nil\n}\n\nfunc (r *AuthenticationReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tconst readSize = 16\n\tmb := make(buf.MultiBuffer, 0, readSize)\n\tif err := r.readInternal(false, &mb); err != nil {\n\t\tbuf.ReleaseMulti(mb)\n\t\treturn nil, err\n\t}\n\n\tfor i := 1; i < readSize; i++ {\n\t\terr := r.readInternal(true, &mb)\n\t\tif err == errSoft || err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn mb, nil\n}\n\ntype AuthenticationWriter struct {\n\tauth         Authenticator\n\twriter       buf.Writer\n\tsizeParser   ChunkSizeEncoder\n\ttransferType protocol.TransferType\n\tpadding      PaddingLengthGenerator\n}\n\nfunc NewAuthenticationWriter(auth Authenticator, sizeParser ChunkSizeEncoder, writer io.Writer, transferType protocol.TransferType, padding PaddingLengthGenerator) *AuthenticationWriter {\n\tw := &AuthenticationWriter{\n\t\tauth:         auth,\n\t\twriter:       buf.NewWriter(writer),\n\t\tsizeParser:   sizeParser,\n\t\ttransferType: transferType,\n\t}\n\tif padding != nil {\n\t\tw.padding = padding\n\t}\n\treturn w\n}\n\nfunc (w *AuthenticationWriter) seal(b []byte) (*buf.Buffer, error) {\n\tencryptedSize := int32(len(b) + w.auth.Overhead())\n\tvar paddingSize int32\n\tif w.padding != nil {\n\t\tpaddingSize = int32(w.padding.NextPaddingLen())\n\t}\n\n\tsizeBytes := w.sizeParser.SizeBytes()\n\ttotalSize := sizeBytes + encryptedSize + paddingSize\n\tif totalSize > buf.Size {\n\t\treturn nil, errors.New(\"size too large: \", totalSize)\n\t}\n\n\teb := buf.New()\n\tw.sizeParser.Encode(uint16(encryptedSize+paddingSize), eb.Extend(sizeBytes))\n\tif _, err := w.auth.Seal(eb.Extend(encryptedSize)[:0], b); err != nil {\n\t\teb.Release()\n\t\treturn nil, err\n\t}\n\tif paddingSize > 0 {\n\t\t// These paddings will send in clear text.\n\t\t// To avoid leakage of PRNG internal state, a cryptographically secure PRNG should be used.\n\t\tpaddingBytes := eb.Extend(paddingSize)\n\t\tcommon.Must2(rand.Read(paddingBytes))\n\t}\n\n\treturn eb, nil\n}\n\nfunc (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\n\tvar maxPadding int32\n\tif w.padding != nil {\n\t\tmaxPadding = int32(w.padding.MaxPaddingLen())\n\t}\n\n\tpayloadSize := buf.Size - int32(w.auth.Overhead()) - w.sizeParser.SizeBytes() - maxPadding\n\tmb2Write := make(buf.MultiBuffer, 0, len(mb)+10)\n\n\ttemp := buf.New()\n\tdefer temp.Release()\n\n\trawBytes := temp.Extend(payloadSize)\n\n\tfor {\n\t\tnb, nBytes := buf.SplitBytes(mb, rawBytes)\n\t\tmb = nb\n\n\t\teb, err := w.seal(rawBytes[:nBytes])\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb2Write)\n\t\t\treturn err\n\t\t}\n\t\tmb2Write = append(mb2Write, eb)\n\t\tif mb.IsEmpty() {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn w.writer.WriteMultiBuffer(mb2Write)\n}\n\nfunc (w *AuthenticationWriter) writePacket(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\n\tmb2Write := make(buf.MultiBuffer, 0, len(mb)+1)\n\n\tfor _, b := range mb {\n\t\tif b.IsEmpty() {\n\t\t\tcontinue\n\t\t}\n\n\t\teb, err := w.seal(b.Bytes())\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tmb2Write = append(mb2Write, eb)\n\t}\n\n\tif mb2Write.IsEmpty() {\n\t\treturn nil\n\t}\n\n\treturn w.writer.WriteMultiBuffer(mb2Write)\n}\n\n// WriteMultiBuffer implements buf.Writer.\nfunc (w *AuthenticationWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tif mb.IsEmpty() {\n\t\teb, err := w.seal([]byte{})\n\t\tcommon.Must(err)\n\t\treturn w.writer.WriteMultiBuffer(buf.MultiBuffer{eb})\n\t}\n\n\tif w.transferType == protocol.TransferTypeStream {\n\t\treturn w.writeStream(mb)\n\t}\n\n\treturn w.writePacket(mb)\n}\n"
  },
  {
    "path": "common/crypto/auth_test.go",
    "content": "package crypto_test\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nfunc TestAuthenticationReaderWriter(t *testing.T) {\n\tkey := make([]byte, 16)\n\trand.Read(key)\n\n\taead := NewAesGcm(key)\n\n\tconst payloadSize = 1024 * 80\n\trawPayload := make([]byte, payloadSize)\n\trand.Read(rawPayload)\n\n\tpayload := buf.MergeBytes(nil, rawPayload)\n\n\tcache := bytes.NewBuffer(nil)\n\tiv := make([]byte, 12)\n\trand.Read(iv)\n\n\twriter := NewAuthenticationWriter(&AEADAuthenticator{\n\t\tAEAD:                    aead,\n\t\tNonceGenerator:          GenerateStaticBytes(iv),\n\t\tAdditionalDataGenerator: GenerateEmptyBytes(),\n\t}, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil)\n\n\tcommon.Must(writer.WriteMultiBuffer(payload))\n\tif cache.Len() <= 1024*80 {\n\t\tt.Error(\"cache len: \", cache.Len())\n\t}\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{}))\n\n\treader := NewAuthenticationReader(&AEADAuthenticator{\n\t\tAEAD:                    aead,\n\t\tNonceGenerator:          GenerateStaticBytes(iv),\n\t\tAdditionalDataGenerator: GenerateEmptyBytes(),\n\t}, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil)\n\n\tvar mb buf.MultiBuffer\n\n\tfor mb.Len() < payloadSize {\n\t\tmb2, err := reader.ReadMultiBuffer()\n\t\tcommon.Must(err)\n\n\t\tmb, _ = buf.MergeMulti(mb, mb2)\n\t}\n\n\tif mb.Len() != payloadSize {\n\t\tt.Error(\"mb len: \", mb.Len())\n\t}\n\n\tmbContent := make([]byte, payloadSize)\n\tbuf.SplitBytes(mb, mbContent)\n\tif r := cmp.Diff(mbContent, rawPayload); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\t_, err := reader.ReadMultiBuffer()\n\tif err != io.EOF {\n\t\tt.Error(\"error: \", err)\n\t}\n}\n\nfunc TestAuthenticationReaderWriterPacket(t *testing.T) {\n\tkey := make([]byte, 16)\n\tcommon.Must2(rand.Read(key))\n\n\taead := NewAesGcm(key)\n\n\tcache := buf.New()\n\tiv := make([]byte, 12)\n\trand.Read(iv)\n\n\twriter := NewAuthenticationWriter(&AEADAuthenticator{\n\t\tAEAD:                    aead,\n\t\tNonceGenerator:          GenerateStaticBytes(iv),\n\t\tAdditionalDataGenerator: GenerateEmptyBytes(),\n\t}, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil)\n\n\tvar payload buf.MultiBuffer\n\tpb1 := buf.New()\n\tpb1.Write([]byte(\"abcd\"))\n\tpayload = append(payload, pb1)\n\n\tpb2 := buf.New()\n\tpb2.Write([]byte(\"efgh\"))\n\tpayload = append(payload, pb2)\n\n\tcommon.Must(writer.WriteMultiBuffer(payload))\n\tif cache.Len() == 0 {\n\t\tt.Error(\"cache len: \", cache.Len())\n\t}\n\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{}))\n\n\treader := NewAuthenticationReader(&AEADAuthenticator{\n\t\tAEAD:                    aead,\n\t\tNonceGenerator:          GenerateStaticBytes(iv),\n\t\tAdditionalDataGenerator: GenerateEmptyBytes(),\n\t}, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil)\n\n\tmb, err := reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\n\tmb, b1 := buf.SplitFirst(mb)\n\tif b1.String() != \"abcd\" {\n\t\tt.Error(\"b1: \", b1.String())\n\t}\n\n\tmb, b2 := buf.SplitFirst(mb)\n\tif b2.String() != \"efgh\" {\n\t\tt.Error(\"b2: \", b2.String())\n\t}\n\n\tif !mb.IsEmpty() {\n\t\tt.Error(\"not empty\")\n\t}\n\n\t_, err = reader.ReadMultiBuffer()\n\tif err != io.EOF {\n\t\tt.Error(\"error: \", err)\n\t}\n}\n"
  },
  {
    "path": "common/crypto/benchmark_test.go",
    "content": "package crypto_test\n\nimport (\n\t\"crypto/cipher\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/crypto\"\n)\n\nconst benchSize = 1024 * 1024\n\nfunc benchmarkStream(b *testing.B, c cipher.Stream) {\n\tb.SetBytes(benchSize)\n\tinput := make([]byte, benchSize)\n\toutput := make([]byte, benchSize)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tc.XORKeyStream(output, input)\n\t}\n}\n\nfunc BenchmarkChaCha20(b *testing.B) {\n\tkey := make([]byte, 32)\n\tnonce := make([]byte, 8)\n\tc := NewChaCha20Stream(key, nonce)\n\tbenchmarkStream(b, c)\n}\n\nfunc BenchmarkChaCha20IETF(b *testing.B) {\n\tkey := make([]byte, 32)\n\tnonce := make([]byte, 12)\n\tc := NewChaCha20Stream(key, nonce)\n\tbenchmarkStream(b, c)\n}\n\nfunc BenchmarkAESEncryption(b *testing.B) {\n\tkey := make([]byte, 32)\n\tiv := make([]byte, 16)\n\tc := NewAesEncryptionStream(key, iv)\n\n\tbenchmarkStream(b, c)\n}\n\nfunc BenchmarkAESDecryption(b *testing.B) {\n\tkey := make([]byte, 32)\n\tiv := make([]byte, 16)\n\tc := NewAesDecryptionStream(key, iv)\n\n\tbenchmarkStream(b, c)\n}\n"
  },
  {
    "path": "common/crypto/chacha20.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/cipher\"\n\n\t\"github.com/xtls/xray-core/common/crypto/internal\"\n)\n\n// NewChaCha20Stream creates a new Chacha20 encryption/descryption stream based on give key and IV.\n// Caller must ensure the length of key is 32 bytes, and length of IV is either 8 or 12 bytes.\nfunc NewChaCha20Stream(key []byte, iv []byte) cipher.Stream {\n\treturn internal.NewChaCha20Stream(key, iv, 20)\n}\n"
  },
  {
    "path": "common/crypto/chacha20_test.go",
    "content": "package crypto_test\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/crypto\"\n)\n\nfunc mustDecodeHex(s string) []byte {\n\tb, err := hex.DecodeString(s)\n\tcommon.Must(err)\n\treturn b\n}\n\nfunc TestChaCha20Stream(t *testing.T) {\n\tcases := []struct {\n\t\tkey    []byte\n\t\tiv     []byte\n\t\toutput []byte\n\t}{\n\t\t{\n\t\t\tkey: mustDecodeHex(\"0000000000000000000000000000000000000000000000000000000000000000\"),\n\t\t\tiv:  mustDecodeHex(\"0000000000000000\"),\n\t\t\toutput: mustDecodeHex(\"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7\" +\n\t\t\t\t\"da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586\" +\n\t\t\t\t\"9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed\" +\n\t\t\t\t\"29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f\"),\n\t\t},\n\t\t{\n\t\t\tkey: mustDecodeHex(\"5555555555555555555555555555555555555555555555555555555555555555\"),\n\t\t\tiv:  mustDecodeHex(\"5555555555555555\"),\n\t\t\toutput: mustDecodeHex(\"bea9411aa453c5434a5ae8c92862f564396855a9ea6e22d6d3b50ae1b3663311\" +\n\t\t\t\t\"a4a3606c671d605ce16c3aece8e61ea145c59775017bee2fa6f88afc758069f7\" +\n\t\t\t\t\"e0b8f676e644216f4d2a3422d7fa36c6c4931aca950e9da42788e6d0b6d1cd83\" +\n\t\t\t\t\"8ef652e97b145b14871eae6c6804c7004db5ac2fce4c68c726d004b10fcaba86\"),\n\t\t},\n\t\t{\n\t\t\tkey:    mustDecodeHex(\"0000000000000000000000000000000000000000000000000000000000000000\"),\n\t\t\tiv:     mustDecodeHex(\"000000000000000000000000\"),\n\t\t\toutput: mustDecodeHex(\"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586\"),\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\ts := NewChaCha20Stream(c.key, c.iv)\n\t\tinput := make([]byte, len(c.output))\n\t\tactualOutout := make([]byte, len(c.output))\n\t\ts.XORKeyStream(actualOutout, input)\n\t\tif r := cmp.Diff(c.output, actualOutout); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n}\n\nfunc TestChaCha20Decoding(t *testing.T) {\n\tkey := make([]byte, 32)\n\tcommon.Must2(rand.Read(key))\n\tiv := make([]byte, 8)\n\tcommon.Must2(rand.Read(iv))\n\tstream := NewChaCha20Stream(key, iv)\n\n\tpayload := make([]byte, 1024)\n\tcommon.Must2(rand.Read(payload))\n\n\tx := make([]byte, len(payload))\n\tstream.XORKeyStream(x, payload)\n\n\tstream2 := NewChaCha20Stream(key, iv)\n\tstream2.XORKeyStream(x, x)\n\tif r := cmp.Diff(x, payload); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n"
  },
  {
    "path": "common/crypto/chunk.go",
    "content": "package crypto\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\n// ChunkSizeDecoder is a utility class to decode size value from bytes.\ntype ChunkSizeDecoder interface {\n\tSizeBytes() int32\n\tDecode([]byte) (uint16, error)\n}\n\n// ChunkSizeEncoder is a utility class to encode size value into bytes.\ntype ChunkSizeEncoder interface {\n\tSizeBytes() int32\n\tEncode(uint16, []byte) []byte\n}\n\ntype PaddingLengthGenerator interface {\n\tMaxPaddingLen() uint16\n\tNextPaddingLen() uint16\n}\n\ntype PlainChunkSizeParser struct{}\n\nfunc (PlainChunkSizeParser) SizeBytes() int32 {\n\treturn 2\n}\n\nfunc (PlainChunkSizeParser) Encode(size uint16, b []byte) []byte {\n\tbinary.BigEndian.PutUint16(b, size)\n\treturn b[:2]\n}\n\nfunc (PlainChunkSizeParser) Decode(b []byte) (uint16, error) {\n\treturn binary.BigEndian.Uint16(b), nil\n}\n\ntype AEADChunkSizeParser struct {\n\tAuth *AEADAuthenticator\n}\n\nfunc (p *AEADChunkSizeParser) SizeBytes() int32 {\n\treturn 2 + int32(p.Auth.Overhead())\n}\n\nfunc (p *AEADChunkSizeParser) Encode(size uint16, b []byte) []byte {\n\tbinary.BigEndian.PutUint16(b, size-uint16(p.Auth.Overhead()))\n\tb, err := p.Auth.Seal(b[:0], b[:2])\n\tcommon.Must(err)\n\treturn b\n}\n\nfunc (p *AEADChunkSizeParser) Decode(b []byte) (uint16, error) {\n\tb, err := p.Auth.Open(b[:0], b)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn binary.BigEndian.Uint16(b) + uint16(p.Auth.Overhead()), nil\n}\n\ntype ChunkStreamReader struct {\n\tsizeDecoder ChunkSizeDecoder\n\treader      *buf.BufferedReader\n\n\tbuffer       []byte\n\tleftOverSize int32\n\tmaxNumChunk  uint32\n\tnumChunk     uint32\n}\n\nfunc NewChunkStreamReader(sizeDecoder ChunkSizeDecoder, reader io.Reader) *ChunkStreamReader {\n\treturn NewChunkStreamReaderWithChunkCount(sizeDecoder, reader, 0)\n}\n\nfunc NewChunkStreamReaderWithChunkCount(sizeDecoder ChunkSizeDecoder, reader io.Reader, maxNumChunk uint32) *ChunkStreamReader {\n\tr := &ChunkStreamReader{\n\t\tsizeDecoder: sizeDecoder,\n\t\tbuffer:      make([]byte, sizeDecoder.SizeBytes()),\n\t\tmaxNumChunk: maxNumChunk,\n\t}\n\tif breader, ok := reader.(*buf.BufferedReader); ok {\n\t\tr.reader = breader\n\t} else {\n\t\tr.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}\n\t}\n\n\treturn r\n}\n\nfunc (r *ChunkStreamReader) readSize() (uint16, error) {\n\tif _, err := io.ReadFull(r.reader, r.buffer); err != nil {\n\t\treturn 0, err\n\t}\n\treturn r.sizeDecoder.Decode(r.buffer)\n}\n\nfunc (r *ChunkStreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tsize := r.leftOverSize\n\tif size == 0 {\n\t\tr.numChunk++\n\t\tif r.maxNumChunk > 0 && r.numChunk > r.maxNumChunk {\n\t\t\treturn nil, io.EOF\n\t\t}\n\t\tnextSize, err := r.readSize()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif nextSize == 0 {\n\t\t\treturn nil, io.EOF\n\t\t}\n\t\tsize = int32(nextSize)\n\t}\n\tr.leftOverSize = size\n\n\tmb, err := r.reader.ReadAtMost(size)\n\tif !mb.IsEmpty() {\n\t\tr.leftOverSize -= mb.Len()\n\t\treturn mb, nil\n\t}\n\treturn nil, err\n}\n\ntype ChunkStreamWriter struct {\n\tsizeEncoder ChunkSizeEncoder\n\twriter      buf.Writer\n}\n\nfunc NewChunkStreamWriter(sizeEncoder ChunkSizeEncoder, writer io.Writer) *ChunkStreamWriter {\n\treturn &ChunkStreamWriter{\n\t\tsizeEncoder: sizeEncoder,\n\t\twriter:      buf.NewWriter(writer),\n\t}\n}\n\nfunc (w *ChunkStreamWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tconst sliceSize = 8192\n\tmbLen := mb.Len()\n\tmb2Write := make(buf.MultiBuffer, 0, mbLen/buf.Size+mbLen/sliceSize+2)\n\n\tfor {\n\t\tmb2, slice := buf.SplitSize(mb, sliceSize)\n\t\tmb = mb2\n\n\t\tb := buf.New()\n\t\tw.sizeEncoder.Encode(uint16(slice.Len()), b.Extend(w.sizeEncoder.SizeBytes()))\n\t\tmb2Write = append(mb2Write, b)\n\t\tmb2Write = append(mb2Write, slice...)\n\n\t\tif mb.IsEmpty() {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn w.writer.WriteMultiBuffer(mb2Write)\n}\n"
  },
  {
    "path": "common/crypto/chunk_test.go",
    "content": "package crypto_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/common/crypto\"\n)\n\nfunc TestChunkStreamIO(t *testing.T) {\n\tcache := bytes.NewBuffer(make([]byte, 0, 8192))\n\n\twriter := NewChunkStreamWriter(PlainChunkSizeParser{}, cache)\n\treader := NewChunkStreamReader(PlainChunkSizeParser{}, cache)\n\n\tb := buf.New()\n\tb.WriteString(\"abcd\")\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))\n\n\tb = buf.New()\n\tb.WriteString(\"efg\")\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))\n\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{}))\n\n\tif cache.Len() != 13 {\n\t\tt.Fatalf(\"Cache length is %d, want 13\", cache.Len())\n\t}\n\n\tmb, err := reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\n\tif s := mb.String(); s != \"abcd\" {\n\t\tt.Error(\"content: \", s)\n\t}\n\n\tmb, err = reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\n\tif s := mb.String(); s != \"efg\" {\n\t\tt.Error(\"content: \", s)\n\t}\n\n\t_, err = reader.ReadMultiBuffer()\n\tif err != io.EOF {\n\t\tt.Error(\"error: \", err)\n\t}\n}\n"
  },
  {
    "path": "common/crypto/crypto.go",
    "content": "// Package crypto provides common crypto libraries for Xray.\npackage crypto // import \"github.com/xtls/xray-core/common/crypto\"\n\nimport (\n\t\"crypto/rand\"\n\t\"math/big\"\n)\n\nfunc RandBetween(from int64, to int64) int64 {\n\tif from == to {\n\t\treturn from\n\t}\n\tif from > to {\n\t\tfrom, to = to, from\n\t}\n\tbigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))\n\treturn from + bigInt.Int64()\n}\n"
  },
  {
    "path": "common/crypto/internal/chacha.go",
    "content": "package internal\n\n//go:generate go run chacha_core_gen.go\n\nimport (\n\t\"encoding/binary\"\n)\n\nconst (\n\twordSize  = 4                    // the size of ChaCha20's words\n\tstateSize = 16                   // the size of ChaCha20's state, in words\n\tblockSize = stateSize * wordSize // the size of ChaCha20's block, in bytes\n)\n\ntype ChaCha20Stream struct {\n\tstate  [stateSize]uint32 // the state as an array of 16 32-bit words\n\tblock  [blockSize]byte   // the keystream as an array of 64 bytes\n\toffset int               // the offset of used bytes in block\n\trounds int\n}\n\nfunc NewChaCha20Stream(key []byte, nonce []byte, rounds int) *ChaCha20Stream {\n\ts := new(ChaCha20Stream)\n\t// the magic constants for 256-bit keys\n\ts.state[0] = 0x61707865\n\ts.state[1] = 0x3320646e\n\ts.state[2] = 0x79622d32\n\ts.state[3] = 0x6b206574\n\n\tfor i := 0; i < 8; i++ {\n\t\ts.state[i+4] = binary.LittleEndian.Uint32(key[i*4 : i*4+4])\n\t}\n\n\tswitch len(nonce) {\n\tcase 8:\n\t\ts.state[14] = binary.LittleEndian.Uint32(nonce[0:])\n\t\ts.state[15] = binary.LittleEndian.Uint32(nonce[4:])\n\tcase 12:\n\t\ts.state[13] = binary.LittleEndian.Uint32(nonce[0:4])\n\t\ts.state[14] = binary.LittleEndian.Uint32(nonce[4:8])\n\t\ts.state[15] = binary.LittleEndian.Uint32(nonce[8:12])\n\tdefault:\n\t\tpanic(\"bad nonce length\")\n\t}\n\n\ts.rounds = rounds\n\tChaCha20Block(&s.state, s.block[:], s.rounds)\n\treturn s\n}\n\nfunc (s *ChaCha20Stream) XORKeyStream(dst, src []byte) {\n\t// Stride over the input in 64-byte blocks, minus the amount of keystream\n\t// previously used. This will produce best results when processing blocks\n\t// of a size evenly divisible by 64.\n\ti := 0\n\tmax := len(src)\n\tfor i < max {\n\t\tgap := blockSize - s.offset\n\n\t\tlimit := i + gap\n\t\tif limit > max {\n\t\t\tlimit = max\n\t\t}\n\n\t\to := s.offset\n\t\tfor j := i; j < limit; j++ {\n\t\t\tdst[j] = src[j] ^ s.block[o]\n\t\t\to++\n\t\t}\n\n\t\ti += gap\n\t\ts.offset = o\n\n\t\tif o == blockSize {\n\t\t\ts.offset = 0\n\t\t\ts.state[12]++\n\t\t\tChaCha20Block(&s.state, s.block[:], s.rounds)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/crypto/internal/chacha_core.generated.go",
    "content": "package internal\n\nimport \"encoding/binary\"\n\nfunc ChaCha20Block(s *[16]uint32, out []byte, rounds int) {\n\tx0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]\n\tfor i := 0; i < rounds; i += 2 {\n\t\tvar x uint32\n\n\t\tx0 += x4\n\t\tx = x12 ^ x0\n\t\tx12 = (x << 16) | (x >> (32 - 16))\n\t\tx8 += x12\n\t\tx = x4 ^ x8\n\t\tx4 = (x << 12) | (x >> (32 - 12))\n\t\tx0 += x4\n\t\tx = x12 ^ x0\n\t\tx12 = (x << 8) | (x >> (32 - 8))\n\t\tx8 += x12\n\t\tx = x4 ^ x8\n\t\tx4 = (x << 7) | (x >> (32 - 7))\n\t\tx1 += x5\n\t\tx = x13 ^ x1\n\t\tx13 = (x << 16) | (x >> (32 - 16))\n\t\tx9 += x13\n\t\tx = x5 ^ x9\n\t\tx5 = (x << 12) | (x >> (32 - 12))\n\t\tx1 += x5\n\t\tx = x13 ^ x1\n\t\tx13 = (x << 8) | (x >> (32 - 8))\n\t\tx9 += x13\n\t\tx = x5 ^ x9\n\t\tx5 = (x << 7) | (x >> (32 - 7))\n\t\tx2 += x6\n\t\tx = x14 ^ x2\n\t\tx14 = (x << 16) | (x >> (32 - 16))\n\t\tx10 += x14\n\t\tx = x6 ^ x10\n\t\tx6 = (x << 12) | (x >> (32 - 12))\n\t\tx2 += x6\n\t\tx = x14 ^ x2\n\t\tx14 = (x << 8) | (x >> (32 - 8))\n\t\tx10 += x14\n\t\tx = x6 ^ x10\n\t\tx6 = (x << 7) | (x >> (32 - 7))\n\t\tx3 += x7\n\t\tx = x15 ^ x3\n\t\tx15 = (x << 16) | (x >> (32 - 16))\n\t\tx11 += x15\n\t\tx = x7 ^ x11\n\t\tx7 = (x << 12) | (x >> (32 - 12))\n\t\tx3 += x7\n\t\tx = x15 ^ x3\n\t\tx15 = (x << 8) | (x >> (32 - 8))\n\t\tx11 += x15\n\t\tx = x7 ^ x11\n\t\tx7 = (x << 7) | (x >> (32 - 7))\n\t\tx0 += x5\n\t\tx = x15 ^ x0\n\t\tx15 = (x << 16) | (x >> (32 - 16))\n\t\tx10 += x15\n\t\tx = x5 ^ x10\n\t\tx5 = (x << 12) | (x >> (32 - 12))\n\t\tx0 += x5\n\t\tx = x15 ^ x0\n\t\tx15 = (x << 8) | (x >> (32 - 8))\n\t\tx10 += x15\n\t\tx = x5 ^ x10\n\t\tx5 = (x << 7) | (x >> (32 - 7))\n\t\tx1 += x6\n\t\tx = x12 ^ x1\n\t\tx12 = (x << 16) | (x >> (32 - 16))\n\t\tx11 += x12\n\t\tx = x6 ^ x11\n\t\tx6 = (x << 12) | (x >> (32 - 12))\n\t\tx1 += x6\n\t\tx = x12 ^ x1\n\t\tx12 = (x << 8) | (x >> (32 - 8))\n\t\tx11 += x12\n\t\tx = x6 ^ x11\n\t\tx6 = (x << 7) | (x >> (32 - 7))\n\t\tx2 += x7\n\t\tx = x13 ^ x2\n\t\tx13 = (x << 16) | (x >> (32 - 16))\n\t\tx8 += x13\n\t\tx = x7 ^ x8\n\t\tx7 = (x << 12) | (x >> (32 - 12))\n\t\tx2 += x7\n\t\tx = x13 ^ x2\n\t\tx13 = (x << 8) | (x >> (32 - 8))\n\t\tx8 += x13\n\t\tx = x7 ^ x8\n\t\tx7 = (x << 7) | (x >> (32 - 7))\n\t\tx3 += x4\n\t\tx = x14 ^ x3\n\t\tx14 = (x << 16) | (x >> (32 - 16))\n\t\tx9 += x14\n\t\tx = x4 ^ x9\n\t\tx4 = (x << 12) | (x >> (32 - 12))\n\t\tx3 += x4\n\t\tx = x14 ^ x3\n\t\tx14 = (x << 8) | (x >> (32 - 8))\n\t\tx9 += x14\n\t\tx = x4 ^ x9\n\t\tx4 = (x << 7) | (x >> (32 - 7))\n\t}\n\tbinary.LittleEndian.PutUint32(out[0:4], s[0]+x0)\n\tbinary.LittleEndian.PutUint32(out[4:8], s[1]+x1)\n\tbinary.LittleEndian.PutUint32(out[8:12], s[2]+x2)\n\tbinary.LittleEndian.PutUint32(out[12:16], s[3]+x3)\n\tbinary.LittleEndian.PutUint32(out[16:20], s[4]+x4)\n\tbinary.LittleEndian.PutUint32(out[20:24], s[5]+x5)\n\tbinary.LittleEndian.PutUint32(out[24:28], s[6]+x6)\n\tbinary.LittleEndian.PutUint32(out[28:32], s[7]+x7)\n\tbinary.LittleEndian.PutUint32(out[32:36], s[8]+x8)\n\tbinary.LittleEndian.PutUint32(out[36:40], s[9]+x9)\n\tbinary.LittleEndian.PutUint32(out[40:44], s[10]+x10)\n\tbinary.LittleEndian.PutUint32(out[44:48], s[11]+x11)\n\tbinary.LittleEndian.PutUint32(out[48:52], s[12]+x12)\n\tbinary.LittleEndian.PutUint32(out[52:56], s[13]+x13)\n\tbinary.LittleEndian.PutUint32(out[56:60], s[14]+x14)\n\tbinary.LittleEndian.PutUint32(out[60:64], s[15]+x15)\n}\n"
  },
  {
    "path": "common/crypto/internal/chacha_core_gen.go",
    "content": "//go:build generate\n// +build generate\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n)\n\nfunc writeQuarterRound(file *os.File, a, b, c, d int) {\n\tadd := \"x%d+=x%d\\n\"\n\txor := \"x=x%d^x%d\\n\"\n\trotate := \"x%d=(x << %d) | (x >> (32 - %d))\\n\"\n\n\tfmt.Fprintf(file, add, a, b)\n\tfmt.Fprintf(file, xor, d, a)\n\tfmt.Fprintf(file, rotate, d, 16, 16)\n\n\tfmt.Fprintf(file, add, c, d)\n\tfmt.Fprintf(file, xor, b, c)\n\tfmt.Fprintf(file, rotate, b, 12, 12)\n\n\tfmt.Fprintf(file, add, a, b)\n\tfmt.Fprintf(file, xor, d, a)\n\tfmt.Fprintf(file, rotate, d, 8, 8)\n\n\tfmt.Fprintf(file, add, c, d)\n\tfmt.Fprintf(file, xor, b, c)\n\tfmt.Fprintf(file, rotate, b, 7, 7)\n}\n\nfunc writeChacha20Block(file *os.File) {\n\tfmt.Fprintln(file, `\nfunc ChaCha20Block(s *[16]uint32, out []byte, rounds int) {\n  var x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15 = s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9],s[10],s[11],s[12],s[13],s[14],s[15]\n\tfor i := 0; i < rounds; i+=2 {\n    var x uint32\n    `)\n\n\twriteQuarterRound(file, 0, 4, 8, 12)\n\twriteQuarterRound(file, 1, 5, 9, 13)\n\twriteQuarterRound(file, 2, 6, 10, 14)\n\twriteQuarterRound(file, 3, 7, 11, 15)\n\twriteQuarterRound(file, 0, 5, 10, 15)\n\twriteQuarterRound(file, 1, 6, 11, 12)\n\twriteQuarterRound(file, 2, 7, 8, 13)\n\twriteQuarterRound(file, 3, 4, 9, 14)\n\tfmt.Fprintln(file, \"}\")\n\tfor i := 0; i < 16; i++ {\n\t\tfmt.Fprintf(file, \"binary.LittleEndian.PutUint32(out[%d:%d], s[%d]+x%d)\\n\", i*4, i*4+4, i, i)\n\t}\n\tfmt.Fprintln(file, \"}\")\n\tfmt.Fprintln(file)\n}\n\nfunc main() {\n\tfile, err := os.OpenFile(\"chacha_core.generated.go\", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to generate chacha_core.go: %v\", err)\n\t}\n\tdefer file.Close()\n\n\tfmt.Fprintln(file, \"package internal\")\n\tfmt.Fprintln(file)\n\tfmt.Fprintln(file, \"import \\\"encoding/binary\\\"\")\n\tfmt.Fprintln(file)\n\twriteChacha20Block(file)\n}\n"
  },
  {
    "path": "common/crypto/io.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/cipher\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\ntype CryptionReader struct {\n\tstream cipher.Stream\n\treader io.Reader\n}\n\nfunc NewCryptionReader(stream cipher.Stream, reader io.Reader) *CryptionReader {\n\treturn &CryptionReader{\n\t\tstream: stream,\n\t\treader: reader,\n\t}\n}\n\nfunc (r *CryptionReader) Read(data []byte) (int, error) {\n\tnBytes, err := r.reader.Read(data)\n\tif nBytes > 0 {\n\t\tr.stream.XORKeyStream(data[:nBytes], data[:nBytes])\n\t}\n\treturn nBytes, err\n}\n\nvar _ buf.Writer = (*CryptionWriter)(nil)\n\ntype CryptionWriter struct {\n\tstream    cipher.Stream\n\twriter    io.Writer\n\tbufWriter buf.Writer\n}\n\n// NewCryptionWriter creates a new CryptionWriter.\nfunc NewCryptionWriter(stream cipher.Stream, writer io.Writer) *CryptionWriter {\n\treturn &CryptionWriter{\n\t\tstream:    stream,\n\t\twriter:    writer,\n\t\tbufWriter: buf.NewWriter(writer),\n\t}\n}\n\n// Write implements io.Writer.Write().\nfunc (w *CryptionWriter) Write(data []byte) (int, error) {\n\tw.stream.XORKeyStream(data, data)\n\n\tif err := buf.WriteAllBytes(w.writer, data, nil); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(data), nil\n}\n\n// WriteMultiBuffer implements buf.Writer.\nfunc (w *CryptionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor _, b := range mb {\n\t\tw.stream.XORKeyStream(b.Bytes(), b.Bytes())\n\t}\n\n\treturn w.bufWriter.WriteMultiBuffer(mb)\n}\n"
  },
  {
    "path": "common/ctx/context.go",
    "content": "package ctx\n\nimport \"context\"\n\ntype SessionKey int\n\n// ID of a session.\ntype ID uint32\n\nconst (\n\tidSessionKey SessionKey = 0\n)\n\n// ContextWithID returns a new context with the given ID.\nfunc ContextWithID(ctx context.Context, id ID) context.Context {\n\treturn context.WithValue(ctx, idSessionKey, id)\n}\n\n// IDFromContext returns ID in this context, or 0 if not contained.\nfunc IDFromContext(ctx context.Context) ID {\n\tif id, ok := ctx.Value(idSessionKey).(ID); ok {\n\t\treturn id\n\t}\n\treturn 0\n}\n"
  },
  {
    "path": "common/dice/dice.go",
    "content": "// Package dice contains common functions to generate random number.\n// It also initialize math/rand with the time in seconds at launch time.\npackage dice // import \"github.com/xtls/xray-core/common/dice\"\n\nimport (\n\t\"math/rand\"\n)\n\n// Roll returns a non-negative number between 0 (inclusive) and n (exclusive).\nfunc Roll(n int) int {\n\tif n == 1 {\n\t\treturn 0\n\t}\n\treturn rand.Intn(n)\n}\n\n// RollInt63n returns a non-negative number between 0 (inclusive) and n (exclusive).\nfunc RollInt63n(n int64) int64 {\n\tif n == 1 {\n\t\treturn 0\n\t}\n\treturn rand.Int63n(n)\n}\n\n// Roll returns a non-negative number between 0 (inclusive) and n (exclusive).\nfunc RollDeterministic(n int, seed int64) int {\n\tif n == 1 {\n\t\treturn 0\n\t}\n\treturn rand.New(rand.NewSource(seed)).Intn(n)\n}\n\n// RollUint16 returns a random uint16 value.\nfunc RollUint16() uint16 {\n\treturn uint16(rand.Int63() >> 47)\n}\n\nfunc RollUint64() uint64 {\n\treturn rand.Uint64()\n}\n\nfunc NewDeterministicDice(seed int64) *DeterministicDice {\n\treturn &DeterministicDice{rand.New(rand.NewSource(seed))}\n}\n\ntype DeterministicDice struct {\n\t*rand.Rand\n}\n\nfunc (dd *DeterministicDice) Roll(n int) int {\n\tif n == 1 {\n\t\treturn 0\n\t}\n\treturn dd.Intn(n)\n}\n"
  },
  {
    "path": "common/dice/dice_test.go",
    "content": "package dice_test\n\nimport (\n\t\"math/rand\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/dice\"\n)\n\nfunc BenchmarkRoll1(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tRoll(1)\n\t}\n}\n\nfunc BenchmarkRoll20(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tRoll(20)\n\t}\n}\n\nfunc BenchmarkIntn1(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\trand.Intn(1)\n\t}\n}\n\nfunc BenchmarkIntn20(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\trand.Intn(20)\n\t}\n}\n\nfunc BenchmarkInt63(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = uint16(rand.Int63() >> 47)\n\t}\n}\n\nfunc BenchmarkInt31(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = uint16(rand.Int31() >> 15)\n\t}\n}\n\nfunc BenchmarkIntn(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = uint16(rand.Intn(65536))\n\t}\n}\n"
  },
  {
    "path": "common/drain/drain.go",
    "content": "package drain\n\nimport \"io\"\n\ntype Drainer interface {\n\tAcknowledgeReceive(size int)\n\tDrain(reader io.Reader) error\n}\n"
  },
  {
    "path": "common/drain/drainer.go",
    "content": "package drain\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype BehaviorSeedLimitedDrainer struct {\n\tDrainSize int\n}\n\nfunc NewBehaviorSeedLimitedDrainer(behaviorSeed int64, drainFoundation, maxBaseDrainSize, maxRandDrain int) (Drainer, error) {\n\tbehaviorRand := dice.NewDeterministicDice(behaviorSeed)\n\tBaseDrainSize := behaviorRand.Roll(maxBaseDrainSize)\n\tRandDrainMax := behaviorRand.Roll(maxRandDrain) + 1\n\tRandDrainRolled := dice.Roll(RandDrainMax)\n\tDrainSize := drainFoundation + BaseDrainSize + RandDrainRolled\n\treturn &BehaviorSeedLimitedDrainer{DrainSize: DrainSize}, nil\n}\n\nfunc (d *BehaviorSeedLimitedDrainer) AcknowledgeReceive(size int) {\n\td.DrainSize -= size\n}\n\nfunc (d *BehaviorSeedLimitedDrainer) Drain(reader io.Reader) error {\n\tif d.DrainSize > 0 {\n\t\terr := drainReadN(reader, d.DrainSize)\n\t\tif err == nil {\n\t\t\treturn errors.New(\"drained connection\")\n\t\t}\n\t\treturn errors.New(\"unable to drain connection\").Base(err)\n\t}\n\treturn nil\n}\n\nfunc drainReadN(reader io.Reader, n int) error {\n\t_, err := io.CopyN(io.Discard, reader, int64(n))\n\treturn err\n}\n\nfunc WithError(drainer Drainer, reader io.Reader, err error) error {\n\tdrainErr := drainer.Drain(reader)\n\tif drainErr == nil {\n\t\treturn err\n\t}\n\treturn errors.New(drainErr).Base(err)\n}\n\ntype NopDrainer struct{}\n\nfunc (n NopDrainer) AcknowledgeReceive(size int) {\n}\n\nfunc (n NopDrainer) Drain(reader io.Reader) error {\n\treturn nil\n}\n\nfunc NewNopDrainer() Drainer {\n\treturn &NopDrainer{}\n}\n"
  },
  {
    "path": "common/errors/errors.go",
    "content": "// Package errors is a drop-in replacement for Golang lib 'errors'.\npackage errors // import \"github.com/xtls/xray-core/common/errors\"\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"strings\"\n\n\tc \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\nconst trim = len(\"github.com/xtls/xray-core/\")\n\ntype hasInnerError interface {\n\t// Unwrap returns the underlying error of this one.\n\tUnwrap() error\n}\n\ntype hasSeverity interface {\n\tSeverity() log.Severity\n}\n\n// Error is an error object with underlying error.\ntype Error struct {\n\tprefix   []interface{}\n\tmessage  []interface{}\n\tcaller   string\n\tinner    error\n\tseverity log.Severity\n}\n\n// Error implements error.Error().\nfunc (err *Error) Error() string {\n\tbuilder := strings.Builder{}\n\tfor _, prefix := range err.prefix {\n\t\tbuilder.WriteByte('[')\n\t\tbuilder.WriteString(serial.ToString(prefix))\n\t\tbuilder.WriteString(\"] \")\n\t}\n\n\tif len(err.caller) > 0 {\n\t\tbuilder.WriteString(err.caller)\n\t\tbuilder.WriteString(\": \")\n\t}\n\n\tmsg := serial.Concat(err.message...)\n\tbuilder.WriteString(msg)\n\n\tif err.inner != nil {\n\t\tbuilder.WriteString(\" > \")\n\t\tbuilder.WriteString(err.inner.Error())\n\t}\n\n\treturn builder.String()\n}\n\n// Unwrap implements hasInnerError.Unwrap()\nfunc (err *Error) Unwrap() error {\n\tif err.inner == nil {\n\t\treturn nil\n\t}\n\treturn err.inner\n}\n\nfunc (err *Error) Base(e error) *Error {\n\terr.inner = e\n\treturn err\n}\n\nfunc (err *Error) atSeverity(s log.Severity) *Error {\n\terr.severity = s\n\treturn err\n}\n\nfunc (err *Error) Severity() log.Severity {\n\tif err.inner == nil {\n\t\treturn err.severity\n\t}\n\n\tif s, ok := err.inner.(hasSeverity); ok {\n\t\tas := s.Severity()\n\t\tif as < err.severity {\n\t\t\treturn as\n\t\t}\n\t}\n\n\treturn err.severity\n}\n\n// AtDebug sets the severity to debug.\nfunc (err *Error) AtDebug() *Error {\n\treturn err.atSeverity(log.Severity_Debug)\n}\n\n// AtInfo sets the severity to info.\nfunc (err *Error) AtInfo() *Error {\n\treturn err.atSeverity(log.Severity_Info)\n}\n\n// AtWarning sets the severity to warning.\nfunc (err *Error) AtWarning() *Error {\n\treturn err.atSeverity(log.Severity_Warning)\n}\n\n// AtError sets the severity to error.\nfunc (err *Error) AtError() *Error {\n\treturn err.atSeverity(log.Severity_Error)\n}\n\n// String returns the string representation of this error.\nfunc (err *Error) String() string {\n\treturn err.Error()\n}\n\ntype ExportOptionHolder struct {\n\tSessionID uint32\n}\n\ntype ExportOption func(*ExportOptionHolder)\n\n// New returns a new error object with message formed from given arguments.\nfunc New(msg ...interface{}) *Error {\n\tpc, _, _, _ := runtime.Caller(1)\n\tdetails := runtime.FuncForPC(pc).Name()\n\tif len(details) >= trim {\n\t\tdetails = details[trim:]\n\t}\n\ti := strings.Index(details, \".\")\n\tif i > 0 {\n\t\tdetails = details[:i]\n\t}\n\treturn &Error{\n\t\tmessage:  msg,\n\t\tseverity: log.Severity_Info,\n\t\tcaller:   details,\n\t}\n}\n\nfunc LogDebug(ctx context.Context, msg ...interface{}) {\n\tdoLog(ctx, nil, log.Severity_Debug, msg...)\n}\n\nfunc LogDebugInner(ctx context.Context, inner error, msg ...interface{}) {\n\tdoLog(ctx, inner, log.Severity_Debug, msg...)\n}\n\nfunc LogInfo(ctx context.Context, msg ...interface{}) {\n\tdoLog(ctx, nil, log.Severity_Info, msg...)\n}\n\nfunc LogInfoInner(ctx context.Context, inner error, msg ...interface{}) {\n\tdoLog(ctx, inner, log.Severity_Info, msg...)\n}\n\nfunc LogWarning(ctx context.Context, msg ...interface{}) {\n\tdoLog(ctx, nil, log.Severity_Warning, msg...)\n}\n\nfunc LogWarningInner(ctx context.Context, inner error, msg ...interface{}) {\n\tdoLog(ctx, inner, log.Severity_Warning, msg...)\n}\n\nfunc LogError(ctx context.Context, msg ...interface{}) {\n\tdoLog(ctx, nil, log.Severity_Error, msg...)\n}\n\nfunc LogErrorInner(ctx context.Context, inner error, msg ...interface{}) {\n\tdoLog(ctx, inner, log.Severity_Error, msg...)\n}\n\nfunc doLog(ctx context.Context, inner error, severity log.Severity, msg ...interface{}) {\n\tpc, _, _, _ := runtime.Caller(2)\n\tdetails := runtime.FuncForPC(pc).Name()\n\tif len(details) >= trim {\n\t\tdetails = details[trim:]\n\t}\n\ti := strings.Index(details, \".\")\n\tif i > 0 {\n\t\tdetails = details[:i]\n\t}\n\terr := &Error{\n\t\tmessage:  msg,\n\t\tseverity: severity,\n\t\tcaller:   details,\n\t\tinner:    inner,\n\t}\n\tif ctx != nil && ctx != context.Background() {\n\t\tid := uint32(c.IDFromContext(ctx))\n\t\tif id > 0 {\n\t\t\terr.prefix = append(err.prefix, id)\n\t\t}\n\t}\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: GetSeverity(err),\n\t\tContent:  err,\n\t})\n}\n\n// Cause returns the root cause of this error.\nfunc Cause(err error) error {\n\tif err == nil {\n\t\treturn nil\n\t}\nL:\n\tfor {\n\t\tswitch inner := err.(type) {\n\t\tcase hasInnerError:\n\t\t\tif inner.Unwrap() == nil {\n\t\t\t\tbreak L\n\t\t\t}\n\t\t\terr = inner.Unwrap()\n\t\tdefault:\n\t\t\tbreak L\n\t\t}\n\t}\n\treturn err\n}\n\n// GetSeverity returns the actual severity of the error, including inner errors.\nfunc GetSeverity(err error) log.Severity {\n\tif s, ok := err.(hasSeverity); ok {\n\t\treturn s.Severity()\n\t}\n\treturn log.Severity_Info\n}\n"
  },
  {
    "path": "common/errors/errors_test.go",
    "content": "package errors_test\n\nimport (\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n)\n\nfunc TestError(t *testing.T) {\n\terr := New(\"TestError\")\n\tif v := GetSeverity(err); v != log.Severity_Info {\n\t\tt.Error(\"severity: \", v)\n\t}\n\n\terr = New(\"TestError2\").Base(io.EOF)\n\tif v := GetSeverity(err); v != log.Severity_Info {\n\t\tt.Error(\"severity: \", v)\n\t}\n\n\terr = New(\"TestError3\").Base(io.EOF).AtWarning()\n\tif v := GetSeverity(err); v != log.Severity_Warning {\n\t\tt.Error(\"severity: \", v)\n\t}\n\n\terr = New(\"TestError4\").Base(io.EOF).AtWarning()\n\terr = New(\"TestError5\").Base(err)\n\tif v := GetSeverity(err); v != log.Severity_Warning {\n\t\tt.Error(\"severity: \", v)\n\t}\n\tif v := err.Error(); !strings.Contains(v, \"EOF\") {\n\t\tt.Error(\"error: \", v)\n\t}\n}\n\nfunc TestErrorMessage(t *testing.T) {\n\tdata := []struct {\n\t\terr error\n\t\tmsg string\n\t}{\n\t\t{\n\t\t\terr: New(\"a\").Base(New(\"b\")),\n\t\t\tmsg: \"common/errors_test: a > common/errors_test: b\",\n\t\t},\n\t}\n\n\tfor _, d := range data {\n\t\tif diff := cmp.Diff(d.msg, d.err.Error()); diff != \"\" {\n\t\t\tt.Error(diff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/errors/feature_errors.go",
    "content": "package errors\n\nimport (\n\t\"context\"\n)\n\n// PrintNonRemovalDeprecatedFeatureWarning prints a warning of the deprecated feature that won't be removed in the near future.\n// Do not remove this function even there is no reference to it.\nfunc PrintNonRemovalDeprecatedFeatureWarning(sourceFeature string, targetFeature string) {\n\tLogWarning(context.Background(), \"The feature \"+sourceFeature+\" is deprecated, not recommended for using and might be removed. Please migrate to \"+targetFeature+\" as soon as possible.\")\n}\n\n// PrintDeprecatedFeatureWarning prints a warning for deprecated and going to be removed feature.\n// Do not remove this function even there is no reference to it.\nfunc PrintDeprecatedFeatureWarning(feature string, migrateFeature string) {\n\tif len(migrateFeature) > 0 {\n\t\tLogWarning(context.Background(), \"This feature \"+feature+\" is deprecated, will be removed soon and being migrated to \"+migrateFeature+\". Please update your config(s) according to release note and documentation before removal.\")\n\t} else {\n\t\tLogWarning(context.Background(), \"This feature \"+feature+\" is deprecated and will be removed soon. Please update your config(s) according to release note and documentation before removal.\")\n\t}\n}\n\n// PrintRemovedFeatureError prints an error message for removed feature then return an error. And after long enough time the message can also be removed, uses as an indicator.\n// Do not remove this function even there is no reference to it.\nfunc PrintRemovedFeatureError(feature string, migrateFeature string) error {\n\tif len(migrateFeature) > 0 {\n\t\treturn New(\"The feature \" + feature + \" has been removed and migrated to \" + migrateFeature + \". Please update your config(s) according to release note and documentation.\")\n\t} else {\n\t\treturn New(\"The feature \" + feature + \" has been removed. Please update your config(s) according to release note and documentation.\")\n\t}\n}\n"
  },
  {
    "path": "common/errors/multi_error.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\ntype multiError []error\n\nfunc (e multiError) Error() string {\n\tvar r strings.Builder\n\tr.WriteString(\"multierr: \")\n\tfor _, err := range e {\n\t\tr.WriteString(err.Error())\n\t\tr.WriteString(\" | \")\n\t}\n\treturn r.String()\n}\n\nfunc Combine(maybeError ...error) error {\n\tvar errs multiError\n\tfor _, err := range maybeError {\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) == 0 {\n\t\treturn nil\n\t}\n\treturn errs\n}\n\nfunc AllEqual(expected error, actual error) bool {\n\tswitch errs := actual.(type) {\n\tcase multiError:\n\t\tif len(errs) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tfor _, err := range errs {\n\t\t\tif !errors.Is(err, expected) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\tdefault:\n\t\treturn errors.Is(errs, expected)\n\t}\n}\n"
  },
  {
    "path": "common/interfaces.go",
    "content": "package common\n\nimport \"github.com/xtls/xray-core/common/errors\"\n\n// Closable is the interface for objects that can release its resources.\n//\n// xray:api:beta\ntype Closable interface {\n\t// Close release all resources used by this object, including goroutines.\n\tClose() error\n}\n\n// Interruptible is an interface for objects that can be stopped before its completion.\n//\n// xray:api:beta\ntype Interruptible interface {\n\tInterrupt()\n}\n\n// Close closes the obj if it is a Closable.\n//\n// xray:api:beta\nfunc Close(obj interface{}) error {\n\tif c, ok := obj.(Closable); ok {\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n\n// Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface.\n//\n// xray:api:beta\nfunc Interrupt(obj interface{}) error {\n\tif c, ok := obj.(Interruptible); ok {\n\t\tc.Interrupt()\n\t\treturn nil\n\t}\n\treturn Close(obj)\n}\n\n// Runnable is the interface for objects that can start to work and stop on demand.\ntype Runnable interface {\n\t// Start starts the runnable object. Upon the method returning nil, the object begins to function properly.\n\tStart() error\n\n\tClosable\n}\n\n// HasType is the interface for objects that knows its type.\ntype HasType interface {\n\t// Type returns the type of the object.\n\t// Usually it returns (*Type)(nil) of the object.\n\tType() interface{}\n}\n\n// ChainedClosable is a Closable that consists of multiple Closable objects.\ntype ChainedClosable []Closable\n\n// Close implements Closable.\nfunc (cc ChainedClosable) Close() error {\n\tvar errs []error\n\tfor _, c := range cc {\n\t\tif err := c.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn errors.Combine(errs...)\n}\n"
  },
  {
    "path": "common/log/access.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\ntype logKey int\n\nconst (\n\taccessMessageKey logKey = iota\n)\n\ntype AccessStatus string\n\nconst (\n\tAccessAccepted = AccessStatus(\"accepted\")\n\tAccessRejected = AccessStatus(\"rejected\")\n)\n\ntype AccessMessage struct {\n\tFrom   interface{}\n\tTo     interface{}\n\tStatus AccessStatus\n\tReason interface{}\n\tEmail  string\n\tDetour string\n}\n\nfunc (m *AccessMessage) String() string {\n\tbuilder := strings.Builder{}\n\tbuilder.WriteString(\"from\")\n\tbuilder.WriteByte(' ')\n\tbuilder.WriteString(serial.ToString(m.From))\n\tbuilder.WriteByte(' ')\n\tbuilder.WriteString(string(m.Status))\n\tbuilder.WriteByte(' ')\n\tbuilder.WriteString(serial.ToString(m.To))\n\n\tif len(m.Detour) > 0 {\n\t\tbuilder.WriteString(\" [\")\n\t\tbuilder.WriteString(m.Detour)\n\t\tbuilder.WriteByte(']')\n\t}\n\n\tif reason := serial.ToString(m.Reason); len(reason) > 0 {\n\t\tbuilder.WriteString(\" \")\n\t\tbuilder.WriteString(reason)\n\t}\n\n\tif len(m.Email) > 0 {\n\t\tbuilder.WriteString(\" email: \")\n\t\tbuilder.WriteString(m.Email)\n\t}\n\n\treturn builder.String()\n}\n\nfunc ContextWithAccessMessage(ctx context.Context, accessMessage *AccessMessage) context.Context {\n\treturn context.WithValue(ctx, accessMessageKey, accessMessage)\n}\n\nfunc AccessMessageFromContext(ctx context.Context) *AccessMessage {\n\tif accessMessage, ok := ctx.Value(accessMessageKey).(*AccessMessage); ok {\n\t\treturn accessMessage\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/log/dns.go",
    "content": "package log\n\nimport (\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype DNSLog struct {\n\tServer  string\n\tDomain  string\n\tResult  []net.IP\n\tStatus  dnsStatus\n\tElapsed time.Duration\n\tError   error\n}\n\nfunc (l *DNSLog) String() string {\n\tbuilder := &strings.Builder{}\n\n\t// Server got answer: domain -> [ip1, ip2] 23ms\n\tbuilder.WriteString(l.Server)\n\tbuilder.WriteString(\" \")\n\tbuilder.WriteString(string(l.Status))\n\tbuilder.WriteString(\" \")\n\tbuilder.WriteString(l.Domain)\n\tbuilder.WriteString(\" -> [\")\n\tbuilder.WriteString(joinNetIP(l.Result))\n\tbuilder.WriteString(\"]\")\n\n\tif l.Elapsed > 0 {\n\t\tbuilder.WriteString(\" \")\n\t\tbuilder.WriteString(l.Elapsed.String())\n\t}\n\tif l.Error != nil {\n\t\tbuilder.WriteString(\" <\")\n\t\tbuilder.WriteString(l.Error.Error())\n\t\tbuilder.WriteString(\">\")\n\t}\n\treturn builder.String()\n}\n\ntype dnsStatus string\n\nvar (\n\tDNSQueried        = dnsStatus(\"got answer:\")\n\tDNSCacheHit       = dnsStatus(\"cache HIT:\")\n\tDNSCacheOptimiste = dnsStatus(\"cache OPTIMISTE:\")\n)\n\nfunc joinNetIP(ips []net.IP) string {\n\tif len(ips) == 0 {\n\t\treturn \"\"\n\t}\n\tsips := make([]string, 0, len(ips))\n\tfor _, ip := range ips {\n\t\tsips = append(sips, ip.String())\n\t}\n\treturn strings.Join(sips, \", \")\n}\n"
  },
  {
    "path": "common/log/log.go",
    "content": "package log // import \"github.com/xtls/xray-core/common/log\"\n\nimport (\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\n// Message is the interface for all log messages.\ntype Message interface {\n\tString() string\n}\n\n// Handler is the interface for log handler.\ntype Handler interface {\n\tHandle(msg Message)\n}\n\n// GeneralMessage is a general log message that can contain all kind of content.\ntype GeneralMessage struct {\n\tSeverity Severity\n\tContent  interface{}\n}\n\n// String implements Message.\nfunc (m *GeneralMessage) String() string {\n\treturn serial.Concat(\"[\", m.Severity, \"] \", m.Content)\n}\n\n// Record writes a message into log stream.\nfunc Record(msg Message) {\n\tlogHandler.Handle(msg)\n}\n\nvar logHandler syncHandler\n\n// RegisterHandler registers a new handler as current log handler. Previous registered handler will be discarded.\nfunc RegisterHandler(handler Handler) {\n\tif handler == nil {\n\t\tpanic(\"Log handler is nil\")\n\t}\n\tlogHandler.Set(handler)\n}\n\ntype syncHandler struct {\n\tsync.RWMutex\n\tHandler\n}\n\nfunc (h *syncHandler) Handle(msg Message) {\n\th.RLock()\n\tdefer h.RUnlock()\n\n\tif h.Handler != nil {\n\t\th.Handler.Handle(msg)\n\t}\n}\n\nfunc (h *syncHandler) Set(handler Handler) {\n\th.Lock()\n\tdefer h.Unlock()\n\n\th.Handler = handler\n}\n"
  },
  {
    "path": "common/log/log.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/log/log.proto\n\npackage log\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Severity int32\n\nconst (\n\tSeverity_Unknown Severity = 0\n\tSeverity_Error   Severity = 1\n\tSeverity_Warning Severity = 2\n\tSeverity_Info    Severity = 3\n\tSeverity_Debug   Severity = 4\n)\n\n// Enum value maps for Severity.\nvar (\n\tSeverity_name = map[int32]string{\n\t\t0: \"Unknown\",\n\t\t1: \"Error\",\n\t\t2: \"Warning\",\n\t\t3: \"Info\",\n\t\t4: \"Debug\",\n\t}\n\tSeverity_value = map[string]int32{\n\t\t\"Unknown\": 0,\n\t\t\"Error\":   1,\n\t\t\"Warning\": 2,\n\t\t\"Info\":    3,\n\t\t\"Debug\":   4,\n\t}\n)\n\nfunc (x Severity) Enum() *Severity {\n\tp := new(Severity)\n\t*p = x\n\treturn p\n}\n\nfunc (x Severity) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Severity) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_common_log_log_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Severity) Type() protoreflect.EnumType {\n\treturn &file_common_log_log_proto_enumTypes[0]\n}\n\nfunc (x Severity) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Severity.Descriptor instead.\nfunc (Severity) EnumDescriptor() ([]byte, []int) {\n\treturn file_common_log_log_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_common_log_log_proto protoreflect.FileDescriptor\n\nconst file_common_log_log_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x14common/log/log.proto\\x12\\x0fxray.common.log*D\\n\" +\n\t\"\\bSeverity\\x12\\v\\n\" +\n\t\"\\aUnknown\\x10\\x00\\x12\\t\\n\" +\n\t\"\\x05Error\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aWarning\\x10\\x02\\x12\\b\\n\" +\n\t\"\\x04Info\\x10\\x03\\x12\\t\\n\" +\n\t\"\\x05Debug\\x10\\x04BO\\n\" +\n\t\"\\x13com.xray.common.logP\\x01Z$github.com/xtls/xray-core/common/log\\xaa\\x02\\x0fXray.Common.Logb\\x06proto3\"\n\nvar (\n\tfile_common_log_log_proto_rawDescOnce sync.Once\n\tfile_common_log_log_proto_rawDescData []byte\n)\n\nfunc file_common_log_log_proto_rawDescGZIP() []byte {\n\tfile_common_log_log_proto_rawDescOnce.Do(func() {\n\t\tfile_common_log_log_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_log_log_proto_rawDesc), len(file_common_log_log_proto_rawDesc)))\n\t})\n\treturn file_common_log_log_proto_rawDescData\n}\n\nvar file_common_log_log_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_common_log_log_proto_goTypes = []any{\n\t(Severity)(0), // 0: xray.common.log.Severity\n}\nvar file_common_log_log_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_common_log_log_proto_init() }\nfunc file_common_log_log_proto_init() {\n\tif File_common_log_log_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_log_log_proto_rawDesc), len(file_common_log_log_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   0,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_log_log_proto_goTypes,\n\t\tDependencyIndexes: file_common_log_log_proto_depIdxs,\n\t\tEnumInfos:         file_common_log_log_proto_enumTypes,\n\t}.Build()\n\tFile_common_log_log_proto = out.File\n\tfile_common_log_log_proto_goTypes = nil\n\tfile_common_log_log_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/log/log.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.log;\noption csharp_namespace = \"Xray.Common.Log\";\noption go_package = \"github.com/xtls/xray-core/common/log\";\noption java_package = \"com.xray.common.log\";\noption java_multiple_files = true;\n\nenum Severity {\n  Unknown = 0;\n  Error = 1;\n  Warning = 2;\n  Info = 3;\n  Debug = 4;\n}\n"
  },
  {
    "path": "common/log/log_test.go",
    "content": "package log_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\ntype testLogger struct {\n\tvalue string\n}\n\nfunc (l *testLogger) Handle(msg log.Message) {\n\tl.value = msg.String()\n}\n\nfunc TestLogRecord(t *testing.T) {\n\tvar logger testLogger\n\tlog.RegisterHandler(&logger)\n\n\tip := \"8.8.8.8\"\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: log.Severity_Error,\n\t\tContent:  net.ParseAddress(ip),\n\t})\n\n\tif diff := cmp.Diff(\"[Error] \"+ip, logger.value); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n}\n"
  },
  {
    "path": "common/log/logger.go",
    "content": "package log\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/common/signal/semaphore\"\n)\n\n// Writer is the interface for writing logs.\ntype Writer interface {\n\tWrite(string) error\n\tio.Closer\n}\n\n// WriterCreator is a function to create LogWriters.\ntype WriterCreator func() Writer\n\ntype generalLogger struct {\n\tcreator WriterCreator\n\tbuffer  chan Message\n\taccess  *semaphore.Instance\n\tdone    *done.Instance\n}\n\ntype serverityLogger struct {\n\tinner    *generalLogger\n\tlogLevel Severity\n}\n\n// NewLogger returns a generic log handler that can handle all type of messages.\nfunc NewLogger(logWriterCreator WriterCreator) Handler {\n\treturn &generalLogger{\n\t\tcreator: logWriterCreator,\n\t\tbuffer:  make(chan Message, 16),\n\t\taccess:  semaphore.New(1),\n\t\tdone:    done.New(),\n\t}\n}\n\nfunc ReplaceWithSeverityLogger(serverity Severity) {\n\tw := CreateStdoutLogWriter()\n\tg := &generalLogger{\n\t\tcreator: w,\n\t\tbuffer:  make(chan Message, 16),\n\t\taccess:  semaphore.New(1),\n\t\tdone:    done.New(),\n\t}\n\ts := &serverityLogger{\n\t\tinner:    g,\n\t\tlogLevel: serverity,\n\t}\n\tRegisterHandler(s)\n}\n\nfunc (l *serverityLogger) Handle(msg Message) {\n\tswitch msg := msg.(type) {\n\tcase *GeneralMessage:\n\t\tif msg.Severity <= l.logLevel {\n\t\t\tl.inner.Handle(msg)\n\t\t}\n\tdefault:\n\t\tl.inner.Handle(msg)\n\t}\n}\n\nfunc (l *generalLogger) run() {\n\tdefer l.access.Signal()\n\n\tdataWritten := false\n\tticker := time.NewTicker(time.Minute)\n\tdefer ticker.Stop()\n\n\tlogger := l.creator()\n\tif logger == nil {\n\t\treturn\n\t}\n\tdefer logger.Close()\n\n\tfor {\n\t\tselect {\n\t\tcase <-l.done.Wait():\n\t\t\treturn\n\t\tcase msg := <-l.buffer:\n\t\t\tlogger.Write(msg.String() + platform.LineSeparator())\n\t\t\tdataWritten = true\n\t\tcase <-ticker.C:\n\t\t\tif !dataWritten {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdataWritten = false\n\t\t}\n\t}\n}\n\nfunc (l *generalLogger) Handle(msg Message) {\n\n\tselect {\n\tcase l.buffer <- msg:\n\tdefault:\n\t}\n\n\tselect {\n\tcase <-l.access.Wait():\n\t\tgo l.run()\n\tdefault:\n\t}\n}\n\nfunc (l *generalLogger) Close() error {\n\treturn l.done.Close()\n}\n\ntype consoleLogWriter struct {\n\tlogger *log.Logger\n}\n\nfunc (w *consoleLogWriter) Write(s string) error {\n\tw.logger.Print(s)\n\treturn nil\n}\n\nfunc (w *consoleLogWriter) Close() error {\n\treturn nil\n}\n\ntype fileLogWriter struct {\n\tfile   *os.File\n\tlogger *log.Logger\n}\n\nfunc (w *fileLogWriter) Write(s string) error {\n\tw.logger.Print(s)\n\treturn nil\n}\n\nfunc (w *fileLogWriter) Close() error {\n\treturn w.file.Close()\n}\n\n// CreateStdoutLogWriter returns a LogWriterCreator that creates LogWriter for stdout.\nfunc CreateStdoutLogWriter() WriterCreator {\n\treturn func() Writer {\n\t\treturn &consoleLogWriter{\n\t\t\tlogger: log.New(os.Stdout, \"\", log.Ldate|log.Ltime|log.Lmicroseconds),\n\t\t}\n\t}\n}\n\n// CreateStderrLogWriter returns a LogWriterCreator that creates LogWriter for stderr.\nfunc CreateStderrLogWriter() WriterCreator {\n\treturn func() Writer {\n\t\treturn &consoleLogWriter{\n\t\t\tlogger: log.New(os.Stderr, \"\", log.Ldate|log.Ltime|log.Lmicroseconds),\n\t\t}\n\t}\n}\n\n// CreateFileLogWriter returns a LogWriterCreator that creates LogWriter for the given file.\nfunc CreateFileLogWriter(path string) (WriterCreator, error) {\n\tfile, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfile.Close()\n\treturn func() Writer {\n\t\tfile, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &fileLogWriter{\n\t\t\tfile:   file,\n\t\t\tlogger: log.New(file, \"\", log.Ldate|log.Ltime|log.Lmicroseconds),\n\t\t}\n\t}, nil\n}\n\nfunc init() {\n\tRegisterHandler(NewLogger(CreateStdoutLogWriter()))\n}\n"
  },
  {
    "path": "common/log/logger_test.go",
    "content": "package log_test\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/common/log\"\n)\n\nfunc TestFileLogger(t *testing.T) {\n\tf, err := os.CreateTemp(\"\", \"vtest\")\n\tcommon.Must(err)\n\tpath := f.Name()\n\tcommon.Must(f.Close())\n\tdefer os.Remove(path)\n\n\tcreator, err := CreateFileLogWriter(path)\n\tcommon.Must(err)\n\n\thandler := NewLogger(creator)\n\thandler.Handle(&GeneralMessage{Content: \"Test Log\"})\n\ttime.Sleep(2 * time.Second)\n\n\tcommon.Must(common.Close(handler))\n\n\tf, err = os.Open(path)\n\tcommon.Must(err)\n\tdefer f.Close()\n\n\tb, err := buf.ReadAllToBytes(f)\n\tcommon.Must(err)\n\tif !strings.Contains(string(b), \"Test Log\") {\n\t\tt.Fatal(\"Expect log text contains 'Test Log', but actually: \", string(b))\n\t}\n}\n"
  },
  {
    "path": "common/mux/client.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/xudp\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\ntype ClientManager struct {\n\tEnabled bool // whether mux is enabled from user config\n\tPicker  WorkerPicker\n}\n\nfunc (m *ClientManager) Dispatch(ctx context.Context, link *transport.Link) error {\n\tfor i := 0; i < 16; i++ {\n\t\tworker, err := m.Picker.PickAvailable()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif worker.Dispatch(ctx, link) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn errors.New(\"unable to find an available mux client\").AtWarning()\n}\n\ntype WorkerPicker interface {\n\tPickAvailable() (*ClientWorker, error)\n}\n\ntype IncrementalWorkerPicker struct {\n\tFactory ClientWorkerFactory\n\n\taccess      sync.Mutex\n\tworkers     []*ClientWorker\n\tcleanupTask *task.Periodic\n}\n\nfunc (p *IncrementalWorkerPicker) cleanupFunc() error {\n\tp.access.Lock()\n\tdefer p.access.Unlock()\n\n\tif len(p.workers) == 0 {\n\t\treturn errors.New(\"no worker\")\n\t}\n\n\tp.cleanup()\n\treturn nil\n}\n\nfunc (p *IncrementalWorkerPicker) cleanup() {\n\tvar activeWorkers []*ClientWorker\n\tfor _, w := range p.workers {\n\t\tif !w.Closed() {\n\t\t\tactiveWorkers = append(activeWorkers, w)\n\t\t}\n\t}\n\tp.workers = activeWorkers\n}\n\nfunc (p *IncrementalWorkerPicker) findAvailable() int {\n\tfor idx, w := range p.workers {\n\t\tif !w.IsFull() {\n\t\t\treturn idx\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (p *IncrementalWorkerPicker) pickInternal() (*ClientWorker, bool, error) {\n\tp.access.Lock()\n\tdefer p.access.Unlock()\n\n\tidx := p.findAvailable()\n\tif idx >= 0 {\n\t\tn := len(p.workers)\n\t\tif n > 1 && idx != n-1 {\n\t\t\tp.workers[n-1], p.workers[idx] = p.workers[idx], p.workers[n-1]\n\t\t}\n\t\treturn p.workers[idx], false, nil\n\t}\n\n\tp.cleanup()\n\n\tworker, err := p.Factory.Create()\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\tp.workers = append(p.workers, worker)\n\n\tif p.cleanupTask == nil {\n\t\tp.cleanupTask = &task.Periodic{\n\t\t\tInterval: time.Second * 30,\n\t\t\tExecute:  p.cleanupFunc,\n\t\t}\n\t}\n\n\treturn worker, true, nil\n}\n\nfunc (p *IncrementalWorkerPicker) PickAvailable() (*ClientWorker, error) {\n\tworker, start, err := p.pickInternal()\n\tif start {\n\t\tcommon.Must(p.cleanupTask.Start())\n\t}\n\n\treturn worker, err\n}\n\ntype ClientWorkerFactory interface {\n\tCreate() (*ClientWorker, error)\n}\n\ntype DialingWorkerFactory struct {\n\tProxy    proxy.Outbound\n\tDialer   internet.Dialer\n\tStrategy ClientStrategy\n}\n\nfunc (f *DialingWorkerFactory) Create() (*ClientWorker, error) {\n\topts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}\n\tuplinkReader, upLinkWriter := pipe.New(opts...)\n\tdownlinkReader, downlinkWriter := pipe.New(opts...)\n\n\tc, err := NewClientWorker(transport.Link{\n\t\tReader: downlinkReader,\n\t\tWriter: upLinkWriter,\n\t}, f.Strategy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func(p proxy.Outbound, d internet.Dialer, c common.Closable) {\n\t\toutbounds := []*session.Outbound{{\n\t\t\tTarget: net.TCPDestination(muxCoolAddress, muxCoolPort),\n\t\t}}\n\t\tctx := session.ContextWithOutbounds(context.Background(), outbounds)\n\t\tctx, cancel := context.WithCancel(ctx)\n\n\t\tif errP := p.Process(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter}, d); errP != nil {\n\t\t\terrC := errors.Cause(errP)\n\t\t\tif !(goerrors.Is(errC, io.EOF) || goerrors.Is(errC, io.ErrClosedPipe) || goerrors.Is(errC, context.Canceled)) {\n\t\t\t\terrors.LogInfoInner(ctx, errP, \"failed to handler mux client connection\")\n\t\t\t}\n\t\t}\n\t\tcommon.Must(c.Close())\n\t\tcancel()\n\t}(f.Proxy, f.Dialer, c.done)\n\n\treturn c, nil\n}\n\ntype ClientStrategy struct {\n\tMaxConcurrency uint32\n\tMaxConnection  uint32\n}\n\ntype ClientWorker struct {\n\tsessionManager *SessionManager\n\tlink           transport.Link\n\tdone           *done.Instance\n\ttimer          *time.Ticker\n\tstrategy       ClientStrategy\n}\n\nvar (\n\tmuxCoolAddress = net.DomainAddress(\"v1.mux.cool\")\n\tmuxCoolPort    = net.Port(9527)\n)\n\n// NewClientWorker creates a new mux.Client.\nfunc NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, error) {\n\tc := &ClientWorker{\n\t\tsessionManager: NewSessionManager(),\n\t\tlink:           stream,\n\t\tdone:           done.New(),\n\t\ttimer:          time.NewTicker(time.Second * 16),\n\t\tstrategy:       s,\n\t}\n\n\tgo c.fetchOutput()\n\tgo c.monitor()\n\n\treturn c, nil\n}\n\nfunc (m *ClientWorker) TotalConnections() uint32 {\n\treturn uint32(m.sessionManager.Count())\n}\n\nfunc (m *ClientWorker) ActiveConnections() uint32 {\n\treturn uint32(m.sessionManager.Size())\n}\n\n// Closed returns true if this Client is closed.\nfunc (m *ClientWorker) Closed() bool {\n\treturn m.done.Done()\n}\n\nfunc (m *ClientWorker) WaitClosed() <-chan struct{} {\n\treturn m.done.Wait()\n}\n\nfunc (m *ClientWorker) Close() error {\n\treturn m.done.Close()\n}\n\nfunc (m *ClientWorker) monitor() {\n\tdefer m.timer.Stop()\n\n\tfor {\n\t\tcheckSize := m.sessionManager.Size()\n\t\tcheckCount := m.sessionManager.Count()\n\t\tselect {\n\t\tcase <-m.done.Wait():\n\t\t\tm.sessionManager.Close()\n\t\t\tcommon.Interrupt(m.link.Writer)\n\t\t\tcommon.Interrupt(m.link.Reader)\n\t\t\treturn\n\t\tcase <-m.timer.C:\n\t\t\tif m.sessionManager.CloseIfNoSessionAndIdle(checkSize, checkCount) {\n\t\t\t\tcommon.Must(m.done.Close())\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc writeFirstPayload(reader buf.Reader, writer *Writer) error {\n\terr := buf.CopyOnceTimeout(reader, writer, time.Millisecond*100)\n\tif err == buf.ErrNotTimeoutReader || err == buf.ErrReadTimeout {\n\t\treturn writer.WriteMultiBuffer(buf.MultiBuffer{})\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc fetchInput(ctx context.Context, s *Session, output buf.Writer) {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\ttransferType := protocol.TransferTypeStream\n\tif ob.Target.Network == net.Network_UDP {\n\t\ttransferType = protocol.TransferTypePacket\n\t}\n\ts.transferType = transferType\n\tvar inbound *session.Inbound\n\tif session.IsReverseMuxFromContext(ctx) {\n\t\tinbound = session.InboundFromContext(ctx)\n\t}\n\twriter := NewWriter(s.ID, ob.Target, output, transferType, xudp.GetGlobalID(ctx), inbound)\n\tdefer s.Close(false)\n\tdefer writer.Close()\n\n\terrors.LogInfo(ctx, \"dispatching request to \", ob.Target)\n\tif err := writeFirstPayload(s.input, writer); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to write first payload\")\n\t\twriter.hasError = true\n\t\treturn\n\t}\n\n\tif err := buf.Copy(s.input, writer); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to fetch all input\")\n\t\twriter.hasError = true\n\t\treturn\n\t}\n}\n\nfunc (m *ClientWorker) IsClosing() bool {\n\tsm := m.sessionManager\n\tif m.strategy.MaxConnection > 0 && sm.Count() >= int(m.strategy.MaxConnection) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// IsFull returns true if this ClientWorker is unable to accept more connections.\n// it might be because it is closing, or the number of connections has reached the limit.\nfunc (m *ClientWorker) IsFull() bool {\n\tif m.IsClosing() || m.Closed() {\n\t\treturn true\n\t}\n\n\tsm := m.sessionManager\n\tif m.strategy.MaxConcurrency > 0 && sm.Size() >= int(m.strategy.MaxConcurrency) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool {\n\tif m.IsFull() {\n\t\treturn false\n\t}\n\n\tsm := m.sessionManager\n\ts := sm.Allocate(&m.strategy)\n\tif s == nil {\n\t\treturn false\n\t}\n\ts.input = link.Reader\n\ts.output = link.Writer\n\tgo fetchInput(ctx, s, m.link.Writer)\n\tif _, ok := link.Reader.(*pipe.Reader); !ok {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\tcase <-s.done.Wait():\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (m *ClientWorker) handleStatueKeepAlive(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif meta.Option.Has(OptionData) {\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\treturn nil\n}\n\nfunc (m *ClientWorker) handleStatusNew(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif meta.Option.Has(OptionData) {\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\treturn nil\n}\n\nfunc (m *ClientWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif !meta.Option.Has(OptionData) {\n\t\treturn nil\n\t}\n\n\ts, found := m.sessionManager.Get(meta.SessionID)\n\tif !found {\n\t\t// Notify remote peer to close this session.\n\t\tclosingWriter := NewResponseWriter(meta.SessionID, m.link.Writer, protocol.TransferTypeStream)\n\t\tclosingWriter.Close()\n\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\n\trr := s.NewReader(reader, &meta.Target)\n\terr := buf.Copy(rr, s.output)\n\tif err != nil && buf.IsWriteError(err) {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to write to downstream. closing session \", s.ID)\n\t\ts.Close(false)\n\t\treturn buf.Copy(rr, buf.Discard)\n\t}\n\n\treturn err\n}\n\nfunc (m *ClientWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif s, found := m.sessionManager.Get(meta.SessionID); found {\n\t\ts.Close(false)\n\t}\n\tif meta.Option.Has(OptionData) {\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\treturn nil\n}\n\nfunc (m *ClientWorker) fetchOutput() {\n\tdefer func() {\n\t\tcommon.Must(m.done.Close())\n\t}()\n\n\treader := &buf.BufferedReader{Reader: m.link.Reader}\n\n\tvar meta FrameMetadata\n\tfor {\n\t\terr := meta.Unmarshal(reader, false)\n\t\tif err != nil {\n\t\t\tif errors.Cause(err) != io.EOF {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to read metadata\")\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tswitch meta.SessionStatus {\n\t\tcase SessionStatusKeepAlive:\n\t\t\terr = m.handleStatueKeepAlive(&meta, reader)\n\t\tcase SessionStatusEnd:\n\t\t\terr = m.handleStatusEnd(&meta, reader)\n\t\tcase SessionStatusNew:\n\t\t\terr = m.handleStatusNew(&meta, reader)\n\t\tcase SessionStatusKeep:\n\t\t\terr = m.handleStatusKeep(&meta, reader)\n\t\tdefault:\n\t\t\tstatus := meta.SessionStatus\n\t\t\terrors.LogError(context.Background(), \"unknown status: \", status)\n\t\t\treturn\n\t\t}\n\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to process data\")\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/mux/client_test.go",
    "content": "package mux_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/testing/mocks\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc TestIncrementalPickerFailure(t *testing.T) {\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tmockWorkerFactory := mocks.NewMuxClientWorkerFactory(mockCtl)\n\tmockWorkerFactory.EXPECT().Create().Return(nil, errors.New(\"test\"))\n\n\tpicker := mux.IncrementalWorkerPicker{\n\t\tFactory: mockWorkerFactory,\n\t}\n\n\t_, err := picker.PickAvailable()\n\tif err == nil {\n\t\tt.Error(\"expected error, but nil\")\n\t}\n}\n\nfunc TestClientWorkerEOF(t *testing.T) {\n\treader, writer := pipe.New(pipe.WithoutSizeLimit())\n\tcommon.Must(writer.Close())\n\n\tworker, err := mux.NewClientWorker(transport.Link{Reader: reader, Writer: writer}, mux.ClientStrategy{})\n\tcommon.Must(err)\n\n\ttime.Sleep(time.Millisecond * 500)\n\n\tf := worker.Dispatch(context.Background(), nil)\n\tif f {\n\t\tt.Error(\"expected failed dispatching, but actually not\")\n\t}\n}\n\nfunc TestClientWorkerClose(t *testing.T) {\n\tmockCtl := gomock.NewController(t)\n\tdefer mockCtl.Finish()\n\n\tr1, w1 := pipe.New(pipe.WithoutSizeLimit())\n\tworker1, err := mux.NewClientWorker(transport.Link{\n\t\tReader: r1,\n\t\tWriter: w1,\n\t}, mux.ClientStrategy{\n\t\tMaxConcurrency: 4,\n\t\tMaxConnection:  4,\n\t})\n\tcommon.Must(err)\n\n\tr2, w2 := pipe.New(pipe.WithoutSizeLimit())\n\tworker2, err := mux.NewClientWorker(transport.Link{\n\t\tReader: r2,\n\t\tWriter: w2,\n\t}, mux.ClientStrategy{\n\t\tMaxConcurrency: 4,\n\t\tMaxConnection:  4,\n\t})\n\tcommon.Must(err)\n\n\tfactory := mocks.NewMuxClientWorkerFactory(mockCtl)\n\tgomock.InOrder(\n\t\tfactory.EXPECT().Create().Return(worker1, nil),\n\t\tfactory.EXPECT().Create().Return(worker2, nil),\n\t)\n\n\tpicker := &mux.IncrementalWorkerPicker{\n\t\tFactory: factory,\n\t}\n\tmanager := &mux.ClientManager{\n\t\tPicker: picker,\n\t}\n\n\ttr1, tw1 := pipe.New(pipe.WithoutSizeLimit())\n\tctx1 := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"www.example.com\"), 80),\n\t}})\n\tcommon.Must(manager.Dispatch(ctx1, &transport.Link{\n\t\tReader: tr1,\n\t\tWriter: tw1,\n\t}))\n\tdefer tw1.Close()\n\n\tcommon.Must(w1.Close())\n\n\ttime.Sleep(time.Millisecond * 500)\n\tif !worker1.Closed() {\n\t\tt.Error(\"worker1 is not finished\")\n\t}\n\n\ttr2, tw2 := pipe.New(pipe.WithoutSizeLimit())\n\tctx2 := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"www.example.com\"), 80),\n\t}})\n\tcommon.Must(manager.Dispatch(ctx2, &transport.Link{\n\t\tReader: tr2,\n\t\tWriter: tw2,\n\t}))\n\tdefer tw2.Close()\n\n\tcommon.Must(w2.Close())\n}\n"
  },
  {
    "path": "common/mux/frame.go",
    "content": "package mux\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/bitmask\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n)\n\ntype SessionStatus byte\n\nconst (\n\tSessionStatusNew       SessionStatus = 0x01\n\tSessionStatusKeep      SessionStatus = 0x02\n\tSessionStatusEnd       SessionStatus = 0x03\n\tSessionStatusKeepAlive SessionStatus = 0x04\n)\n\nconst (\n\tOptionData  bitmask.Byte = 0x01\n\tOptionError bitmask.Byte = 0x02\n)\n\ntype TargetNetwork byte\n\nconst (\n\tTargetNetworkTCP TargetNetwork = 0x01\n\tTargetNetworkUDP TargetNetwork = 0x02\n)\n\nvar addrParser = protocol.NewAddressParser(\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6),\n\tprotocol.PortThenAddress(),\n)\n\n/*\nFrame format\n2 bytes - length\n2 bytes - session id\n1 bytes - status\n1 bytes - option\n\n1 byte - network\n2 bytes - port\nn bytes - address\n\n*/\n\ntype FrameMetadata struct {\n\tTarget        net.Destination\n\tSessionID     uint16\n\tOption        bitmask.Byte\n\tSessionStatus SessionStatus\n\tGlobalID      [8]byte\n\tInbound       *session.Inbound\n}\n\nfunc (f FrameMetadata) WriteTo(b *buf.Buffer) error {\n\tlenBytes := b.Extend(2)\n\n\tlen0 := b.Len()\n\tsessionBytes := b.Extend(2)\n\tbinary.BigEndian.PutUint16(sessionBytes, f.SessionID)\n\n\tcommon.Must(b.WriteByte(byte(f.SessionStatus)))\n\tcommon.Must(b.WriteByte(byte(f.Option)))\n\n\tif f.SessionStatus == SessionStatusNew {\n\t\tswitch f.Target.Network {\n\t\tcase net.Network_TCP:\n\t\t\tcommon.Must(b.WriteByte(byte(TargetNetworkTCP)))\n\t\tcase net.Network_UDP:\n\t\t\tcommon.Must(b.WriteByte(byte(TargetNetworkUDP)))\n\t\t}\n\t\tif err := addrParser.WriteAddressPort(b, f.Target.Address, f.Target.Port); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif f.Inbound != nil {\n\t\t\tif f.Inbound.Source.Network == net.Network_TCP || f.Inbound.Source.Network == net.Network_UDP {\n\t\t\t\tcommon.Must(b.WriteByte(byte(f.Inbound.Source.Network - 1)))\n\t\t\t\tif err := addrParser.WriteAddressPort(b, f.Inbound.Source.Address, f.Inbound.Source.Port); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif f.Inbound.Local.Network == net.Network_TCP || f.Inbound.Local.Network == net.Network_UDP {\n\t\t\t\t\tcommon.Must(b.WriteByte(byte(f.Inbound.Local.Network - 1)))\n\t\t\t\t\tif err := addrParser.WriteAddressPort(b, f.Inbound.Local.Address, f.Inbound.Local.Port); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if b.UDP != nil { // make sure it's user's proxy request\n\t\t\tb.Write(f.GlobalID[:]) // no need to check whether it's empty\n\t\t}\n\t} else if b.UDP != nil {\n\t\tb.WriteByte(byte(TargetNetworkUDP))\n\t\taddrParser.WriteAddressPort(b, b.UDP.Address, b.UDP.Port)\n\t}\n\n\tlen1 := b.Len()\n\tbinary.BigEndian.PutUint16(lenBytes, uint16(len1-len0))\n\treturn nil\n}\n\n// Unmarshal reads FrameMetadata from the given reader.\nfunc (f *FrameMetadata) Unmarshal(reader io.Reader, readSourceAndLocal bool) error {\n\tmetaLen, err := serial.ReadUint16(reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif metaLen > 512 {\n\t\treturn errors.New(\"invalid metalen \", metaLen).AtError()\n\t}\n\n\tb := buf.New()\n\tdefer b.Release()\n\n\tif _, err := b.ReadFullFrom(reader, int32(metaLen)); err != nil {\n\t\treturn err\n\t}\n\treturn f.UnmarshalFromBuffer(b, readSourceAndLocal)\n}\n\n// UnmarshalFromBuffer reads a FrameMetadata from the given buffer.\n// Visible for testing only.\nfunc (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer, readSourceAndLocal bool) error {\n\tif b.Len() < 4 {\n\t\treturn errors.New(\"insufficient buffer: \", b.Len())\n\t}\n\n\tf.SessionID = binary.BigEndian.Uint16(b.BytesTo(2))\n\tf.SessionStatus = SessionStatus(b.Byte(2))\n\tf.Option = bitmask.Byte(b.Byte(3))\n\tf.Target.Network = net.Network_Unknown\n\n\tif f.SessionStatus == SessionStatusNew || (f.SessionStatus == SessionStatusKeep && b.Len() > 4 &&\n\t\tTargetNetwork(b.Byte(4)) == TargetNetworkUDP) { // MUST check the flag first\n\t\tif b.Len() < 8 {\n\t\t\treturn errors.New(\"insufficient buffer: \", b.Len())\n\t\t}\n\t\tnetwork := TargetNetwork(b.Byte(4))\n\t\tb.Advance(5)\n\n\t\taddr, port, err := addrParser.ReadAddressPort(nil, b)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to parse address and port\").Base(err)\n\t\t}\n\n\t\tswitch network {\n\t\tcase TargetNetworkTCP:\n\t\t\tf.Target = net.TCPDestination(addr, port)\n\t\tcase TargetNetworkUDP:\n\t\t\tf.Target = net.UDPDestination(addr, port)\n\t\tdefault:\n\t\t\treturn errors.New(\"unknown network type: \", network)\n\t\t}\n\t}\n\n\tif f.SessionStatus == SessionStatusNew && readSourceAndLocal {\n\t\tf.Inbound = &session.Inbound{}\n\n\t\tif b.Len() == 0 {\n\t\t\treturn nil // for heartbeat, etc.\n\t\t}\n\t\tnetwork := TargetNetwork(b.Byte(0))\n\t\tif network == 0 {\n\t\t\treturn nil // may be padding\n\t\t}\n\t\tb.Advance(1)\n\t\taddr, port, err := addrParser.ReadAddressPort(nil, b)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"reading source: failed to parse address and port\").Base(err)\n\t\t}\n\t\tswitch network {\n\t\tcase TargetNetworkTCP:\n\t\t\tf.Inbound.Source = net.TCPDestination(addr, port)\n\t\tcase TargetNetworkUDP:\n\t\t\tf.Inbound.Source = net.UDPDestination(addr, port)\n\t\tdefault:\n\t\t\treturn errors.New(\"reading source: unknown network type: \", network)\n\t\t}\n\n\t\tif b.Len() == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tnetwork = TargetNetwork(b.Byte(0))\n\t\tif network == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tb.Advance(1)\n\t\taddr, port, err = addrParser.ReadAddressPort(nil, b)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"reading local: failed to parse address and port\").Base(err)\n\t\t}\n\t\tswitch network {\n\t\tcase TargetNetworkTCP:\n\t\t\tf.Inbound.Local = net.TCPDestination(addr, port)\n\t\tcase TargetNetworkUDP:\n\t\t\tf.Inbound.Local = net.UDPDestination(addr, port)\n\t\tdefault:\n\t\t\treturn errors.New(\"reading local: unknown network type: \", network)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Application data is essential, to test whether the pipe is closed.\n\tif f.SessionStatus == SessionStatusNew && f.Option.Has(OptionData) &&\n\t\tf.Target.Network == net.Network_UDP && b.Len() >= 8 {\n\t\tcopy(f.GlobalID[:], b.Bytes())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "common/mux/frame_test.go",
    "content": "package mux_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nfunc BenchmarkFrameWrite(b *testing.B) {\n\tframe := mux.FrameMetadata{\n\t\tTarget:        net.TCPDestination(net.DomainAddress(\"www.example.com\"), net.Port(80)),\n\t\tSessionID:     1,\n\t\tSessionStatus: mux.SessionStatusNew,\n\t}\n\twriter := buf.New()\n\tdefer writer.Release()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(frame.WriteTo(writer))\n\t\twriter.Clear()\n\t}\n}\n"
  },
  {
    "path": "common/mux/mux.go",
    "content": "package mux\n"
  },
  {
    "path": "common/mux/mux_test.go",
    "content": "package mux_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc readAll(reader buf.Reader) (buf.MultiBuffer, error) {\n\tvar mb buf.MultiBuffer\n\tfor {\n\t\tb, err := reader.ReadMultiBuffer()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmb = append(mb, b...)\n\t}\n\treturn mb, nil\n}\n\nfunc TestReaderWriter(t *testing.T) {\n\tpReader, pWriter := pipe.New(pipe.WithSizeLimit(1024))\n\n\tdest := net.TCPDestination(net.DomainAddress(\"example.com\"), 80)\n\twriter := NewWriter(1, dest, pWriter, protocol.TransferTypeStream, [8]byte{}, &session.Inbound{})\n\n\tdest2 := net.TCPDestination(net.LocalHostIP, 443)\n\twriter2 := NewWriter(2, dest2, pWriter, protocol.TransferTypeStream, [8]byte{}, &session.Inbound{})\n\n\tdest3 := net.TCPDestination(net.LocalHostIPv6, 18374)\n\twriter3 := NewWriter(3, dest3, pWriter, protocol.TransferTypeStream, [8]byte{}, &session.Inbound{})\n\n\twritePayload := func(writer *Writer, payload ...byte) error {\n\t\tb := buf.New()\n\t\tb.Write(payload)\n\t\treturn writer.WriteMultiBuffer(buf.MultiBuffer{b})\n\t}\n\n\tcommon.Must(writePayload(writer, 'a', 'b', 'c', 'd'))\n\tcommon.Must(writePayload(writer2))\n\n\tcommon.Must(writePayload(writer, 'e', 'f', 'g', 'h'))\n\tcommon.Must(writePayload(writer3, 'x'))\n\n\twriter.Close()\n\twriter3.Close()\n\n\tcommon.Must(writePayload(writer2, 'y'))\n\twriter2.Close()\n\n\tbytesReader := &buf.BufferedReader{Reader: pReader}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     1,\n\t\t\tSessionStatus: SessionStatusNew,\n\t\t\tTarget:        dest,\n\t\t\tOption:        OptionData,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"metadata: \", r)\n\t\t}\n\n\t\tdata, err := readAll(NewStreamReader(bytesReader))\n\t\tcommon.Must(err)\n\t\tif s := data.String(); s != \"abcd\" {\n\t\t\tt.Error(\"data: \", s)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionStatus: SessionStatusNew,\n\t\t\tSessionID:     2,\n\t\t\tOption:        0,\n\t\t\tTarget:        dest2,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     1,\n\t\t\tSessionStatus: SessionStatusKeep,\n\t\t\tOption:        1,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\n\t\tdata, err := readAll(NewStreamReader(bytesReader))\n\t\tcommon.Must(err)\n\t\tif s := data.String(); s != \"efgh\" {\n\t\t\tt.Error(\"data: \", s)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     3,\n\t\t\tSessionStatus: SessionStatusNew,\n\t\t\tOption:        1,\n\t\t\tTarget:        dest3,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\n\t\tdata, err := readAll(NewStreamReader(bytesReader))\n\t\tcommon.Must(err)\n\t\tif s := data.String(); s != \"x\" {\n\t\t\tt.Error(\"data: \", s)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     1,\n\t\t\tSessionStatus: SessionStatusEnd,\n\t\t\tOption:        0,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     3,\n\t\t\tSessionStatus: SessionStatusEnd,\n\t\t\tOption:        0,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     2,\n\t\t\tSessionStatus: SessionStatusKeep,\n\t\t\tOption:        1,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\n\t\tdata, err := readAll(NewStreamReader(bytesReader))\n\t\tcommon.Must(err)\n\t\tif s := data.String(); s != \"y\" {\n\t\t\tt.Error(\"data: \", s)\n\t\t}\n\t}\n\n\t{\n\t\tvar meta FrameMetadata\n\t\tcommon.Must(meta.Unmarshal(bytesReader, false))\n\t\tif r := cmp.Diff(meta, FrameMetadata{\n\t\t\tSessionID:     2,\n\t\t\tSessionStatus: SessionStatusEnd,\n\t\t\tOption:        0,\n\t\t}); r != \"\" {\n\t\t\tt.Error(\"meta: \", r)\n\t\t}\n\t}\n\n\tpWriter.Close()\n\n\t{\n\t\tvar meta FrameMetadata\n\t\terr := meta.Unmarshal(bytesReader, false)\n\t\tif err == nil {\n\t\t\tt.Error(\"nil error\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/mux/reader.go",
    "content": "package mux\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\n// PacketReader is an io.Reader that reads whole chunk of Mux frames every time.\ntype PacketReader struct {\n\treader io.Reader\n\teof    bool\n\tdest   *net.Destination\n}\n\n// NewPacketReader creates a new PacketReader.\nfunc NewPacketReader(reader io.Reader, dest *net.Destination) *PacketReader {\n\treturn &PacketReader{\n\t\treader: reader,\n\t\teof:    false,\n\t\tdest:   dest,\n\t}\n}\n\n// ReadMultiBuffer implements buf.Reader.\nfunc (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tif r.eof {\n\t\treturn nil, io.EOF\n\t}\n\n\tsize, err := serial.ReadUint16(r.reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif size > buf.Size {\n\t\treturn nil, errors.New(\"packet size too large: \", size)\n\t}\n\n\tb := buf.New()\n\tif _, err := b.ReadFullFrom(r.reader, int32(size)); err != nil {\n\t\tb.Release()\n\t\treturn nil, err\n\t}\n\tr.eof = true\n\tif r.dest != nil && r.dest.Network == net.Network_UDP {\n\t\tb.UDP = r.dest\n\t}\n\treturn buf.MultiBuffer{b}, nil\n}\n\n// NewStreamReader creates a new StreamReader.\nfunc NewStreamReader(reader *buf.BufferedReader) buf.Reader {\n\treturn crypto.NewChunkStreamReaderWithChunkCount(crypto.PlainChunkSizeParser{}, reader, 1)\n}\n"
  },
  {
    "path": "common/mux/server.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\ntype Server struct {\n\tdispatcher routing.Dispatcher\n}\n\n// NewServer creates a new mux.Server.\nfunc NewServer(ctx context.Context) *Server {\n\ts := &Server{}\n\tcore.RequireFeatures(ctx, func(d routing.Dispatcher) {\n\t\ts.dispatcher = d\n\t})\n\treturn s\n}\n\n// Type implements common.HasType.\nfunc (s *Server) Type() interface{} {\n\treturn s.dispatcher.Type()\n}\n\n// Dispatch implements routing.Dispatcher\nfunc (s *Server) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {\n\tif dest.Address != muxCoolAddress {\n\t\treturn s.dispatcher.Dispatch(ctx, dest)\n\t}\n\n\topts := pipe.OptionsFromContext(ctx)\n\tuplinkReader, uplinkWriter := pipe.New(opts...)\n\tdownlinkReader, downlinkWriter := pipe.New(opts...)\n\n\t_, err := NewServerWorker(ctx, s.dispatcher, &transport.Link{\n\t\tReader: uplinkReader,\n\t\tWriter: downlinkWriter,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, nil\n}\n\n// DispatchLink implements routing.Dispatcher\nfunc (s *Server) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error {\n\tif dest.Address != muxCoolAddress {\n\t\treturn s.dispatcher.DispatchLink(ctx, dest, link)\n\t}\n\tworker, err := NewServerWorker(ctx, s.dispatcher, link)\n\tif err != nil {\n\t\treturn err\n\t}\n\tselect {\n\tcase <-ctx.Done():\n\tcase <-worker.done.Wait():\n\t}\n\treturn nil\n}\n\n// Start implements common.Runnable.\nfunc (s *Server) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (s *Server) Close() error {\n\treturn nil\n}\n\ntype ServerWorker struct {\n\tdispatcher     routing.Dispatcher\n\tlink           *transport.Link\n\tsessionManager *SessionManager\n\tdone           *done.Instance\n\ttimer          *time.Ticker\n}\n\nfunc NewServerWorker(ctx context.Context, d routing.Dispatcher, link *transport.Link) (*ServerWorker, error) {\n\tworker := &ServerWorker{\n\t\tdispatcher:     d,\n\t\tlink:           link,\n\t\tsessionManager: NewSessionManager(),\n\t\tdone:           done.New(),\n\t\ttimer:          time.NewTicker(60 * time.Second),\n\t}\n\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\tinbound.CanSpliceCopy = 3\n\t}\n\tgo worker.run(ctx)\n\tgo worker.monitor()\n\treturn worker, nil\n}\n\nfunc handle(ctx context.Context, s *Session, output buf.Writer) {\n\twriter := NewResponseWriter(s.ID, output, s.transferType)\n\tif err := buf.Copy(s.input, writer); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"session \", s.ID, \" ends.\")\n\t\twriter.hasError = true\n\t}\n\n\twriter.Close()\n\ts.Close(false)\n}\n\nfunc (w *ServerWorker) monitor() {\n\tdefer w.timer.Stop()\n\n\tfor {\n\t\tcheckSize := w.sessionManager.Size()\n\t\tcheckCount := w.sessionManager.Count()\n\t\tselect {\n\t\tcase <-w.done.Wait():\n\t\t\tw.sessionManager.Close()\n\t\t\tcommon.Interrupt(w.link.Writer)\n\t\t\tcommon.Interrupt(w.link.Reader)\n\t\t\treturn\n\t\tcase <-w.timer.C:\n\t\t\tif w.sessionManager.CloseIfNoSessionAndIdle(checkSize, checkCount) {\n\t\t\t\tcommon.Must(w.done.Close())\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (w *ServerWorker) ActiveConnections() uint32 {\n\treturn uint32(w.sessionManager.Size())\n}\n\nfunc (w *ServerWorker) Closed() bool {\n\treturn w.done.Done()\n}\n\nfunc (w *ServerWorker) WaitClosed() <-chan struct{} {\n\treturn w.done.Wait()\n}\n\nfunc (w *ServerWorker) Close() error {\n\treturn w.done.Close()\n}\n\nfunc (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif meta.Option.Has(OptionData) {\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\treturn nil\n}\n\nfunc (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tctx = session.SubContextFromMuxInbound(ctx)\n\tif meta.Inbound != nil && meta.Inbound.Source.IsValid() && meta.Inbound.Local.IsValid() {\n\t\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\t\tnewInbound := *inbound\n\t\t\tnewInbound.Source = meta.Inbound.Source\n\t\t\tnewInbound.Local = meta.Inbound.Local\n\t\t\tctx = session.ContextWithInbound(ctx, &newInbound)\n\t\t}\n\t}\n\terrors.LogInfo(ctx, \"received request for \", meta.Target)\n\t{\n\t\tmsg := &log.AccessMessage{\n\t\t\tTo:     meta.Target,\n\t\t\tStatus: log.AccessAccepted,\n\t\t\tReason: \"\",\n\t\t}\n\t\tif inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.IsValid() {\n\t\t\tmsg.From = inbound.Source\n\t\t\tmsg.Email = inbound.User.Email\n\t\t}\n\t\tctx = log.ContextWithAccessMessage(ctx, msg)\n\t}\n\n\tif network := session.AllowedNetworkFromContext(ctx); network != net.Network_Unknown {\n\t\tif meta.Target.Network != network {\n\t\t\treturn errors.New(\"unexpected network \", meta.Target.Network) // it will break the whole Mux connection\n\t\t}\n\t}\n\n\tif meta.GlobalID != [8]byte{} { // MUST ignore empty Global ID\n\t\tmb, err := NewPacketReader(reader, &meta.Target).ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tXUDPManager.Lock()\n\t\tx := XUDPManager.Map[meta.GlobalID]\n\t\tif x == nil {\n\t\t\tx = &XUDP{GlobalID: meta.GlobalID}\n\t\t\tXUDPManager.Map[meta.GlobalID] = x\n\t\t\tXUDPManager.Unlock()\n\t\t} else {\n\t\t\tif x.Status == Initializing { // nearly impossible\n\t\t\t\tXUDPManager.Unlock()\n\t\t\t\terrors.LogWarningInner(ctx, errors.New(\"conflict\"), \"XUDP hit \", meta.GlobalID)\n\t\t\t\t// It's not a good idea to return an err here, so just let client wait.\n\t\t\t\t// Client will receive an End frame after sending a Keep frame.\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tx.Status = Initializing\n\t\t\tXUDPManager.Unlock()\n\t\t\tx.Mux.Close(false) // detach from previous Mux\n\t\t\tb := buf.New()\n\t\t\tb.Write(mb[0].Bytes())\n\t\t\tb.UDP = mb[0].UDP\n\t\t\tif err = x.Mux.output.WriteMultiBuffer(mb); err != nil {\n\t\t\t\tx.Interrupt()\n\t\t\t\tmb = buf.MultiBuffer{b}\n\t\t\t} else {\n\t\t\t\tb.Release()\n\t\t\t\tmb = nil\n\t\t\t}\n\t\t\terrors.LogInfoInner(ctx, err, \"XUDP hit \", meta.GlobalID)\n\t\t}\n\t\tif mb != nil {\n\t\t\tctx = session.ContextWithTimeoutOnly(ctx, true)\n\t\t\t// Actually, it won't return an error in Xray-core's implementations.\n\t\t\tlink, err := w.dispatcher.Dispatch(ctx, meta.Target)\n\t\t\tif err != nil {\n\t\t\t\tXUDPManager.Lock()\n\t\t\t\tdelete(XUDPManager.Map, x.GlobalID)\n\t\t\t\tXUDPManager.Unlock()\n\t\t\t\terr = errors.New(\"XUDP new \", meta.GlobalID).Base(errors.New(\"failed to dispatch request to \", meta.Target).Base(err))\n\t\t\t\treturn err // it will break the whole Mux connection\n\t\t\t}\n\t\t\tlink.Writer.WriteMultiBuffer(mb) // it's meaningless to test a new pipe\n\t\t\tx.Mux = &Session{\n\t\t\t\tinput:  link.Reader,\n\t\t\t\toutput: link.Writer,\n\t\t\t}\n\t\t\terrors.LogInfoInner(ctx, err, \"XUDP new \", meta.GlobalID)\n\t\t}\n\t\tx.Mux = &Session{\n\t\t\tinput:        x.Mux.input,\n\t\t\toutput:       x.Mux.output,\n\t\t\tparent:       w.sessionManager,\n\t\t\tID:           meta.SessionID,\n\t\t\ttransferType: protocol.TransferTypePacket,\n\t\t\tXUDP:         x,\n\t\t}\n\t\tx.Status = Active\n\t\tif !w.sessionManager.Add(x.Mux) {\n\t\t\tx.Mux.Close(false)\n\t\t\treturn errors.New(\"failed to add new session\")\n\t\t}\n\t\tgo handle(ctx, x.Mux, w.link.Writer)\n\t\treturn nil\n\t}\n\n\tlink, err := w.dispatcher.Dispatch(ctx, meta.Target)\n\tif err != nil {\n\t\tif meta.Option.Has(OptionData) {\n\t\t\tbuf.Copy(NewStreamReader(reader), buf.Discard)\n\t\t}\n\t\treturn errors.New(\"failed to dispatch request.\").Base(err)\n\t}\n\ts := &Session{\n\t\tinput:        link.Reader,\n\t\toutput:       link.Writer,\n\t\tparent:       w.sessionManager,\n\t\tID:           meta.SessionID,\n\t\ttransferType: protocol.TransferTypeStream,\n\t}\n\tif meta.Target.Network == net.Network_UDP {\n\t\ts.transferType = protocol.TransferTypePacket\n\t}\n\tif !w.sessionManager.Add(s) {\n\t\ts.Close(false)\n\t\treturn errors.New(\"failed to add new session\")\n\t}\n\tgo handle(ctx, s, w.link.Writer)\n\tif !meta.Option.Has(OptionData) {\n\t\treturn nil\n\t}\n\n\trr := s.NewReader(reader, &meta.Target)\n\terr = buf.Copy(rr, s.output)\n\n\tif err != nil && buf.IsWriteError(err) {\n\t\ts.Close(false)\n\t\treturn buf.Copy(rr, buf.Discard)\n\t}\n\treturn err\n}\n\nfunc (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif !meta.Option.Has(OptionData) {\n\t\treturn nil\n\t}\n\n\ts, found := w.sessionManager.Get(meta.SessionID)\n\tif !found {\n\t\t// Notify remote peer to close this session.\n\t\tclosingWriter := NewResponseWriter(meta.SessionID, w.link.Writer, protocol.TransferTypeStream)\n\t\tclosingWriter.Close()\n\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\n\trr := s.NewReader(reader, &meta.Target)\n\terr := buf.Copy(rr, s.output)\n\n\tif err != nil && buf.IsWriteError(err) {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to write to downstream writer. closing session \", s.ID)\n\t\ts.Close(false)\n\t\treturn buf.Copy(rr, buf.Discard)\n\t}\n\n\treturn err\n}\n\nfunc (w *ServerWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.BufferedReader) error {\n\tif s, found := w.sessionManager.Get(meta.SessionID); found {\n\t\ts.Close(false)\n\t}\n\tif meta.Option.Has(OptionData) {\n\t\treturn buf.Copy(NewStreamReader(reader), buf.Discard)\n\t}\n\treturn nil\n}\n\nfunc (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedReader) error {\n\tvar meta FrameMetadata\n\terr := meta.Unmarshal(reader, session.IsReverseMuxFromContext(ctx))\n\tif err != nil {\n\t\treturn errors.New(\"failed to read metadata\").Base(err)\n\t}\n\n\tswitch meta.SessionStatus {\n\tcase SessionStatusKeepAlive:\n\t\terr = w.handleStatusKeepAlive(&meta, reader)\n\tcase SessionStatusEnd:\n\t\terr = w.handleStatusEnd(&meta, reader)\n\tcase SessionStatusNew:\n\t\terr = w.handleStatusNew(session.ContextWithIsReverseMux(ctx, false), &meta, reader)\n\tcase SessionStatusKeep:\n\t\terr = w.handleStatusKeep(&meta, reader)\n\tdefault:\n\t\tstatus := meta.SessionStatus\n\t\treturn errors.New(\"unknown status: \", status).AtError()\n\t}\n\n\tif err != nil {\n\t\treturn errors.New(\"failed to process data\").Base(err)\n\t}\n\treturn nil\n}\n\nfunc (w *ServerWorker) run(ctx context.Context) {\n\tdefer func() {\n\t\tcommon.Must(w.done.Close())\n\t}()\n\n\treader := &buf.BufferedReader{Reader: w.link.Reader}\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\terr := w.handleFrame(ctx, reader)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Cause(err) != io.EOF {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"unexpected EOF\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/mux/server_test.go",
    "content": "package mux_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc newLinkPair() (*transport.Link, *transport.Link) {\n\topt := pipe.WithoutSizeLimit()\n\tuplinkReader, uplinkWriter := pipe.New(opt)\n\tdownlinkReader, downlinkWriter := pipe.New(opt)\n\n\tuplink := &transport.Link{\n\t\tReader: uplinkReader,\n\t\tWriter: downlinkWriter,\n\t}\n\n\tdownlink := &transport.Link{\n\t\tReader: downlinkReader,\n\t\tWriter: uplinkWriter,\n\t}\n\n\treturn uplink, downlink\n}\n\ntype TestDispatcher struct {\n\tOnDispatch func(ctx context.Context, dest net.Destination) (*transport.Link, error)\n}\n\nfunc (d *TestDispatcher) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {\n\treturn d.OnDispatch(ctx, dest)\n}\n\nfunc (d *TestDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {\n\treturn nil\n}\n\nfunc (d *TestDispatcher) Start() error {\n\treturn nil\n}\n\nfunc (d *TestDispatcher) Close() error {\n\treturn nil\n}\n\nfunc (*TestDispatcher) Type() interface{} {\n\treturn routing.DispatcherType()\n}\n\nfunc TestRegressionOutboundLeak(t *testing.T) {\n\toriginalOutbounds := []*session.Outbound{{}}\n\tserverCtx := session.ContextWithOutbounds(context.Background(), originalOutbounds)\n\n\twebsiteUplink, websiteDownlink := newLinkPair()\n\n\tdispatcher := TestDispatcher{\n\t\tOnDispatch: func(ctx context.Context, dest net.Destination) (*transport.Link, error) {\n\t\t\t// emulate what DefaultRouter.Dispatch does, and mutate something on the context\n\t\t\tob := session.OutboundsFromContext(ctx)[0]\n\t\t\tob.Target = dest\n\t\t\treturn websiteDownlink, nil\n\t\t},\n\t}\n\n\tmuxServerUplink, muxServerDownlink := newLinkPair()\n\t_, err := mux.NewServerWorker(serverCtx, &dispatcher, muxServerUplink)\n\tcommon.Must(err)\n\n\tclient, err := mux.NewClientWorker(*muxServerDownlink, mux.ClientStrategy{})\n\tcommon.Must(err)\n\n\tclientCtx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{\n\t\tTarget: net.TCPDestination(net.DomainAddress(\"www.example.com\"), 80),\n\t}})\n\n\tmuxClientUplink, muxClientDownlink := newLinkPair()\n\n\tok := client.Dispatch(clientCtx, muxClientUplink)\n\tif !ok {\n\t\tt.Error(\"failed to dispatch\")\n\t}\n\n\t{\n\t\tb := buf.FromBytes([]byte(\"hello\"))\n\t\tcommon.Must(muxClientDownlink.Writer.WriteMultiBuffer(buf.MultiBuffer{b}))\n\t}\n\n\tresMb, err := websiteUplink.Reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tres := resMb.String()\n\tif res != \"hello\" {\n\t\tt.Error(\"upload: \", res)\n\t}\n\n\t{\n\t\tb := buf.FromBytes([]byte(\"world\"))\n\t\tcommon.Must(websiteUplink.Writer.WriteMultiBuffer(buf.MultiBuffer{b}))\n\t}\n\n\tresMb, err = muxClientDownlink.Reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tres = resMb.String()\n\tif res != \"world\" {\n\t\tt.Error(\"download: \", res)\n\t}\n\n\toutbounds := session.OutboundsFromContext(serverCtx)\n\tif outbounds[0] != originalOutbounds[0] {\n\t\tt.Error(\"outbound got reassigned: \", outbounds[0])\n\t}\n\n\tif outbounds[0].Target.Address != nil {\n\t\tt.Error(\"outbound target got leaked: \", outbounds[0].Target.String())\n\t}\n}\n"
  },
  {
    "path": "common/mux/session.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\ntype SessionManager struct {\n\tsync.RWMutex\n\tsessions map[uint16]*Session\n\tcount    uint16\n\tclosed   bool\n}\n\nfunc NewSessionManager() *SessionManager {\n\treturn &SessionManager{\n\t\tcount:    0,\n\t\tsessions: make(map[uint16]*Session, 16),\n\t}\n}\n\nfunc (m *SessionManager) Closed() bool {\n\tm.RLock()\n\tdefer m.RUnlock()\n\n\treturn m.closed\n}\n\nfunc (m *SessionManager) Size() int {\n\tm.RLock()\n\tdefer m.RUnlock()\n\n\treturn len(m.sessions)\n}\n\nfunc (m *SessionManager) Count() int {\n\tm.RLock()\n\tdefer m.RUnlock()\n\n\treturn int(m.count)\n}\n\nfunc (m *SessionManager) Allocate(Strategy *ClientStrategy) *Session {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\tMaxConcurrency := int(Strategy.MaxConcurrency)\n\tMaxConnection := uint16(Strategy.MaxConnection)\n\n\tif m.closed || (MaxConcurrency > 0 && len(m.sessions) >= MaxConcurrency) || (MaxConnection > 0 && m.count >= MaxConnection) {\n\t\treturn nil\n\t}\n\n\tm.count++\n\ts := &Session{\n\t\tID:     m.count,\n\t\tparent: m,\n\t\tdone:   done.New(),\n\t}\n\tm.sessions[s.ID] = s\n\treturn s\n}\n\nfunc (m *SessionManager) Add(s *Session) bool {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\tif m.closed {\n\t\treturn false\n\t}\n\n\tm.count++\n\tm.sessions[s.ID] = s\n\treturn true\n}\n\nfunc (m *SessionManager) Remove(locked bool, id uint16) {\n\tif !locked {\n\t\tm.Lock()\n\t\tdefer m.Unlock()\n\t}\n\tlocked = true\n\n\tif m.closed {\n\t\treturn\n\t}\n\n\tdelete(m.sessions, id)\n\n\t/*\n\t\tif len(m.sessions) == 0 {\n\t\t\tm.sessions = make(map[uint16]*Session, 16)\n\t\t}\n\t*/\n}\n\nfunc (m *SessionManager) Get(id uint16) (*Session, bool) {\n\tm.RLock()\n\tdefer m.RUnlock()\n\n\tif m.closed {\n\t\treturn nil, false\n\t}\n\n\ts, found := m.sessions[id]\n\treturn s, found\n}\n\nfunc (m *SessionManager) CloseIfNoSessionAndIdle(checkSize int, checkCount int) bool {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\tif m.closed {\n\t\treturn true\n\t}\n\n\tif len(m.sessions) != 0 || checkSize != 0 || checkCount != int(m.count) {\n\t\treturn false\n\t}\n\n\tm.closed = true\n\n\tm.sessions = nil\n\treturn true\n}\n\nfunc (m *SessionManager) Close() error {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\tif m.closed {\n\t\treturn nil\n\t}\n\n\tm.closed = true\n\n\tfor _, s := range m.sessions {\n\t\ts.Close(true)\n\t}\n\n\tm.sessions = nil\n\treturn nil\n}\n\n// Session represents a client connection in a Mux connection.\ntype Session struct {\n\tinput        buf.Reader\n\toutput       buf.Writer\n\tparent       *SessionManager\n\tID           uint16\n\ttransferType protocol.TransferType\n\tclosed       bool\n\tdone         *done.Instance\n\tXUDP         *XUDP\n}\n\n// Close closes all resources associated with this session.\nfunc (s *Session) Close(locked bool) error {\n\tif !locked {\n\t\ts.parent.Lock()\n\t\tdefer s.parent.Unlock()\n\t}\n\tlocked = true\n\tif s.closed {\n\t\treturn nil\n\t}\n\ts.closed = true\n\tif s.done != nil {\n\t\ts.done.Close()\n\t}\n\tif s.XUDP == nil {\n\t\tcommon.Interrupt(s.input)\n\t\tcommon.Close(s.output)\n\t} else {\n\t\t// Stop existing handle(), then trigger writer.Close().\n\t\t// Note that s.output may be dispatcher.SizeStatWriter.\n\t\ts.input.(*pipe.Reader).ReturnAnError(io.EOF)\n\t\truntime.Gosched()\n\t\t// If the error set by ReturnAnError still exists, clear it.\n\t\ts.input.(*pipe.Reader).Recover()\n\t\tXUDPManager.Lock()\n\t\tif s.XUDP.Status == Active {\n\t\t\ts.XUDP.Expire = time.Now().Add(time.Minute)\n\t\t\ts.XUDP.Status = Expiring\n\t\t\terrors.LogDebug(context.Background(), \"XUDP put \", s.XUDP.GlobalID)\n\t\t}\n\t\tXUDPManager.Unlock()\n\t}\n\ts.parent.Remove(locked, s.ID)\n\treturn nil\n}\n\n// NewReader creates a buf.Reader based on the transfer type of this Session.\nfunc (s *Session) NewReader(reader *buf.BufferedReader, dest *net.Destination) buf.Reader {\n\tif s.transferType == protocol.TransferTypeStream {\n\t\treturn NewStreamReader(reader)\n\t}\n\treturn NewPacketReader(reader, dest)\n}\n\nconst (\n\tInitializing = 0\n\tActive       = 1\n\tExpiring     = 2\n)\n\ntype XUDP struct {\n\tGlobalID [8]byte\n\tStatus   uint64\n\tExpire   time.Time\n\tMux      *Session\n}\n\nfunc (x *XUDP) Interrupt() {\n\tcommon.Interrupt(x.Mux.input)\n\tcommon.Close(x.Mux.output)\n}\n\nvar XUDPManager struct {\n\tsync.Mutex\n\tMap map[[8]byte]*XUDP\n}\n\nfunc init() {\n\tXUDPManager.Map = make(map[[8]byte]*XUDP)\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Minute)\n\t\t\tnow := time.Now()\n\t\t\tXUDPManager.Lock()\n\t\t\tfor id, x := range XUDPManager.Map {\n\t\t\t\tif x.Status == Expiring && now.After(x.Expire) {\n\t\t\t\t\tx.Interrupt()\n\t\t\t\t\tdelete(XUDPManager.Map, id)\n\t\t\t\t\terrors.LogDebug(context.Background(), \"XUDP del \", id)\n\t\t\t\t}\n\t\t\t}\n\t\t\tXUDPManager.Unlock()\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "common/mux/session_test.go",
    "content": "package mux_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/mux\"\n)\n\nfunc TestSessionManagerAdd(t *testing.T) {\n\tm := NewSessionManager()\n\n\ts := m.Allocate(&ClientStrategy{})\n\tif s.ID != 1 {\n\t\tt.Error(\"id: \", s.ID)\n\t}\n\tif m.Size() != 1 {\n\t\tt.Error(\"size: \", m.Size())\n\t}\n\n\ts = m.Allocate(&ClientStrategy{})\n\tif s.ID != 2 {\n\t\tt.Error(\"id: \", s.ID)\n\t}\n\tif m.Size() != 2 {\n\t\tt.Error(\"size: \", m.Size())\n\t}\n\n\ts = &Session{\n\t\tID: 4,\n\t}\n\tm.Add(s)\n\tif s.ID != 4 {\n\t\tt.Error(\"id: \", s.ID)\n\t}\n\tif m.Size() != 3 {\n\t\tt.Error(\"size: \", m.Size())\n\t}\n}\n\nfunc TestSessionManagerClose(t *testing.T) {\n\tm := NewSessionManager()\n\ts := m.Allocate(&ClientStrategy{})\n\n\tif m.CloseIfNoSessionAndIdle(m.Size(), m.Count()) {\n\t\tt.Error(\"able to close\")\n\t}\n\tm.Remove(false, s.ID)\n\tif !m.CloseIfNoSessionAndIdle(m.Size(), m.Count()) {\n\t\tt.Error(\"not able to close\")\n\t}\n}\n"
  },
  {
    "path": "common/mux/writer.go",
    "content": "package mux\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n)\n\ntype Writer struct {\n\tdest         net.Destination\n\twriter       buf.Writer\n\tid           uint16\n\tfollowup     bool\n\thasError     bool\n\ttransferType protocol.TransferType\n\tglobalID     [8]byte\n\tinbound      *session.Inbound\n}\n\nfunc NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType protocol.TransferType, globalID [8]byte, inbound *session.Inbound) *Writer {\n\treturn &Writer{\n\t\tid:           id,\n\t\tdest:         dest,\n\t\twriter:       writer,\n\t\tfollowup:     false,\n\t\ttransferType: transferType,\n\t\tglobalID:     globalID,\n\t\tinbound:      inbound,\n\t}\n}\n\nfunc NewResponseWriter(id uint16, writer buf.Writer, transferType protocol.TransferType) *Writer {\n\treturn &Writer{\n\t\tid:           id,\n\t\twriter:       writer,\n\t\tfollowup:     true,\n\t\ttransferType: transferType,\n\t}\n}\n\nfunc (w *Writer) getNextFrameMeta() FrameMetadata {\n\tmeta := FrameMetadata{\n\t\tSessionID: w.id,\n\t\tTarget:    w.dest,\n\t\tGlobalID:  w.globalID,\n\t\tInbound:   w.inbound,\n\t}\n\n\tif w.followup {\n\t\tmeta.SessionStatus = SessionStatusKeep\n\t} else {\n\t\tw.followup = true\n\t\tmeta.SessionStatus = SessionStatusNew\n\t}\n\n\treturn meta\n}\n\nfunc (w *Writer) writeMetaOnly() error {\n\tmeta := w.getNextFrameMeta()\n\tb := buf.New()\n\tif err := meta.WriteTo(b); err != nil {\n\t\treturn err\n\t}\n\treturn w.writer.WriteMultiBuffer(buf.MultiBuffer{b})\n}\n\nfunc writeMetaWithFrame(writer buf.Writer, meta FrameMetadata, data buf.MultiBuffer) error {\n\tframe := buf.New()\n\tif len(data) == 1 {\n\t\tframe.UDP = data[0].UDP\n\t}\n\tif err := meta.WriteTo(frame); err != nil {\n\t\treturn err\n\t}\n\tif _, err := serial.WriteUint16(frame, uint16(data.Len())); err != nil {\n\t\treturn err\n\t}\n\n\tmb2 := make(buf.MultiBuffer, 0, len(data)+1)\n\tmb2 = append(mb2, frame)\n\tmb2 = append(mb2, data...)\n\treturn writer.WriteMultiBuffer(mb2)\n}\n\nfunc (w *Writer) writeData(mb buf.MultiBuffer) error {\n\tmeta := w.getNextFrameMeta()\n\tmeta.Option.Set(OptionData)\n\n\treturn writeMetaWithFrame(w.writer, meta, mb)\n}\n\n// WriteMultiBuffer implements buf.Writer.\nfunc (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\n\tif mb.IsEmpty() {\n\t\treturn w.writeMetaOnly()\n\t}\n\n\tfor !mb.IsEmpty() {\n\t\tvar chunk buf.MultiBuffer\n\t\tif w.transferType == protocol.TransferTypeStream {\n\t\t\tmb, chunk = buf.SplitSize(mb, 8*1024)\n\t\t} else {\n\t\t\tmb2, b := buf.SplitFirst(mb)\n\t\t\tmb = mb2\n\t\t\tchunk = buf.MultiBuffer{b}\n\t\t}\n\t\tif err := w.writeData(chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (w *Writer) Close() error {\n\tmeta := FrameMetadata{\n\t\tSessionID:     w.id,\n\t\tSessionStatus: SessionStatusEnd,\n\t}\n\tif w.hasError {\n\t\tmeta.Option.Set(OptionError)\n\t}\n\n\tframe := buf.New()\n\tcommon.Must(meta.WriteTo(frame))\n\n\tw.writer.WriteMultiBuffer(buf.MultiBuffer{frame})\n\treturn nil\n}\n"
  },
  {
    "path": "common/net/address.go",
    "content": "package net\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nvar (\n\t// LocalHostIP is a constant value for localhost IP in IPv4.\n\tLocalHostIP = IPAddress([]byte{127, 0, 0, 1})\n\n\t// AnyIP is a constant value for any IP in IPv4.\n\tAnyIP = IPAddress([]byte{0, 0, 0, 0})\n\n\t// LocalHostDomain is a constant value for localhost domain.\n\tLocalHostDomain = DomainAddress(\"localhost\")\n\n\t// LocalHostIPv6 is a constant value for localhost IP in IPv6.\n\tLocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})\n\n\t// AnyIPv6 is a constant value for any IP in IPv6.\n\tAnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})\n)\n\n// AddressFamily is the type of address.\ntype AddressFamily byte\n\nconst (\n\t// AddressFamilyIPv4 represents address as IPv4\n\tAddressFamilyIPv4 = AddressFamily(0)\n\n\t// AddressFamilyIPv6 represents address as IPv6\n\tAddressFamilyIPv6 = AddressFamily(1)\n\n\t// AddressFamilyDomain represents address as Domain\n\tAddressFamilyDomain = AddressFamily(2)\n)\n\n// IsIPv4 returns true if current AddressFamily is IPv4.\nfunc (af AddressFamily) IsIPv4() bool {\n\treturn af == AddressFamilyIPv4\n}\n\n// IsIPv6 returns true if current AddressFamily is IPv6.\nfunc (af AddressFamily) IsIPv6() bool {\n\treturn af == AddressFamilyIPv6\n}\n\n// IsIP returns true if current AddressFamily is IPv6 or IPv4.\nfunc (af AddressFamily) IsIP() bool {\n\treturn af == AddressFamilyIPv4 || af == AddressFamilyIPv6\n}\n\n// IsDomain returns true if current AddressFamily is Domain.\nfunc (af AddressFamily) IsDomain() bool {\n\treturn af == AddressFamilyDomain\n}\n\n// Address represents a network address to be communicated with. It may be an IP address or domain\n// address, not both. This interface doesn't resolve IP address for a given domain.\ntype Address interface {\n\tIP() net.IP     // IP of this Address\n\tDomain() string // Domain of this Address\n\tFamily() AddressFamily\n\n\tString() string // String representation of this Address\n}\n\nfunc isAlphaNum(c byte) bool {\n\treturn (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')\n}\n\n// ParseAddress parses a string into an Address. The return value will be an IPAddress when\n// the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise.\nfunc ParseAddress(addr string) Address {\n\t// Handle IPv6 address in form as \"[2001:4860:0:2001::68]\"\n\tlenAddr := len(addr)\n\tif lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' {\n\t\taddr = addr[1 : lenAddr-1]\n\t\tlenAddr -= 2\n\t}\n\n\tif lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) {\n\t\taddr = strings.TrimSpace(addr)\n\t}\n\n\tip := net.ParseIP(addr)\n\tif ip != nil {\n\t\treturn IPAddress(ip)\n\t}\n\treturn DomainAddress(addr)\n}\n\nvar bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}\n\n// IPAddress creates an Address with given IP.\nfunc IPAddress(ip []byte) Address {\n\tswitch len(ip) {\n\tcase net.IPv4len:\n\t\tvar addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]}\n\t\treturn addr\n\tcase net.IPv6len:\n\t\tif bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff {\n\t\t\treturn IPAddress(ip[12:16])\n\t\t}\n\t\tvar addr ipv6Address = [16]byte{\n\t\t\tip[0], ip[1], ip[2], ip[3],\n\t\t\tip[4], ip[5], ip[6], ip[7],\n\t\t\tip[8], ip[9], ip[10], ip[11],\n\t\t\tip[12], ip[13], ip[14], ip[15],\n\t\t}\n\t\treturn addr\n\tdefault:\n\t\terrors.LogError(context.Background(), \"invalid IP format: \", ip)\n\t\treturn nil\n\t}\n}\n\n// DomainAddress creates an Address with given domain.\n// This is an internal function that forcibly converts a string to domain.\n// It's mainly used in test files and mux.\n// Unless you have a specific reason, use net.ParseAddress instead,\n// as this function does not check whether the input is an IP address.\n// Otherwise, you will get strange results like domain: 1.1.1.1\nfunc DomainAddress(domain string) Address {\n\treturn domainAddress(domain)\n}\n\ntype ipv4Address [4]byte\n\nfunc (a ipv4Address) IP() net.IP {\n\treturn net.IP(a[:])\n}\n\nfunc (ipv4Address) Domain() string {\n\tpanic(\"Calling Domain() on an IPv4Address.\")\n}\n\nfunc (ipv4Address) Family() AddressFamily {\n\treturn AddressFamilyIPv4\n}\n\nfunc (a ipv4Address) String() string {\n\treturn a.IP().String()\n}\n\ntype ipv6Address [16]byte\n\nfunc (a ipv6Address) IP() net.IP {\n\treturn net.IP(a[:])\n}\n\nfunc (ipv6Address) Domain() string {\n\tpanic(\"Calling Domain() on an IPv6Address.\")\n}\n\nfunc (ipv6Address) Family() AddressFamily {\n\treturn AddressFamilyIPv6\n}\n\nfunc (a ipv6Address) String() string {\n\treturn \"[\" + a.IP().String() + \"]\"\n}\n\ntype domainAddress string\n\nfunc (domainAddress) IP() net.IP {\n\tpanic(\"Calling IP() on a DomainAddress.\")\n}\n\nfunc (a domainAddress) Domain() string {\n\treturn string(a)\n}\n\nfunc (domainAddress) Family() AddressFamily {\n\treturn AddressFamilyDomain\n}\n\nfunc (a domainAddress) String() string {\n\treturn a.Domain()\n}\n\n// AsAddress translates IPOrDomain to Address.\nfunc (d *IPOrDomain) AsAddress() Address {\n\tif d == nil {\n\t\treturn nil\n\t}\n\tswitch addr := d.Address.(type) {\n\tcase *IPOrDomain_Ip:\n\t\treturn IPAddress(addr.Ip)\n\tcase *IPOrDomain_Domain:\n\t\treturn DomainAddress(addr.Domain)\n\t}\n\tpanic(\"Common|Net: Invalid address.\")\n}\n\n// NewIPOrDomain translates Address to IPOrDomain\nfunc NewIPOrDomain(addr Address) *IPOrDomain {\n\tswitch addr.Family() {\n\tcase AddressFamilyDomain:\n\t\treturn &IPOrDomain{\n\t\t\tAddress: &IPOrDomain_Domain{\n\t\t\t\tDomain: addr.Domain(),\n\t\t\t},\n\t\t}\n\tcase AddressFamilyIPv4, AddressFamilyIPv6:\n\t\treturn &IPOrDomain{\n\t\t\tAddress: &IPOrDomain_Ip{\n\t\t\t\tIp: addr.IP(),\n\t\t\t},\n\t\t}\n\tdefault:\n\t\tpanic(\"Unknown Address type.\")\n\t}\n}\n"
  },
  {
    "path": "common/net/address.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/net/address.proto\n\npackage net\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Address of a network host. It may be either an IP address or a domain\n// address.\ntype IPOrDomain struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Types that are valid to be assigned to Address:\n\t//\n\t//\t*IPOrDomain_Ip\n\t//\t*IPOrDomain_Domain\n\tAddress       isIPOrDomain_Address `protobuf_oneof:\"address\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IPOrDomain) Reset() {\n\t*x = IPOrDomain{}\n\tmi := &file_common_net_address_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IPOrDomain) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IPOrDomain) ProtoMessage() {}\n\nfunc (x *IPOrDomain) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_address_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IPOrDomain.ProtoReflect.Descriptor instead.\nfunc (*IPOrDomain) Descriptor() ([]byte, []int) {\n\treturn file_common_net_address_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *IPOrDomain) GetAddress() isIPOrDomain_Address {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *IPOrDomain) GetIp() []byte {\n\tif x != nil {\n\t\tif x, ok := x.Address.(*IPOrDomain_Ip); ok {\n\t\t\treturn x.Ip\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (x *IPOrDomain) GetDomain() string {\n\tif x != nil {\n\t\tif x, ok := x.Address.(*IPOrDomain_Domain); ok {\n\t\t\treturn x.Domain\n\t\t}\n\t}\n\treturn \"\"\n}\n\ntype isIPOrDomain_Address interface {\n\tisIPOrDomain_Address()\n}\n\ntype IPOrDomain_Ip struct {\n\t// IP address. Must by either 4 or 16 bytes.\n\tIp []byte `protobuf:\"bytes,1,opt,name=ip,proto3,oneof\"`\n}\n\ntype IPOrDomain_Domain struct {\n\t// Domain address.\n\tDomain string `protobuf:\"bytes,2,opt,name=domain,proto3,oneof\"`\n}\n\nfunc (*IPOrDomain_Ip) isIPOrDomain_Address() {}\n\nfunc (*IPOrDomain_Domain) isIPOrDomain_Address() {}\n\nvar File_common_net_address_proto protoreflect.FileDescriptor\n\nconst file_common_net_address_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x18common/net/address.proto\\x12\\x0fxray.common.net\\\"C\\n\" +\n\t\"\\n\" +\n\t\"IPOrDomain\\x12\\x10\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\fH\\x00R\\x02ip\\x12\\x18\\n\" +\n\t\"\\x06domain\\x18\\x02 \\x01(\\tH\\x00R\\x06domainB\\t\\n\" +\n\t\"\\aaddressBO\\n\" +\n\t\"\\x13com.xray.common.netP\\x01Z$github.com/xtls/xray-core/common/net\\xaa\\x02\\x0fXray.Common.Netb\\x06proto3\"\n\nvar (\n\tfile_common_net_address_proto_rawDescOnce sync.Once\n\tfile_common_net_address_proto_rawDescData []byte\n)\n\nfunc file_common_net_address_proto_rawDescGZIP() []byte {\n\tfile_common_net_address_proto_rawDescOnce.Do(func() {\n\t\tfile_common_net_address_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_net_address_proto_rawDesc), len(file_common_net_address_proto_rawDesc)))\n\t})\n\treturn file_common_net_address_proto_rawDescData\n}\n\nvar file_common_net_address_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_net_address_proto_goTypes = []any{\n\t(*IPOrDomain)(nil), // 0: xray.common.net.IPOrDomain\n}\nvar file_common_net_address_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_common_net_address_proto_init() }\nfunc file_common_net_address_proto_init() {\n\tif File_common_net_address_proto != nil {\n\t\treturn\n\t}\n\tfile_common_net_address_proto_msgTypes[0].OneofWrappers = []any{\n\t\t(*IPOrDomain_Ip)(nil),\n\t\t(*IPOrDomain_Domain)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_net_address_proto_rawDesc), len(file_common_net_address_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_net_address_proto_goTypes,\n\t\tDependencyIndexes: file_common_net_address_proto_depIdxs,\n\t\tMessageInfos:      file_common_net_address_proto_msgTypes,\n\t}.Build()\n\tFile_common_net_address_proto = out.File\n\tfile_common_net_address_proto_goTypes = nil\n\tfile_common_net_address_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/net/address.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.net;\noption csharp_namespace = \"Xray.Common.Net\";\noption go_package = \"github.com/xtls/xray-core/common/net\";\noption java_package = \"com.xray.common.net\";\noption java_multiple_files = true;\n\n// Address of a network host. It may be either an IP address or a domain\n// address.\nmessage IPOrDomain {\n  oneof address {\n    // IP address. Must by either 4 or 16 bytes.\n    bytes ip = 1;\n\n    // Domain address.\n    string domain = 2;\n  }\n}\n"
  },
  {
    "path": "common/net/address_test.go",
    "content": "package net_test\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/common/net\"\n)\n\nfunc TestAddressProperty(t *testing.T) {\n\ttype addrProprty struct {\n\t\tIP     []byte\n\t\tDomain string\n\t\tFamily AddressFamily\n\t\tString string\n\t}\n\n\ttestCases := []struct {\n\t\tInput  Address\n\t\tOutput addrProprty\n\t}{\n\t\t{\n\t\t\tInput: IPAddress([]byte{byte(1), byte(2), byte(3), byte(4)}),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{byte(1), byte(2), byte(3), byte(4)},\n\t\t\t\tFamily: AddressFamilyIPv4,\n\t\t\t\tString: \"1.2.3.4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: IPAddress([]byte{\n\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t}),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP: []byte{\n\t\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t\t},\n\t\t\t\tFamily: AddressFamilyIPv6,\n\t\t\t\tString: \"[102:304:102:304:102:304:102:304]\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: IPAddress([]byte{\n\t\t\t\tbyte(0), byte(0), byte(0), byte(0),\n\t\t\t\tbyte(0), byte(0), byte(0), byte(0),\n\t\t\t\tbyte(0), byte(0), byte(255), byte(255),\n\t\t\t\tbyte(1), byte(2), byte(3), byte(4),\n\t\t\t}),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{byte(1), byte(2), byte(3), byte(4)},\n\t\t\t\tFamily: AddressFamilyIPv4,\n\t\t\t\tString: \"1.2.3.4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: DomainAddress(\"example.com\"),\n\t\t\tOutput: addrProprty{\n\t\t\t\tDomain: \"example.com\",\n\t\t\t\tFamily: AddressFamilyDomain,\n\t\t\t\tString: \"example.com\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: IPAddress(net.IPv4(1, 2, 3, 4)),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{byte(1), byte(2), byte(3), byte(4)},\n\t\t\t\tFamily: AddressFamilyIPv4,\n\t\t\t\tString: \"1.2.3.4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: ParseAddress(\"[2001:4860:0:2001::68]\"),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{0x20, 0x01, 0x48, 0x60, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68},\n\t\t\t\tFamily: AddressFamilyIPv6,\n\t\t\t\tString: \"[2001:4860:0:2001::68]\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: ParseAddress(\"::0\"),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     AnyIPv6.IP(),\n\t\t\t\tFamily: AddressFamilyIPv6,\n\t\t\t\tString: \"[::]\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: ParseAddress(\"[::ffff:123.151.71.143]\"),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{123, 151, 71, 143},\n\t\t\t\tFamily: AddressFamilyIPv4,\n\t\t\t\tString: \"123.151.71.143\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: NewIPOrDomain(ParseAddress(\"example.com\")).AsAddress(),\n\t\t\tOutput: addrProprty{\n\t\t\t\tDomain: \"example.com\",\n\t\t\t\tFamily: AddressFamilyDomain,\n\t\t\t\tString: \"example.com\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: NewIPOrDomain(ParseAddress(\"8.8.8.8\")).AsAddress(),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{8, 8, 8, 8},\n\t\t\t\tFamily: AddressFamilyIPv4,\n\t\t\t\tString: \"8.8.8.8\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: NewIPOrDomain(ParseAddress(\"[2001:4860:0:2001::68]\")).AsAddress(),\n\t\t\tOutput: addrProprty{\n\t\t\t\tIP:     []byte{0x20, 0x01, 0x48, 0x60, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68},\n\t\t\t\tFamily: AddressFamilyIPv6,\n\t\t\t\tString: \"[2001:4860:0:2001::68]\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tactual := addrProprty{\n\t\t\tFamily: testCase.Input.Family(),\n\t\t\tString: testCase.Input.String(),\n\t\t}\n\t\tif testCase.Input.Family().IsIP() {\n\t\t\tactual.IP = testCase.Input.IP()\n\t\t} else {\n\t\t\tactual.Domain = testCase.Input.Domain()\n\t\t}\n\n\t\tif r := cmp.Diff(actual, testCase.Output); r != \"\" {\n\t\t\tt.Error(\"for input: \", testCase.Input, \":\", r)\n\t\t}\n\t}\n}\n\nfunc TestInvalidAddressConvertion(t *testing.T) {\n\tpanics := func(f func()) (ret bool) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tret = true\n\t\t\t}\n\t\t}()\n\t\tf()\n\t\treturn false\n\t}\n\n\ttestCases := []func(){\n\t\tfunc() { ParseAddress(\"8.8.8.8\").Domain() },\n\t\tfunc() { ParseAddress(\"2001:4860:0:2001::68\").Domain() },\n\t\tfunc() { ParseAddress(\"example.com\").IP() },\n\t}\n\tfor idx, testCase := range testCases {\n\t\tif !panics(testCase) {\n\t\t\tt.Error(\"case \", idx, \" failed\")\n\t\t}\n\t}\n}\n\nfunc BenchmarkParseAddressIPv4(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\taddr := ParseAddress(\"8.8.8.8\")\n\t\tif addr.Family() != AddressFamilyIPv4 {\n\t\t\tpanic(\"not ipv4\")\n\t\t}\n\t}\n}\n\nfunc BenchmarkParseAddressIPv6(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\taddr := ParseAddress(\"2001:4860:0:2001::68\")\n\t\tif addr.Family() != AddressFamilyIPv6 {\n\t\t\tpanic(\"not ipv6\")\n\t\t}\n\t}\n}\n\nfunc BenchmarkParseAddressDomain(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\taddr := ParseAddress(\"example.com\")\n\t\tif addr.Family() != AddressFamilyDomain {\n\t\t\tpanic(\"not domain\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/net/cnc/connection.go",
    "content": "package cnc\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n)\n\ntype ConnectionOption func(*Connection)\n\nfunc ConnectionLocalAddr(a net.Addr) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.local = a\n\t}\n}\n\nfunc ConnectionRemoteAddr(a net.Addr) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.remote = a\n\t}\n}\n\nfunc ConnectionInput(writer io.Writer) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.writer = buf.NewWriter(writer)\n\t}\n}\n\nfunc ConnectionInputMulti(writer buf.Writer) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.writer = writer\n\t}\n}\n\nfunc ConnectionOutput(reader io.Reader) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}\n\t}\n}\n\nfunc ConnectionOutputMulti(reader buf.Reader) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.reader = &buf.BufferedReader{Reader: reader}\n\t}\n}\n\nfunc ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.reader = &buf.BufferedReader{\n\t\t\tReader:   reader,\n\t\t\tSplitter: buf.SplitFirstBytes,\n\t\t}\n\t}\n}\n\nfunc ConnectionOnClose(n io.Closer) ConnectionOption {\n\treturn func(c *Connection) {\n\t\tc.onClose = n\n\t}\n}\n\nfunc NewConnection(opts ...ConnectionOption) net.Conn {\n\tc := &Connection{\n\t\tdone: done.New(),\n\t\tlocal: &net.TCPAddr{\n\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\tPort: 0,\n\t\t},\n\t\tremote: &net.TCPAddr{\n\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\tPort: 0,\n\t\t},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(c)\n\t}\n\n\treturn c\n}\n\ntype Connection struct {\n\treader  *buf.BufferedReader\n\twriter  buf.Writer\n\tdone    *done.Instance\n\tonClose io.Closer\n\tlocal   net.Addr\n\tremote  net.Addr\n}\n\nfunc (c *Connection) Read(b []byte) (int, error) {\n\treturn c.reader.Read(b)\n}\n\n// ReadMultiBuffer implements buf.Reader.\nfunc (c *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\treturn c.reader.ReadMultiBuffer()\n}\n\n// Write implements net.Conn.Write().\nfunc (c *Connection) Write(b []byte) (int, error) {\n\tif c.done.Done() {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\tl := len(b)\n\tmb := make(buf.MultiBuffer, 0, l/buf.Size+1)\n\tmb = buf.MergeBytes(mb, b)\n\treturn l, c.writer.WriteMultiBuffer(mb)\n}\n\nfunc (c *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tif c.done.Done() {\n\t\tbuf.ReleaseMulti(mb)\n\t\treturn io.ErrClosedPipe\n\t}\n\n\treturn c.writer.WriteMultiBuffer(mb)\n}\n\n// Close implements net.Conn.Close().\nfunc (c *Connection) Close() error {\n\tcommon.Must(c.done.Close())\n\tcommon.Interrupt(c.reader)\n\tcommon.Close(c.writer)\n\tif c.onClose != nil {\n\t\treturn c.onClose.Close()\n\t}\n\n\treturn nil\n}\n\n// LocalAddr implements net.Conn.LocalAddr().\nfunc (c *Connection) LocalAddr() net.Addr {\n\treturn c.local\n}\n\n// RemoteAddr implements net.Conn.RemoteAddr().\nfunc (c *Connection) RemoteAddr() net.Addr {\n\treturn c.remote\n}\n\n// SetDeadline implements net.Conn.SetDeadline().\nfunc (c *Connection) SetDeadline(t time.Time) error {\n\treturn nil\n}\n\n// SetReadDeadline implements net.Conn.SetReadDeadline().\nfunc (c *Connection) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\n// SetWriteDeadline implements net.Conn.SetWriteDeadline().\nfunc (c *Connection) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n"
  },
  {
    "path": "common/net/destination.go",
    "content": "package net\n\nimport (\n\t\"net\"\n\t\"strings\"\n)\n\n// Destination represents a network destination including address and protocol (tcp / udp).\ntype Destination struct {\n\tAddress Address\n\tPort    Port\n\tNetwork Network\n}\n\n// DestinationFromAddr generates a Destination from a net address.\nfunc DestinationFromAddr(addr net.Addr) Destination {\n\tswitch addr := addr.(type) {\n\tcase *net.TCPAddr:\n\t\treturn TCPDestination(IPAddress(addr.IP), Port(addr.Port))\n\tcase *net.UDPAddr:\n\t\treturn UDPDestination(IPAddress(addr.IP), Port(addr.Port))\n\tcase *net.UnixAddr:\n\t\treturn UnixDestination(DomainAddress(addr.Name))\n\tdefault:\n\t\tpanic(\"Net: Unknown address type.\")\n\t}\n}\n\n// ParseDestination converts a destination from its string presentation.\nfunc ParseDestination(dest string) (Destination, error) {\n\td := Destination{\n\t\tAddress: AnyIP,\n\t\tPort:    Port(0),\n\t}\n\tif strings.HasPrefix(dest, \"tcp:\") {\n\t\td.Network = Network_TCP\n\t\tdest = dest[4:]\n\t} else if strings.HasPrefix(dest, \"udp:\") {\n\t\td.Network = Network_UDP\n\t\tdest = dest[4:]\n\t} else if strings.HasPrefix(dest, \"unix:\") {\n\t\td = UnixDestination(DomainAddress(dest[5:]))\n\t\treturn d, nil\n\t}\n\n\thstr, pstr, err := SplitHostPort(dest)\n\tif err != nil {\n\t\treturn d, err\n\t}\n\tif len(hstr) > 0 {\n\t\td.Address = ParseAddress(hstr)\n\t}\n\tif len(pstr) > 0 {\n\t\tport, err := PortFromString(pstr)\n\t\tif err != nil {\n\t\t\treturn d, err\n\t\t}\n\t\td.Port = port\n\t}\n\treturn d, nil\n}\n\n// TCPDestination creates a TCP destination with given address\nfunc TCPDestination(address Address, port Port) Destination {\n\treturn Destination{\n\t\tNetwork: Network_TCP,\n\t\tAddress: address,\n\t\tPort:    port,\n\t}\n}\n\n// UDPDestination creates a UDP destination with given address\nfunc UDPDestination(address Address, port Port) Destination {\n\treturn Destination{\n\t\tNetwork: Network_UDP,\n\t\tAddress: address,\n\t\tPort:    port,\n\t}\n}\n\n// UnixDestination creates a Unix destination with given address\nfunc UnixDestination(address Address) Destination {\n\treturn Destination{\n\t\tNetwork: Network_UNIX,\n\t\tAddress: address,\n\t}\n}\n\n// NetAddr returns the network address in this Destination in string form.\nfunc (d Destination) NetAddr() string {\n\taddr := \"\"\n\tif d.Network == Network_TCP || d.Network == Network_UDP {\n\t\taddr = d.Address.String() + \":\" + d.Port.String()\n\t} else if d.Network == Network_UNIX {\n\t\taddr = d.Address.String()\n\t}\n\treturn addr\n}\n\n// RawNetAddr converts a net.Addr from its Destination presentation.\nfunc (d Destination) RawNetAddr() net.Addr {\n\tvar addr net.Addr\n\tswitch d.Network {\n\tcase Network_TCP:\n\t\tif d.Address.Family().IsIP() {\n\t\t\taddr = &net.TCPAddr{\n\t\t\t\tIP:   d.Address.IP(),\n\t\t\t\tPort: int(d.Port),\n\t\t\t}\n\t\t}\n\tcase Network_UDP:\n\t\tif d.Address.Family().IsIP() {\n\t\t\taddr = &net.UDPAddr{\n\t\t\t\tIP:   d.Address.IP(),\n\t\t\t\tPort: int(d.Port),\n\t\t\t}\n\t\t}\n\tcase Network_UNIX:\n\t\tif d.Address.Family().IsDomain() {\n\t\t\taddr = &net.UnixAddr{\n\t\t\t\tName: d.Address.String(),\n\t\t\t\tNet:  d.Network.SystemString(),\n\t\t\t}\n\t\t}\n\t}\n\treturn addr\n}\n\n// String returns the strings form of this Destination.\nfunc (d Destination) String() string {\n\tprefix := \"unknown:\"\n\tswitch d.Network {\n\tcase Network_TCP:\n\t\tprefix = \"tcp:\"\n\tcase Network_UDP:\n\t\tprefix = \"udp:\"\n\tcase Network_UNIX:\n\t\tprefix = \"unix:\"\n\t}\n\treturn prefix + d.NetAddr()\n}\n\n// IsValid returns true if this Destination is valid.\nfunc (d Destination) IsValid() bool {\n\treturn d.Network != Network_Unknown\n}\n\n// AsDestination converts current Endpoint into Destination.\nfunc (p *Endpoint) AsDestination() Destination {\n\treturn Destination{\n\t\tNetwork: p.Network,\n\t\tAddress: p.Address.AsAddress(),\n\t\tPort:    Port(p.Port),\n\t}\n}\n"
  },
  {
    "path": "common/net/destination.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/net/destination.proto\n\npackage net\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Endpoint of a network connection.\ntype Endpoint struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNetwork       Network                `protobuf:\"varint,1,opt,name=network,proto3,enum=xray.common.net.Network\" json:\"network,omitempty\"`\n\tAddress       *IPOrDomain            `protobuf:\"bytes,2,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tPort          uint32                 `protobuf:\"varint,3,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Endpoint) Reset() {\n\t*x = Endpoint{}\n\tmi := &file_common_net_destination_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Endpoint) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Endpoint) ProtoMessage() {}\n\nfunc (x *Endpoint) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_destination_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Endpoint.ProtoReflect.Descriptor instead.\nfunc (*Endpoint) Descriptor() ([]byte, []int) {\n\treturn file_common_net_destination_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Endpoint) GetNetwork() Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn Network_Unknown\n}\n\nfunc (x *Endpoint) GetAddress() *IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *Endpoint) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nvar File_common_net_destination_proto protoreflect.FileDescriptor\n\nconst file_common_net_destination_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1ccommon/net/destination.proto\\x12\\x0fxray.common.net\\x1a\\x18common/net/network.proto\\x1a\\x18common/net/address.proto\\\"\\x89\\x01\\n\" +\n\t\"\\bEndpoint\\x122\\n\" +\n\t\"\\anetwork\\x18\\x01 \\x01(\\x0e2\\x18.xray.common.net.NetworkR\\anetwork\\x125\\n\" +\n\t\"\\aaddress\\x18\\x02 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x03 \\x01(\\rR\\x04portBO\\n\" +\n\t\"\\x13com.xray.common.netP\\x01Z$github.com/xtls/xray-core/common/net\\xaa\\x02\\x0fXray.Common.Netb\\x06proto3\"\n\nvar (\n\tfile_common_net_destination_proto_rawDescOnce sync.Once\n\tfile_common_net_destination_proto_rawDescData []byte\n)\n\nfunc file_common_net_destination_proto_rawDescGZIP() []byte {\n\tfile_common_net_destination_proto_rawDescOnce.Do(func() {\n\t\tfile_common_net_destination_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_net_destination_proto_rawDesc), len(file_common_net_destination_proto_rawDesc)))\n\t})\n\treturn file_common_net_destination_proto_rawDescData\n}\n\nvar file_common_net_destination_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_net_destination_proto_goTypes = []any{\n\t(*Endpoint)(nil),   // 0: xray.common.net.Endpoint\n\t(Network)(0),       // 1: xray.common.net.Network\n\t(*IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain\n}\nvar file_common_net_destination_proto_depIdxs = []int32{\n\t1, // 0: xray.common.net.Endpoint.network:type_name -> xray.common.net.Network\n\t2, // 1: xray.common.net.Endpoint.address:type_name -> xray.common.net.IPOrDomain\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_common_net_destination_proto_init() }\nfunc file_common_net_destination_proto_init() {\n\tif File_common_net_destination_proto != nil {\n\t\treturn\n\t}\n\tfile_common_net_network_proto_init()\n\tfile_common_net_address_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_net_destination_proto_rawDesc), len(file_common_net_destination_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_net_destination_proto_goTypes,\n\t\tDependencyIndexes: file_common_net_destination_proto_depIdxs,\n\t\tMessageInfos:      file_common_net_destination_proto_msgTypes,\n\t}.Build()\n\tFile_common_net_destination_proto = out.File\n\tfile_common_net_destination_proto_goTypes = nil\n\tfile_common_net_destination_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/net/destination.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.net;\noption csharp_namespace = \"Xray.Common.Net\";\noption go_package = \"github.com/xtls/xray-core/common/net\";\noption java_package = \"com.xray.common.net\";\noption java_multiple_files = true;\n\nimport \"common/net/network.proto\";\nimport \"common/net/address.proto\";\n\n// Endpoint of a network connection.\nmessage Endpoint {\n  Network network = 1;\n  IPOrDomain address = 2;\n  uint32 port = 3;\n}\n"
  },
  {
    "path": "common/net/destination_test.go",
    "content": "package net_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/common/net\"\n)\n\nfunc TestDestinationProperty(t *testing.T) {\n\ttestCases := []struct {\n\t\tInput     Destination\n\t\tNetwork   Network\n\t\tString    string\n\t\tNetString string\n\t}{\n\t\t{\n\t\t\tInput:     TCPDestination(IPAddress([]byte{1, 2, 3, 4}), 80),\n\t\t\tNetwork:   Network_TCP,\n\t\t\tString:    \"tcp:1.2.3.4:80\",\n\t\t\tNetString: \"1.2.3.4:80\",\n\t\t},\n\t\t{\n\t\t\tInput:     UDPDestination(IPAddress([]byte{0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88}), 53),\n\t\t\tNetwork:   Network_UDP,\n\t\t\tString:    \"udp:[2001:4860:4860::8888]:53\",\n\t\t\tNetString: \"[2001:4860:4860::8888]:53\",\n\t\t},\n\t\t{\n\t\t\tInput:     UnixDestination(DomainAddress(\"/tmp/test.sock\")),\n\t\t\tNetwork:   Network_UNIX,\n\t\t\tString:    \"unix:/tmp/test.sock\",\n\t\t\tNetString: \"/tmp/test.sock\",\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tdest := testCase.Input\n\t\tif r := cmp.Diff(dest.Network, testCase.Network); r != \"\" {\n\t\t\tt.Error(\"unexpected Network in \", dest.String(), \": \", r)\n\t\t}\n\t\tif r := cmp.Diff(dest.String(), testCase.String); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t\tif r := cmp.Diff(dest.NetAddr(), testCase.NetString); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t}\n}\n\nfunc TestDestinationParse(t *testing.T) {\n\tcases := []struct {\n\t\tInput  string\n\t\tOutput Destination\n\t\tError  bool\n\t}{\n\t\t{\n\t\t\tInput:  \"tcp:127.0.0.1:80\",\n\t\t\tOutput: TCPDestination(LocalHostIP, Port(80)),\n\t\t},\n\t\t{\n\t\t\tInput:  \"udp:8.8.8.8:53\",\n\t\t\tOutput: UDPDestination(IPAddress([]byte{8, 8, 8, 8}), Port(53)),\n\t\t},\n\t\t{\n\t\t\tInput:  \"unix:/tmp/test.sock\",\n\t\t\tOutput: UnixDestination(DomainAddress(\"/tmp/test.sock\")),\n\t\t},\n\t\t{\n\t\t\tInput: \"8.8.8.8:53\",\n\t\t\tOutput: Destination{\n\t\t\t\tAddress: IPAddress([]byte{8, 8, 8, 8}),\n\t\t\t\tPort:    Port(53),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: \":53\",\n\t\t\tOutput: Destination{\n\t\t\t\tAddress: AnyIP,\n\t\t\t\tPort:    Port(53),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: \"8.8.8.8\",\n\t\t\tError: true,\n\t\t},\n\t\t{\n\t\t\tInput: \"8.8.8.8:http\",\n\t\t\tError: true,\n\t\t},\n\t\t{\n\t\t\tInput: \"/tmp/test.sock\",\n\t\t\tError: true,\n\t\t},\n\t}\n\n\tfor _, testcase := range cases {\n\t\td, err := ParseDestination(testcase.Input)\n\t\tif !testcase.Error {\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"for test case: \", testcase.Input, \" expected no error, but got \", err)\n\t\t\t}\n\t\t\tif d != testcase.Output {\n\t\t\t\tt.Error(\"for test case: \", testcase.Input, \" expected output: \", testcase.Output.String(), \" but got \", d.String())\n\t\t\t}\n\t\t} else if err == nil {\n\t\t\tt.Error(\"for test case: \", testcase.Input, \" expected error, but got nil\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/net/find_process_linux.go",
    "content": "//go:build linux\n\npackage net\n\nimport (\n\t\"bufio\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nfunc FindProcess(dest Destination) (PID int, Name string, AbsolutePath string, err error) {\n\tisLocal, err := IsLocal(dest.Address.IP())\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"failed to determine if address is local: \", err)\n\t}\n\tif !isLocal {\n\t\treturn 0, \"\", \"\", ErrNotLocal\n\t}\n\tif dest.Network != Network_TCP && dest.Network != Network_UDP {\n\t\tpanic(\"Unsupported network type for process lookup.\")\n\t}\n\t// the core should never has a domain as source(?\n\tif dest.Address.Family() == AddressFamilyDomain {\n\t\tpanic(\"Domain addresses are not supported for process lookup.\")\n\t}\n\tvar procFile string\n\n\tswitch dest.Network {\n\tcase Network_TCP:\n\t\tif dest.Address.Family() == AddressFamilyIPv4 {\n\t\t\tprocFile = \"/proc/net/tcp\"\n\t\t}\n\t\tif dest.Address.Family() == AddressFamilyIPv6 {\n\t\t\tprocFile = \"/proc/net/tcp6\"\n\t\t}\n\tcase Network_UDP:\n\t\tif dest.Address.Family() == AddressFamilyIPv4 {\n\t\t\tprocFile = \"/proc/net/udp\"\n\t\t}\n\t\tif dest.Address.Family() == AddressFamilyIPv6 {\n\t\t\tprocFile = \"/proc/net/udp6\"\n\t\t}\n\tdefault:\n\t\tpanic(\"Unsupported network type for process lookup.\")\n\t}\n\n\ttargetHexAddr, err := formatLittleEndianString(dest.Address, dest.Port)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"failed to format address: \", err)\n\t}\n\n\tinode, err := findInodeInFile(procFile, targetHexAddr)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"could not search in \", procFile).Base(err)\n\t}\n\tif inode == \"\" {\n\t\treturn 0, \"\", \"\", errors.New(\"connection for \", dest.Address, \":\", dest.Port, \" not found in \", procFile)\n\t}\n\n\tpidStr, err := findPidByInode(inode)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"could not find PID for inode \", inode, \": \", err)\n\t}\n\tif pidStr == \"\" {\n\t\treturn 0, \"\", \"\", errors.New(\"no process found for inode \", inode)\n\t}\n\n\tabsPath, err := getAbsPath(pidStr)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"could not get process name for PID \", pidStr, \":\", err)\n\t}\n\n\tnameSplit := strings.Split(absPath, \"/\")\n\tprocName := nameSplit[len(nameSplit)-1]\n\n\tpid, err := strconv.Atoi(pidStr)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"failed to parse PID: \", err)\n\t}\n\n\treturn pid, procName, absPath, nil\n}\n\nfunc formatLittleEndianString(addr Address, port Port) (string, error) {\n\tip := addr.IP()\n\tvar ipBytes []byte\n\tif addr.Family() == AddressFamilyIPv4 {\n\t\tipBytes = ip.To4()\n\t} else {\n\t\tipBytes = ip.To16()\n\t}\n\tif ipBytes == nil {\n\t\treturn \"\", errors.New(\"invalid IP format for \", addr.Family(), \": \", ip)\n\t}\n\n\tfor i, j := 0, len(ipBytes)-1; i < j; i, j = i+1, j-1 {\n\t\tipBytes[i], ipBytes[j] = ipBytes[j], ipBytes[i]\n\t}\n\tportHex := fmt.Sprintf(\"%04X\", uint16(port))\n\tipHex := strings.ToUpper(hex.EncodeToString(ipBytes))\n\treturn fmt.Sprintf(\"%s:%s\", ipHex, portHex), nil\n}\n\nfunc findInodeInFile(filePath, targetHexAddr string) (string, error) {\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer file.Close()\n\n\tscanner := bufio.NewScanner(file)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tfields := strings.Fields(line)\n\n\t\tif len(fields) < 10 {\n\t\t\tcontinue\n\t\t}\n\n\t\tlocalAddress := fields[1]\n\t\tif localAddress == targetHexAddr {\n\t\t\tinode := fields[9]\n\t\t\treturn inode, nil\n\t\t}\n\t}\n\n\treturn \"\", scanner.Err()\n}\n\nfunc findPidByInode(inode string) (string, error) {\n\tprocDir, err := os.ReadDir(\"/proc\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttargetLink := \"socket:[\" + inode + \"]\"\n\n\tfor _, entry := range procDir {\n\t\tif !entry.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tpid := entry.Name()\n\t\tif _, err := strconv.Atoi(pid); err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfdPath := fmt.Sprintf(\"/proc/%s/fd\", pid)\n\t\tfdDir, err := os.ReadDir(fdPath)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, fdEntry := range fdDir {\n\t\t\tlinkPath := fmt.Sprintf(\"%s/%s\", fdPath, fdEntry.Name())\n\t\t\tlinkTarget, err := os.Readlink(linkPath)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif linkTarget == targetLink {\n\t\t\t\treturn pid, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", nil\n}\n\nfunc getAbsPath(pid string) (string, error) {\n\tpath := fmt.Sprintf(\"/proc/%s/exe\", pid)\n\treturn os.Readlink(path)\n}\n"
  },
  {
    "path": "common/net/find_process_others.go",
    "content": "//go:build !windows && !linux\n\npackage net\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nfunc FindProcess(dest Destination) (int, string, string, error) {\n\treturn 0, \"\", \"\", errors.New(\"process lookup is not supported on this platform\")\n}\n"
  },
  {
    "path": "common/net/find_process_windows.go",
    "content": "//go:build windows\n\npackage net\n\nimport (\n\t\"net/netip\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nconst (\n\ttcpTableFunc    = \"GetExtendedTcpTable\"\n\ttcpTablePidConn = 4\n\tudpTableFunc    = \"GetExtendedUdpTable\"\n\tudpTablePid     = 1\n)\n\nvar (\n\tgetExTCPTable uintptr\n\tgetExUDPTable uintptr\n\n\tonce    sync.Once\n\tinitErr error\n)\n\nfunc initWin32API() error {\n\th, err := windows.LoadLibrary(\"iphlpapi.dll\")\n\tif err != nil {\n\t\treturn errors.New(\"LoadLibrary iphlpapi.dll failed\").Base(err)\n\t}\n\n\tgetExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)\n\tif err != nil {\n\t\treturn errors.New(\"GetProcAddress of \", tcpTableFunc, \" failed\").Base(err)\n\t}\n\n\tgetExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)\n\tif err != nil {\n\t\treturn errors.New(\"GetProcAddress of \", udpTableFunc, \" failed\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc FindProcess(dest Destination) (PID int, Name string, AbsolutePath string, err error) {\n\tonce.Do(func() {\n\t\tinitErr = initWin32API()\n\t})\n\tif initErr != nil {\n\t\treturn 0, \"\", \"\", initErr\n\t}\n\tisLocal, err := IsLocal(dest.Address.IP())\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"failed to determine if address is local: \", err)\n\t}\n\tif !isLocal {\n\t\treturn 0, \"\", \"\", ErrNotLocal\n\t}\n\tif dest.Network != Network_TCP && dest.Network != Network_UDP {\n\t\tpanic(\"Unsupported network type for process lookup.\")\n\t}\n\t// the core should never has a domain as source(?\n\tif dest.Address.Family() == AddressFamilyDomain {\n\t\tpanic(\"Domain addresses are not supported for process lookup.\")\n\t}\n\tvar class int\n\tvar fn uintptr\n\tswitch dest.Network {\n\tcase Network_TCP:\n\t\tfn = getExTCPTable\n\t\tclass = tcpTablePidConn\n\tcase Network_UDP:\n\t\tfn = getExUDPTable\n\t\tclass = udpTablePid\n\tdefault:\n\t\tpanic(\"Unsupported network type for process lookup.\")\n\t}\n\tip := dest.Address.IP()\n\tport := int(dest.Port)\n\n\taddr, ok := netip.AddrFromSlice(ip)\n\tif !ok {\n\t\treturn 0, \"\", \"\", errors.New(\"invalid IP address\")\n\t}\n\taddr = addr.Unmap()\n\n\tfamily := windows.AF_INET\n\tif addr.Is6() {\n\t\tfamily = windows.AF_INET6\n\t}\n\n\tbuf, err := getTransportTable(fn, family, class)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", err\n\t}\n\n\ts := newSearcher(dest.Network, dest.Address.Family())\n\n\tpid, err := s.Search(buf, addr, uint16(port))\n\tif err != nil {\n\t\treturn 0, \"\", \"\", err\n\t}\n\tNameWithPath, err := getExecPathFromPID(pid)\n\tNameWithPath = filepath.ToSlash(NameWithPath)\n\n\t// drop .exe and path\n\tnameSplit := strings.Split(NameWithPath, \"/\")\n\tprocName := nameSplit[len(nameSplit)-1]\n\tprocName = strings.TrimSuffix(procName, \".exe\")\n\treturn int(pid), procName, NameWithPath, err\n}\n\ntype searcher struct {\n\titemSize int\n\tport     int\n\tip       int\n\tipSize   int\n\tpid      int\n\ttcpState int\n}\n\nfunc (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {\n\tn := int(readNativeUint32(b[:4]))\n\titemSize := s.itemSize\n\tfor i := range n {\n\t\trow := b[4+itemSize*i : 4+itemSize*(i+1)]\n\n\t\tif s.tcpState >= 0 {\n\t\t\ttcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])\n\t\t\t// MIB_TCP_STATE_ESTAB, only check established connections for TCP\n\t\t\tif tcpState != 5 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.\n\t\t// this field can be illustrated as follows depends on different machine endianess:\n\t\t//     little endian: [ MSB LSB  0   0  ]   interpret as native uint32 is ((LSB<<8)|MSB)\n\t\t//       big  endian: [  0   0  MSB LSB ]   interpret as native uint32 is ((MSB<<8)|LSB)\n\t\t// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32\n\t\tsrcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))\n\t\tif srcPort != port {\n\t\t\tcontinue\n\t\t}\n\n\t\tsrcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])\n\t\tsrcIP = srcIP.Unmap()\n\t\t// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto\n\t\tif ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {\n\t\t\tcontinue\n\t\t}\n\n\t\tpid := readNativeUint32(row[s.pid : s.pid+4])\n\t\treturn pid, nil\n\t}\n\treturn 0, errors.New(\"not found\")\n}\n\nfunc newSearcher(network Network, family AddressFamily) *searcher {\n\tvar itemSize, port, ip, ipSize, pid int\n\ttcpState := -1\n\tswitch network {\n\tcase Network_TCP:\n\t\tif family == AddressFamilyIPv4 {\n\t\t\t// struct MIB_TCPROW_OWNER_PID\n\t\t\titemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0\n\t\t}\n\t\tif family == AddressFamilyIPv6 {\n\t\t\t// struct MIB_TCP6ROW_OWNER_PID\n\t\t\titemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48\n\t\t}\n\tcase Network_UDP:\n\t\tif family == AddressFamilyIPv4 {\n\t\t\t// struct MIB_UDPROW_OWNER_PID\n\t\t\titemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8\n\t\t}\n\t\tif family == AddressFamilyIPv6 {\n\t\t\t// struct MIB_UDP6ROW_OWNER_PID\n\t\t\titemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24\n\t\t}\n\t}\n\n\treturn &searcher{\n\t\titemSize: itemSize,\n\t\tport:     port,\n\t\tip:       ip,\n\t\tipSize:   ipSize,\n\t\tpid:      pid,\n\t\ttcpState: tcpState,\n\t}\n}\n\nfunc getTransportTable(fn uintptr, family int, class int) ([]byte, error) {\n\tfor size, buf := uint32(8), make([]byte, 8); ; {\n\t\tptr := unsafe.Pointer(&buf[0])\n\t\terr, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)\n\n\t\tswitch err {\n\t\tcase 0:\n\t\t\treturn buf, nil\n\t\tcase uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):\n\t\t\tbuf = make([]byte, size)\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"syscall error: \", int(err))\n\t\t}\n\t}\n}\n\nfunc readNativeUint32(b []byte) uint32 {\n\treturn *(*uint32)(unsafe.Pointer(&b[0]))\n}\n\nfunc getExecPathFromPID(pid uint32) (string, error) {\n\t// kernel process starts with a colon in order to distinguish with normal processes\n\tswitch pid {\n\tcase 0:\n\t\t// reserved pid for system idle process\n\t\treturn \":System Idle Process\", nil\n\tcase 4:\n\t\t// reserved pid for windows kernel image\n\t\treturn \":System\", nil\n\t}\n\th, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer windows.CloseHandle(h)\n\n\tbuf := make([]uint16, syscall.MAX_LONG_PATH)\n\tsize := uint32(len(buf))\n\terr = windows.QueryFullProcessImageName(h, 0, &buf[0], &size)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn syscall.UTF16ToString(buf[:size]), nil\n}\n"
  },
  {
    "path": "common/net/net.go",
    "content": "// Package net is a drop-in replacement to Golang's net package, with some more functionalities.\npackage net // import \"github.com/xtls/xray-core/common/net\"\n\nimport (\n\t\"net\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\n// defines the maximum time an idle TCP session can survive in the tunnel, so\n// it should be consistent across HTTP versions and with other transports.\nconst ConnIdleTimeout = 300 * time.Second\n\n// consistent with quic-go\nconst QuicgoH3KeepAlivePeriod = 10 * time.Second\n\n// consistent with chrome\nconst ChromeH2KeepAlivePeriod = 45 * time.Second\n\nvar ErrNotLocal = errors.New(\"the source address is not from local machine.\")\n\ntype localIPCacheEntry struct {\n\taddrs      []net.Addr\n\tlastUpdate time.Time\n}\n\nvar localIPCache = atomic.Pointer[localIPCacheEntry]{}\n\nfunc IsLocal(ip net.IP) (bool, error) {\n\tvar addrs []net.Addr\n\tif entry := localIPCache.Load(); entry == nil || time.Since(entry.lastUpdate) > time.Minute {\n\t\tvar err error\n\t\taddrs, err = net.InterfaceAddrs()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tlocalIPCache.Store(&localIPCacheEntry{\n\t\t\taddrs:      addrs,\n\t\t\tlastUpdate: time.Now(),\n\t\t})\n\t} else {\n\t\taddrs = entry.addrs\n\t}\n\tfor _, addr := range addrs {\n\t\tif ipnet, ok := addr.(*net.IPNet); ok {\n\t\t\tif ipnet.IP.Equal(ip) {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn false, nil\n}\n"
  },
  {
    "path": "common/net/network.go",
    "content": "package net\n\nfunc (n Network) SystemString() string {\n\tswitch n {\n\tcase Network_TCP:\n\t\treturn \"tcp\"\n\tcase Network_UDP:\n\t\treturn \"udp\"\n\tcase Network_UNIX:\n\t\treturn \"unix\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\n// HasNetwork returns true if the network list has a certain network.\nfunc HasNetwork(list []Network, network Network) bool {\n\tfor _, value := range list {\n\t\tif value == network {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "common/net/network.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/net/network.proto\n\npackage net\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Network int32\n\nconst (\n\tNetwork_Unknown Network = 0\n\tNetwork_TCP     Network = 2\n\tNetwork_UDP     Network = 3\n\tNetwork_UNIX    Network = 4\n)\n\n// Enum value maps for Network.\nvar (\n\tNetwork_name = map[int32]string{\n\t\t0: \"Unknown\",\n\t\t2: \"TCP\",\n\t\t3: \"UDP\",\n\t\t4: \"UNIX\",\n\t}\n\tNetwork_value = map[string]int32{\n\t\t\"Unknown\": 0,\n\t\t\"TCP\":     2,\n\t\t\"UDP\":     3,\n\t\t\"UNIX\":    4,\n\t}\n)\n\nfunc (x Network) Enum() *Network {\n\tp := new(Network)\n\t*p = x\n\treturn p\n}\n\nfunc (x Network) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Network) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_common_net_network_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Network) Type() protoreflect.EnumType {\n\treturn &file_common_net_network_proto_enumTypes[0]\n}\n\nfunc (x Network) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Network.Descriptor instead.\nfunc (Network) EnumDescriptor() ([]byte, []int) {\n\treturn file_common_net_network_proto_rawDescGZIP(), []int{0}\n}\n\n// NetworkList is a list of Networks.\ntype NetworkList struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNetwork       []Network              `protobuf:\"varint,1,rep,packed,name=network,proto3,enum=xray.common.net.Network\" json:\"network,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NetworkList) Reset() {\n\t*x = NetworkList{}\n\tmi := &file_common_net_network_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NetworkList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NetworkList) ProtoMessage() {}\n\nfunc (x *NetworkList) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_network_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NetworkList.ProtoReflect.Descriptor instead.\nfunc (*NetworkList) Descriptor() ([]byte, []int) {\n\treturn file_common_net_network_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *NetworkList) GetNetwork() []Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn nil\n}\n\nvar File_common_net_network_proto protoreflect.FileDescriptor\n\nconst file_common_net_network_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x18common/net/network.proto\\x12\\x0fxray.common.net\\\"A\\n\" +\n\t\"\\vNetworkList\\x122\\n\" +\n\t\"\\anetwork\\x18\\x01 \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\anetwork*2\\n\" +\n\t\"\\aNetwork\\x12\\v\\n\" +\n\t\"\\aUnknown\\x10\\x00\\x12\\a\\n\" +\n\t\"\\x03TCP\\x10\\x02\\x12\\a\\n\" +\n\t\"\\x03UDP\\x10\\x03\\x12\\b\\n\" +\n\t\"\\x04UNIX\\x10\\x04BO\\n\" +\n\t\"\\x13com.xray.common.netP\\x01Z$github.com/xtls/xray-core/common/net\\xaa\\x02\\x0fXray.Common.Netb\\x06proto3\"\n\nvar (\n\tfile_common_net_network_proto_rawDescOnce sync.Once\n\tfile_common_net_network_proto_rawDescData []byte\n)\n\nfunc file_common_net_network_proto_rawDescGZIP() []byte {\n\tfile_common_net_network_proto_rawDescOnce.Do(func() {\n\t\tfile_common_net_network_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_net_network_proto_rawDesc), len(file_common_net_network_proto_rawDesc)))\n\t})\n\treturn file_common_net_network_proto_rawDescData\n}\n\nvar file_common_net_network_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_common_net_network_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_net_network_proto_goTypes = []any{\n\t(Network)(0),        // 0: xray.common.net.Network\n\t(*NetworkList)(nil), // 1: xray.common.net.NetworkList\n}\nvar file_common_net_network_proto_depIdxs = []int32{\n\t0, // 0: xray.common.net.NetworkList.network:type_name -> xray.common.net.Network\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_common_net_network_proto_init() }\nfunc file_common_net_network_proto_init() {\n\tif File_common_net_network_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_net_network_proto_rawDesc), len(file_common_net_network_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_net_network_proto_goTypes,\n\t\tDependencyIndexes: file_common_net_network_proto_depIdxs,\n\t\tEnumInfos:         file_common_net_network_proto_enumTypes,\n\t\tMessageInfos:      file_common_net_network_proto_msgTypes,\n\t}.Build()\n\tFile_common_net_network_proto = out.File\n\tfile_common_net_network_proto_goTypes = nil\n\tfile_common_net_network_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/net/network.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.net;\noption csharp_namespace = \"Xray.Common.Net\";\noption go_package = \"github.com/xtls/xray-core/common/net\";\noption java_package = \"com.xray.common.net\";\noption java_multiple_files = true;\n\nenum Network {\n  Unknown = 0;\n\n  TCP = 2;\n  UDP = 3;\n  UNIX = 4;\n}\n\n// NetworkList is a list of Networks.\nmessage NetworkList { repeated Network network = 1; }\n"
  },
  {
    "path": "common/net/port.go",
    "content": "package net\n\nimport (\n\t\"encoding/binary\"\n\t\"strconv\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\n// Port represents a network port in TCP and UDP protocol.\ntype Port uint16\n\n// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order.\n// @unsafe Caller must ensure that the byte array has at least 2 elements.\nfunc PortFromBytes(port []byte) Port {\n\treturn Port(binary.BigEndian.Uint16(port))\n}\n\n// PortFromInt converts an integer to a Port.\n// @error when the integer is not positive or larger then 65535\nfunc PortFromInt(val uint32) (Port, error) {\n\tif val > 65535 {\n\t\treturn Port(0), errors.New(\"invalid port range: \", val)\n\t}\n\treturn Port(val), nil\n}\n\n// PortFromString converts a string to a Port.\n// @error when the string is not an integer or the integral value is a not a valid Port.\nfunc PortFromString(s string) (Port, error) {\n\tval, err := strconv.ParseUint(s, 10, 32)\n\tif err != nil {\n\t\treturn Port(0), errors.New(\"invalid port range: \", s)\n\t}\n\treturn PortFromInt(uint32(val))\n}\n\n// Value return the corresponding uint16 value of a Port.\nfunc (p Port) Value() uint16 {\n\treturn uint16(p)\n}\n\n// String returns the string presentation of a Port.\nfunc (p Port) String() string {\n\treturn strconv.Itoa(int(p))\n}\n\n// FromPort returns the beginning port of this PortRange.\nfunc (p *PortRange) FromPort() Port {\n\treturn Port(p.From)\n}\n\n// ToPort returns the end port of this PortRange.\nfunc (p *PortRange) ToPort() Port {\n\treturn Port(p.To)\n}\n\n// Contains returns true if the given port is within the range of a PortRange.\nfunc (p *PortRange) Contains(port Port) bool {\n\treturn p.FromPort() <= port && port <= p.ToPort()\n}\n\n// SinglePortRange returns a PortRange contains a single port.\nfunc SinglePortRange(p Port) *PortRange {\n\treturn &PortRange{\n\t\tFrom: uint32(p),\n\t\tTo:   uint32(p),\n\t}\n}\n\ntype MemoryPortRange struct {\n\tFrom Port\n\tTo   Port\n}\n\nfunc (r MemoryPortRange) Contains(port Port) bool {\n\treturn r.From <= port && port <= r.To\n}\n\ntype MemoryPortList []MemoryPortRange\n\nfunc PortListFromProto(l *PortList) MemoryPortList {\n\tmpl := make(MemoryPortList, 0, len(l.Range))\n\tfor _, r := range l.Range {\n\t\tmpl = append(mpl, MemoryPortRange{From: Port(r.From), To: Port(r.To)})\n\t}\n\treturn mpl\n}\n\nfunc (l *PortList) Ports() []uint32 {\n\tvar ports []uint32\n\tfor _, r := range l.Range {\n\t\tfor i := uint32(r.From); i <= uint32(r.To); i++ {\n\t\t\tports = append(ports, i)\n\t\t}\n\t}\n\treturn ports\n}\n\nfunc (mpl MemoryPortList) Contains(port Port) bool {\n\tfor _, pr := range mpl {\n\t\tif pr.Contains(port) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "common/net/port.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/net/port.proto\n\npackage net\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// PortRange represents a range of ports.\ntype PortRange struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The port that this range starts from.\n\tFrom uint32 `protobuf:\"varint,1,opt,name=From,proto3\" json:\"From,omitempty\"`\n\t// The port that this range ends with (inclusive).\n\tTo            uint32 `protobuf:\"varint,2,opt,name=To,proto3\" json:\"To,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PortRange) Reset() {\n\t*x = PortRange{}\n\tmi := &file_common_net_port_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PortRange) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PortRange) ProtoMessage() {}\n\nfunc (x *PortRange) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_port_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PortRange.ProtoReflect.Descriptor instead.\nfunc (*PortRange) Descriptor() ([]byte, []int) {\n\treturn file_common_net_port_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *PortRange) GetFrom() uint32 {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn 0\n}\n\nfunc (x *PortRange) GetTo() uint32 {\n\tif x != nil {\n\t\treturn x.To\n\t}\n\treturn 0\n}\n\n// PortList is a list of ports.\ntype PortList struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRange         []*PortRange           `protobuf:\"bytes,1,rep,name=range,proto3\" json:\"range,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PortList) Reset() {\n\t*x = PortList{}\n\tmi := &file_common_net_port_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PortList) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PortList) ProtoMessage() {}\n\nfunc (x *PortList) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_port_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PortList.ProtoReflect.Descriptor instead.\nfunc (*PortList) Descriptor() ([]byte, []int) {\n\treturn file_common_net_port_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *PortList) GetRange() []*PortRange {\n\tif x != nil {\n\t\treturn x.Range\n\t}\n\treturn nil\n}\n\nvar File_common_net_port_proto protoreflect.FileDescriptor\n\nconst file_common_net_port_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x15common/net/port.proto\\x12\\x0fxray.common.net\\\"/\\n\" +\n\t\"\\tPortRange\\x12\\x12\\n\" +\n\t\"\\x04From\\x18\\x01 \\x01(\\rR\\x04From\\x12\\x0e\\n\" +\n\t\"\\x02To\\x18\\x02 \\x01(\\rR\\x02To\\\"<\\n\" +\n\t\"\\bPortList\\x120\\n\" +\n\t\"\\x05range\\x18\\x01 \\x03(\\v2\\x1a.xray.common.net.PortRangeR\\x05rangeBO\\n\" +\n\t\"\\x13com.xray.common.netP\\x01Z$github.com/xtls/xray-core/common/net\\xaa\\x02\\x0fXray.Common.Netb\\x06proto3\"\n\nvar (\n\tfile_common_net_port_proto_rawDescOnce sync.Once\n\tfile_common_net_port_proto_rawDescData []byte\n)\n\nfunc file_common_net_port_proto_rawDescGZIP() []byte {\n\tfile_common_net_port_proto_rawDescOnce.Do(func() {\n\t\tfile_common_net_port_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_net_port_proto_rawDesc), len(file_common_net_port_proto_rawDesc)))\n\t})\n\treturn file_common_net_port_proto_rawDescData\n}\n\nvar file_common_net_port_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_common_net_port_proto_goTypes = []any{\n\t(*PortRange)(nil), // 0: xray.common.net.PortRange\n\t(*PortList)(nil),  // 1: xray.common.net.PortList\n}\nvar file_common_net_port_proto_depIdxs = []int32{\n\t0, // 0: xray.common.net.PortList.range:type_name -> xray.common.net.PortRange\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_common_net_port_proto_init() }\nfunc file_common_net_port_proto_init() {\n\tif File_common_net_port_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_net_port_proto_rawDesc), len(file_common_net_port_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_net_port_proto_goTypes,\n\t\tDependencyIndexes: file_common_net_port_proto_depIdxs,\n\t\tMessageInfos:      file_common_net_port_proto_msgTypes,\n\t}.Build()\n\tFile_common_net_port_proto = out.File\n\tfile_common_net_port_proto_goTypes = nil\n\tfile_common_net_port_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/net/port.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.net;\noption csharp_namespace = \"Xray.Common.Net\";\noption go_package = \"github.com/xtls/xray-core/common/net\";\noption java_package = \"com.xray.common.net\";\noption java_multiple_files = true;\n\n// PortRange represents a range of ports.\nmessage PortRange {\n  // The port that this range starts from.\n  uint32 From = 1;\n  // The port that this range ends with (inclusive).\n  uint32 To = 2;\n}\n\n// PortList is a list of ports.\nmessage PortList {\n  repeated PortRange range = 1;\n}\n"
  },
  {
    "path": "common/net/port_test.go",
    "content": "package net_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/net\"\n)\n\nfunc TestPortRangeContains(t *testing.T) {\n\tportRange := &PortRange{\n\t\tFrom: 53,\n\t\tTo:   53,\n\t}\n\n\tif !portRange.Contains(Port(53)) {\n\t\tt.Error(\"expected port range containing 53, but actually not\")\n\t}\n}\n"
  },
  {
    "path": "common/net/system.go",
    "content": "package net\n\nimport \"net\"\n\n// DialTCP is an alias of net.DialTCP.\nvar (\n\tDialTCP  = net.DialTCP\n\tDialUDP  = net.DialUDP\n\tDialUnix = net.DialUnix\n\tDial     = net.Dial\n)\n\ntype ListenConfig = net.ListenConfig\n\ntype KeepAliveConfig = net.KeepAliveConfig\n\nvar (\n\tListen     = net.Listen\n\tListenTCP  = net.ListenTCP\n\tListenUDP  = net.ListenUDP\n\tListenUnix = net.ListenUnix\n)\n\nvar LookupIP = net.LookupIP\n\nvar FileConn = net.FileConn\n\n// ParseIP is an alias of net.ParseIP\nvar ParseIP = net.ParseIP\n\nvar ParseCIDR = net.ParseCIDR\n\nvar ResolveIPAddr = net.ResolveIPAddr\n\nvar InterfaceByName = net.InterfaceByName\n\nvar SplitHostPort = net.SplitHostPort\n\nvar CIDRMask = net.CIDRMask\n\ntype (\n\tAddr       = net.Addr\n\tConn       = net.Conn\n\tPacketConn = net.PacketConn\n)\n\ntype (\n\tTCPAddr = net.TCPAddr\n\tTCPConn = net.TCPConn\n)\n\ntype (\n\tUDPAddr = net.UDPAddr\n\tUDPConn = net.UDPConn\n)\n\ntype (\n\tUnixAddr = net.UnixAddr\n\tUnixConn = net.UnixConn\n)\n\ntype IPAddr = net.IPAddr\n\n// IP is an alias for net.IP.\ntype (\n\tIP     = net.IP\n\tIPMask = net.IPMask\n\tIPNet  = net.IPNet\n)\n\nconst (\n\tIPv4len = net.IPv4len\n\tIPv6len = net.IPv6len\n)\n\ntype (\n\tError     = net.Error\n\tAddrError = net.AddrError\n)\n\ntype (\n\tDialer       = net.Dialer\n\tListener     = net.Listener\n\tTCPListener  = net.TCPListener\n\tUnixListener = net.UnixListener\n)\n\nvar (\n\tResolveTCPAddr  = net.ResolveTCPAddr\n\tResolveUDPAddr  = net.ResolveUDPAddr\n\tResolveUnixAddr = net.ResolveUnixAddr\n)\n\ntype Resolver = net.Resolver\n\nvar DefaultResolver = net.DefaultResolver\n\nvar JoinHostPort = net.JoinHostPort\n\nvar InterfaceAddrs = net.InterfaceAddrs\n\nvar Interfaces = net.Interfaces\n"
  },
  {
    "path": "common/ocsp/ocsp.go",
    "content": "package ocsp\n\nimport (\n\t\"bytes\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"golang.org/x/crypto/ocsp\"\n)\n\nfunc GetOCSPForFile(path string) ([]byte, error) {\n\treturn filesystem.ReadFile(path)\n}\n\nfunc CheckOCSPFileIsNotExist(path string) bool {\n\t_, err := os.Stat(path)\n\tif err != nil {\n\t\treturn os.IsNotExist(err)\n\t}\n\treturn false\n}\n\nfunc GetOCSPStapling(cert [][]byte, path string) ([]byte, error) {\n\tocspData, err := GetOCSPForFile(path)\n\tif err != nil {\n\t\tocspData, err = GetOCSPForCert(cert)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !CheckOCSPFileIsNotExist(path) {\n\t\t\terr = os.Remove(path)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tnewFile, err := os.Create(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnewFile.Write(ocspData)\n\t\tdefer newFile.Close()\n\t}\n\treturn ocspData, nil\n}\n\nfunc GetOCSPForCert(cert [][]byte) ([]byte, error) {\n\tbundle := new(bytes.Buffer)\n\tfor _, derBytes := range cert {\n\t\terr := pem.Encode(bundle, &pem.Block{Type: \"CERTIFICATE\", Bytes: derBytes})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tpemBundle := bundle.Bytes()\n\n\tcertificates, err := parsePEMBundle(pemBundle)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tissuedCert := certificates[0]\n\tif len(issuedCert.OCSPServer) == 0 {\n\t\treturn nil, errors.New(\"no OCSP server specified in cert\")\n\t}\n\tif len(certificates) == 1 {\n\t\tif len(issuedCert.IssuingCertificateURL) == 0 {\n\t\t\treturn nil, errors.New(\"no issuing certificate URL\")\n\t\t}\n\t\tresp, errC := http.Get(issuedCert.IssuingCertificateURL[0])\n\t\tif errC != nil {\n\t\t\treturn nil, errors.New(\"no issuing certificate URL\")\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tissuerBytes, errC := io.ReadAll(resp.Body)\n\t\tif errC != nil {\n\t\t\treturn nil, errors.New(errC)\n\t\t}\n\n\t\tissuerCert, errC := x509.ParseCertificate(issuerBytes)\n\t\tif errC != nil {\n\t\t\treturn nil, errors.New(errC)\n\t\t}\n\n\t\tcertificates = append(certificates, issuerCert)\n\t}\n\tissuerCert := certificates[1]\n\n\tocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treader := bytes.NewReader(ocspReq)\n\treq, err := http.Post(issuedCert.OCSPServer[0], \"application/ocsp-request\", reader)\n\tif err != nil {\n\t\treturn nil, errors.New(err)\n\t}\n\tdefer req.Body.Close()\n\tocspResBytes, err := io.ReadAll(req.Body)\n\tif err != nil {\n\t\treturn nil, errors.New(err)\n\t}\n\treturn ocspResBytes, nil\n}\n\n// parsePEMBundle parses a certificate bundle from top to bottom and returns\n// a slice of x509 certificates. This function will error if no certificates are found.\nfunc parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {\n\tvar certificates []*x509.Certificate\n\tvar certDERBlock *pem.Block\n\n\tfor {\n\t\tcertDERBlock, bundle = pem.Decode(bundle)\n\t\tif certDERBlock == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif certDERBlock.Type == \"CERTIFICATE\" {\n\t\t\tcert, err := x509.ParseCertificate(certDERBlock.Bytes)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcertificates = append(certificates, cert)\n\t\t}\n\t}\n\n\tif len(certificates) == 0 {\n\t\treturn nil, errors.New(\"no certificates were found while parsing the bundle\")\n\t}\n\n\treturn certificates, nil\n}\n"
  },
  {
    "path": "common/peer/latency.go",
    "content": "package peer\n\nimport (\n\t\"sync\"\n)\n\ntype Latency interface {\n\tValue() uint64\n}\n\ntype HasLatency interface {\n\tConnectionLatency() Latency\n\tHandshakeLatency() Latency\n}\n\ntype AverageLatency struct {\n\taccess sync.Mutex\n\tvalue  uint64\n}\n\nfunc (al *AverageLatency) Update(newValue uint64) {\n\tal.access.Lock()\n\tdefer al.access.Unlock()\n\n\tal.value = (al.value + newValue*2) / 3\n}\n\nfunc (al *AverageLatency) Value() uint64 {\n\treturn al.value\n}\n"
  },
  {
    "path": "common/peer/peer.go",
    "content": "package peer\n"
  },
  {
    "path": "common/platform/filesystem/file.go",
    "content": "package filesystem\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/platform\"\n)\n\ntype FileReaderFunc func(path string) (io.ReadCloser, error)\n\nvar NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) {\n\treturn os.Open(path)\n}\n\nfunc ReadFile(path string) ([]byte, error) {\n\treader, err := NewFileReader(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reader.Close()\n\n\treturn buf.ReadAllToBytes(reader)\n}\n\nfunc ReadAsset(file string) ([]byte, error) {\n\treturn ReadFile(platform.GetAssetLocation(file))\n}\n\nfunc OpenAsset(file string) (io.ReadCloser, error) {\n\treturn NewFileReader(platform.GetAssetLocation(file))\n}\n\nfunc ReadCert(file string) ([]byte, error) {\n\tif filepath.IsAbs(file) {\n\t\treturn ReadFile(file)\n\t}\n\treturn ReadFile(platform.GetCertLocation(file))\n}\n\nfunc CopyFile(dst string, src string) error {\n\tbytes, err := ReadFile(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tf, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\t_, err = f.Write(bytes)\n\treturn err\n}\n"
  },
  {
    "path": "common/platform/others.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage platform\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc LineSeparator() string {\n\treturn \"\\n\"\n}\n\n// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations\nfunc GetAssetLocation(file string) string {\n\tassetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)\n\tdefPath := filepath.Join(assetPath, file)\n\tfor _, p := range []string{\n\t\tdefPath,\n\t\tfilepath.Join(\"/usr/local/share/xray/\", file),\n\t\tfilepath.Join(\"/usr/share/xray/\", file),\n\t\tfilepath.Join(\"/opt/share/xray/\", file),\n\t} {\n\t\tif _, err := os.Stat(p); os.IsNotExist(err) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// asset found\n\t\treturn p\n\t}\n\n\t// asset not found, let the caller throw out the error\n\treturn defPath\n}\n\n// GetCertLocation searches for `file` in the env dir and the executable dir\nfunc GetCertLocation(file string) string {\n\tcertPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)\n\treturn filepath.Join(certPath, file)\n}\n"
  },
  {
    "path": "common/platform/platform.go",
    "content": "package platform // import \"github.com/xtls/xray-core/common/platform\"\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tConfigLocation  = \"xray.location.config\"\n\tConfdirLocation = \"xray.location.confdir\"\n\tAssetLocation   = \"xray.location.asset\"\n\tCertLocation    = \"xray.location.cert\"\n\n\tUseReadV         = \"xray.buf.readv\"\n\tUseFreedomSplice = \"xray.buf.splice\"\n\tUseVmessPadding  = \"xray.vmess.padding\"\n\tUseCone          = \"xray.cone.disabled\"\n\n\tBufferSize           = \"xray.ray.buffer.size\"\n\tBrowserDialerAddress = \"xray.browser.dialer\"\n\tXUDPLog              = \"xray.xudp.show\"\n\tXUDPBaseKey          = \"xray.xudp.basekey\"\n\n\tTunFdKey = \"xray.tun.fd\"\n\n\tMphCachePath = \"xray.mph.cache\"\n)\n\ntype EnvFlag struct {\n\tName    string\n\tAltName string\n}\n\nfunc NewEnvFlag(name string) EnvFlag {\n\treturn EnvFlag{\n\t\tName:    name,\n\t\tAltName: NormalizeEnvName(name),\n\t}\n}\n\nfunc (f EnvFlag) GetValue(defaultValue func() string) string {\n\tif v, found := os.LookupEnv(f.Name); found {\n\t\treturn v\n\t}\n\tif len(f.AltName) > 0 {\n\t\tif v, found := os.LookupEnv(f.AltName); found {\n\t\t\treturn v\n\t\t}\n\t}\n\n\treturn defaultValue()\n}\n\nfunc (f EnvFlag) GetValueAsInt(defaultValue int) int {\n\tuseDefaultValue := false\n\ts := f.GetValue(func() string {\n\t\tuseDefaultValue = true\n\t\treturn \"\"\n\t})\n\tif useDefaultValue {\n\t\treturn defaultValue\n\t}\n\tv, err := strconv.ParseInt(s, 10, 32)\n\tif err != nil {\n\t\treturn defaultValue\n\t}\n\treturn int(v)\n}\n\nfunc NormalizeEnvName(name string) string {\n\treturn strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), \".\", \"_\")\n}\n\nfunc getExecutableDir() string {\n\texec, err := os.Executable()\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn filepath.Dir(exec)\n}\n\nfunc GetConfigurationPath() string {\n\tconfigPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)\n\treturn filepath.Join(configPath, \"config.json\")\n}\n\n// GetConfDirPath reads \"xray.location.confdir\"\nfunc GetConfDirPath() string {\n\tconfigPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return \"\" })\n\treturn configPath\n}\n"
  },
  {
    "path": "common/platform/platform_test.go",
    "content": "package platform_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/platform\"\n)\n\nfunc TestNormalizeEnvName(t *testing.T) {\n\tcases := []struct {\n\t\tinput  string\n\t\toutput string\n\t}{\n\t\t{\n\t\t\tinput:  \"a\",\n\t\t\toutput: \"A\",\n\t\t},\n\t\t{\n\t\t\tinput:  \"a.a\",\n\t\t\toutput: \"A_A\",\n\t\t},\n\t\t{\n\t\t\tinput:  \"A.A.B\",\n\t\t\toutput: \"A_A_B\",\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tif v := NormalizeEnvName(test.input); v != test.output {\n\t\t\tt.Error(\"unexpected output: \", v, \" want \", test.output)\n\t\t}\n\t}\n}\n\nfunc TestEnvFlag(t *testing.T) {\n\tif v := (EnvFlag{\n\t\tName: \"xxxxx.y\",\n\t}.GetValueAsInt(10)); v != 10 {\n\t\tt.Error(\"env value: \", v)\n\t}\n}\n\nfunc TestGetAssetLocation(t *testing.T) {\n\texec, err := os.Executable()\n\tcommon.Must(err)\n\n\tloc := GetAssetLocation(\"t\")\n\tif filepath.Dir(loc) != filepath.Dir(exec) {\n\t\tt.Error(\"asset dir: \", loc, \" not in \", exec)\n\t}\n\n\tos.Setenv(\"xray.location.asset\", \"/xray\")\n\tif runtime.GOOS == \"windows\" {\n\t\tif v := GetAssetLocation(\"t\"); v != \"\\\\xray\\\\t\" {\n\t\t\tt.Error(\"asset loc: \", v)\n\t\t}\n\t} else {\n\t\tif v := GetAssetLocation(\"t\"); v != \"/xray/t\" {\n\t\t\tt.Error(\"asset loc: \", v)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/platform/windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage platform\n\nimport \"path/filepath\"\n\nfunc LineSeparator() string {\n\treturn \"\\r\\n\"\n}\n\n// GetAssetLocation searches for `file` in the env dir and the executable dir\nfunc GetAssetLocation(file string) string {\n\tassetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)\n\treturn filepath.Join(assetPath, file)\n}\n\n// GetCertLocation searches for `file` in the env dir and the executable dir\nfunc GetCertLocation(file string) string {\n\tcertPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)\n\treturn filepath.Join(certPath, file)\n}\n"
  },
  {
    "path": "common/protocol/account.go",
    "content": "package protocol\n\nimport \"google.golang.org/protobuf/proto\"\n\n// Account is a user identity used for authentication.\ntype Account interface {\n\tEquals(Account) bool\n\tToProto() proto.Message\n}\n\n// AsAccount is an object can be converted into account.\ntype AsAccount interface {\n\tAsAccount() (Account, error)\n}\n"
  },
  {
    "path": "common/protocol/address.go",
    "content": "package protocol\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\ntype AddressOption func(*option)\n\nfunc PortThenAddress() AddressOption {\n\treturn func(p *option) {\n\t\tp.portFirst = true\n\t}\n}\n\nfunc AddressFamilyByte(b byte, f net.AddressFamily) AddressOption {\n\tif b >= 16 {\n\t\tpanic(\"address family byte too big\")\n\t}\n\treturn func(p *option) {\n\t\tp.addrTypeMap[b] = f\n\t\tp.addrByteMap[f] = b\n\t}\n}\n\ntype AddressTypeParser func(byte) byte\n\nfunc WithAddressTypeParser(atp AddressTypeParser) AddressOption {\n\treturn func(p *option) {\n\t\tp.typeParser = atp\n\t}\n}\n\ntype AddressSerializer interface {\n\tReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error)\n\n\tWriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error\n}\n\nconst afInvalid = 255\n\ntype option struct {\n\taddrTypeMap [16]net.AddressFamily\n\taddrByteMap [16]byte\n\tportFirst   bool\n\ttypeParser  AddressTypeParser\n}\n\n// NewAddressParser creates a new AddressParser\nfunc NewAddressParser(options ...AddressOption) AddressSerializer {\n\tvar o option\n\tfor i := range o.addrByteMap {\n\t\to.addrByteMap[i] = afInvalid\n\t}\n\tfor i := range o.addrTypeMap {\n\t\to.addrTypeMap[i] = net.AddressFamily(afInvalid)\n\t}\n\tfor _, opt := range options {\n\t\topt(&o)\n\t}\n\n\tap := &addressParser{\n\t\taddrByteMap: o.addrByteMap,\n\t\taddrTypeMap: o.addrTypeMap,\n\t}\n\n\tif o.typeParser != nil {\n\t\tap.typeParser = o.typeParser\n\t}\n\n\tif o.portFirst {\n\t\treturn portFirstAddressParser{ap: ap}\n\t}\n\n\treturn portLastAddressParser{ap: ap}\n}\n\ntype portFirstAddressParser struct {\n\tap *addressParser\n}\n\nfunc (p portFirstAddressParser) ReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error) {\n\tif buffer == nil {\n\t\tbuffer = buf.New()\n\t\tdefer buffer.Release()\n\t}\n\n\tport, err := readPort(buffer, input)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\taddr, err := p.ap.readAddress(buffer, input)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treturn addr, port, nil\n}\n\nfunc (p portFirstAddressParser) WriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error {\n\tif err := writePort(writer, port); err != nil {\n\t\treturn err\n\t}\n\n\treturn p.ap.writeAddress(writer, addr)\n}\n\ntype portLastAddressParser struct {\n\tap *addressParser\n}\n\nfunc (p portLastAddressParser) ReadAddressPort(buffer *buf.Buffer, input io.Reader) (net.Address, net.Port, error) {\n\tif buffer == nil {\n\t\tbuffer = buf.New()\n\t\tdefer buffer.Release()\n\t}\n\n\taddr, err := p.ap.readAddress(buffer, input)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tport, err := readPort(buffer, input)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn addr, port, nil\n}\n\nfunc (p portLastAddressParser) WriteAddressPort(writer io.Writer, addr net.Address, port net.Port) error {\n\tif err := p.ap.writeAddress(writer, addr); err != nil {\n\t\treturn err\n\t}\n\n\treturn writePort(writer, port)\n}\n\nfunc readPort(b *buf.Buffer, reader io.Reader) (net.Port, error) {\n\tif _, err := b.ReadFullFrom(reader, 2); err != nil {\n\t\treturn 0, err\n\t}\n\treturn net.PortFromBytes(b.BytesFrom(-2)), nil\n}\n\nfunc writePort(writer io.Writer, port net.Port) error {\n\treturn common.Error2(serial.WriteUint16(writer, port.Value()))\n}\n\nfunc maybeIPPrefix(b byte) bool {\n\treturn b == '[' || (b >= '0' && b <= '9')\n}\n\nfunc isValidDomain(d string) bool {\n\tfor _, c := range d {\n\t\tif !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '.' || c == '_') {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\ntype addressParser struct {\n\taddrTypeMap [16]net.AddressFamily\n\taddrByteMap [16]byte\n\ttypeParser  AddressTypeParser\n}\n\nfunc (p *addressParser) readAddress(b *buf.Buffer, reader io.Reader) (net.Address, error) {\n\tif _, err := b.ReadFullFrom(reader, 1); err != nil {\n\t\treturn nil, err\n\t}\n\n\taddrType := b.Byte(b.Len() - 1)\n\tif p.typeParser != nil {\n\t\taddrType = p.typeParser(addrType)\n\t}\n\n\tif addrType >= 16 {\n\t\treturn nil, errors.New(\"unknown address type: \", addrType)\n\t}\n\n\taddrFamily := p.addrTypeMap[addrType]\n\tif addrFamily == net.AddressFamily(afInvalid) {\n\t\treturn nil, errors.New(\"unknown address type: \", addrType)\n\t}\n\n\tswitch addrFamily {\n\tcase net.AddressFamilyIPv4:\n\t\tif _, err := b.ReadFullFrom(reader, 4); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn net.IPAddress(b.BytesFrom(-4)), nil\n\tcase net.AddressFamilyIPv6:\n\t\tif _, err := b.ReadFullFrom(reader, 16); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn net.IPAddress(b.BytesFrom(-16)), nil\n\tcase net.AddressFamilyDomain:\n\t\tif _, err := b.ReadFullFrom(reader, 1); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdomainLength := int32(b.Byte(b.Len() - 1))\n\t\tif _, err := b.ReadFullFrom(reader, domainLength); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdomain := string(b.BytesFrom(-domainLength))\n\t\tif maybeIPPrefix(domain[0]) {\n\t\t\taddr := net.ParseAddress(domain)\n\t\t\tif addr.Family().IsIP() {\n\t\t\t\treturn addr, nil\n\t\t\t}\n\t\t}\n\t\tif !isValidDomain(domain) {\n\t\t\treturn nil, errors.New(\"invalid domain name: \", domain)\n\t\t}\n\t\treturn net.DomainAddress(domain), nil\n\tdefault:\n\t\tpanic(\"impossible case\")\n\t}\n}\n\nfunc (p *addressParser) writeAddress(writer io.Writer, address net.Address) error {\n\ttb := p.addrByteMap[address.Family()]\n\tif tb == afInvalid {\n\t\treturn errors.New(\"unknown address family\", address.Family())\n\t}\n\n\tswitch address.Family() {\n\tcase net.AddressFamilyIPv4, net.AddressFamilyIPv6:\n\t\tif _, err := writer.Write([]byte{tb}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := writer.Write(address.IP()); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase net.AddressFamilyDomain:\n\t\tdomain := address.Domain()\n\t\tif isDomainTooLong(domain) {\n\t\t\treturn errors.New(\"Super long domain is not supported: \", domain)\n\t\t}\n\n\t\tif _, err := writer.Write([]byte{tb, byte(len(domain))}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := writer.Write([]byte(domain)); err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\tpanic(\"Unknown family type.\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "common/protocol/address_test.go",
    "content": "package protocol_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t. \"github.com/xtls/xray-core/common/protocol\"\n)\n\nfunc TestAddressReading(t *testing.T) {\n\tdata := []struct {\n\t\tOptions []AddressOption\n\t\tInput   []byte\n\t\tAddress net.Address\n\t\tPort    net.Port\n\t\tError   bool\n\t}{\n\t\t{\n\t\t\tOptions: []AddressOption{},\n\t\t\tInput:   []byte{},\n\t\t\tError:   true,\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{},\n\t\t\tInput:   []byte{0, 0, 0, 0, 0},\n\t\t\tError:   true,\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)},\n\t\t\tInput:   []byte{1, 0, 0, 0, 0, 0, 53},\n\t\t\tAddress: net.IPAddress([]byte{0, 0, 0, 0}),\n\t\t\tPort:    net.Port(53),\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4), PortThenAddress()},\n\t\t\tInput:   []byte{0, 53, 1, 0, 0, 0, 0},\n\t\t\tAddress: net.IPAddress([]byte{0, 0, 0, 0}),\n\t\t\tPort:    net.Port(53),\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)},\n\t\t\tInput:   []byte{1, 0, 0, 0, 0},\n\t\t\tError:   true,\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x04, net.AddressFamilyIPv6)},\n\t\t\tInput:   []byte{4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 80},\n\t\t\tAddress: net.IPAddress([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}),\n\t\t\tPort:    net.Port(80),\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},\n\t\t\tInput:   []byte{3, 11, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 0, 80},\n\t\t\tAddress: net.DomainAddress(\"example.com\"),\n\t\t\tPort:    net.Port(80),\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},\n\t\t\tInput:   []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0},\n\t\t\tError:   true,\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},\n\t\t\tInput:   []byte{3, 7, 56, 46, 56, 46, 56, 46, 56, 0, 80},\n\t\t\tAddress: net.ParseAddress(\"8.8.8.8\"),\n\t\t\tPort:    net.Port(80),\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},\n\t\t\tInput:   []byte{3, 7, 10, 46, 56, 46, 56, 46, 56, 0, 80},\n\t\t\tError:   true,\n\t\t},\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x03, net.AddressFamilyDomain)},\n\t\t\tInput:   append(append([]byte{3, 24}, []byte(\"2a00:1450:4007:816::200e\")...), 0, 80),\n\t\t\tAddress: net.ParseAddress(\"2a00:1450:4007:816::200e\"),\n\t\t\tPort:    net.Port(80),\n\t\t},\n\t}\n\n\tfor _, tc := range data {\n\t\tparser := NewAddressParser(tc.Options...)\n\n\t\tb := buf.New()\n\t\taddr, port, err := parser.ReadAddressPort(b, bytes.NewReader(tc.Input))\n\t\tb.Release()\n\t\tif tc.Error {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Expect error but not: %v\", tc)\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expect no error but: %s %v\", err.Error(), tc)\n\t\t\t}\n\n\t\t\tif addr != tc.Address {\n\t\t\t\tt.Error(\"Got address \", addr.String(), \" want \", tc.Address.String())\n\t\t\t}\n\n\t\t\tif tc.Port != port {\n\t\t\t\tt.Error(\"Got port \", port, \" want \", tc.Port)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestAddressWriting(t *testing.T) {\n\tdata := []struct {\n\t\tOptions []AddressOption\n\t\tAddress net.Address\n\t\tPort    net.Port\n\t\tBytes   []byte\n\t\tError   bool\n\t}{\n\t\t{\n\t\t\tOptions: []AddressOption{AddressFamilyByte(0x01, net.AddressFamilyIPv4)},\n\t\t\tAddress: net.LocalHostIP,\n\t\t\tPort:    net.Port(80),\n\t\t\tBytes:   []byte{1, 127, 0, 0, 1, 0, 80},\n\t\t},\n\t}\n\n\tfor _, tc := range data {\n\t\tparser := NewAddressParser(tc.Options...)\n\n\t\tb := buf.New()\n\t\terr := parser.WriteAddressPort(b, tc.Address, tc.Port)\n\t\tif tc.Error {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"Expect error but nil\")\n\t\t\t}\n\t\t} else {\n\t\t\tcommon.Must(err)\n\t\t\tif diff := cmp.Diff(tc.Bytes, b.Bytes()); diff != \"\" {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc BenchmarkAddressReadingIPv4(b *testing.B) {\n\tparser := NewAddressParser(AddressFamilyByte(0x01, net.AddressFamilyIPv4))\n\tcache := buf.New()\n\tdefer cache.Release()\n\n\tpayload := buf.New()\n\tdefer payload.Release()\n\n\traw := []byte{1, 0, 0, 0, 0, 0, 53}\n\tpayload.Write(raw)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _, err := parser.ReadAddressPort(cache, payload)\n\t\tcommon.Must(err)\n\t\tcache.Clear()\n\t\tpayload.Clear()\n\t\tpayload.Extend(int32(len(raw)))\n\t}\n}\n\nfunc BenchmarkAddressReadingIPv6(b *testing.B) {\n\tparser := NewAddressParser(AddressFamilyByte(0x04, net.AddressFamilyIPv6))\n\tcache := buf.New()\n\tdefer cache.Release()\n\n\tpayload := buf.New()\n\tdefer payload.Release()\n\n\traw := []byte{4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 0, 80}\n\tpayload.Write(raw)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _, err := parser.ReadAddressPort(cache, payload)\n\t\tcommon.Must(err)\n\t\tcache.Clear()\n\t\tpayload.Clear()\n\t\tpayload.Extend(int32(len(raw)))\n\t}\n}\n\nfunc BenchmarkAddressReadingDomain(b *testing.B) {\n\tparser := NewAddressParser(AddressFamilyByte(0x03, net.AddressFamilyDomain))\n\tcache := buf.New()\n\tdefer cache.Release()\n\n\tpayload := buf.New()\n\tdefer payload.Release()\n\n\traw := []byte{3, 9, 118, 50, 114, 97, 121, 46, 99, 111, 109, 0, 80}\n\tpayload.Write(raw)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _, err := parser.ReadAddressPort(cache, payload)\n\t\tcommon.Must(err)\n\t\tcache.Clear()\n\t\tpayload.Clear()\n\t\tpayload.Extend(int32(len(raw)))\n\t}\n}\n\nfunc BenchmarkAddressWritingIPv4(b *testing.B) {\n\tparser := NewAddressParser(AddressFamilyByte(0x01, net.AddressFamilyIPv4))\n\twriter := buf.New()\n\tdefer writer.Release()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(parser.WriteAddressPort(writer, net.LocalHostIP, net.Port(80)))\n\t\twriter.Clear()\n\t}\n}\n\nfunc BenchmarkAddressWritingIPv6(b *testing.B) {\n\tparser := NewAddressParser(AddressFamilyByte(0x04, net.AddressFamilyIPv6))\n\twriter := buf.New()\n\tdefer writer.Release()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(parser.WriteAddressPort(writer, net.LocalHostIPv6, net.Port(80)))\n\t\twriter.Clear()\n\t}\n}\n\nfunc BenchmarkAddressWritingDomain(b *testing.B) {\n\tparser := NewAddressParser(AddressFamilyByte(0x02, net.AddressFamilyDomain))\n\twriter := buf.New()\n\tdefer writer.Release()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(parser.WriteAddressPort(writer, net.DomainAddress(\"www.example.com\"), net.Port(80)))\n\t\twriter.Clear()\n\t}\n}\n"
  },
  {
    "path": "common/protocol/bittorrent/bittorrent.go",
    "content": "package bittorrent\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\ntype SniffHeader struct{}\n\nfunc (h *SniffHeader) Protocol() string {\n\treturn \"bittorrent\"\n}\n\nfunc (h *SniffHeader) Domain() string {\n\treturn \"\"\n}\n\nvar errNotBittorrent = errors.New(\"not bittorrent header\")\n\nfunc SniffBittorrent(b []byte) (*SniffHeader, error) {\n\tif len(b) < 20 {\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\tif b[0] == 19 && string(b[1:20]) == \"BitTorrent protocol\" {\n\t\treturn &SniffHeader{}, nil\n\t}\n\n\treturn nil, errNotBittorrent\n}\n\nfunc SniffUTP(b []byte) (*SniffHeader, error) {\n\tif len(b) < 20 {\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\tbuffer := buf.FromBytes(b)\n\n\tvar typeAndVersion uint8\n\n\tif binary.Read(buffer, binary.BigEndian, &typeAndVersion) != nil {\n\t\treturn nil, common.ErrNoClue\n\t} else if b[0]>>4&0xF > 4 || b[0]&0xF != 1 {\n\t\treturn nil, errNotBittorrent\n\t}\n\n\tvar extension uint8\n\n\tif binary.Read(buffer, binary.BigEndian, &extension) != nil {\n\t\treturn nil, common.ErrNoClue\n\t} else if extension != 0 && extension != 1 {\n\t\treturn nil, errNotBittorrent\n\t}\n\n\tfor extension != 0 {\n\t\tif extension != 1 {\n\t\t\treturn nil, errNotBittorrent\n\t\t}\n\t\tif binary.Read(buffer, binary.BigEndian, &extension) != nil {\n\t\t\treturn nil, common.ErrNoClue\n\t\t}\n\n\t\tvar length uint8\n\t\tif err := binary.Read(buffer, binary.BigEndian, &length); err != nil {\n\t\t\treturn nil, common.ErrNoClue\n\t\t}\n\t\tif common.Error2(buffer.ReadBytes(int32(length))) != nil {\n\t\t\treturn nil, common.ErrNoClue\n\t\t}\n\t}\n\n\tif common.Error2(buffer.ReadBytes(2)) != nil {\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\tvar timestamp uint32\n\tif err := binary.Read(buffer, binary.BigEndian, &timestamp); err != nil {\n\t\treturn nil, common.ErrNoClue\n\t}\n\tif math.Abs(float64(time.Now().UnixMicro()-int64(timestamp))) > float64(24*time.Hour) {\n\t\treturn nil, errNotBittorrent\n\t}\n\n\treturn &SniffHeader{}, nil\n}\n"
  },
  {
    "path": "common/protocol/context.go",
    "content": "package protocol\n\nimport (\n\t\"context\"\n)\n\ntype key int\n\nconst (\n\trequestKey key = iota\n)\n\nfunc ContextWithRequestHeader(ctx context.Context, request *RequestHeader) context.Context {\n\treturn context.WithValue(ctx, requestKey, request)\n}\n\nfunc RequestHeaderFromContext(ctx context.Context) *RequestHeader {\n\trequest := ctx.Value(requestKey)\n\tif request == nil {\n\t\treturn nil\n\t}\n\treturn request.(*RequestHeader)\n}\n"
  },
  {
    "path": "common/protocol/dns/io.go",
    "content": "package dns\n\nimport (\n\t\"encoding/binary\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"golang.org/x/net/dns/dnsmessage\"\n)\n\nfunc PackMessage(msg *dnsmessage.Message) (*buf.Buffer, error) {\n\tbuffer := buf.New()\n\trawBytes := buffer.Extend(buf.Size)\n\tpacked, err := msg.AppendPack(rawBytes[:0])\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, err\n\t}\n\tbuffer.Resize(0, int32(len(packed)))\n\treturn buffer, nil\n}\n\ntype MessageReader interface {\n\tReadMessage() (*buf.Buffer, error)\n}\n\ntype UDPReader struct {\n\tbuf.Reader\n\n\taccess sync.Mutex\n\tcache  buf.MultiBuffer\n}\n\nfunc (r *UDPReader) readCache() *buf.Buffer {\n\tr.access.Lock()\n\tdefer r.access.Unlock()\n\n\tmb, b := buf.SplitFirst(r.cache)\n\tr.cache = mb\n\treturn b\n}\n\nfunc (r *UDPReader) refill() error {\n\tmb, err := r.Reader.ReadMultiBuffer()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.access.Lock()\n\tr.cache = mb\n\tr.access.Unlock()\n\treturn nil\n}\n\n// ReadMessage implements MessageReader.\nfunc (r *UDPReader) ReadMessage() (*buf.Buffer, error) {\n\tfor {\n\t\tb := r.readCache()\n\t\tif b != nil {\n\t\t\treturn b, nil\n\t\t}\n\t\tif err := r.refill(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n}\n\n// Close implements common.Closable.\nfunc (r *UDPReader) Close() error {\n\tdefer func() {\n\t\tr.access.Lock()\n\t\tbuf.ReleaseMulti(r.cache)\n\t\tr.cache = nil\n\t\tr.access.Unlock()\n\t}()\n\n\treturn common.Close(r.Reader)\n}\n\ntype TCPReader struct {\n\treader *buf.BufferedReader\n}\n\nfunc NewTCPReader(reader buf.Reader) *TCPReader {\n\treturn &TCPReader{\n\t\treader: &buf.BufferedReader{\n\t\t\tReader: reader,\n\t\t},\n\t}\n}\n\nfunc (r *TCPReader) ReadMessage() (*buf.Buffer, error) {\n\tsize, err := serial.ReadUint16(r.reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif size > buf.Size {\n\t\treturn nil, errors.New(\"message size too large: \", size)\n\t}\n\tb := buf.New()\n\tif _, err := b.ReadFullFrom(r.reader, int32(size)); err != nil {\n\t\treturn nil, err\n\t}\n\treturn b, nil\n}\n\nfunc (r *TCPReader) Interrupt() {\n\tcommon.Interrupt(r.reader)\n}\n\nfunc (r *TCPReader) Close() error {\n\treturn common.Close(r.reader)\n}\n\ntype MessageWriter interface {\n\tWriteMessage(msg *buf.Buffer) error\n}\n\ntype UDPWriter struct {\n\tbuf.Writer\n}\n\nfunc (w *UDPWriter) WriteMessage(b *buf.Buffer) error {\n\treturn w.WriteMultiBuffer(buf.MultiBuffer{b})\n}\n\ntype TCPWriter struct {\n\tbuf.Writer\n}\n\nfunc (w *TCPWriter) WriteMessage(b *buf.Buffer) error {\n\tif b.IsEmpty() {\n\t\treturn nil\n\t}\n\n\tmb := make(buf.MultiBuffer, 0, 2)\n\n\tsize := buf.New()\n\tbinary.BigEndian.PutUint16(size.Extend(2), uint16(b.Len()))\n\tmb = append(mb, size, b)\n\treturn w.WriteMultiBuffer(mb)\n}\n"
  },
  {
    "path": "common/protocol/headers.go",
    "content": "package protocol\n\nimport (\n\t\"runtime\"\n\n\t\"github.com/xtls/xray-core/common/bitmask\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"golang.org/x/sys/cpu\"\n)\n\n// RequestCommand is a custom command in a proxy request.\ntype RequestCommand byte\n\nconst (\n\tRequestCommandTCP = RequestCommand(0x01)\n\tRequestCommandUDP = RequestCommand(0x02)\n\tRequestCommandMux = RequestCommand(0x03)\n\tRequestCommandRvs = RequestCommand(0x04)\n)\n\nfunc (c RequestCommand) TransferType() TransferType {\n\tswitch c {\n\tcase RequestCommandTCP, RequestCommandMux, RequestCommandRvs:\n\t\treturn TransferTypeStream\n\tcase RequestCommandUDP:\n\t\treturn TransferTypePacket\n\tdefault:\n\t\treturn TransferTypeStream\n\t}\n}\n\nconst (\n\t// [DEPRECATED 2023-06] RequestOptionChunkStream indicates request payload is chunked. Each chunk consists of length, authentication and payload.\n\tRequestOptionChunkStream bitmask.Byte = 0x01\n\n\t// 0x02 legacy setting\n\n\tRequestOptionChunkMasking bitmask.Byte = 0x04\n\n\tRequestOptionGlobalPadding bitmask.Byte = 0x08\n\n\tRequestOptionAuthenticatedLength bitmask.Byte = 0x10\n)\n\ntype RequestHeader struct {\n\tVersion  byte\n\tCommand  RequestCommand\n\tOption   bitmask.Byte\n\tSecurity SecurityType\n\tPort     net.Port\n\tAddress  net.Address\n\tUser     *MemoryUser\n}\n\nfunc (h *RequestHeader) Destination() net.Destination {\n\tif h.Command == RequestCommandUDP {\n\t\treturn net.UDPDestination(h.Address, h.Port)\n\t}\n\treturn net.TCPDestination(h.Address, h.Port)\n}\n\nconst (\n\tResponseOptionConnectionReuse bitmask.Byte = 0x01\n)\n\ntype ResponseCommand interface{}\n\ntype ResponseHeader struct {\n\tOption  bitmask.Byte\n\tCommand ResponseCommand\n}\n\nvar (\n\t// Keep in sync with crypto/tls/cipher_suites.go.\n\thasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3\n\thasGCMAsmARM64 = (cpu.ARM64.HasAES && cpu.ARM64.HasPMULL) || (runtime.GOOS == \"darwin\" && runtime.GOARCH == \"arm64\")\n\thasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH\n\thasGCMAsmPPC64 = runtime.GOARCH == \"ppc64\" || runtime.GOARCH == \"ppc64le\"\n\n\tHasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64\n)\n\nfunc (sc *SecurityConfig) GetSecurityType() SecurityType {\n\tif sc == nil || sc.Type == SecurityType_AUTO {\n\t\tif HasAESGCMHardwareSupport {\n\t\t\treturn SecurityType_AES128_GCM\n\t\t}\n\t\treturn SecurityType_CHACHA20_POLY1305\n\t}\n\treturn sc.Type\n}\n\nfunc isDomainTooLong(domain string) bool {\n\treturn len(domain) > 256\n}\n"
  },
  {
    "path": "common/protocol/headers.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/protocol/headers.proto\n\npackage protocol\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype SecurityType int32\n\nconst (\n\tSecurityType_UNKNOWN           SecurityType = 0\n\tSecurityType_AUTO              SecurityType = 2\n\tSecurityType_AES128_GCM        SecurityType = 3\n\tSecurityType_CHACHA20_POLY1305 SecurityType = 4\n\tSecurityType_NONE              SecurityType = 5 // [DEPRECATED 2023-06]\n\tSecurityType_ZERO              SecurityType = 6\n)\n\n// Enum value maps for SecurityType.\nvar (\n\tSecurityType_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t2: \"AUTO\",\n\t\t3: \"AES128_GCM\",\n\t\t4: \"CHACHA20_POLY1305\",\n\t\t5: \"NONE\",\n\t\t6: \"ZERO\",\n\t}\n\tSecurityType_value = map[string]int32{\n\t\t\"UNKNOWN\":           0,\n\t\t\"AUTO\":              2,\n\t\t\"AES128_GCM\":        3,\n\t\t\"CHACHA20_POLY1305\": 4,\n\t\t\"NONE\":              5,\n\t\t\"ZERO\":              6,\n\t}\n)\n\nfunc (x SecurityType) Enum() *SecurityType {\n\tp := new(SecurityType)\n\t*p = x\n\treturn p\n}\n\nfunc (x SecurityType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (SecurityType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_common_protocol_headers_proto_enumTypes[0].Descriptor()\n}\n\nfunc (SecurityType) Type() protoreflect.EnumType {\n\treturn &file_common_protocol_headers_proto_enumTypes[0]\n}\n\nfunc (x SecurityType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use SecurityType.Descriptor instead.\nfunc (SecurityType) EnumDescriptor() ([]byte, []int) {\n\treturn file_common_protocol_headers_proto_rawDescGZIP(), []int{0}\n}\n\ntype SecurityConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          SecurityType           `protobuf:\"varint,1,opt,name=type,proto3,enum=xray.common.protocol.SecurityType\" json:\"type,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SecurityConfig) Reset() {\n\t*x = SecurityConfig{}\n\tmi := &file_common_protocol_headers_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SecurityConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SecurityConfig) ProtoMessage() {}\n\nfunc (x *SecurityConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_protocol_headers_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SecurityConfig.ProtoReflect.Descriptor instead.\nfunc (*SecurityConfig) Descriptor() ([]byte, []int) {\n\treturn file_common_protocol_headers_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *SecurityConfig) GetType() SecurityType {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn SecurityType_UNKNOWN\n}\n\nvar File_common_protocol_headers_proto protoreflect.FileDescriptor\n\nconst file_common_protocol_headers_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1dcommon/protocol/headers.proto\\x12\\x14xray.common.protocol\\\"H\\n\" +\n\t\"\\x0eSecurityConfig\\x126\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2\\\".xray.common.protocol.SecurityTypeR\\x04type*`\\n\" +\n\t\"\\fSecurityType\\x12\\v\\n\" +\n\t\"\\aUNKNOWN\\x10\\x00\\x12\\b\\n\" +\n\t\"\\x04AUTO\\x10\\x02\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"AES128_GCM\\x10\\x03\\x12\\x15\\n\" +\n\t\"\\x11CHACHA20_POLY1305\\x10\\x04\\x12\\b\\n\" +\n\t\"\\x04NONE\\x10\\x05\\x12\\b\\n\" +\n\t\"\\x04ZERO\\x10\\x06B^\\n\" +\n\t\"\\x18com.xray.common.protocolP\\x01Z)github.com/xtls/xray-core/common/protocol\\xaa\\x02\\x14Xray.Common.Protocolb\\x06proto3\"\n\nvar (\n\tfile_common_protocol_headers_proto_rawDescOnce sync.Once\n\tfile_common_protocol_headers_proto_rawDescData []byte\n)\n\nfunc file_common_protocol_headers_proto_rawDescGZIP() []byte {\n\tfile_common_protocol_headers_proto_rawDescOnce.Do(func() {\n\t\tfile_common_protocol_headers_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_protocol_headers_proto_rawDesc), len(file_common_protocol_headers_proto_rawDesc)))\n\t})\n\treturn file_common_protocol_headers_proto_rawDescData\n}\n\nvar file_common_protocol_headers_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_common_protocol_headers_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_protocol_headers_proto_goTypes = []any{\n\t(SecurityType)(0),      // 0: xray.common.protocol.SecurityType\n\t(*SecurityConfig)(nil), // 1: xray.common.protocol.SecurityConfig\n}\nvar file_common_protocol_headers_proto_depIdxs = []int32{\n\t0, // 0: xray.common.protocol.SecurityConfig.type:type_name -> xray.common.protocol.SecurityType\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_common_protocol_headers_proto_init() }\nfunc file_common_protocol_headers_proto_init() {\n\tif File_common_protocol_headers_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_protocol_headers_proto_rawDesc), len(file_common_protocol_headers_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_protocol_headers_proto_goTypes,\n\t\tDependencyIndexes: file_common_protocol_headers_proto_depIdxs,\n\t\tEnumInfos:         file_common_protocol_headers_proto_enumTypes,\n\t\tMessageInfos:      file_common_protocol_headers_proto_msgTypes,\n\t}.Build()\n\tFile_common_protocol_headers_proto = out.File\n\tfile_common_protocol_headers_proto_goTypes = nil\n\tfile_common_protocol_headers_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/protocol/headers.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.protocol;\noption csharp_namespace = \"Xray.Common.Protocol\";\noption go_package = \"github.com/xtls/xray-core/common/protocol\";\noption java_package = \"com.xray.common.protocol\";\noption java_multiple_files = true;\n\nenum SecurityType {\n  UNKNOWN = 0;\n  AUTO = 2;\n  AES128_GCM = 3;\n  CHACHA20_POLY1305 = 4;\n  NONE = 5; // [DEPRECATED 2023-06] \n  ZERO = 6;\n}\n\nmessage SecurityConfig {\n  SecurityType type = 1;\n}\n"
  },
  {
    "path": "common/protocol/http/headers.go",
    "content": "package http\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it.\nfunc ParseXForwardedFor(header http.Header) []net.Address {\n\txff := header.Get(\"X-Forwarded-For\")\n\tif xff == \"\" {\n\t\treturn nil\n\t}\n\tlist := strings.Split(xff, \",\")\n\taddrs := make([]net.Address, 0, len(list))\n\tfor _, proxy := range list {\n\t\taddrs = append(addrs, net.ParseAddress(proxy))\n\t}\n\treturn addrs\n}\n\n// RemoveHopByHopHeaders removes hop by hop headers in http header list.\nfunc RemoveHopByHopHeaders(header http.Header) {\n\t// Strip hop-by-hop header based on RFC:\n\t// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1\n\t// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do\n\n\theader.Del(\"Proxy-Connection\")\n\theader.Del(\"Proxy-Authenticate\")\n\theader.Del(\"Proxy-Authorization\")\n\theader.Del(\"TE\")\n\theader.Del(\"Trailers\")\n\theader.Del(\"Transfer-Encoding\")\n\theader.Del(\"Upgrade\")\n\n\tconnections := header.Get(\"Connection\")\n\theader.Del(\"Connection\")\n\tif connections == \"\" {\n\t\treturn\n\t}\n\tfor _, h := range strings.Split(connections, \",\") {\n\t\theader.Del(strings.TrimSpace(h))\n\t}\n}\n\n// ParseHost splits host and port from a raw string. Default port is used when raw string doesn't contain port.\nfunc ParseHost(rawHost string, defaultPort net.Port) (net.Destination, error) {\n\tport := defaultPort\n\thost, rawPort, err := net.SplitHostPort(rawHost)\n\tif err != nil {\n\t\tif addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, \"missing port\") {\n\t\t\thost = rawHost\n\t\t} else {\n\t\t\treturn net.Destination{}, err\n\t\t}\n\t} else if len(rawPort) > 0 {\n\t\tintPort, err := strconv.Atoi(rawPort)\n\t\tif err != nil {\n\t\t\treturn net.Destination{}, err\n\t\t}\n\t\tport = net.Port(intPort)\n\t}\n\n\treturn net.TCPDestination(net.ParseAddress(host), port), nil\n}\n"
  },
  {
    "path": "common/protocol/http/headers_test.go",
    "content": "package http_test\n\nimport (\n\t\"bufio\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t. \"github.com/xtls/xray-core/common/protocol/http\"\n)\n\nfunc TestParseXForwardedFor(t *testing.T) {\n\theader := http.Header{}\n\theader.Add(\"X-Forwarded-For\", \"129.78.138.66, 129.78.64.103\")\n\taddrs := ParseXForwardedFor(header)\n\tif r := cmp.Diff(addrs, []net.Address{net.ParseAddress(\"129.78.138.66\"), net.ParseAddress(\"129.78.64.103\")}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestHopByHopHeadersRemoving(t *testing.T) {\n\trawRequest := `GET /pkg/net/http/ HTTP/1.1\nHost: golang.org\nConnection: keep-alive,Foo, Bar\nFoo: foo\nBar: bar\nProxy-Connection: keep-alive\nProxy-Authenticate: abc\nAccept-Encoding: gzip\nAccept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7\nCache-Control: no-cache\nAccept-Language: de,en;q=0.7,en-us;q=0.3\n\n`\n\tb := bufio.NewReader(strings.NewReader(rawRequest))\n\treq, err := http.ReadRequest(b)\n\tcommon.Must(err)\n\theaders := []struct {\n\t\tKey   string\n\t\tValue string\n\t}{\n\t\t{\n\t\t\tKey:   \"Foo\",\n\t\t\tValue: \"foo\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"Bar\",\n\t\t\tValue: \"bar\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"Connection\",\n\t\t\tValue: \"keep-alive,Foo, Bar\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"Proxy-Connection\",\n\t\t\tValue: \"keep-alive\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"Proxy-Authenticate\",\n\t\t\tValue: \"abc\",\n\t\t},\n\t}\n\tfor _, header := range headers {\n\t\tif v := req.Header.Get(header.Key); v != header.Value {\n\t\t\tt.Error(\"header \", header.Key, \" = \", v, \" want \", header.Value)\n\t\t}\n\t}\n\n\tRemoveHopByHopHeaders(req.Header)\n\n\tfor _, header := range []string{\"Connection\", \"Foo\", \"Bar\", \"Proxy-Connection\", \"Proxy-Authenticate\"} {\n\t\tif v := req.Header.Get(header); v != \"\" {\n\t\t\tt.Error(\"header \", header, \" = \", v)\n\t\t}\n\t}\n}\n\nfunc TestParseHost(t *testing.T) {\n\ttestCases := []struct {\n\t\tRawHost     string\n\t\tDefaultPort net.Port\n\t\tDestination net.Destination\n\t\tError       bool\n\t}{\n\t\t{\n\t\t\tRawHost:     \"example.com:80\",\n\t\t\tDefaultPort: 443,\n\t\t\tDestination: net.TCPDestination(net.DomainAddress(\"example.com\"), 80),\n\t\t},\n\t\t{\n\t\t\tRawHost:     \"tls.example.com\",\n\t\t\tDefaultPort: 443,\n\t\t\tDestination: net.TCPDestination(net.DomainAddress(\"tls.example.com\"), 443),\n\t\t},\n\t\t{\n\t\t\tRawHost:     \"[2401:1bc0:51f0:ec08::1]:80\",\n\t\t\tDefaultPort: 443,\n\t\t\tDestination: net.TCPDestination(net.ParseAddress(\"[2401:1bc0:51f0:ec08::1]\"), 80),\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tdest, err := ParseHost(testCase.RawHost, testCase.DefaultPort)\n\t\tif testCase.Error {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"for test case: \", testCase.RawHost, \" expected error, but actually nil\")\n\t\t\t}\n\t\t} else {\n\t\t\tif dest != testCase.Destination {\n\t\t\t\tt.Error(\"for test case: \", testCase.RawHost, \" expected host: \", testCase.Destination.String(), \" but got \", dest.String())\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/protocol/http/sniff.go",
    "content": "package http\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n)\n\ntype version byte\n\nconst (\n\tHTTP1 version = iota\n\tHTTP2\n)\n\ntype SniffHeader struct {\n\tversion version\n\thost    string\n}\n\nfunc (h *SniffHeader) Protocol() string {\n\tswitch h.version {\n\tcase HTTP1:\n\t\treturn \"http1\"\n\tcase HTTP2:\n\t\treturn \"http2\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\nfunc (h *SniffHeader) Domain() string {\n\treturn h.host\n}\n\nvar (\n\tmethods = [...]string{\"get\", \"post\", \"head\", \"put\", \"delete\", \"options\", \"connect\"}\n\n\terrNotHTTPMethod = errors.New(\"not an HTTP method\")\n)\n\nfunc beginWithHTTPMethod(b []byte) error {\n\tfor _, m := range &methods {\n\t\tif len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) {\n\t\t\treturn nil\n\t\t}\n\n\t\tif len(b) < len(m) {\n\t\t\treturn common.ErrNoClue\n\t\t}\n\t}\n\n\treturn errNotHTTPMethod\n}\n\nfunc SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {\n\tcontent := session.ContentFromContext(c)\n\tShouldSniffAttr := true\n\t// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.\n\t// It will set attributes, so skip it.\n\tif content == nil || len(content.Attributes) != 0 {\n\t\tShouldSniffAttr = false\n\t}\n\tif err := beginWithHTTPMethod(b); err != nil {\n\t\treturn nil, err\n\t}\n\n\tsh := &SniffHeader{\n\t\tversion: HTTP1,\n\t}\n\n\theaders := bytes.Split(b, []byte{'\\n'})\n\tfor i := 1; i < len(headers); i++ {\n\t\theader := headers[i]\n\t\tif len(header) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tparts := bytes.SplitN(header, []byte{':'}, 2)\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := strings.ToLower(string(parts[0]))\n\t\tvalue := string(bytes.TrimSpace(parts[1]))\n\t\tif ShouldSniffAttr {\n\t\t\tcontent.SetAttribute(key, value) // Put header in attribute\n\t\t}\n\t\tif key == \"host\" {\n\t\t\trawHost := strings.ToLower(value)\n\t\t\tdest, err := ParseHost(rawHost, net.Port(80))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tsh.host = dest.Address.String()\n\t\t}\n\t}\n\t// Parse request line\n\t// Request line is like this\n\t// \"GET /homo/114514 HTTP/1.1\"\n\tif len(headers) > 0 && ShouldSniffAttr {\n\t\tRequestLineParts := bytes.Split(headers[0], []byte{' '})\n\t\tif len(RequestLineParts) == 3 {\n\t\t\tcontent.SetAttribute(\":method\", string(RequestLineParts[0]))\n\t\t\tcontent.SetAttribute(\":path\", string(RequestLineParts[1]))\n\t\t}\n\t}\n\n\tif len(sh.host) > 0 {\n\t\treturn sh, nil\n\t}\n\n\treturn nil, common.ErrNoClue\n}\n"
  },
  {
    "path": "common/protocol/http/sniff_test.go",
    "content": "package http_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/protocol/http\"\n)\n\nfunc TestHTTPHeaders(t *testing.T) {\n\tcases := []struct {\n\t\tinput  string\n\t\tdomain string\n\t\terr    bool\n\t}{\n\t\t{\n\t\t\tinput: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1\nHost: net.tutsplus.com\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-us,en;q=0.5\nAccept-Encoding: gzip,deflate\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\nKeep-Alive: 300\nConnection: keep-alive\nCookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120\nPragma: no-cache\nCache-Control: no-cache`,\n\t\t\tdomain: \"net.tutsplus.com\",\n\t\t},\n\t\t{\n\t\t\tinput: `POST /foo.php HTTP/1.1\nHost: localhost\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-us,en;q=0.5\nAccept-Encoding: gzip,deflate\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\nKeep-Alive: 300\nConnection: keep-alive\nReferer: http://localhost/test.php\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 43\n \nfirst_name=John&last_name=Doe&action=Submit`,\n\t\t\tdomain: \"localhost\",\n\t\t},\n\t\t{\n\t\t\tinput: `X /foo.php HTTP/1.1\nHost: localhost\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-us,en;q=0.5\nAccept-Encoding: gzip,deflate\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\nKeep-Alive: 300\nConnection: keep-alive\nReferer: http://localhost/test.php\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 43\n \nfirst_name=John&last_name=Doe&action=Submit`,\n\t\t\tdomain: \"\",\n\t\t\terr:    true,\n\t\t},\n\t\t{\n\t\t\tinput: `GET /foo.php HTTP/1.1\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\nAccept-Language: en-us,en;q=0.5\nAccept-Encoding: gzip,deflate\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\nKeep-Alive: 300\nConnection: keep-alive\nReferer: http://localhost/test.php\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 43\n\nHost: localhost\nfirst_name=John&last_name=Doe&action=Submit`,\n\t\t\tdomain: \"\",\n\t\t\terr:    true,\n\t\t},\n\t\t{\n\t\t\tinput:  `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1`,\n\t\t\tdomain: \"\",\n\t\t\terr:    true,\n\t\t},\n\t}\n\n\tfor _, test := range cases {\n\t\theader, err := SniffHTTP([]byte(test.input), context.TODO())\n\t\tif test.err {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Expect error but nil, in test: %v\", test)\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expect no error but actually %s in test %v\", err.Error(), test)\n\t\t\t}\n\t\t\tif header.Domain() != test.domain {\n\t\t\t\tt.Error(\"expected domain \", test.domain, \" but got \", header.Domain())\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/protocol/id.go",
    "content": "package protocol\n\nimport (\n\t\"crypto/md5\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\nconst (\n\tIDBytesLen = 16\n)\n\n// The ID of en entity, in the form of a UUID.\ntype ID struct {\n\tuuid   uuid.UUID\n\tcmdKey [IDBytesLen]byte\n}\n\n// Equals returns true if this ID equals to the other one.\nfunc (id *ID) Equals(another *ID) bool {\n\treturn id.uuid.Equals(&(another.uuid))\n}\n\nfunc (id *ID) Bytes() []byte {\n\treturn id.uuid.Bytes()\n}\n\nfunc (id *ID) String() string {\n\treturn id.uuid.String()\n}\n\nfunc (id *ID) UUID() uuid.UUID {\n\treturn id.uuid\n}\n\nfunc (id ID) CmdKey() []byte {\n\treturn id.cmdKey[:]\n}\n\n// NewID returns an ID with given UUID.\nfunc NewID(uuid uuid.UUID) *ID {\n\tid := &ID{uuid: uuid}\n\tmd5hash := md5.New()\n\tcommon.Must2(md5hash.Write(uuid.Bytes()))\n\tcommon.Must2(md5hash.Write([]byte(\"c48619fe-8f02-49e0-b9e9-edf763e17e21\")))\n\tmd5hash.Sum(id.cmdKey[:0])\n\treturn id\n}\n"
  },
  {
    "path": "common/protocol/id_test.go",
    "content": "package protocol_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\nfunc TestIdEquals(t *testing.T) {\n\tid1 := NewID(uuid.New())\n\tid2 := NewID(id1.UUID())\n\n\tif !id1.Equals(id2) {\n\t\tt.Error(\"expected id1 to equal id2, but actually not\")\n\t}\n\n\tif id1.String() != id2.String() {\n\t\tt.Error(id1.String(), \" != \", id2.String())\n\t}\n}\n"
  },
  {
    "path": "common/protocol/payload.go",
    "content": "package protocol\n\ntype TransferType byte\n\nconst (\n\tTransferTypeStream TransferType = 0\n\tTransferTypePacket TransferType = 1\n)\n\ntype AddressType byte\n\nconst (\n\tAddressTypeIPv4   AddressType = 1\n\tAddressTypeDomain AddressType = 2\n\tAddressTypeIPv6   AddressType = 3\n)\n"
  },
  {
    "path": "common/protocol/protocol.go",
    "content": "package protocol // import \"github.com/xtls/xray-core/common/protocol\"\n\nimport (\n\t\"errors\"\n)\n\nvar ErrProtoNeedMoreData = errors.New(\"protocol matches, but need more data to complete sniffing\")\n"
  },
  {
    "path": "common/protocol/quic/qtls_go118.go",
    "content": "package quic\n\nimport (\n\t\"crypto\"\n\t\"crypto/cipher\"\n\t_ \"crypto/tls\"\n\t_ \"unsafe\"\n)\n\ntype CipherSuiteTLS13 struct {\n\tID     uint16\n\tKeyLen int\n\tAEAD   func(key, fixedNonce []byte) cipher.AEAD\n\tHash   crypto.Hash\n}\n\n//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13\nfunc AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD\n"
  },
  {
    "path": "common/protocol/quic/sniff.go",
    "content": "package quic\n\nimport (\n\t\"crypto\"\n\t\"crypto/aes\"\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/apernet/quic-go/quicvarint\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\tptls \"github.com/xtls/xray-core/common/protocol/tls\"\n\t\"golang.org/x/crypto/hkdf\"\n)\n\ntype SniffHeader struct {\n\tdomain string\n}\n\nfunc (s SniffHeader) Protocol() string {\n\treturn \"quic\"\n}\n\nfunc (s SniffHeader) Domain() string {\n\treturn s.domain\n}\n\nconst (\n\tversionDraft29 uint32 = 0xff00001d\n\tversion1       uint32 = 0x1\n)\n\nvar (\n\tquicSaltOld  = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}\n\tquicSalt     = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}\n\tinitialSuite = &CipherSuiteTLS13{\n\t\tID:     tls.TLS_AES_128_GCM_SHA256,\n\t\tKeyLen: 16,\n\t\tAEAD:   AEADAESGCMTLS13,\n\t\tHash:   crypto.SHA256,\n\t}\n\terrNotQuic        = errors.New(\"not quic\")\n\terrNotQuicInitial = errors.New(\"not initial packet\")\n)\n\nfunc SniffQUIC(b []byte) (*SniffHeader, error) {\n\tif len(b) == 0 {\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\t// Crypto data separated across packets\n\tcryptoLen := int32(0)\n\tcryptoDataBuf := buf.NewWithSize(32767)\n\tdefer cryptoDataBuf.Release()\n\tcache := buf.New()\n\tdefer cache.Release()\n\n\t// Parse QUIC packets\n\tfor len(b) > 0 {\n\t\tbuffer := buf.FromBytes(b)\n\t\ttypeByte, err := buffer.ReadByte()\n\t\tif err != nil {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\n\t\tisLongHeader := typeByte&0x80 > 0\n\t\tif !isLongHeader || typeByte&0x40 == 0 {\n\t\t\treturn nil, errNotQuicInitial\n\t\t}\n\n\t\tvb, err := buffer.ReadBytes(4)\n\t\tif err != nil {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\n\t\tversionNumber := binary.BigEndian.Uint32(vb)\n\t\tif versionNumber != 0 && typeByte&0x40 == 0 {\n\t\t\treturn nil, errNotQuic\n\t\t} else if versionNumber != versionDraft29 && versionNumber != version1 {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\n\t\tpacketType := (typeByte & 0x30) >> 4\n\t\tisQuicInitial := packetType == 0x0\n\n\t\tvar destConnID []byte\n\t\tif l, err := buffer.ReadByte(); err != nil {\n\t\t\treturn nil, errNotQuic\n\t\t} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\n\t\tif l, err := buffer.ReadByte(); err != nil {\n\t\t\treturn nil, errNotQuic\n\t\t} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\n\t\tif isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2\n\t\t\ttokenLen, err := quicvarint.Read(buffer)\n\t\t\tif err != nil || tokenLen > uint64(len(b)) {\n\t\t\t\treturn nil, errNotQuic\n\t\t\t}\n\n\t\t\tif _, err = buffer.ReadBytes(int32(tokenLen)); err != nil {\n\t\t\t\treturn nil, errNotQuic\n\t\t\t}\n\t\t}\n\n\t\tpacketLen, err := quicvarint.Read(buffer)\n\t\tif err != nil {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\t\t// packetLen is impossible to be shorter than this\n\t\tif packetLen < 4 {\n\t\t\treturn nil, errNotQuic\n\t\t}\n\n\t\thdrLen := len(b) - int(buffer.Len())\n\t\tif len(b) < hdrLen+int(packetLen) {\n\t\t\treturn nil, common.ErrNoClue // Not enough data to read as a QUIC packet. QUIC is UDP-based, so this is unlikely to happen.\n\t\t}\n\n\t\trestPayload := b[hdrLen+int(packetLen):]\n\t\tif !isQuicInitial { // Skip this packet if it's not initial packet\n\t\t\tb = restPayload\n\t\t\tcontinue\n\t\t}\n\n\t\tvar salt []byte\n\t\tif versionNumber == version1 {\n\t\t\tsalt = quicSalt\n\t\t} else {\n\t\t\tsalt = quicSaltOld\n\t\t}\n\t\tinitialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)\n\t\tsecret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, \"client in\", crypto.SHA256.Size())\n\t\thpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, \"quic hp\", initialSuite.KeyLen)\n\t\tblock, err := aes.NewCipher(hpKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcache.Clear()\n\t\tmask := cache.Extend(int32(block.BlockSize()))\n\t\tblock.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])\n\t\tb[0] ^= mask[0] & 0xf\n\t\tpacketNumberLength := int(b[0]&0x3 + 1)\n\t\tfor i := range packetNumberLength {\n\t\t\tb[hdrLen+i] ^= mask[i+1]\n\t\t}\n\n\t\tkey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, \"quic key\", 16)\n\t\tiv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, \"quic iv\", 12)\n\t\tcipher := AEADAESGCMTLS13(key, iv)\n\n\t\tnonce := cache.Extend(int32(cipher.NonceSize()))\n\t\t_, err = buffer.Read(nonce[len(nonce)-packetNumberLength:])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\textHdrLen := hdrLen + packetNumberLength\n\t\tdata := b[extHdrLen : int(packetLen)+hdrLen]\n\t\tdecrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbuffer = buf.FromBytes(decrypted)\n\t\tfor !buffer.IsEmpty() {\n\t\t\tframeType, _ := buffer.ReadByte()\n\t\t\tfor frameType == 0x0 && !buffer.IsEmpty() {\n\t\t\t\tframeType, _ = buffer.ReadByte()\n\t\t\t}\n\t\t\tswitch frameType {\n\t\t\tcase 0x00: // PADDING frame\n\t\t\tcase 0x01: // PING frame\n\t\t\tcase 0x02, 0x03: // ACK frame\n\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tfor i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range\n\t\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap\n\t\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length\n\t\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif frameType == 0x03 {\n\t\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count\n\t\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count\n\t\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count\n\t\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase 0x06: // CRYPTO frame, we will use this frame\n\t\t\t\toffset, err := quicvarint.Read(buffer) // Field: Offset\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tlength, err := quicvarint.Read(buffer) // Field: Length\n\t\t\t\tif err != nil || length > uint64(buffer.Len()) {\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tcurrentCryptoLen := int32(offset + length)\n\t\t\t\tif cryptoLen < currentCryptoLen {\n\t\t\t\t\tif cryptoDataBuf.Cap() < currentCryptoLen {\n\t\t\t\t\t\treturn nil, io.ErrShortBuffer\n\t\t\t\t\t}\n\t\t\t\t\tcryptoDataBuf.Extend(currentCryptoLen - cryptoLen)\n\t\t\t\t\tcryptoLen = currentCryptoLen\n\t\t\t\t}\n\t\t\t\tif _, err := buffer.Read(cryptoDataBuf.BytesRange(int32(offset), currentCryptoLen)); err != nil { // Field: Crypto Data\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\tcase 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet\n\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tif _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tlength, err := quicvarint.Read(buffer) // Field: Reason Phrase Length\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\t\tif _, err := buffer.ReadBytes(int32(length)); err != nil { // Field: Reason Phrase\n\t\t\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Only above frame types are permitted in initial packet.\n\t\t\t\t// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8\n\t\t\t\treturn nil, errNotQuicInitial\n\t\t\t}\n\t\t}\n\n\t\ttlsHdr := &ptls.SniffHeader{}\n\t\terr = ptls.ReadClientHello(cryptoDataBuf.BytesRange(0, cryptoLen), tlsHdr)\n\t\tif err != nil {\n\t\t\t// The crypto data may have not been fully recovered in current packets,\n\t\t\t// So we continue to sniff rest packets.\n\t\t\tb = restPayload\n\t\t\tcontinue\n\t\t}\n\t\treturn &SniffHeader{domain: tlsHdr.Domain()}, nil\n\t}\n\t// All payload is parsed as valid QUIC packets, but we need more packets for crypto data to read client hello.\n\treturn nil, protocol.ErrProtoNeedMoreData\n}\n\nfunc hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {\n\tb := make([]byte, 3, 3+6+len(label)+1+len(context))\n\tbinary.BigEndian.PutUint16(b, uint16(length))\n\tb[2] = uint8(6 + len(label))\n\tb = append(b, []byte(\"tls13 \")...)\n\tb = append(b, []byte(label)...)\n\tb = b[:3+6+len(label)+1]\n\tb[3+6+len(label)] = uint8(len(context))\n\tb = append(b, context...)\n\n\tout := make([]byte, length)\n\tn, err := hkdf.Expand(hash.New, secret, b).Read(out)\n\tif err != nil || n != length {\n\t\tpanic(\"quic: HKDF-Expand-Label invocation failed unexpectedly\")\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "common/protocol/quic/sniff_test.go",
    "content": "package quic_test\n\nimport (\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/protocol/quic\"\n)\n\nfunc TestSniffQUIC(t *testing.T) {\n\tpkt, err := hex.DecodeString(\"cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b\")\n\tcommon.Must(err)\n\tquicHdr, err := quic.SniffQUIC(pkt)\n\tif err != nil || quicHdr.Domain() != \"www.google.com\" {\n\t\tt.Error(\"failed\")\n\t}\n}\n\nfunc TestSniffQUICComplex(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\thexData       string\n\t\tdomain        string\n\t\twantErr       bool\n\t\tneedsMoreData bool\n\t}{\n\t\t{\n\t\t\tname:          \"EmptyPacket\",\n\t\t\thexData:       \"0000000000000000000000000000000000000000000000000000000000000000\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"NTP Packet Client\",\n\t\t\thexData:       \"23000000000000000000000000000000000000000000000000000000000000000000000000000000acb84a797d4044c9\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"NTP Packet Server\",\n\t\t\thexData:       \"240106ec000000000000000e47505373ea4dcaef2f4b4c31acb84a797d4044c9eb58b8693dd70c27eb58b8693dd7dde2\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"DNS Packet Client\",\n\t\t\thexData:       \"4500004a8e2d40003f1146392a2a2d03080808081eea00350036a8175ad4010000010000000000000675706461746504636f64650c76697375616c73747564696f03636f6d0000010001\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"DNS Packet Client\",\n\t\t\thexData:       \"4500004a667a40003f116dec2a2a2d030808080866980035003605d9b524010000010000000000000675706461746504636f64650c76697375616c73747564696f03636f6d0000410001\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"DNS Packet Server\",\n\t\t\thexData:       \"b524818000010006000100000675706461746504636f64650c76697375616c73747564696f03636f6d0000410001c00c00050001000000ec00301e7673636f64652d7570646174652d67366763623667676474686b63746439037a303107617a7572656664036e657400c03a000500010000000b002311737461722d617a75726566642d70726f640e747261666669636d616e61676572c065c076000500010000003c002c0473686564086475616c2d6c6f770b732d706172742d3030313706742d3030303908742d6d7365646765c065c0a5000500010000006c001411617a75726566642d742d66622d70726f64c088c0dd000500010000003c0026046475616c0b732d706172742d3030313706742d303030390b66622d742d6d7365646765c065c0fd00050001000000300002c102c1150006000100000030002d036e7331c115066d736e687374096d6963726f736f6674c0257848b78d00000708000003840024ea000000003c\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"DNS Packet Server\",\n\t\t\thexData:       \"5ad4818000010007000000000675706461746504636f64650c76697375616c73747564696f03636f6d0000010001c00c000500010000008400301e7673636f64652d7570646174652d67366763623667676474686b63746439037a303107617a7572656664036e657400c03a000500010000001e002311737461722d617a75726566642d70726f640e747261666669636d616e61676572c065c076000500010000003c002c0473686564086475616c2d6c6f770b732d706172742d3030313706742d3030303908742d6d7365646765c065c0a50005000100000010001411617a75726566642d742d66622d70726f64c088c0dd000500010000003c0026046475616c0b732d706172742d3030313706742d303030390b66622d742d6d7365646765c065c0fd00050001000000100002c102c102000100010000001000040d6bfd2d\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC, NonHandshake Packet\",\n\t\t\thexData:       \"548439ba3a0cffd27dabe08ebf9e603dd4801781e133b1a0276d29a047c3b8856adcced0067c4b11a08985bf93c05863305bd4b43ee9168cd5fdae0c392ff74ae06ce13e8d97dabec81ee927a844fa840f781edf9deb22f3162bf77009b3f5800c5e45539ac104368e7df8ba\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC, NonHandshake Packet\",\n\t\t\thexData:       \"53f4144825dab3ba251b83d0089e910210bec1a6507cca92ad9ff539cc21f6c75e3551ca44003d9a\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC, NonHandshake Packet\",\n\t\t\thexData:       \"528dc5524c03e7517949422cc3f6ffbfff74b2ec30a87654a71a\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[1]; packet 1\",\n\t\t\thexData:       \"cb00000001088ca3be26059ca269000044d088950f316207d551c91c88d791557c440a19184322536d2c900034358c1b3964f2d2935337b8d044d35bf62b4eea9ceaac64121aa634c7cd28630722d169fa0f215b940d47d7996ca56f0d463dbf97a4a1b5818c5297a26fe58f5553dfb513ad589750a61682f229996555c7121c8bf48b06b68ab06427b01af485d832f9894099a20d3baadcff7b1cf07e2c059d3e7ba88d4ad35ef0ffea1fdc6ac3db271dfcca892a41ab25284936225c9bc593ce242b11b8abed4a8df902987eef0c6d90669e3606f47dd6ad05f44ba3a0cd356854261bbb1e2d8f6b83cc57cfa57eda3e5d7181b6ec418f6eeca81c259a33e4b0a913de720f2f8782764766ac9602a7f52a1082ec3da30dbefcf38c781a3e033810c4f2babf9b72adf7164159d98142181492e4468c0e10ab29013bf238e7360e09767ca49d59a9eb18f06a372bad711fefa90295f8e0839b1080570648212b321e5bd6f614bf0d3dc2817628b0c052a32820c16cb7f531c49244c48eb1429625246f9c164ae4ee1e83eaa8ff0eef1acf5a3d8ca88f1e4597db5ba5c0cb23d6100dd53da4f439ae64c4d3d43d1fbb5677f4fdc3bd2c2948dfc7e0be1a33c842033da15529cfd3cae00da68343d835db867f746854804410ba68f0dd7711b0fe55817b83f6ce1a12ad38acf2a3156f819f0dc68ea799c05583d9728f2856577811b260dba40d6c5e82c9e558c5b8f3f4599caf05ea591118e0b80ad621e0a76e4926047593a896752cb168420cb1b02d4211de5e5b7c891f319b5c0cf687e1d261a01f2acbade6bd73cd1ade0a02e240e9351384e1a6868c21a4878f39f0fa94ee1e36c5a46449241a3fe0147ff50176787eca7f3a936c901aeef56770bff74feecb985e6670d20dfd8ed17952dca5a5292213345c61db09bb5bcf5bf74565f61f9dccab51a289c3160ffe4a9b29cc76ea46778d9317a890efea2ad905f4219463a3baca3c02f5c3682634be7c2e86e366272a8263fec8e871644a79299d4aa74f1b1414b2f963cce6e059978faf813625af7869c1dec92035478c0e46dc66d938d4131aca27a59b2103b8cefa8e08aeb44b53b205b932902aea8d519faaaa12e354a6f532b4f716d7929e655dc2e98b494a99153854af5732a2659f2c21e4069896a1835ad05c5e53781cab16599cf4af47c196deeff9115c80d13f93aeb28b08023e6a1d3cf7da2a4457a9e443176bcdfef8f8de630c02bd0efdc5ddda56ad8f6b47edbda6353205e6e655f690092a48deb7f8a5254a7d778e07216cd97dfefcf740c1acd2977ef0fa17f798ea9752bae46e3aa3ec9b13f4c95c20a7839b8409000fa1f17e8dc46cc05c41bff696ee03c0371cae8638e8018ff4ebedd9f27d56443e534a72dd3d18a64790b676ddd060376759fa4a12ffc17f4be83492126ec1dc0fcd4aefef73a0b9c443ec3532b9a66b1a60daacf45e6557115edc0cc4d08758754a44beffedaa0d1265e50beed1a01752904ee3f7e706ed290b1a79071b142105b7c02e692ff318710e3ce9c3b9ec557cdecef173796417341ada414faa06b52adf645db454b56468ccf0da50a942ebc09487797cb45a085ec1e2e06fcd1f5b72eac291955a62e5aa379a374aea3a0dec3e4e0ba1dde350a94c72dbea7505922e26e99d62f751c2b301413a73fb6b20a36052151473ebecd04d0a771ec326957bc28c2020fdf6f01d9abed69b3c3e73168b404a1748b15310b167396da01c7d\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[1]; packet 1 - 2\",\n\t\t\thexData:       \"cb00000001088ca3be26059ca269000044d088950f316207d551c91c88d791557c440a19184322536d2c900034358c1b3964f2d2935337b8d044d35bf62b4eea9ceaac64121aa634c7cd28630722d169fa0f215b940d47d7996ca56f0d463dbf97a4a1b5818c5297a26fe58f5553dfb513ad589750a61682f229996555c7121c8bf48b06b68ab06427b01af485d832f9894099a20d3baadcff7b1cf07e2c059d3e7ba88d4ad35ef0ffea1fdc6ac3db271dfcca892a41ab25284936225c9bc593ce242b11b8abed4a8df902987eef0c6d90669e3606f47dd6ad05f44ba3a0cd356854261bbb1e2d8f6b83cc57cfa57eda3e5d7181b6ec418f6eeca81c259a33e4b0a913de720f2f8782764766ac9602a7f52a1082ec3da30dbefcf38c781a3e033810c4f2babf9b72adf7164159d98142181492e4468c0e10ab29013bf238e7360e09767ca49d59a9eb18f06a372bad711fefa90295f8e0839b1080570648212b321e5bd6f614bf0d3dc2817628b0c052a32820c16cb7f531c49244c48eb1429625246f9c164ae4ee1e83eaa8ff0eef1acf5a3d8ca88f1e4597db5ba5c0cb23d6100dd53da4f439ae64c4d3d43d1fbb5677f4fdc3bd2c2948dfc7e0be1a33c842033da15529cfd3cae00da68343d835db867f746854804410ba68f0dd7711b0fe55817b83f6ce1a12ad38acf2a3156f819f0dc68ea799c05583d9728f2856577811b260dba40d6c5e82c9e558c5b8f3f4599caf05ea591118e0b80ad621e0a76e4926047593a896752cb168420cb1b02d4211de5e5b7c891f319b5c0cf687e1d261a01f2acbade6bd73cd1ade0a02e240e9351384e1a6868c21a4878f39f0fa94ee1e36c5a46449241a3fe0147ff50176787eca7f3a936c901aeef56770bff74feecb985e6670d20dfd8ed17952dca5a5292213345c61db09bb5bcf5bf74565f61f9dccab51a289c3160ffe4a9b29cc76ea46778d9317a890efea2ad905f4219463a3baca3c02f5c3682634be7c2e86e366272a8263fec8e871644a79299d4aa74f1b1414b2f963cce6e059978faf813625af7869c1dec92035478c0e46dc66d938d4131aca27a59b2103b8cefa8e08aeb44b53b205b932902aea8d519faaaa12e354a6f532b4f716d7929e655dc2e98b494a99153854af5732a2659f2c21e4069896a1835ad05c5e53781cab16599cf4af47c196deeff9115c80d13f93aeb28b08023e6a1d3cf7da2a4457a9e443176bcdfef8f8de630c02bd0efdc5ddda56ad8f6b47edbda6353205e6e655f690092a48deb7f8a5254a7d778e07216cd97dfefcf740c1acd2977ef0fa17f798ea9752bae46e3aa3ec9b13f4c95c20a7839b8409000fa1f17e8dc46cc05c41bff696ee03c0371cae8638e8018ff4ebedd9f27d56443e534a72dd3d18a64790b676ddd060376759fa4a12ffc17f4be83492126ec1dc0fcd4aefef73a0b9c443ec3532b9a66b1a60daacf45e6557115edc0cc4d08758754a44beffedaa0d1265e50beed1a01752904ee3f7e706ed290b1a79071b142105b7c02e692ff318710e3ce9c3b9ec557cdecef173796417341ada414faa06b52adf645db454b56468ccf0da50a942ebc09487797cb45a085ec1e2e06fcd1f5b72eac291955a62e5aa379a374aea3a0dec3e4e0ba1dde350a94c72dbea7505922e26e99d62f751c2b301413a73fb6b20a36052151473ebecd04d0a771ec326957bc28c2020fdf6f01d9abed69b3c3e73168b404a1748b15310b167396da01c7dc700000001088ca3be26059ca269000044d00a7e7a252620d0fdfb63c0c193d6a9fe6a36aa9ce1b29dfa5f11f2567850b88384a2cc682eca2e292749365b833e5f7540019cd4f3143ed078aec07990b0d6ece18310403e73e1fe2975a8f9cb05796fa6196faaba3ee12a22b63a28a624cf4f7bedd44de000dc5ea698c65664df995b7d5fade0aab1cf0ecc5afd5ecb8fb80deecae3a8c97c20171f00ac3b5dc9a9027ca9c25571c72bb32070f6e3fb583560b0da6041b72e0a9601b8ad17d3c45e9dcc059f9f4758e8c35a839a9f6f4c501cb64e32e886fc733bc51069fbe4406f04d908285974c387d5b3e5f0f674941d05993bf8bda0d5ffd8c4fb528e150ff4bf37e38bd9c6346816fe360d4a206da81e815c1f7905184b6146b33427c6e38f1179981c18b82a3544442dd997c182d956037ae8f106eaf67ba133e7f15f1550b257d431f01ba0472659c6a5c2e6ff5e4ce9e692f4ef9fb169a75df4eb13f0b20e1994f3f8687bdca300c7e749af7b7a3b6597a6b950fe378a68c77766fdabe95248ed41d37805756b7ffa9cee0898bd661f6657cbf1af9aa8c7e437d432ca854c95307e6a7dfb6504ee3f7852fb3c246d168a03810b6c3d4e3d40bdee3def579effb66563f5bac98cfa1b071cd6f33e425e016bb3514a183b72cb3a393e9e519ba60e2177c98f530835e3b6eab78cdcb8abdbc769bc07e10c8e38bea710d5de1bdb2fa8d0d9b19e8cc31d16725a696e55342c89b667497e3d7f90e48f8503d8ead2a32a1930c3b24a4a9dcf2d8ec781705dd97d7df6e26828712fe42114419d5b8346bd86c239bd02f34e55f71400cb10c1fac7d8efa1a2ab258c17ace4288c8576ab92447b648fd15f4e038ec1c81a135e3bbb6f581a994c6a4902aeb1b5588cb1b5b53c8540296d96b6d2eccd67bae9609233f36304b5186d4698b88bb3ce8b1191a62b990436cf10718fd5759cb2281ac122f49ccbef8a3206348c1a930e7fc4bb498a11d89374e1480c7b8725b5f65e8c8d6f58da17f9134abce77eb9a6fcda514e7d3ab2e3610f86945f0dca519a3844da1b3a4b0e03c80528a2f79be478d07ff26166e30294bf0e69bf07a5bbd6d879adf6d618a1ec8365023408980bf67f0525a2fdee97fccc38fe104d4f58ed15e3671dfedf684856a27fbe286adba40ff0336def93f0174e9e35d341f5de73190d330d72227db9a866b69418e17e8e19ec884c1ffe2f0ad6deec37c9d49d536d0242fab282b0cf86cc9b15341757e0d361bddcbe5cbb062b3148d7c3c62af5c5dd5922a49920f351647030f62ed16929a404aa514fcbc38e67ba4f275e02a04c486b1a8e5b5efda197fd63e6f41fdeffa652c690dd6b00ca65df3688672ead9744f7d631e42e3b42f3ed1bff51b30f89211a7467cde65eab3659af7690cf307420a5823f31999d8f63c6c6ba0296ed4a46d5df6404f8db33e7252cc6bfcf7f55fee1f1e3b0573b6c6615793ff0691b7cfd23c195f66eb333d7efb0cfb74cf159787f87ad01fc131c6763bb1117bbfb8c2e8197ffba6b8c747565b1332bdbd6553b840939c2f98aa8eb1c549491c640e012fc549852fa7a93f81e5db152c761fc7d01bce0325619965c09f6730a162e7be53af7d9ce4b5ac0f4eb487361d2ac231d4ce92e5d9a084bc7b609ccf60056ecc82cd0c06a088cfbcf7d764b3109331c42f989da82b05cfe4c134a6784e664fa67a89c0624e3cc73ccfdea3f292db28f7c7b1b109f680f6b537f135c62f764\",\n\t\t\tdomain:        \"dns.google\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[2]; packet 1\",\n\t\t\thexData:       \"cf0000000108452af27900a1723300404600ca8530029a6bc59ff3e85beb5fc838ac3147ba5c2f6421ddcffdd85167d8de70eff2b1fa016dad4918337dec0feb660edb98e078dea51fa914055984b7957bd732fa4c831e448967af34752fd95835e3caba1e022d6d164f3f53f1bd7f60d560a8684079e90626aa1a4d3fe728158f7e1055ff76d1566072113982b193fb932265381e4de7afb35caa4ec56f31595a33fa2eb0bc84feb9f273224050938825fd21aa7317042ad00785ffd36151aee566a5dfe17d72591af1235059171568e5af0d13fc56e7897c3d632be753d8dea184c3d96d92bc56978cc669d94dd4c5e8dc3dcba7f0a39368fb1e87981e54bba7b86fbd8e8023a94d84f0290f402a5244cb4b0eeaaa57610ea59711a43932c521f10edb4560375693cbea60240389b8cebfd94035cabe4fc96ce8a726b979775e06c3bb0e3c4c866fe82e89fb725499e711e39310b93c785b313459f22d4ba37f90b19447165c2584269d98bf47d1f7ca89585797e4d6f1a4a1db7d2b0ae91a93fb15c3bb0ab953c3656b3b2ca20833d15e95329baff6d2ade1b0921b5ed3ae96648bf123b5265e27b049e9a8674455ff5f763f039568026e4fbe9882fef761c573d8f12e342c274a8dd3ad9854a688ce57cdddb52c758161ae3a59f67fc0d5b85f12e27617e7f4366e97a61fcda084e620dde35686f01dac49ce4bd76b986e3223c215919a1b228beeb74b7fcf32827d55be8f1b3b5fed24df2db023faecbb313b18a151cc4af8199d4bb08f8127b8207a0286d52758eaca87fd476ece0e3b17bcd8afb0289e8fd33c4455d4db6f058826c301ea303bfe2c0a6651a8fb6a2e1897852d758076adb04ad907077c5d5f94089da78d8923a34f1022ed672f378fe0dd81a709b372c0a2042a42e683c051c653e42b43c4a0ea8e961074d2901d4157ac9878b13a207b05ec471cff10d922b74d05623513cd6a4ea192ad21d4089de269633d4d2d1388d98d7c8a9e29848d5558b8aa2b73b437446a640230e6adb7f4b317ee5d66681c4aae11f69b1e5f96cb32ca6331405426cb706167d86f6f8fd588a72d7b2a6906798b81f174d808e1e3fc461e598e797c41bced26b87d09282d7b6d95076c285462e0c420a6f0e171ffe2791b5d221c03520409fe36622ff77796d9b7ef82babb25313acda9c621b22bf45ed909f9365b508860645af4c3aca78e6abca2d3a65c9159fbcd577438505d3f65a57c9412c12c069ad4d6db450beb08603abef621a9e029593fb5881dbd524ea2953b4acaaf59269b584c754e88c033247bb7c032e548d34fd9b2678e62fdf953dabf2be21c3e2d7b18ec7e3aedaf2cd082e19a369c1bcd4ca67e3d464e2200ecc3df98b0aa7f349415d68bcab0441ac3366607eff024bb786aec031a4619f8a24f554fe93c8520a03affcf11e40b6d5002f98c1708cac6c56e77eccba85ea6600d1391cfd202cc7914bfbaa3303266d1a820bf2dc84d2dfcdc4cdb79e6de3fbe3c02b288dcf955652f674f3f59b50849ea7dbf755bdafa27fba3db1267fb1354d8bf25a60cacb900b4d7ba913f9ba5f6b00559ad58b2f34a658ff7ef7f7d1ceeffd9c8325f271e6b5ba44d89685b744306963aa5e05ac0e8b00ada772dd5ae5ffb7043109afea86593743564c7acb4c8e7ef0e57d081eb1b9c0916078b113ece8a6036264a9b9781183c035342d50c7b069f3a01a40230e37ed8efde073c07d0e68066541d78c2f3cbe1e603cfcaaa\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[2]; packet 1-2\",\n\t\t\thexData:       \"cf0000000108452af27900a1723300404600ca8530029a6bc59ff3e85beb5fc838ac3147ba5c2f6421ddcffdd85167d8de70eff2b1fa016dad4918337dec0feb660edb98e078dea51fa914055984b7957bd732fa4c831e448967af34752fd95835e3caba1e022d6d164f3f53f1bd7f60d560a8684079e90626aa1a4d3fe728158f7e1055ff76d1566072113982b193fb932265381e4de7afb35caa4ec56f31595a33fa2eb0bc84feb9f273224050938825fd21aa7317042ad00785ffd36151aee566a5dfe17d72591af1235059171568e5af0d13fc56e7897c3d632be753d8dea184c3d96d92bc56978cc669d94dd4c5e8dc3dcba7f0a39368fb1e87981e54bba7b86fbd8e8023a94d84f0290f402a5244cb4b0eeaaa57610ea59711a43932c521f10edb4560375693cbea60240389b8cebfd94035cabe4fc96ce8a726b979775e06c3bb0e3c4c866fe82e89fb725499e711e39310b93c785b313459f22d4ba37f90b19447165c2584269d98bf47d1f7ca89585797e4d6f1a4a1db7d2b0ae91a93fb15c3bb0ab953c3656b3b2ca20833d15e95329baff6d2ade1b0921b5ed3ae96648bf123b5265e27b049e9a8674455ff5f763f039568026e4fbe9882fef761c573d8f12e342c274a8dd3ad9854a688ce57cdddb52c758161ae3a59f67fc0d5b85f12e27617e7f4366e97a61fcda084e620dde35686f01dac49ce4bd76b986e3223c215919a1b228beeb74b7fcf32827d55be8f1b3b5fed24df2db023faecbb313b18a151cc4af8199d4bb08f8127b8207a0286d52758eaca87fd476ece0e3b17bcd8afb0289e8fd33c4455d4db6f058826c301ea303bfe2c0a6651a8fb6a2e1897852d758076adb04ad907077c5d5f94089da78d8923a34f1022ed672f378fe0dd81a709b372c0a2042a42e683c051c653e42b43c4a0ea8e961074d2901d4157ac9878b13a207b05ec471cff10d922b74d05623513cd6a4ea192ad21d4089de269633d4d2d1388d98d7c8a9e29848d5558b8aa2b73b437446a640230e6adb7f4b317ee5d66681c4aae11f69b1e5f96cb32ca6331405426cb706167d86f6f8fd588a72d7b2a6906798b81f174d808e1e3fc461e598e797c41bced26b87d09282d7b6d95076c285462e0c420a6f0e171ffe2791b5d221c03520409fe36622ff77796d9b7ef82babb25313acda9c621b22bf45ed909f9365b508860645af4c3aca78e6abca2d3a65c9159fbcd577438505d3f65a57c9412c12c069ad4d6db450beb08603abef621a9e029593fb5881dbd524ea2953b4acaaf59269b584c754e88c033247bb7c032e548d34fd9b2678e62fdf953dabf2be21c3e2d7b18ec7e3aedaf2cd082e19a369c1bcd4ca67e3d464e2200ecc3df98b0aa7f349415d68bcab0441ac3366607eff024bb786aec031a4619f8a24f554fe93c8520a03affcf11e40b6d5002f98c1708cac6c56e77eccba85ea6600d1391cfd202cc7914bfbaa3303266d1a820bf2dc84d2dfcdc4cdb79e6de3fbe3c02b288dcf955652f674f3f59b50849ea7dbf755bdafa27fba3db1267fb1354d8bf25a60cacb900b4d7ba913f9ba5f6b00559ad58b2f34a658ff7ef7f7d1ceeffd9c8325f271e6b5ba44d89685b744306963aa5e05ac0e8b00ada772dd5ae5ffb7043109afea86593743564c7acb4c8e7ef0e57d081eb1b9c0916078b113ece8a6036264a9b9781183c035342d50c7b069f3a01a40230e37ed8efde073c07d0e68066541d78c2f3cbe1e603cfcaaac40000000108452af27900a1723300404600ca8530029a6bc59ff3e85beb5fc838ac3147ba5c2f6421ddcffdd85167d8de70eff2b1fa016dad4918337dec0feb660edb98e078dea51fa914055984b7957bd732fa4c831e4489522d29bb5c84749f83c8e1edfd9da8d1738164a8a9c59e37a5c9994d90bb982dcfa69b20f868960dc139618f1adc2546d34340ae13d826260c54a456bbf7469ee37b1be1d7177004468d7e92cac62a0b165d6a114ad479861dd58959e094b5a6250359301d4a614d529660760e3d1cdec9bf444a3761309bab40e4a977bc749e0dae431952f5f7e6b1ebc1383d343359a387da4301f7fa4b400475e9b82367e56278376dd1c80349f083988945a13649008109cc12a3acf569ffcc5481fbcd86b544e7dc8434e9dd42bd8e5716a844d37879568db046857389d36cc7550c75f94e314db6749aa987f0fc730fae0fcf465d01c2fb745269dfc10132ddb5404dda2f9455780f5818730834aa9db4740793359884b9927b0bd1a5ca96052b4f17397d8b78aa891401bb8bed6726ea2229d919798c50e24d5f40576ac204847be9244aadbe5c773684c37475036541d209c177d4e9c22a1253292ce4ffb886b925b6cf83cc251976a68887eda2777590f51804b790b51eee77e717b7ef0eea71634594df36e6ae9e7574d65c51ac3196f0b2a3b0f023c81f05f7807f958dda03418ed49e14b645e814b9aa55b37c809be3172ca21fe4c7a78e17e9ece8def2dd2949310ecaa41b1b477f4e85db5288aa144e333f47ef291d0e822941181c13859d9fd6d640904ee764c9276125228c932dff3fb12f564f039b52f5ba1ab4d119641df8fe13f784802b99347f0046da63f471e34b1d12d3111cffe7b5d90cf5999879f6f23e7785f09cb10df32821bb68dd8fdcfcdbedd63f2428b2292b9f0e76ff36403c9644fd43e01112ee6218d0ec1c86f6d147e4b802293e906750c7046f53bf05a144e321d3b45e08e4064fd3828fdd1b5d1ceed74081f61319dc0ad9a6e8a3b9cc802e952d24e2271712e2c2cda7daca2f835e6c804feeef8d918404cc82a1aa9534bddff68a472b208a0d0a7fd68a08fbc411132af47a6b67a32617b7b9991524c21599e8e3cb9395cdab87a3f5bf5d1833a9c7ea021b29cf428c877c6b21d62f99340ac7f85ae721acc10968e7d79f111ca40c75e14060d07cffa046d71151a0b00eab657300344b04bd1a8871650c34ceda8610d7c1ba8d37673da6aaa580400e0230c69fba8ba21927de2f5897656144694550d1df3d268804adc707e7b236501734aeabb2e61cb08012bd96eca5a486d7a55f996992c36233815abd71c30e263ba0c5d9456fe0828df16f6af7929390bb143c426d9dfeaf4bb373554479ebe609b36b4bc3dd08ce216b9cdc5726edb458c5e4036d0edc688d3e39d20f8254b5d1f174518f15b344efc27fc56572c0159aa593d5b46bc33818f986e3df8caebd4c7b702ef50dd582714a2b94ecd1c4e90af37d388445c478a32ff6e8f5852ee115966b708eed04da322b98813a69423e95f90b89ce85518e39bcef36fdd5bd312b2c6c5ee85962675274c18f39ee35155517f70fd74b31bb2de6b5108d369252e6fb289e453833132ef7960da1cc0934790c039b9a1b0c74f23eb3b61fe9b4d0ea67de757b93af451eef303b1373199af446a0fa98d5991bbd4771ee63317e6da86efbe213dfff595c41b98e0e89f4f2df110104e760feebf4cb3361171c9fceb1e1c809a268\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[2]; packet 1-3\",\n\t\t\thexData:       \"cf0000000108452af27900a1723300404600ca8530029a6bc59ff3e85beb5fc838ac3147ba5c2f6421ddcffdd85167d8de70eff2b1fa016dad4918337dec0feb660edb98e078dea51fa914055984b7957bd732fa4c831e448967af34752fd95835e3caba1e022d6d164f3f53f1bd7f60d560a8684079e90626aa1a4d3fe728158f7e1055ff76d1566072113982b193fb932265381e4de7afb35caa4ec56f31595a33fa2eb0bc84feb9f273224050938825fd21aa7317042ad00785ffd36151aee566a5dfe17d72591af1235059171568e5af0d13fc56e7897c3d632be753d8dea184c3d96d92bc56978cc669d94dd4c5e8dc3dcba7f0a39368fb1e87981e54bba7b86fbd8e8023a94d84f0290f402a5244cb4b0eeaaa57610ea59711a43932c521f10edb4560375693cbea60240389b8cebfd94035cabe4fc96ce8a726b979775e06c3bb0e3c4c866fe82e89fb725499e711e39310b93c785b313459f22d4ba37f90b19447165c2584269d98bf47d1f7ca89585797e4d6f1a4a1db7d2b0ae91a93fb15c3bb0ab953c3656b3b2ca20833d15e95329baff6d2ade1b0921b5ed3ae96648bf123b5265e27b049e9a8674455ff5f763f039568026e4fbe9882fef761c573d8f12e342c274a8dd3ad9854a688ce57cdddb52c758161ae3a59f67fc0d5b85f12e27617e7f4366e97a61fcda084e620dde35686f01dac49ce4bd76b986e3223c215919a1b228beeb74b7fcf32827d55be8f1b3b5fed24df2db023faecbb313b18a151cc4af8199d4bb08f8127b8207a0286d52758eaca87fd476ece0e3b17bcd8afb0289e8fd33c4455d4db6f058826c301ea303bfe2c0a6651a8fb6a2e1897852d758076adb04ad907077c5d5f94089da78d8923a34f1022ed672f378fe0dd81a709b372c0a2042a42e683c051c653e42b43c4a0ea8e961074d2901d4157ac9878b13a207b05ec471cff10d922b74d05623513cd6a4ea192ad21d4089de269633d4d2d1388d98d7c8a9e29848d5558b8aa2b73b437446a640230e6adb7f4b317ee5d66681c4aae11f69b1e5f96cb32ca6331405426cb706167d86f6f8fd588a72d7b2a6906798b81f174d808e1e3fc461e598e797c41bced26b87d09282d7b6d95076c285462e0c420a6f0e171ffe2791b5d221c03520409fe36622ff77796d9b7ef82babb25313acda9c621b22bf45ed909f9365b508860645af4c3aca78e6abca2d3a65c9159fbcd577438505d3f65a57c9412c12c069ad4d6db450beb08603abef621a9e029593fb5881dbd524ea2953b4acaaf59269b584c754e88c033247bb7c032e548d34fd9b2678e62fdf953dabf2be21c3e2d7b18ec7e3aedaf2cd082e19a369c1bcd4ca67e3d464e2200ecc3df98b0aa7f349415d68bcab0441ac3366607eff024bb786aec031a4619f8a24f554fe93c8520a03affcf11e40b6d5002f98c1708cac6c56e77eccba85ea6600d1391cfd202cc7914bfbaa3303266d1a820bf2dc84d2dfcdc4cdb79e6de3fbe3c02b288dcf955652f674f3f59b50849ea7dbf755bdafa27fba3db1267fb1354d8bf25a60cacb900b4d7ba913f9ba5f6b00559ad58b2f34a658ff7ef7f7d1ceeffd9c8325f271e6b5ba44d89685b744306963aa5e05ac0e8b00ada772dd5ae5ffb7043109afea86593743564c7acb4c8e7ef0e57d081eb1b9c0916078b113ece8a6036264a9b9781183c035342d50c7b069f3a01a40230e37ed8efde073c07d0e68066541d78c2f3cbe1e603cfcaaac40000000108452af27900a1723300404600ca8530029a6bc59ff3e85beb5fc838ac3147ba5c2f6421ddcffdd85167d8de70eff2b1fa016dad4918337dec0feb660edb98e078dea51fa914055984b7957bd732fa4c831e4489522d29bb5c84749f83c8e1edfd9da8d1738164a8a9c59e37a5c9994d90bb982dcfa69b20f868960dc139618f1adc2546d34340ae13d826260c54a456bbf7469ee37b1be1d7177004468d7e92cac62a0b165d6a114ad479861dd58959e094b5a6250359301d4a614d529660760e3d1cdec9bf444a3761309bab40e4a977bc749e0dae431952f5f7e6b1ebc1383d343359a387da4301f7fa4b400475e9b82367e56278376dd1c80349f083988945a13649008109cc12a3acf569ffcc5481fbcd86b544e7dc8434e9dd42bd8e5716a844d37879568db046857389d36cc7550c75f94e314db6749aa987f0fc730fae0fcf465d01c2fb745269dfc10132ddb5404dda2f9455780f5818730834aa9db4740793359884b9927b0bd1a5ca96052b4f17397d8b78aa891401bb8bed6726ea2229d919798c50e24d5f40576ac204847be9244aadbe5c773684c37475036541d209c177d4e9c22a1253292ce4ffb886b925b6cf83cc251976a68887eda2777590f51804b790b51eee77e717b7ef0eea71634594df36e6ae9e7574d65c51ac3196f0b2a3b0f023c81f05f7807f958dda03418ed49e14b645e814b9aa55b37c809be3172ca21fe4c7a78e17e9ece8def2dd2949310ecaa41b1b477f4e85db5288aa144e333f47ef291d0e822941181c13859d9fd6d640904ee764c9276125228c932dff3fb12f564f039b52f5ba1ab4d119641df8fe13f784802b99347f0046da63f471e34b1d12d3111cffe7b5d90cf5999879f6f23e7785f09cb10df32821bb68dd8fdcfcdbedd63f2428b2292b9f0e76ff36403c9644fd43e01112ee6218d0ec1c86f6d147e4b802293e906750c7046f53bf05a144e321d3b45e08e4064fd3828fdd1b5d1ceed74081f61319dc0ad9a6e8a3b9cc802e952d24e2271712e2c2cda7daca2f835e6c804feeef8d918404cc82a1aa9534bddff68a472b208a0d0a7fd68a08fbc411132af47a6b67a32617b7b9991524c21599e8e3cb9395cdab87a3f5bf5d1833a9c7ea021b29cf428c877c6b21d62f99340ac7f85ae721acc10968e7d79f111ca40c75e14060d07cffa046d71151a0b00eab657300344b04bd1a8871650c34ceda8610d7c1ba8d37673da6aaa580400e0230c69fba8ba21927de2f5897656144694550d1df3d268804adc707e7b236501734aeabb2e61cb08012bd96eca5a486d7a55f996992c36233815abd71c30e263ba0c5d9456fe0828df16f6af7929390bb143c426d9dfeaf4bb373554479ebe609b36b4bc3dd08ce216b9cdc5726edb458c5e4036d0edc688d3e39d20f8254b5d1f174518f15b344efc27fc56572c0159aa593d5b46bc33818f986e3df8caebd4c7b702ef50dd582714a2b94ecd1c4e90af37d388445c478a32ff6e8f5852ee115966b708eed04da322b98813a69423e95f90b89ce85518e39bcef36fdd5bd312b2c6c5ee85962675274c18f39ee35155517f70fd74b31bb2de6b5108d369252e6fb289e453833132ef7960da1cc0934790c039b9a1b0c74f23eb3b61fe9b4d0ea67de757b93af451eef303b1373199af446a0fa98d5991bbd4771ee63317e6da86efbe213dfff595c41b98e0e89f4f2df110104e760feebf4cb3361171c9fceb1e1c809a268c60000000108452af27900a1723300404600ca8530029a6bc59ff3e85beb5fc838ac3147ba5c2f6421ddcffdd85167d8de70eff2b1fa016dad4918337dec0feb660edb98e078dea51fa914055984b7957bd732fa4c831e44892ff5e6b16d8a259a9128c2c0c3c525462781a344c3df7f19a747e0e79ca8714995c867fc697a3cb87b35e769465a8e966bcb35b7e897ad036aa23a6c021e2445a0eb79962151cd20dbb43ae1231847de01caf4e5589dfebf026e95f7d1d742e140d9dda849396a70cc0798f1eef06fd5f4cfbc9a190ddf04cc332c5b7b15e53af311190ced92a1291c12b8799f2b50e076539a8370ee667e1791a78f38e565a48acbaa1c78ba941dba8b0d040f8fb8bbcc9f6bf5705efa613a24b12d6ac9cebb4f3fac1b09a07b49d8a3a62808eb0a324629f13a012e6ad0feb11ad97c1572983c713b62f27584809ba43e64e4af9845af807c0783104838f4e2ac33fa848866f3cc64a7b6203a5c09e8ad231f0f06ae2fb7b39a64cedd823b0ff297ad9be1ccac436777ccb3e22ef6b9c12e6d5e34926f50e8ca8c8c0532c810b074d001c11791a01bf25786b57a5da54065dcee4962822e929f47ee44d3b8c83d45a8b7a936dc2a6fa396e4194fa032d1627eca59f69857fc40dab5835d3613dade1c74b09c345bd32c509e9545d2330b157a7acb76409f3ac8eaa22802414f38c5422fe4c5189caaf5c1b93ce7c0892f0cfc477490d335aa78961d632a973cf106bd974c2714176fb0f98cf12f2887a0d7bd491756dd374331eb3e6adb9f2bd0d6b273403fd14b314eb27ebbb6f6e78ce310437004b757c048149cf04429ae4a6d6e65c9b3e0b9c9c4d4ef52007eaaad9670320f10cd5317b3d3edc374d45c98b217dd28fb3c2c2fb6e74a3aced143e3242084b192ba6df24e69fdb883e850714fe27a45f43883486a986574fd1fc10f259fe90786441554514c8dade1f3b86fdaf5f54ab655e2d803c98aa56073b00c32148a1ed367dff3a2bd934ecba55141389990b661bbd9ce1ef1def13747d45500daf92cec9e60908274703e761cd46affd46622f2a2192a79425ebf51c875fc7ba3598e15e0ba2465fc3e87c8a5da1915d3b8abe4b16d21259f311183eee1e7d2b808a91a7c89b284df0eb6a2a79c610bbe47722b3e04d5a6c0e574816a94d97349b6976010eb8c7debf42210982f78de482b7cd068051bf57908dbf46b5ceaf64f5fb33ede4412c1ce81eb1dfb4e99e10dd9b57ebc6e62ecbf4ee2db04d9e48c62bd45f8fa51704d414296a2d51d25ced6a192034a44c67e09d8985b573f98e03fa36dd8dcce2c04b4d5b1f276b6a642aadbdcafcf09de1d234bf8bbbf64aeadf01519ddafb419b3e62d204e04c3d7ebaf54b09e387ac3e9c4781c11625a2f44fddb7a1886f21929bd01c283f64903b6ccbb463984dfadc00f6af2a421517da023fd319f528195cac5fe907624b70c0172479d07d78e266dbf20ab8fc302228f279ffdba7395a839c4a9d7a4e001a260e1702393968f1e9722f023b204cf09cfee9a7bba045e4a2a449ee9fbb5c36e93028cfc87a2e34914b1b4f01beeded175ac0fa73fea9292f2bc3b1247164d8e05cddc3981bdc24e5f596571c418f6fa00fd9d4d0898cbf0d2f5413bed5f100f1854903017b6bc88bd7e303b5e0e2417bbcc984731128eda550d31f9af0e6e743eb6916466bbd435617d56fa60b05cd7dca66a9f6f4be23d3c5ff5d900822c6d1d8d71b0bab24f57d9682381a87c\",\n\t\t\tdomain:        \"signaler-pa.clients6.google.com\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[3]; packet 1\",\n\t\t\thexData:       \"cb0000000108676ef9ec3514fd5f004046001ae0c831dde6ac72f1337c9ca111f5b32afdf102d75c017d3bccb6fa89902b750b00c51bb226afa517754b72e962ff007c1c8c8749a21be1d8d94f7bf73437c010cd60e0bd4489d0a84868f32ecd4bd0d4c95a8a08d60d8303ef02323bdbcd96a33940824d25b9594bd8ae5716b9d043ab27ba03a2593c4d149b923711dd98e45456db19c8ea71e982562ae787c1b6cbe3ca1b03df2df62aa8e3127c5f68bdf80ed90588e7ec1f41f5a6281b87a12348cb17a04e9eaa461fef2e0e3ae70ebdc7bffa15c6b61ab270173e46dec0fd081f935a5fe6338d3b9cd38fcb52cb159edb66d2927238294313990da25c22f40d40e3cd72e76bbd066de731cf8fb6b4b7bc7639efb788c0b108dbf8280845a2cc62fbaf5fb8e1ecbe5ba7791aab94786c1c71c9058d0153b34a3f5bc8903e0d120f353defbe973cd33568bd03609dcdab8af1e8563897f5dd0251c6e6514bf40bd447d376fed21b2c54ebf74680df241bdc2ea5579bc0736cf3257c20d275746e8e6853aa89dcda8c2dbe523438ab92ca1ed1ab4f109e4ea84de57dfb6c544d695a5b710fe2d432f2b58644f8aeb965752d3a1d1a3057c2229192f89b254f5d292c22f1060642729df3667ef39e27691c82da9be847a59a17ba7345d23a37e31ec135633cc5ea84c752f56d4ec75878a2920b93e9b4e091e0114552712e1e50ade42e26ac0266b84043a493e1ce2e80cd57422de16a88deceaa55385dc2a977ffc9063e7c427200b6d8511ef9004f89412587bd6d0057898f5ae284db78b0ec861fed36dfb7c7a9679ad0480eefe71985ba6f731bd0e816a901e0c017dd0cb7fc8a4606dec2091a51aab16d6f9bbdecf3fea177671e68250a84fe19de8df78d711e22b81372bc22ae21ac7208ed41201f6e26cd6748e9d6e2f4884f5acba736b2432536718891638d43991bd97c232829e26be6e6bb303d44849b245ef758eb2813bc87cf21a30f132360111e3015de5d1e4f0c5a98aff159c29f6debed7c2f18f455dfc7f33995a90b7625688507ecef1e7db48e7030ea6c4fa835bbc1dfbea6c0a6c704d658d4866a42b9860b1c8b5b64cb669e102c81e369b5f07b8fa08816a566a99f4d2910f6e8d751d52f1e2889f0ec9acfcb4627e0da5c35452be05c7766eddf3c42ceb6a312044075a4231b4203718c886498a313f3ba12e44e368b04ec3ea6e72d6fed9b6b334cbc0ba89f0aa9a129b1bad5b0ad8690291a344967f58e52415859852c6ca3ea24bc93ec1041fd1dc8a6a181326d3026098db0cddec90b3cd6df1e7638a3703f70c9a3baff8f005b90f362459a275a8b39daa78ff24613434594f96b8023a41a17d815e5c0319a39e07d32841339f14f404030b4a22551b86ba94832a1c49053d63140b503f2f64354ce10abe6c08f6cdaf6d8dc361c3c9d1a8077ad34dccc699b6fe07c16f8f7743d04003d672f82e643b3f1d5e263495504e11e6b2e676c11b3d0033d5f837e6bfd01602584ff181e3cb86f081015a9311eed546b42a8280680aa538353949f89674c554b43241e36536430ae9e0190729ce902e8f06a952d23b62816deb3b62b45375033ede2d8065a8e7b38f5aee0a5c66eb2f21f33fa6795d4b086e6f6ac941ba0c883ccf6e54e52164384045e0b0d74a9361f224303c841ec907be250725ab06cf79dd8bff8f46c08963a409b9b71b5c634c987c5e163f73fc32553be1231c72444c5e2a91189824034a784948f\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[3]; packet 1-2\",\n\t\t\thexData:       \"cb0000000108676ef9ec3514fd5f004046001ae0c831dde6ac72f1337c9ca111f5b32afdf102d75c017d3bccb6fa89902b750b00c51bb226afa517754b72e962ff007c1c8c8749a21be1d8d94f7bf73437c010cd60e0bd4489d0a84868f32ecd4bd0d4c95a8a08d60d8303ef02323bdbcd96a33940824d25b9594bd8ae5716b9d043ab27ba03a2593c4d149b923711dd98e45456db19c8ea71e982562ae787c1b6cbe3ca1b03df2df62aa8e3127c5f68bdf80ed90588e7ec1f41f5a6281b87a12348cb17a04e9eaa461fef2e0e3ae70ebdc7bffa15c6b61ab270173e46dec0fd081f935a5fe6338d3b9cd38fcb52cb159edb66d2927238294313990da25c22f40d40e3cd72e76bbd066de731cf8fb6b4b7bc7639efb788c0b108dbf8280845a2cc62fbaf5fb8e1ecbe5ba7791aab94786c1c71c9058d0153b34a3f5bc8903e0d120f353defbe973cd33568bd03609dcdab8af1e8563897f5dd0251c6e6514bf40bd447d376fed21b2c54ebf74680df241bdc2ea5579bc0736cf3257c20d275746e8e6853aa89dcda8c2dbe523438ab92ca1ed1ab4f109e4ea84de57dfb6c544d695a5b710fe2d432f2b58644f8aeb965752d3a1d1a3057c2229192f89b254f5d292c22f1060642729df3667ef39e27691c82da9be847a59a17ba7345d23a37e31ec135633cc5ea84c752f56d4ec75878a2920b93e9b4e091e0114552712e1e50ade42e26ac0266b84043a493e1ce2e80cd57422de16a88deceaa55385dc2a977ffc9063e7c427200b6d8511ef9004f89412587bd6d0057898f5ae284db78b0ec861fed36dfb7c7a9679ad0480eefe71985ba6f731bd0e816a901e0c017dd0cb7fc8a4606dec2091a51aab16d6f9bbdecf3fea177671e68250a84fe19de8df78d711e22b81372bc22ae21ac7208ed41201f6e26cd6748e9d6e2f4884f5acba736b2432536718891638d43991bd97c232829e26be6e6bb303d44849b245ef758eb2813bc87cf21a30f132360111e3015de5d1e4f0c5a98aff159c29f6debed7c2f18f455dfc7f33995a90b7625688507ecef1e7db48e7030ea6c4fa835bbc1dfbea6c0a6c704d658d4866a42b9860b1c8b5b64cb669e102c81e369b5f07b8fa08816a566a99f4d2910f6e8d751d52f1e2889f0ec9acfcb4627e0da5c35452be05c7766eddf3c42ceb6a312044075a4231b4203718c886498a313f3ba12e44e368b04ec3ea6e72d6fed9b6b334cbc0ba89f0aa9a129b1bad5b0ad8690291a344967f58e52415859852c6ca3ea24bc93ec1041fd1dc8a6a181326d3026098db0cddec90b3cd6df1e7638a3703f70c9a3baff8f005b90f362459a275a8b39daa78ff24613434594f96b8023a41a17d815e5c0319a39e07d32841339f14f404030b4a22551b86ba94832a1c49053d63140b503f2f64354ce10abe6c08f6cdaf6d8dc361c3c9d1a8077ad34dccc699b6fe07c16f8f7743d04003d672f82e643b3f1d5e263495504e11e6b2e676c11b3d0033d5f837e6bfd01602584ff181e3cb86f081015a9311eed546b42a8280680aa538353949f89674c554b43241e36536430ae9e0190729ce902e8f06a952d23b62816deb3b62b45375033ede2d8065a8e7b38f5aee0a5c66eb2f21f33fa6795d4b086e6f6ac941ba0c883ccf6e54e52164384045e0b0d74a9361f224303c841ec907be250725ab06cf79dd8bff8f46c08963a409b9b71b5c634c987c5e163f73fc32553be1231c72444c5e2a91189824034a784948fc90000000108676ef9ec3514fd5f004046001ae0c831dde6ac72f1337c9ca111f5b32afdf102d75c017d3bccb6fa89902b750b00c51bb226afa517754b72e962ff007c1c8c8749a21be1d8d94f7bf73437c010cd60e0bd448983eee52163a177650f57b2cd8404bc619b9b59e796f9808bcd549ae6ae30d448c90f2783978bf9314a8038f45c0da5983163bd26f38f559f59447e8cf004f93b6b5c8af7b09603db021d4bdfa641bd83926eae1709a7a427add14df90cb258c6d4663d4d29709da89c90613d2ff9334637d53ca89407804eb863f78e110b866af2734c980705d9f969730a41132e788fc9e426d0f68ed24157aaff0383438d2715262e9b8b03cff850ba88127a05a8b68ac9a5ae5b098bb9ba5eaadd71ae846b3c0f68db728361eb8c8ed899c77725afbdfabf93812c49cbf4ee64047a96ea71258dbe5be3f988029d005fa2d9fc6e1e53fbeb6888074521b972e2ac71b4f22b754fc743e0de21af1e2ab416b2481e03227a1c7d7ea6cac5bc37ee3597d3bf11bf13a688dfa3d9aeff1eb1a7fdfcf8b6c722a4853f7c2b2d31e0b2b691f4273d4793fbb7a00f27a25577bfcba95e60699c9d2a926e71d64f535b633f2fd03320b28fe86c6619c54b34e6caf8f5a71b8a144c9236bf07edaacb486ff8ac63173af099efe7c9d006a5bb756449fb32b1fbff2e315fd5e96b586bb922a9795e29ffe6ead037c556e1bf30e24afb344cf873201007096b6f687f157588e236b71ade4d9245d8f065f2e23b36fad798d0f5504ddf25b828698d0cbdc28478b20d692d2ab605797a67232b0795927d886de798f00b4e7c69517d62b748e62e01d53dd1e77ac9a1605c0408713ff309ad53ff8f2bef17f9074f01134374068bf1f5dc07125180b5ea6902ec2d55c7d6d5f7ed4ef8732f9d34b4627678611fc9579e4321cea012c4e457dee6a11c41bdd1eb965056e885757af389079a558434eb3d59ae56a232302759431172ecc88de1c5400265f0f47e21396e3c38e0ba022c3e55ee4b85527cf49dece94445adc740cd26c18004a1cb984cc1732a138844da1ab003f89c589b6f3cc10c99a1b0d87be763f83e1b12c6fa6938ebc55d2ba33c25ca816dde207f7186f0c70b56b33feb538eb31175fdcfb036e365087f1b630628affdbbdee20d1976cb009f32db5f35aadb8117aa02ff2da9bdeaffbcc8bf3412efefeb00365e5f1ea577afd6e1c3585c67ffe1fa120382aa54028dcae9bbd624432a6256687d05483f2611f1ddd14b40f66fdf547e7eba904a79bd27733c9a8fbfb01154dda3457c4eacff8116941777ec570ff040e217d648ea5076588a6417462481eba68ebc59af04ba49b92f70b68a007977fde48b94b0af35475ea19cbec92df6449b065880bf03452cb3b3582f3d1a010e585be6506f3e067226471a94ce46c515f20502b3866553c10f037d9be89ad5858d6b2d2d94c70159247f66958d0e841d1c5b4254809d52475fdf96d087c3c6647b86006147a9ebb3f52ea6f4b89d886725b9e9243efd95e434bd8dd785143c57c06863b68df8f832987eb0c730c8b96634c1f888da2ef420cb0ebacf81f4b25c65962ae40c09ac4b0b2d440e3bdaa7309d87a1fa6af1c2e13e7a63c253fae027ceb2067cef8421b62d205f5d37c7204eaf594b1b43f9d9b67509a6709df48769ab9e1078f9e59d7656ec2132b5ebccf297e757a052835fffe94ae073131ac49c4f4374a1904cd4bf3041b236b73ea19eaa583db577fe35\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[3]; packet 1-3\",\n\t\t\thexData:       \"cb0000000108676ef9ec3514fd5f004046001ae0c831dde6ac72f1337c9ca111f5b32afdf102d75c017d3bccb6fa89902b750b00c51bb226afa517754b72e962ff007c1c8c8749a21be1d8d94f7bf73437c010cd60e0bd4489d0a84868f32ecd4bd0d4c95a8a08d60d8303ef02323bdbcd96a33940824d25b9594bd8ae5716b9d043ab27ba03a2593c4d149b923711dd98e45456db19c8ea71e982562ae787c1b6cbe3ca1b03df2df62aa8e3127c5f68bdf80ed90588e7ec1f41f5a6281b87a12348cb17a04e9eaa461fef2e0e3ae70ebdc7bffa15c6b61ab270173e46dec0fd081f935a5fe6338d3b9cd38fcb52cb159edb66d2927238294313990da25c22f40d40e3cd72e76bbd066de731cf8fb6b4b7bc7639efb788c0b108dbf8280845a2cc62fbaf5fb8e1ecbe5ba7791aab94786c1c71c9058d0153b34a3f5bc8903e0d120f353defbe973cd33568bd03609dcdab8af1e8563897f5dd0251c6e6514bf40bd447d376fed21b2c54ebf74680df241bdc2ea5579bc0736cf3257c20d275746e8e6853aa89dcda8c2dbe523438ab92ca1ed1ab4f109e4ea84de57dfb6c544d695a5b710fe2d432f2b58644f8aeb965752d3a1d1a3057c2229192f89b254f5d292c22f1060642729df3667ef39e27691c82da9be847a59a17ba7345d23a37e31ec135633cc5ea84c752f56d4ec75878a2920b93e9b4e091e0114552712e1e50ade42e26ac0266b84043a493e1ce2e80cd57422de16a88deceaa55385dc2a977ffc9063e7c427200b6d8511ef9004f89412587bd6d0057898f5ae284db78b0ec861fed36dfb7c7a9679ad0480eefe71985ba6f731bd0e816a901e0c017dd0cb7fc8a4606dec2091a51aab16d6f9bbdecf3fea177671e68250a84fe19de8df78d711e22b81372bc22ae21ac7208ed41201f6e26cd6748e9d6e2f4884f5acba736b2432536718891638d43991bd97c232829e26be6e6bb303d44849b245ef758eb2813bc87cf21a30f132360111e3015de5d1e4f0c5a98aff159c29f6debed7c2f18f455dfc7f33995a90b7625688507ecef1e7db48e7030ea6c4fa835bbc1dfbea6c0a6c704d658d4866a42b9860b1c8b5b64cb669e102c81e369b5f07b8fa08816a566a99f4d2910f6e8d751d52f1e2889f0ec9acfcb4627e0da5c35452be05c7766eddf3c42ceb6a312044075a4231b4203718c886498a313f3ba12e44e368b04ec3ea6e72d6fed9b6b334cbc0ba89f0aa9a129b1bad5b0ad8690291a344967f58e52415859852c6ca3ea24bc93ec1041fd1dc8a6a181326d3026098db0cddec90b3cd6df1e7638a3703f70c9a3baff8f005b90f362459a275a8b39daa78ff24613434594f96b8023a41a17d815e5c0319a39e07d32841339f14f404030b4a22551b86ba94832a1c49053d63140b503f2f64354ce10abe6c08f6cdaf6d8dc361c3c9d1a8077ad34dccc699b6fe07c16f8f7743d04003d672f82e643b3f1d5e263495504e11e6b2e676c11b3d0033d5f837e6bfd01602584ff181e3cb86f081015a9311eed546b42a8280680aa538353949f89674c554b43241e36536430ae9e0190729ce902e8f06a952d23b62816deb3b62b45375033ede2d8065a8e7b38f5aee0a5c66eb2f21f33fa6795d4b086e6f6ac941ba0c883ccf6e54e52164384045e0b0d74a9361f224303c841ec907be250725ab06cf79dd8bff8f46c08963a409b9b71b5c634c987c5e163f73fc32553be1231c72444c5e2a91189824034a784948fc90000000108676ef9ec3514fd5f004046001ae0c831dde6ac72f1337c9ca111f5b32afdf102d75c017d3bccb6fa89902b750b00c51bb226afa517754b72e962ff007c1c8c8749a21be1d8d94f7bf73437c010cd60e0bd448983eee52163a177650f57b2cd8404bc619b9b59e796f9808bcd549ae6ae30d448c90f2783978bf9314a8038f45c0da5983163bd26f38f559f59447e8cf004f93b6b5c8af7b09603db021d4bdfa641bd83926eae1709a7a427add14df90cb258c6d4663d4d29709da89c90613d2ff9334637d53ca89407804eb863f78e110b866af2734c980705d9f969730a41132e788fc9e426d0f68ed24157aaff0383438d2715262e9b8b03cff850ba88127a05a8b68ac9a5ae5b098bb9ba5eaadd71ae846b3c0f68db728361eb8c8ed899c77725afbdfabf93812c49cbf4ee64047a96ea71258dbe5be3f988029d005fa2d9fc6e1e53fbeb6888074521b972e2ac71b4f22b754fc743e0de21af1e2ab416b2481e03227a1c7d7ea6cac5bc37ee3597d3bf11bf13a688dfa3d9aeff1eb1a7fdfcf8b6c722a4853f7c2b2d31e0b2b691f4273d4793fbb7a00f27a25577bfcba95e60699c9d2a926e71d64f535b633f2fd03320b28fe86c6619c54b34e6caf8f5a71b8a144c9236bf07edaacb486ff8ac63173af099efe7c9d006a5bb756449fb32b1fbff2e315fd5e96b586bb922a9795e29ffe6ead037c556e1bf30e24afb344cf873201007096b6f687f157588e236b71ade4d9245d8f065f2e23b36fad798d0f5504ddf25b828698d0cbdc28478b20d692d2ab605797a67232b0795927d886de798f00b4e7c69517d62b748e62e01d53dd1e77ac9a1605c0408713ff309ad53ff8f2bef17f9074f01134374068bf1f5dc07125180b5ea6902ec2d55c7d6d5f7ed4ef8732f9d34b4627678611fc9579e4321cea012c4e457dee6a11c41bdd1eb965056e885757af389079a558434eb3d59ae56a232302759431172ecc88de1c5400265f0f47e21396e3c38e0ba022c3e55ee4b85527cf49dece94445adc740cd26c18004a1cb984cc1732a138844da1ab003f89c589b6f3cc10c99a1b0d87be763f83e1b12c6fa6938ebc55d2ba33c25ca816dde207f7186f0c70b56b33feb538eb31175fdcfb036e365087f1b630628affdbbdee20d1976cb009f32db5f35aadb8117aa02ff2da9bdeaffbcc8bf3412efefeb00365e5f1ea577afd6e1c3585c67ffe1fa120382aa54028dcae9bbd624432a6256687d05483f2611f1ddd14b40f66fdf547e7eba904a79bd27733c9a8fbfb01154dda3457c4eacff8116941777ec570ff040e217d648ea5076588a6417462481eba68ebc59af04ba49b92f70b68a007977fde48b94b0af35475ea19cbec92df6449b065880bf03452cb3b3582f3d1a010e585be6506f3e067226471a94ce46c515f20502b3866553c10f037d9be89ad5858d6b2d2d94c70159247f66958d0e841d1c5b4254809d52475fdf96d087c3c6647b86006147a9ebb3f52ea6f4b89d886725b9e9243efd95e434bd8dd785143c57c06863b68df8f832987eb0c730c8b96634c1f888da2ef420cb0ebacf81f4b25c65962ae40c09ac4b0b2d440e3bdaa7309d87a1fa6af1c2e13e7a63c253fae027ceb2067cef8421b62d205f5d37c7204eaf594b1b43f9d9b67509a6709df48769ab9e1078f9e59d7656ec2132b5ebccf297e757a052835fffe94ae073131ac49c4f4374a1904cd4bf3041b236b73ea19eaa583db577fe35ca0000000108676ef9ec3514fd5f004046001ae0c831dde6ac72f1337c9ca111f5b32afdf102d75c017d3bccb6fa89902b750b00c51bb226afa517754b72e962ff007c1c8c8749a21be1d8d94f7bf73437c010cd60e0bd4489ea77dbb530c7ba127c66c3d7bbc00c336fd4e09e1775c646dffaa8696f7b8b00bf91261fc5164d57a4b9652b7cff4e301d32224b4e48cbfca535b2070ac46181615358d87e244ba6e369f6719bd5a551ac05dc78c222fd0969d0d943cbfaa3570ec25ab2768e9679d1cd1a3528659d010c409a0719526c44e4d9915dc5b0618ebc9e35f06b31bfd8e01fad99dabe32f6bfa00b3a5db5a01920d6685c34efb958729ffc5acfe46b3605715149b65b2f638007885a0866bbdde6765992b9acce2f527de906443f8643845489f1224fd3bbbb3fa78ca4848fe0167ec7cff8a05a17eb7c7a05a80c3106647e5d9aae350f33d10f3a60ab1c705858323a8f610d98cc68ef3cea66eedbb788b9a3da873bfd44ed632aa952ed7bb2004f4502260cef0596ec6e82e7683bdd2cb1f63b01b3f928ffa86b89cbeee922f1fd192fea0bdd17cf62d14f06f9e27bf5cafec90ab26f103e1dcb96ae4335e444b1fbad294cc395c5dc3a1c0c1fb4078d362eb229c42bc42ff53115c51f137d75596b3e6d3d28974720d6935430054c6b630bade51ad508d31fdfd572bd37f70e3dc06021d2b0ccf91a7975aa501e152d62980f02ce0ee94b547a2fbede47cf1f5c0a541ccc8992dd006f77437ce6a6b1f4f91833914a1cc51acf9336a620c4a22073966cde3ecac3224941dec004e741e05c11b43796dc531ce33e7c9a4fa68fa689880842e37a3a04fb75f3fcee86813388df74d443d1c35d7adea290effa98309b22ceca9bc252ccb4c443733db691adf0af559a5b7565043f84e91c5ed9f79ebf49f0bd60b68a7b8730032574e8e21548204c75321a374bbbf822efb1281ddf32feeced4bfe22bcf7c1a309954c1e356175a8a1a1a074a22f4561acd872d813c88ea9f0f22ac8d7b7b2088bc8565e1c56dfbc84f57aa38c2600ee20e8736076a91ee73f3137e8da3fe3871587b8bdb0a08af40babbe5493f036b45eea837dc15f761d9475d27a512a2d9dfc1ccdb81e2b581f91a5d7fe67cf6955427315a4e9c158806e651e4acff40051cd8a44b0108876c82f7b4d69033bfd8216234de545bf8fb58e489bc74d366db5e48711ba7f317dcdd1708ed5de97468a6026e15bc68ab11efc90f5465b4466bf384a8cc95f9c7fff91d776cccceeae5badebc31c3516c93a7b4682212e5a4902a9fd0327234749d83c141db2eee9688a76f4361f8b6213c88ebde69ebb84488c9ab8f42737da123eaf39373ac687df65f817939296f5477f92ad3fda0effbdd5d0594eb59d80265eef6cfcaf81b386c9d03c205c1b6714bf31be15e8f871b4791aec10884938285d6b8c18a0dfe750b753de88a2d2d855b9d1a0068ac4d2ce3a259bfdd30414380bf8b287abf2a28de442552f1d70a0aeb0867d9c7ed4e9717565ebc6aca21d85d2845faacfd8fadb1a76d9a2cd619413e631666009085bc7dad7492654ec20431e37ddd55588d2cd4d256021547cac768dfe3f7dcc9a18f0c72a743de799e98398b9bb2f216aea727d240b2f52ee269f4df4b8d7bdb439f074e3ef179ae2ae44daba64864fe427574b659a5e79defaf43e45e1357e1ff48e28ba6384c559cd036c9229151f917865b5575cda11e1ee0690bfbdb628b9a17e9c37190102\",\n\t\t\tdomain:        \"signaler-pa.clients6.google.com\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[4]; packet 1\",\n\t\t\thexData:       \"cc0000000108a3e7f3133d37e2970040460094b49b4808cfaa190ce163e91b9d2c0105f36a2c93f670114f7b60598f03d6c596ceb19f410e9660903f590e3f25cf619e5c171001990b1d1b97f789595d3039e666345cc944894c6153ddd936992f160ae349757507f79fd0485766987d986518d9f19270a0021c52bff14e594c074f3c5664cf3de3b761cd36c16acf68565aaaeebf196da581533f19a464b22404b13b46ae3e4819bd4a7a85db6ddf379bb84f8dffcbb412c9ce405d8b4c98d303cb13df2e80a88ca0f09e2e2489c8d0b6d5ba9262b85869f8f989e9b82c4a270592894fda96bd27ce03e0cb4d4a4e130d9655e6da02f4348c949bc9b2aa609fdb4b9a0a3e45be25b18fbde569bb996d6419e98f2d9b7a0ca63f52f054b50771dc7c580596d86b4a94642be9a9b78a01ad2deae4607d8e0e25641aa8ed46b20fe027f4b9f77d1736aa0fec4f837cf5d878b3dcefdf3c6e82eb943dd022e98d623403950e6ea3addc0f93f92a422ed686b7beee437a4040f4a440dfd071d8c09f1344c28545b4488765a455e33e13d17434b9642dedadcb6a13ec35d51c3e7a03ae9a76cca6b1cba8312b7d8bae703e0a378300a17b7483d07893ddd941dd3e545a66faa0fabb4dc967c807665ebf4562fede176719d1a126f228acc0902e7235972a6db4eedd6547c38705629ee2574c5d2dd6c76c5c82e741f33291506e66a8df65ef6d1e7e6628fe4a4f5e141d482fc5f9d26609e64da8061eef5c0fca421d5334b199ca8270612074a1f9fbaad8b98ff7b81f8871f4ada6976f254e47c51e03d4c628beb3471ba375642ae0b41d65dec1419cd31f20ec779f5666717c1e7b4240fdcfa46a774234961083e1915e938ae0d41a66868b91949d856065c4e6813e0cbd9680a916b5eb78655dea9ad0f9c0ad0f5c244a72fcb8b589321519e34f0e6e25e6bb43abfa84a7241bc02555fa9060b9ef55f1e3a9dc3a575e16b23a36aabbd3ddeaf8f2516224179a4039a891e7f29631c2a08745bb184c66ffe98bc960e6c08a14524ae34444433591cdf7adc14419310b74594305f67c3087a2c21733e6e0be748e7af6fb6717946c313fbc0935ad3559e2d6323979cdc3bd48753b5a438605e15832efb8a0c4144060f41ed27a82dd067f2caaea3830abc97d9e080b3fd762aecbd58e8b2b17dae553dacdf3ee44198d2f19c0522b6a1b17923a210cf24902c5590afe808fd22e54e586399665d588a7febd0b402a4e6283679e1f95a2d4d7d3945e2bb8f44225ad8aa07cd07d3323ce94f39ae4c9466c05ceeb0a30981cea022d1bcab8a4b0c8e42e08211ee727728c74d7945f2350a149eb9cb7eb3a280954b64e612b53b19016a4c07427945345ffb86982c113ed797172ded4428d6b95b9ce64b48e98ab96421a179983c4a74b986f3f52d9a2d7fac8ea0955835d241bf4817a42950e2b298e51de20c026df81fdb0d28c68841bf62dfcfb4684def62c13ceddce9a25b446043056006ec8582aea14eee602eb2963f575dedfd2313d7d561c6ceac0d08c94645a222b25b7493542fee52c316f06f583612ab2ec3d420a01a61fe80b099386c2fe647292769d4571239592fe7e27f4324456ef894643f72ac450628cdcc9f376607b85f369a092c64d7d5a0559193e29cbc48e9ed77fa3fa05776d6169fbdbafa507db1e1d33a4550003a3a1a794b266e886f483eba76a8629d17d9596574068ffce61a52b209c21c77f5e7337e5541755c9f1c6b4\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[4]; packet 1-2\",\n\t\t\thexData:       \"cc0000000108a3e7f3133d37e2970040460094b49b4808cfaa190ce163e91b9d2c0105f36a2c93f670114f7b60598f03d6c596ceb19f410e9660903f590e3f25cf619e5c171001990b1d1b97f789595d3039e666345cc944894c6153ddd936992f160ae349757507f79fd0485766987d986518d9f19270a0021c52bff14e594c074f3c5664cf3de3b761cd36c16acf68565aaaeebf196da581533f19a464b22404b13b46ae3e4819bd4a7a85db6ddf379bb84f8dffcbb412c9ce405d8b4c98d303cb13df2e80a88ca0f09e2e2489c8d0b6d5ba9262b85869f8f989e9b82c4a270592894fda96bd27ce03e0cb4d4a4e130d9655e6da02f4348c949bc9b2aa609fdb4b9a0a3e45be25b18fbde569bb996d6419e98f2d9b7a0ca63f52f054b50771dc7c580596d86b4a94642be9a9b78a01ad2deae4607d8e0e25641aa8ed46b20fe027f4b9f77d1736aa0fec4f837cf5d878b3dcefdf3c6e82eb943dd022e98d623403950e6ea3addc0f93f92a422ed686b7beee437a4040f4a440dfd071d8c09f1344c28545b4488765a455e33e13d17434b9642dedadcb6a13ec35d51c3e7a03ae9a76cca6b1cba8312b7d8bae703e0a378300a17b7483d07893ddd941dd3e545a66faa0fabb4dc967c807665ebf4562fede176719d1a126f228acc0902e7235972a6db4eedd6547c38705629ee2574c5d2dd6c76c5c82e741f33291506e66a8df65ef6d1e7e6628fe4a4f5e141d482fc5f9d26609e64da8061eef5c0fca421d5334b199ca8270612074a1f9fbaad8b98ff7b81f8871f4ada6976f254e47c51e03d4c628beb3471ba375642ae0b41d65dec1419cd31f20ec779f5666717c1e7b4240fdcfa46a774234961083e1915e938ae0d41a66868b91949d856065c4e6813e0cbd9680a916b5eb78655dea9ad0f9c0ad0f5c244a72fcb8b589321519e34f0e6e25e6bb43abfa84a7241bc02555fa9060b9ef55f1e3a9dc3a575e16b23a36aabbd3ddeaf8f2516224179a4039a891e7f29631c2a08745bb184c66ffe98bc960e6c08a14524ae34444433591cdf7adc14419310b74594305f67c3087a2c21733e6e0be748e7af6fb6717946c313fbc0935ad3559e2d6323979cdc3bd48753b5a438605e15832efb8a0c4144060f41ed27a82dd067f2caaea3830abc97d9e080b3fd762aecbd58e8b2b17dae553dacdf3ee44198d2f19c0522b6a1b17923a210cf24902c5590afe808fd22e54e586399665d588a7febd0b402a4e6283679e1f95a2d4d7d3945e2bb8f44225ad8aa07cd07d3323ce94f39ae4c9466c05ceeb0a30981cea022d1bcab8a4b0c8e42e08211ee727728c74d7945f2350a149eb9cb7eb3a280954b64e612b53b19016a4c07427945345ffb86982c113ed797172ded4428d6b95b9ce64b48e98ab96421a179983c4a74b986f3f52d9a2d7fac8ea0955835d241bf4817a42950e2b298e51de20c026df81fdb0d28c68841bf62dfcfb4684def62c13ceddce9a25b446043056006ec8582aea14eee602eb2963f575dedfd2313d7d561c6ceac0d08c94645a222b25b7493542fee52c316f06f583612ab2ec3d420a01a61fe80b099386c2fe647292769d4571239592fe7e27f4324456ef894643f72ac450628cdcc9f376607b85f369a092c64d7d5a0559193e29cbc48e9ed77fa3fa05776d6169fbdbafa507db1e1d33a4550003a3a1a794b266e886f483eba76a8629d17d9596574068ffce61a52b209c21c77f5e7337e5541755c9f1c6b4cd0000000108a3e7f3133d37e2970040460094b49b4808cfaa190ce163e91b9d2c0105f36a2c93f670114f7b60598f03d6c596ceb19f410e9660903f590e3f25cf619e5c171001990b1d1b97f789595d3039e666345cc944892d4ba45357f2ca515d03b90820bb91c531a4be27266fda6022856da650cfb9c34139e8a3180e93cb73a6864471f849bdfa26c03e30c0e4d00309207cd46fb48887f60d7c51b208c247d1b311b35da70dd682cb1f7ae6a64215e5fefe25249daf308083837a3898e6052ebcf6cef3cb8e987ee1eb5ea797642d76391ae363b8eb2409d7486dd4a67c9e9b755376ca61009cb853835850e4fc1844f8e9eebca73e89317003482f70c4795ce9e2724c6d62172e010233e7bf203dde6eea9976f29896df562e8640a4ed88b5b3dff50296d0db43885f162c588d72de357c2ee049d9532642576de64d4e13cce77208e0aa9cf9838166f3375a968a5a6a01cf066ea0ca27fe4471cf0bf7eb36227867928076985588d05692d3f81d9a1158d150b2701399ee0a32693aaac43c27b76c657343b2a307e7018c2e9fe6317ec09f9afb762075430140b15016ae44acffc7467f4b1cec619942e916047c2db27f89742e53856d8c7c098beaba710340674a3f8455ab38fa2a4156fa3a45dffca1e20ec86ae792988dcb52bbf2ac97ea878e80511d3e4e70ece4b2816401ad450b9d13a1fecd7a5a363dd120285a972d52c06b632362cb8f897f799fb8342850b6670eb5083347347bd48b559f118839aa627598379963ecf18c2a900399ed936ab77ebdf95bdc5eaa75a903005e38dd99362b3d99f07ee2aea1a0ada77ec5ba76a7da2a80b672f4709bd32fad36787e37467e75fe594a24b402a6fa7e858c2abe9cffd9e885cc7091c035e4354779fc113bc084b5f6fcbd7bc618e9cc15205538cf781c96b658565cd8e39d95001d085d52f87a2970eb8f72149f4061d629ca0a928442b586aef9105326e13c015ce2b987c442b574180550302c48c2cf763b45492e25adee42d23bb2608e284caea4b3a28b77a20768dee6c0002b16e5d714eca22ca0c58195e03951b9564079ffa61c81afb2c5783ea2b8c0605d3994cfaed3c33af2279469c771269174fc5879b67617f571eb376161241ca5a89332074635413661b7fc5f925d86afb56b296dedce33d2b5011fc3b85cfb769c4a1cf5d6217ba3db367ead2159a310cd398b31df83f48f722a1df6c4b6604b2e288aa57cbc9afcf42764ccbc718a3ca42895b8e8287d632d2dd47d933a7472fd7be596c4241220917e636a44a139ec16600d74104fa6055a77c34bdbe14fb1ee56b4084d1d2dc1d56f8d636a8393385fcb4916670eaf8553b2126b12c8f187f58f00ba9bdbe8f06662688b9b8c7327b2d2c2f1443c8b87930d0948c3db62c8becb5b38cc7e484be184400f8c6293568abf1714e2832ad2c0aeb88dde64686fa7b4cd8c452b7365e70221e62dd971db057d4f8eed86e6802bbe8f56f75a8fe65d8216c81265e514042dca73f20a50373fb32e9bd62b741021337a3200b5f0a1b1329e99c57c75a60850f6c2f82cbeb9001edb54d985b7d5cd5957b31f79ee77dd8e1ce5e70d50806273885886b93a12c3dc0f211383d180a475d65bb54b0c5f761f456bfc7d045acd2c7ac648f3bf27bdb52218be48cdef7aa1ddc93e3ce11067ef4819796a4e2a30459c879841789b358b9430368b0910d6e6eaa14894f36b12fe9dd2bd1a20ddb6b12ab8fcec7d0629c9a7\",\n\t\t\tdomain:        \"play.google.com\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[5]; packet 1\",\n\t\t\thexData:       \"c200000001085b15f32cf8fb5a3f00404600f909e38ed514455146c4ab8e53dd225a775dab2a06eaf73c2ec6b36c95c40873910eaa7e424e1b1fcb15892cf9e37a0ad2a1b67e9e8c6ec0c3a99e3d8775afe1e0aac824704489c14d28b48268df096f2db0c040c0ffdfb1817c4e1dd32005e87a74104515069ccd1b12f6fa3f657891bca8f0d27f4bb42b23c8310897ac09241f511888ac2e431e3ba8d9114b3f8bb1470324c8a0815dd1589686d058d03fa2669ad7367dc8877abce4700ae999112eb7cba755b20b7db66918021a36a66c60ecaaeb3e0859e3f50d14c1f8c3f56966043f437bfc16098deeb02e2236cc42d2f2802f4082bec30afc347a75f78c5e7337c52502e8e8d945bb4d792ef69419e57ee4e8f3465de4d0a49955787dff0756f1bcd98268919c112ae5e87ec333d590988e111724627143bf5a0aa69c77ee322b1240a65718e0033c4b70c6a5dc2d3248aef71598d73effa0eb397b79279ce846d9c2556fca28595eb3b196f40f052f0127711abbbc334571b8de67f7d39bd9437f9196bfe0650c4972462473776eccae42418b16edd5f45414e1a5e13300ae6705a49ef68e1748e876ce04fe611c973f94fcbe18b0c1a0ae506ce6eac268021e9744899c33ffe44dabdf1ea5413c20ab1013326255ae6df2d6861a7e914001e231ce4065e0d89fb439afb928d660cea26f99eafe65c9f89c5639f3ba8ffdcdf7a777d7859ca948f123a5c4f1491e22803754373ab0f1b605ac09ca6018c6acca7f52aad786ce96d9ae28d09a77dd88d77f23e2e33475973a23dea914fb049662800cfbed416b61835bd3bc22dace817b3b271c816ccf9603de2209067def5b8747112c49659cf6c5281402bed38a4176c0331e0841592c503d342188ac97266866a102864f76b0ff2adf781e0af84ae725fb6db418545dbb70adde5d4ea6d87f8c9b64b579d4f969830056dc0007cdad648035ccdf2c24264905422dfc1a85ac7f519f475351e046a27268312d4ab6a66ea55be01226fd5dfce06f804cafe85ad997946db3d029ef28ebdd36103c8fc05b522c1d4debe034523595cef576c771f0848a221d76c63dd044ea61a3adcbe5d4f0ebcd0bb631f4db1d88a609a1c2a6cc3de29ccd8a40e8a770d806abdae300cc178a32b4ba979652d0b8849dff252571f0b09935744c5b33628a190ae2eb43543ed8dee8539fd464abfcd3cf826aa18c4e84cd11975c5e3bfd0827f7cd211a2084c8626ed4e32bb9877f4801dabe695132b835e335563cb2f4c3e9377cac57f7766a10620f1c57c9f485d66918613daf8b257035ec91481d0076899c45abdbec38b63745428dee481c1cba81b0ab6e7efd6b0c431e018b6503cb13d4df18dbbff195bad1063d59ca5066e0733d3f499109111d22304066e7656e755518ceb6d862095ae54e532fad82f6c21c0c4d776dfecc516b00fa59e117e79481d3fd386c9b0c4d6fa4ec295a5b5b67207c3db20a206e0e31dad2c8dafb38e35dde331730bea33af32893653763e82565689009265db76b3b142d60f302a9545d82900aa6a017643c5c60583aefae43066ea3908e299612b078d06f102280dce4429461a42d80acdb6279c7aa190f9a8f61b485ffa430cbef199e1732da3d9b96cfac7ecc3b4959b86260eba763abb74c249180f67997967e716c95788bb6c04f182f40b795a929370c7fc72f1785f94cd36e3ddaddc15c7adb226cc131abc1863352caceb541d3924094db9c8a1c21c21640\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[5]; packet 1-2\",\n\t\t\thexData:       \"c200000001085b15f32cf8fb5a3f00404600f909e38ed514455146c4ab8e53dd225a775dab2a06eaf73c2ec6b36c95c40873910eaa7e424e1b1fcb15892cf9e37a0ad2a1b67e9e8c6ec0c3a99e3d8775afe1e0aac824704489c14d28b48268df096f2db0c040c0ffdfb1817c4e1dd32005e87a74104515069ccd1b12f6fa3f657891bca8f0d27f4bb42b23c8310897ac09241f511888ac2e431e3ba8d9114b3f8bb1470324c8a0815dd1589686d058d03fa2669ad7367dc8877abce4700ae999112eb7cba755b20b7db66918021a36a66c60ecaaeb3e0859e3f50d14c1f8c3f56966043f437bfc16098deeb02e2236cc42d2f2802f4082bec30afc347a75f78c5e7337c52502e8e8d945bb4d792ef69419e57ee4e8f3465de4d0a49955787dff0756f1bcd98268919c112ae5e87ec333d590988e111724627143bf5a0aa69c77ee322b1240a65718e0033c4b70c6a5dc2d3248aef71598d73effa0eb397b79279ce846d9c2556fca28595eb3b196f40f052f0127711abbbc334571b8de67f7d39bd9437f9196bfe0650c4972462473776eccae42418b16edd5f45414e1a5e13300ae6705a49ef68e1748e876ce04fe611c973f94fcbe18b0c1a0ae506ce6eac268021e9744899c33ffe44dabdf1ea5413c20ab1013326255ae6df2d6861a7e914001e231ce4065e0d89fb439afb928d660cea26f99eafe65c9f89c5639f3ba8ffdcdf7a777d7859ca948f123a5c4f1491e22803754373ab0f1b605ac09ca6018c6acca7f52aad786ce96d9ae28d09a77dd88d77f23e2e33475973a23dea914fb049662800cfbed416b61835bd3bc22dace817b3b271c816ccf9603de2209067def5b8747112c49659cf6c5281402bed38a4176c0331e0841592c503d342188ac97266866a102864f76b0ff2adf781e0af84ae725fb6db418545dbb70adde5d4ea6d87f8c9b64b579d4f969830056dc0007cdad648035ccdf2c24264905422dfc1a85ac7f519f475351e046a27268312d4ab6a66ea55be01226fd5dfce06f804cafe85ad997946db3d029ef28ebdd36103c8fc05b522c1d4debe034523595cef576c771f0848a221d76c63dd044ea61a3adcbe5d4f0ebcd0bb631f4db1d88a609a1c2a6cc3de29ccd8a40e8a770d806abdae300cc178a32b4ba979652d0b8849dff252571f0b09935744c5b33628a190ae2eb43543ed8dee8539fd464abfcd3cf826aa18c4e84cd11975c5e3bfd0827f7cd211a2084c8626ed4e32bb9877f4801dabe695132b835e335563cb2f4c3e9377cac57f7766a10620f1c57c9f485d66918613daf8b257035ec91481d0076899c45abdbec38b63745428dee481c1cba81b0ab6e7efd6b0c431e018b6503cb13d4df18dbbff195bad1063d59ca5066e0733d3f499109111d22304066e7656e755518ceb6d862095ae54e532fad82f6c21c0c4d776dfecc516b00fa59e117e79481d3fd386c9b0c4d6fa4ec295a5b5b67207c3db20a206e0e31dad2c8dafb38e35dde331730bea33af32893653763e82565689009265db76b3b142d60f302a9545d82900aa6a017643c5c60583aefae43066ea3908e299612b078d06f102280dce4429461a42d80acdb6279c7aa190f9a8f61b485ffa430cbef199e1732da3d9b96cfac7ecc3b4959b86260eba763abb74c249180f67997967e716c95788bb6c04f182f40b795a929370c7fc72f1785f94cd36e3ddaddc15c7adb226cc131abc1863352caceb541d3924094db9c8a1c21c21640c900000001085b15f32cf8fb5a3f00404600f909e38ed514455146c4ab8e53dd225a775dab2a06eaf73c2ec6b36c95c40873910eaa7e424e1b1fcb15892cf9e37a0ad2a1b67e9e8c6ec0c3a99e3d8775afe1e0aac8247044898c283466f3b200164ad9b30e17b425e07f6722df94b9a77dd555fbf25e5b0bae4fef254daf03f156b78afd967614c78208deaadef3552040c055487804c047604c5d6846e67d33e5fb5f743a81f688220d6f4c87091a860885af95d2db27e9c5dd2361f8196f5c1ade8fb37e159980547c38c6a6ddd0e055fbbd52bd7615615ffca15a0c144899d25f21156c53cb3922d2ccb83073e26074025a3c39f64a67dc02044ce0d630d9120041b2233bd26282bd2d7d1a81d486b64cab6dba7fb4375e200f53523f714a066b96769f9b1dc7c14353fc1faa51c0aeb99507ff3ae90ad6f4bfbc9b9ea00a87f8bf8e213a84a9efe2ce624e629261d93c83642df97fe146bdef478cc92bba387c9b524ac83cde55ad8f4a4d8fb3c09c8245a50ac16ebb67d651110a10ad1f7dc74ed32b9d644bc4b229c56942072aae5c311059165ef6839e7cfa0717c7032b667b8618527722fdc10a4c0ea900e9b414521b89ca83f253ea414a410a8b0b13da25c4b618f2ccd79e5d2a7579b4c431107d56ab8df16125bc25673181a6bd5abb09941515806fda32659e5281457d8c093314da0087169781b306f348d3994d84bdef936bb11355c41ce02c359174dc2a366509c130c96ddf5f156e0eb7912ac56611cf4f59ff3a8785034e81738a53ebc7fb60aaed709d78980d39822aae7e9a9d985aee6c11252a5e984ff1d167b9d4dc5d02ca8ba1167422dfc0f68b3b5902f3fd032204a5020b14a288ecd845816575fca7ef13c85765385e9964a9f0f6e2e5c4e600b190b275cd91ee8a38ecf73d35bad7371c15fdb73059afb3178786e76750845232ef787767c6985ea9b9ed9ff73430b7afb8e0b66aa210ab1943b606021bb8f50c55534e1014003d947533c1c0bd0c16dfaabf4914497583b818e643a2d8bc32d33a07133b4eecf8bcbd802dfaae894ebd473338e074af1b3672c6f03c55e6c3885848a4379cdb5873d9ff3015f9ddf8d954b3cb4eb9f56f2e8ccb519dcba72c5832146011068d3074520370fc8008b62f3fdd65978865df1a85e58fb62315dc617a3367e6c9564fbf74f5d5015932d435f3aaaaed37bc7d14d974c0e15d899c684f17db3b4790bdeba076624f4f45a0cc1c6c078a527695110842e837ded310f8c6ddf69a18e455f09130d4b4136ca6d02eee0f103b9923adefe8cb36b8fd554a27fa7300c4a6173835ca2c4d38cc11e3849ebbf73c475b7d0713caed6e70f55b34ccffe03978f231d5f92bc1e2da7ce589e2cc2cd76183394469fa537cb927db14a383dc589cf10819eb90dbdb7981cd1eeb3a284c9ce55ed859fbfbe73ca22328073b534bc81d319b5fdb97e66d532b5ffc19516b4c70ecb68f963b053a82915f3f74c8809f082bbc89d97dff9e8c82a65ed4be8e2975b82f0b7c73f34797c61db4ea8ee273116564288563a71747cf31568317363dffdff1d9e159b2f9bcd3fe5be96a9c50ac885493cc16234c58cdf471381664c1e3b54d147ea5c779c9c9b06d5c493231d9c03b50c6cba315a5ee766a6e8578105306e0911270d347e8ade2b354037f507e4993708191d32b51a79f023f4fccabb7d2ac262004745cc528b8a6dc1d8849e9d4a0152ab326b1eab8a505eef65076aca156f7b9\",\n\t\t\tdomain:        \"optimizationguide-pa.googleapis.com\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[6]; packet 1\",\n\t\t\thexData:       \"c500000001087e13dee82c592724000044d0a17c2a188dadf8c82a018cabf25d25211d9987189b2900ad36a8fa366990353230bc8d41ec5543e91e119a0fd35f72d320d670074d02f412a5418c9a3e25489db73b1f5e5ddb8fc8d6c51fd6a12dcade71e1fc8e89d2663e206185247847d218d695793a8a1054afd18d42a4feaa9bb5fae7651eab0bc5e9aa91539c311cbfd17f46d8894ac45eade657bd456ee87b4475f5e84a2c07571b082ecfdafd1a660075692ae22048d163067a60e06cf879eab20a464c6e44b242da94cc6b92e064ee6a52cb34c115056925d4615e51157da4e88f019ae144040827cf2cd2806b3f4018cde5f42e041ef55eab35fe65ff79dba1ddad5aba6221e93d4555510176463888666b1d1f53a2f3c71e8872b05717002717edd3a9c5c651be47a663ee877962623ee92310b2b1dbcd75ec2f31d9e2f0c87affe3ee083aaa0868c03c9b4e78660d8afa4e936f81dfcb2fc33dbc1ed3828195c8de3130a2757641652de23d270c8a25976546c46460d9635f499a650475a4288d1c62241f65ddd346e2bde3f4fb5fec76646f51d829b55e16a0975494c11101abb504f5398a761d59355a4843e14bfeced024239ca06717d27e932df5e050d7c54c9b3cc937f6b0731f0b1dc312b14cf4388bbc0c601abf8ca16ed7d9b597dd414156d75b486ac256f8989a072fe680961ac7446201aca359337ef0e58ffa2aa12bb3c9bd1a9f6237b5c37880cd450d8a95db7c1463c53ac4bca174f278be1a99fb75ded9fe282e0e44a03b51ad943cf8970e4ca7f8567d9a3883d07bc0ff6c11639ebc7614fa88496488cb2a49a158d0f008a6a03caf97d77c3d03a98c5a611b04f865d134e258f4094bd220e00bde789c3b3158e622d509073179c0725c7a14dde1cd76b4c6b61fc7ba87f05742f081a5c42dbe83445132b8ad1539ee36e221a9dc700ccab55e34688b082a1a8edc48335760f3d5fb7e92b548e7a09d11f15b97bda08ebb5ea355e274587720d7c1189e9ffbb00dba3c13da94d646e7742e3641d59aee50a5bd400cd992c5614325d8f1ff3529637c3f60adc1682fa6b5e26f3d52de73236680abde87ead74368e05c600a2fd291a592be00fba64eea78c9e1f5e55e61f691b3eb0ef17b7bd11f06427c89c4c930d18c0b28221a6c918322cae56397c7e2978bf253e7f2d987f4cbe66c44a96b45cddb9d5f6dae47bbc9e1f9dcbe6280e50c5dfc91efb469f408b28fed49c583800009dc8bfb4bc42174175df987a3be833582abd9aa09ba0425973de2ea9a4149a81ae1863e0c9f1b1075c26bf965dcbec2bea47ff6042495ed715b65fdd3266800994463c95960dfb6ceadfa07d58910d329fa7ef7a8f14da4a6d3b09faa5b17cbac8481ea46cbfcfb54f660929e268bcf2f86cd88a1b065dcc27f18110db9efb6fcf1eec62874ed3657b1d43419f39e785c510d239c021b7e97d258d789c90d39b434f1667495bd4f5dc5e0eb97df376d801cced0da3a85aab6ca12893d8622314b5d530f28ead33075891d0ee553d5bedcffb20fce9933ab5e117df816c96398f6a60c9e6f5b9182fd7d58869de01635b2c178ae7738beb81e318934ecd752393129fda6833718d6984d8e1a8d7bf52e9d93ad0902c0fbe3e66ae8305e43363d8996626646a684bebfb1809ac9823be750e84308ef573243b884d09ef294094ef256cfc13cb0fedb1e095ff71687c09a767bff308e562e1a6ce9964014a7afc8db2481d8d07486\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[6]; packet 1-2\",\n\t\t\thexData:       \"c500000001087e13dee82c592724000044d0a17c2a188dadf8c82a018cabf25d25211d9987189b2900ad36a8fa366990353230bc8d41ec5543e91e119a0fd35f72d320d670074d02f412a5418c9a3e25489db73b1f5e5ddb8fc8d6c51fd6a12dcade71e1fc8e89d2663e206185247847d218d695793a8a1054afd18d42a4feaa9bb5fae7651eab0bc5e9aa91539c311cbfd17f46d8894ac45eade657bd456ee87b4475f5e84a2c07571b082ecfdafd1a660075692ae22048d163067a60e06cf879eab20a464c6e44b242da94cc6b92e064ee6a52cb34c115056925d4615e51157da4e88f019ae144040827cf2cd2806b3f4018cde5f42e041ef55eab35fe65ff79dba1ddad5aba6221e93d4555510176463888666b1d1f53a2f3c71e8872b05717002717edd3a9c5c651be47a663ee877962623ee92310b2b1dbcd75ec2f31d9e2f0c87affe3ee083aaa0868c03c9b4e78660d8afa4e936f81dfcb2fc33dbc1ed3828195c8de3130a2757641652de23d270c8a25976546c46460d9635f499a650475a4288d1c62241f65ddd346e2bde3f4fb5fec76646f51d829b55e16a0975494c11101abb504f5398a761d59355a4843e14bfeced024239ca06717d27e932df5e050d7c54c9b3cc937f6b0731f0b1dc312b14cf4388bbc0c601abf8ca16ed7d9b597dd414156d75b486ac256f8989a072fe680961ac7446201aca359337ef0e58ffa2aa12bb3c9bd1a9f6237b5c37880cd450d8a95db7c1463c53ac4bca174f278be1a99fb75ded9fe282e0e44a03b51ad943cf8970e4ca7f8567d9a3883d07bc0ff6c11639ebc7614fa88496488cb2a49a158d0f008a6a03caf97d77c3d03a98c5a611b04f865d134e258f4094bd220e00bde789c3b3158e622d509073179c0725c7a14dde1cd76b4c6b61fc7ba87f05742f081a5c42dbe83445132b8ad1539ee36e221a9dc700ccab55e34688b082a1a8edc48335760f3d5fb7e92b548e7a09d11f15b97bda08ebb5ea355e274587720d7c1189e9ffbb00dba3c13da94d646e7742e3641d59aee50a5bd400cd992c5614325d8f1ff3529637c3f60adc1682fa6b5e26f3d52de73236680abde87ead74368e05c600a2fd291a592be00fba64eea78c9e1f5e55e61f691b3eb0ef17b7bd11f06427c89c4c930d18c0b28221a6c918322cae56397c7e2978bf253e7f2d987f4cbe66c44a96b45cddb9d5f6dae47bbc9e1f9dcbe6280e50c5dfc91efb469f408b28fed49c583800009dc8bfb4bc42174175df987a3be833582abd9aa09ba0425973de2ea9a4149a81ae1863e0c9f1b1075c26bf965dcbec2bea47ff6042495ed715b65fdd3266800994463c95960dfb6ceadfa07d58910d329fa7ef7a8f14da4a6d3b09faa5b17cbac8481ea46cbfcfb54f660929e268bcf2f86cd88a1b065dcc27f18110db9efb6fcf1eec62874ed3657b1d43419f39e785c510d239c021b7e97d258d789c90d39b434f1667495bd4f5dc5e0eb97df376d801cced0da3a85aab6ca12893d8622314b5d530f28ead33075891d0ee553d5bedcffb20fce9933ab5e117df816c96398f6a60c9e6f5b9182fd7d58869de01635b2c178ae7738beb81e318934ecd752393129fda6833718d6984d8e1a8d7bf52e9d93ad0902c0fbe3e66ae8305e43363d8996626646a684bebfb1809ac9823be750e84308ef573243b884d09ef294094ef256cfc13cb0fedb1e095ff71687c09a767bff308e562e1a6ce9964014a7afc8db2481d8d07486cd00000001087e13dee82c592724000044d0b2c403d66eaa310a954540668e9edc4b17a321446ac931672ca3d9097b2854efdfa7a8f610be76592b56ef9154b9ab42f4dafc575a9b9572433fa2bba180194a32a72a92a42560ae840508317cf34015b1d7d88a633588ce4333cd12bec1f9d53fe905130eb136852d4f405ef66254a0807640fe415e6d667b9f5e2148826d5a6c36b3c44b822cfb7a1e76954610a522e1bc9703e155f8f1e0c705e411d09c5dec1183c61608174767408558da03290f5536682373a09c762deb829dfe3ee061ac9f509a2b3d20dbb77f262e9ba8a5ff50f895165742c90c4ff48eb0438d5ff8491700f97fbfced20e8e95ab9b67078ece6664527eb8a4e78944c07c6bc5c48776e141418c3b5a0e58d24114b7d65a83619525f5a10be53a55f6e3e65080cf42109aa2ec166fbb9079444ea6c809b5062227fed7cc81609a6d7ea471bc82cd0759a354aa896c7a8242c3d8c845aa52225bbaf7546a0de6510189cd2852f7b70a29687eee6a3a99a3a288f3c3672a72dba2843fcee320e18ea906adf1629cc7f8220b6be890a8d37f4093022717d8c6d76080d07798ba74e9cda1a4c26189c367a6d12da14621b93fbfd2d2365465350b5864e4981955750c6d4038b74644827cd60c8cad0d6a18869f99d61b214becedd8f71a476c2a1a0ea8fade7b15de6ee522b6b90d0cc4e6d40eb85d9b5097e2969e85eae6710285016ae47fbc858d63e13d882721aaef3cbe0a6e4c4910e8b3e465fa66a738d8979a444b4a4804baf5b4f9a5fc8438f4991c32d280c5d273d0a1e0d9eea30d003162cd313780a5b000f70fb7797e6111b354c3deac241cb816328b77d71584852966392cab9b1a3d64ead878a2cfac085452397ba17b5cddf96415c31846a80fa9a5a21fd5f9963324ad75505d4b5c70903ec3b5f91c3460d88a7d1c6b111a5206d11accac8a2115e1ce8e834b01f48e041dcd71585c6fcdd3a2e83ec2ff1a2b85e75134ec966afec023d375a2a8bcd54ef0c42d50e20c6cd9b9fdab63785e62c5a4ceb8451c560b647a6cd87698e56e0be4e2990337f45f1cb4c73f0dbf98da1158d75df6eb60ab7a61d836c7b7f3e5c809b850a5cc5369ddd4e455da5e88179e932311873d177febf6226a378c72324ea710e10ef74f6462e7dba25b7df336cb2947df749ad0b8455f3c5c9ba5c0c8eb1e665fe50451c928ce87cc81a0ae2102e7a4fc297392818db885943112fae3c4547ea48c89ef9cd44c2edba4856dbc72b956e4fc6a875bdac57b1cd2378ddb9322e5a1d1844977ff2a6e94d8a00f4ef0a7ef7949f0840c6cc781252830c70d37df9846a69eb95bc733ec420b7b3724572521496da27bba1fcb73c28eed1eefb094283ab01284ebd82004e0be9977f23fbf135b8162ac41a2bf9e1b941b05cb52c80279775070cf2f851bb0168235c97de47880d2d51f1d5ba8327af33f16f49d44326b0d08d20ca7880ff88a14d3201452d7fd452b24fc3b69b89c5149795bc4d0bf51b6ace2c9146048e5ca0259b4955a069a16d5a73e72248cec44b2ca542dcd326aca3ba53bd48368719f44d53eddbace11ff28c11c7fd90c38966752532d3f81325d1682839b2da3d69acc85275c71ef3b10c4d983cfb0cb464f554d9360b0d6ec8fd25b85824350866207eef9e2c66e43f5d08e86aa6186b36d9ece72f56e06ac8e51a1b53fab4cc496e69307e9810a9b5cf960f306ec54131dd8e917a5861ba92f6885e41993\",\n\t\t\tdomain:        \"lh3.google.com\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[7]; packet 1\",\n\t\t\thexData:       \"c9000000010852e6d0c4f5f177b700404600eae9a1449fd711efdca846f59b6c9975a2189c1430f740e0e0eb4d50be086b798a47e037ee6afe9785b45df6302cfb87d39b9c03d0712f11793adab33e74904249722c4a1544896cd650da62160bdb601194f82a98c06df2546b79b5bab9503eafdbfbcfd1128204b52c9c5dde4d661cb84f2e015b7de9e10109874176362b7f06d4b49d850f2dcc8485aacb82fed8017fe454a1c31cfb82d392d6894081fa3ab1e5f2b71df79bf5a52a68a90d4752cf441220acbb274e3473f65c1a6e4cea3295da23e4a07818d5ba80b0668638c6ecb0b3ee12ecb8bed4ca32e27f4eee7376010b3905b8f7055728b8b1eda2e2707338b86f9a6115a23b29a010769d9a0e19d2bd06b81a507c4775d7215030862a026f168664255912c321669a5eff84ded4fec82af394cee909110758e5ad4de945236001bc0627d8e88ead3c3a6790d7e50887ced96f8380a80b11477147abc24dc4963c1907a07fec13e3cd8d5bab83cabd7cf8bb66af4911dfcdaf0f13aade34865b0740bebe4b4a482f1e3105bb9c3e5fece975187fa4eae5cb8fc868376b8b8329f89e407618aa5265f8ce73437e9f2714d752dad78025a38eaca1ebd6617a9a8a0bdefa8b3d3daad006978ba85d17ce3269da06be92eec61e4c5c99712517f01588b0b7deb4d7a75cadd5643daebc731f4a7bfaf17e8f002721b054a5d6d6ca7a3430c9480daff6adfd0a5480968b4fe0ee616d2e1f2f01268dcda2ee523fa593a0c83cbfc051dd7d503d48b152eb53894f79d4f6d38f05af35b287d16df579575b755f36e87b6df082108fda812195d5f60d2ceb59a54f09bb270d660c1d923348c7fdad97dc081e749a393af83bbcdd3c290efa19bce1a0f68dab0061df9dbee778dffb7754db10cbcc354a4b66c614e29d216a3a45a38594d337f775cb88c940924734ad52f8856606147b14ae2cf2990e1c401d79d27e4d6723afa4047454580698b9108b952b78b6bfd31db3b376f0879b2a0d8949b8443ffb9f7eb84b8024c620680481944312e86d183735effadab2d9f144a72d170fb035ed230f1451c94cb3f39bc28929e7480b22bbf6c906070eda03884cdd7ada9b10b7af27315c3bde8b4a273d46d1f764669aca152120da3e0a4fcf1be84b4f3cdfefe235a16598022cc74ad8f32e78aa06eb74ef61cccfc82ed35bcb70f008368ccdc17a6f64a316fcc9f006f307fa7a1a50f28c343c4c93a39df73a0d0a6dfc6f8ab10e966f738bee331e5a29d91ed993fd2638f0ec9d0ce539552b0a312fb85ed5e9f392fbc76a6164298b9de2c47dcb21a895957e92bc1270dfef3f00f44cc42c5f5005132dd030f9aec804045731c6a3eed3776beebe9451488932a1172b979f371aa370308037e57513a8fc9dd03d63fc2e5f7dcd683de26e116ea11a1d3b5e61fb5bbddc98e4ccd20be9ee71c02cacc95cbb17dd404558f586d4f0334bd12fc0a584d29eef3b4c2ce3f87babc462b6d24ca10aa8f1eb1abbd29d11ce3f1c92426c4950e53ba6c914cb4bf0bb1b44b25452cafbf246b76ce17f829bef3178174fbac4f932e6ac18e579fbbf8790611187c6f01de70fa82a21e979c90eed3c7f7b7e416491a000b5f2216e54858fa61893391b115573b2b960a0f7dc1e2a703a6f38485589c9133b4509fb54b4a602cc7d3341298e8e5da88d5e06aad28738650c08d8c71239735375e5bca7ea91dd78d748d65af598192317fd69e03daeecc99b8b\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[7]; packet 1-2\",\n\t\t\thexData:       \"c9000000010852e6d0c4f5f177b700404600eae9a1449fd711efdca846f59b6c9975a2189c1430f740e0e0eb4d50be086b798a47e037ee6afe9785b45df6302cfb87d39b9c03d0712f11793adab33e74904249722c4a1544896cd650da62160bdb601194f82a98c06df2546b79b5bab9503eafdbfbcfd1128204b52c9c5dde4d661cb84f2e015b7de9e10109874176362b7f06d4b49d850f2dcc8485aacb82fed8017fe454a1c31cfb82d392d6894081fa3ab1e5f2b71df79bf5a52a68a90d4752cf441220acbb274e3473f65c1a6e4cea3295da23e4a07818d5ba80b0668638c6ecb0b3ee12ecb8bed4ca32e27f4eee7376010b3905b8f7055728b8b1eda2e2707338b86f9a6115a23b29a010769d9a0e19d2bd06b81a507c4775d7215030862a026f168664255912c321669a5eff84ded4fec82af394cee909110758e5ad4de945236001bc0627d8e88ead3c3a6790d7e50887ced96f8380a80b11477147abc24dc4963c1907a07fec13e3cd8d5bab83cabd7cf8bb66af4911dfcdaf0f13aade34865b0740bebe4b4a482f1e3105bb9c3e5fece975187fa4eae5cb8fc868376b8b8329f89e407618aa5265f8ce73437e9f2714d752dad78025a38eaca1ebd6617a9a8a0bdefa8b3d3daad006978ba85d17ce3269da06be92eec61e4c5c99712517f01588b0b7deb4d7a75cadd5643daebc731f4a7bfaf17e8f002721b054a5d6d6ca7a3430c9480daff6adfd0a5480968b4fe0ee616d2e1f2f01268dcda2ee523fa593a0c83cbfc051dd7d503d48b152eb53894f79d4f6d38f05af35b287d16df579575b755f36e87b6df082108fda812195d5f60d2ceb59a54f09bb270d660c1d923348c7fdad97dc081e749a393af83bbcdd3c290efa19bce1a0f68dab0061df9dbee778dffb7754db10cbcc354a4b66c614e29d216a3a45a38594d337f775cb88c940924734ad52f8856606147b14ae2cf2990e1c401d79d27e4d6723afa4047454580698b9108b952b78b6bfd31db3b376f0879b2a0d8949b8443ffb9f7eb84b8024c620680481944312e86d183735effadab2d9f144a72d170fb035ed230f1451c94cb3f39bc28929e7480b22bbf6c906070eda03884cdd7ada9b10b7af27315c3bde8b4a273d46d1f764669aca152120da3e0a4fcf1be84b4f3cdfefe235a16598022cc74ad8f32e78aa06eb74ef61cccfc82ed35bcb70f008368ccdc17a6f64a316fcc9f006f307fa7a1a50f28c343c4c93a39df73a0d0a6dfc6f8ab10e966f738bee331e5a29d91ed993fd2638f0ec9d0ce539552b0a312fb85ed5e9f392fbc76a6164298b9de2c47dcb21a895957e92bc1270dfef3f00f44cc42c5f5005132dd030f9aec804045731c6a3eed3776beebe9451488932a1172b979f371aa370308037e57513a8fc9dd03d63fc2e5f7dcd683de26e116ea11a1d3b5e61fb5bbddc98e4ccd20be9ee71c02cacc95cbb17dd404558f586d4f0334bd12fc0a584d29eef3b4c2ce3f87babc462b6d24ca10aa8f1eb1abbd29d11ce3f1c92426c4950e53ba6c914cb4bf0bb1b44b25452cafbf246b76ce17f829bef3178174fbac4f932e6ac18e579fbbf8790611187c6f01de70fa82a21e979c90eed3c7f7b7e416491a000b5f2216e54858fa61893391b115573b2b960a0f7dc1e2a703a6f38485589c9133b4509fb54b4a602cc7d3341298e8e5da88d5e06aad28738650c08d8c71239735375e5bca7ea91dd78d748d65af598192317fd69e03daeecc99b8bc4000000010852e6d0c4f5f177b700404600eae9a1449fd711efdca846f59b6c9975a2189c1430f740e0e0eb4d50be086b798a47e037ee6afe9785b45df6302cfb87d39b9c03d0712f11793adab33e74904249722c4a1544898bcc839c7364f9518287bbf01c06e9fd67f74096cab78bc885ad264ca9151a48420ea11d5b9e3bc899cc0d663509c926bee5ef2a389a9945f5919677472b3245a2a68ab91654ba5f75026ae738a9e10b6b7439503961bedbfa2494099a9bba3b9595f70cacd8950abd5d3b8a85ac61e6482847e0ac71cbd63df462f4978afc7084ec5c9e65f3b57ad22f802d699c94f260212a61db6e6958ed60356bf991d3af9582cd1724a90f08e869ebd7f29fc3fb9e1198819a967096de1bdeea692fade6229b41c5efe6cdae313014db328e99bae1f323584a308f575b923a11185634b24c8cc1d608303c8ca5c1ae3d54b8b7ecb689d5e0e22323c5e47d8f9093080bcf1553f7fa238e156658ef50281f633f184e47992645c82ab9985b9b13f97f0bbd9db94285c131fff5870ea38691ade2e017ee6417a118a9b31f5b85ba2c76163f8668d90d448976d9d37238605d36b0b7e7abfb0e9c2866bb11b128bb70f003aeb77f439c6148d28719a4f336f4d622140faff82595233cff94906695e402b9115ba7b99f7d832f55d8de481af808ed9d48a493c3301d2b633cdcdc7a64acefaa9408b99a52768edeb9824cdb49257c6d3ea3f23763f70b74fc46252a97f7b3e173a93e81cca50fb12e8b0ea1083f9262e1607156296b5ff85e1531335441c90b442aa5dc616ed7b520bc040502280a0bdb011d6e9dac154587f033b3e9434cef567b724bf5203c315ef5e283028ec72ff33736499b8d326cd643e3a37d53e70b9a2384ac6037f488075eef8b2e507774d3d450ea4b1b65c1587eb2a60c6aad0ae20745a2c50f8acd8bc1e25740b4e7ab4b03a59731ccc487831b46ca0211264f4fd4a498e145aca0d7339663d29a7f32a14fc2b62a32f6354abf2d7de5ed9d7e0a1d6dbbfe14af25ca8c927dc8eeb2618f308eac81405552f1bafcaaede51f560fe5eb6aa78b8650ae97040b46469503c85994ebcf7ffa0222905657111325f861d380935c2a5b9ad26094e85b92327f79f66b4a2f8cd674344f931f0e056ba58a084a1026bc422b80ec11b17de1ca965e8ac9ede1464588303986ba8fabb395b55de5580131ae3bf61822e4e2817dacf765e034542e435c4e9ebc58f21cfa7901dfbcc2b2be98cc55fa6d9e0030e17a32abf84d551a74f7391dd204847c25ee6382f6374b8ecc5b29b598b9ba562e0a6f25ebb932570ae8ab7ef7d06e444fdf79845e88b3132ffb6e1f330e3424272da082486aba357bd254ef0738bb7f5c9db73d2a9ba0c9afeb34e09ff0e20bd44ba1a46ad3081db2f750d00b647756dec1bb41032e1aaf56f58d7046102aef6ae40517f9cbc148692401ad06ea4ff6ed3e5c8a0cff8f9466a3530a99cfb9b5a857a967675df0cfbe3b798fdc2d929fa4c86e3b3e3c45b75fa5db6c15953f25bcd025d7efc3172b2206a200128f6d8caca97154b2608511b35b0cba9a550d8f791552faed64036cfb8498dc60492207ab81c3f3edeafd9d3acf5bca2f2735117a273c70345d3b7e289b9948edda3d5ebb8adde7de2451fe2d942d92ab383dc9a9adb6fb9e8cbc0b73d852f176e0265a6107e6a59746e2f2e7203fd445e5fe278d02cd5dac1b7988e205c37bc5f35dc27572743810453b9b27e55c\",\n\t\t\tdomain:        \"\",\n\t\t\twantErr:       true,\n\t\t\tneedsMoreData: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"QUIC Chromebook Handshake[7]; packet 1-3\",\n\t\t\thexData:       \"c9000000010852e6d0c4f5f177b700404600eae9a1449fd711efdca846f59b6c9975a2189c1430f740e0e0eb4d50be086b798a47e037ee6afe9785b45df6302cfb87d39b9c03d0712f11793adab33e74904249722c4a1544896cd650da62160bdb601194f82a98c06df2546b79b5bab9503eafdbfbcfd1128204b52c9c5dde4d661cb84f2e015b7de9e10109874176362b7f06d4b49d850f2dcc8485aacb82fed8017fe454a1c31cfb82d392d6894081fa3ab1e5f2b71df79bf5a52a68a90d4752cf441220acbb274e3473f65c1a6e4cea3295da23e4a07818d5ba80b0668638c6ecb0b3ee12ecb8bed4ca32e27f4eee7376010b3905b8f7055728b8b1eda2e2707338b86f9a6115a23b29a010769d9a0e19d2bd06b81a507c4775d7215030862a026f168664255912c321669a5eff84ded4fec82af394cee909110758e5ad4de945236001bc0627d8e88ead3c3a6790d7e50887ced96f8380a80b11477147abc24dc4963c1907a07fec13e3cd8d5bab83cabd7cf8bb66af4911dfcdaf0f13aade34865b0740bebe4b4a482f1e3105bb9c3e5fece975187fa4eae5cb8fc868376b8b8329f89e407618aa5265f8ce73437e9f2714d752dad78025a38eaca1ebd6617a9a8a0bdefa8b3d3daad006978ba85d17ce3269da06be92eec61e4c5c99712517f01588b0b7deb4d7a75cadd5643daebc731f4a7bfaf17e8f002721b054a5d6d6ca7a3430c9480daff6adfd0a5480968b4fe0ee616d2e1f2f01268dcda2ee523fa593a0c83cbfc051dd7d503d48b152eb53894f79d4f6d38f05af35b287d16df579575b755f36e87b6df082108fda812195d5f60d2ceb59a54f09bb270d660c1d923348c7fdad97dc081e749a393af83bbcdd3c290efa19bce1a0f68dab0061df9dbee778dffb7754db10cbcc354a4b66c614e29d216a3a45a38594d337f775cb88c940924734ad52f8856606147b14ae2cf2990e1c401d79d27e4d6723afa4047454580698b9108b952b78b6bfd31db3b376f0879b2a0d8949b8443ffb9f7eb84b8024c620680481944312e86d183735effadab2d9f144a72d170fb035ed230f1451c94cb3f39bc28929e7480b22bbf6c906070eda03884cdd7ada9b10b7af27315c3bde8b4a273d46d1f764669aca152120da3e0a4fcf1be84b4f3cdfefe235a16598022cc74ad8f32e78aa06eb74ef61cccfc82ed35bcb70f008368ccdc17a6f64a316fcc9f006f307fa7a1a50f28c343c4c93a39df73a0d0a6dfc6f8ab10e966f738bee331e5a29d91ed993fd2638f0ec9d0ce539552b0a312fb85ed5e9f392fbc76a6164298b9de2c47dcb21a895957e92bc1270dfef3f00f44cc42c5f5005132dd030f9aec804045731c6a3eed3776beebe9451488932a1172b979f371aa370308037e57513a8fc9dd03d63fc2e5f7dcd683de26e116ea11a1d3b5e61fb5bbddc98e4ccd20be9ee71c02cacc95cbb17dd404558f586d4f0334bd12fc0a584d29eef3b4c2ce3f87babc462b6d24ca10aa8f1eb1abbd29d11ce3f1c92426c4950e53ba6c914cb4bf0bb1b44b25452cafbf246b76ce17f829bef3178174fbac4f932e6ac18e579fbbf8790611187c6f01de70fa82a21e979c90eed3c7f7b7e416491a000b5f2216e54858fa61893391b115573b2b960a0f7dc1e2a703a6f38485589c9133b4509fb54b4a602cc7d3341298e8e5da88d5e06aad28738650c08d8c71239735375e5bca7ea91dd78d748d65af598192317fd69e03daeecc99b8bc4000000010852e6d0c4f5f177b700404600eae9a1449fd711efdca846f59b6c9975a2189c1430f740e0e0eb4d50be086b798a47e037ee6afe9785b45df6302cfb87d39b9c03d0712f11793adab33e74904249722c4a1544898bcc839c7364f9518287bbf01c06e9fd67f74096cab78bc885ad264ca9151a48420ea11d5b9e3bc899cc0d663509c926bee5ef2a389a9945f5919677472b3245a2a68ab91654ba5f75026ae738a9e10b6b7439503961bedbfa2494099a9bba3b9595f70cacd8950abd5d3b8a85ac61e6482847e0ac71cbd63df462f4978afc7084ec5c9e65f3b57ad22f802d699c94f260212a61db6e6958ed60356bf991d3af9582cd1724a90f08e869ebd7f29fc3fb9e1198819a967096de1bdeea692fade6229b41c5efe6cdae313014db328e99bae1f323584a308f575b923a11185634b24c8cc1d608303c8ca5c1ae3d54b8b7ecb689d5e0e22323c5e47d8f9093080bcf1553f7fa238e156658ef50281f633f184e47992645c82ab9985b9b13f97f0bbd9db94285c131fff5870ea38691ade2e017ee6417a118a9b31f5b85ba2c76163f8668d90d448976d9d37238605d36b0b7e7abfb0e9c2866bb11b128bb70f003aeb77f439c6148d28719a4f336f4d622140faff82595233cff94906695e402b9115ba7b99f7d832f55d8de481af808ed9d48a493c3301d2b633cdcdc7a64acefaa9408b99a52768edeb9824cdb49257c6d3ea3f23763f70b74fc46252a97f7b3e173a93e81cca50fb12e8b0ea1083f9262e1607156296b5ff85e1531335441c90b442aa5dc616ed7b520bc040502280a0bdb011d6e9dac154587f033b3e9434cef567b724bf5203c315ef5e283028ec72ff33736499b8d326cd643e3a37d53e70b9a2384ac6037f488075eef8b2e507774d3d450ea4b1b65c1587eb2a60c6aad0ae20745a2c50f8acd8bc1e25740b4e7ab4b03a59731ccc487831b46ca0211264f4fd4a498e145aca0d7339663d29a7f32a14fc2b62a32f6354abf2d7de5ed9d7e0a1d6dbbfe14af25ca8c927dc8eeb2618f308eac81405552f1bafcaaede51f560fe5eb6aa78b8650ae97040b46469503c85994ebcf7ffa0222905657111325f861d380935c2a5b9ad26094e85b92327f79f66b4a2f8cd674344f931f0e056ba58a084a1026bc422b80ec11b17de1ca965e8ac9ede1464588303986ba8fabb395b55de5580131ae3bf61822e4e2817dacf765e034542e435c4e9ebc58f21cfa7901dfbcc2b2be98cc55fa6d9e0030e17a32abf84d551a74f7391dd204847c25ee6382f6374b8ecc5b29b598b9ba562e0a6f25ebb932570ae8ab7ef7d06e444fdf79845e88b3132ffb6e1f330e3424272da082486aba357bd254ef0738bb7f5c9db73d2a9ba0c9afeb34e09ff0e20bd44ba1a46ad3081db2f750d00b647756dec1bb41032e1aaf56f58d7046102aef6ae40517f9cbc148692401ad06ea4ff6ed3e5c8a0cff8f9466a3530a99cfb9b5a857a967675df0cfbe3b798fdc2d929fa4c86e3b3e3c45b75fa5db6c15953f25bcd025d7efc3172b2206a200128f6d8caca97154b2608511b35b0cba9a550d8f791552faed64036cfb8498dc60492207ab81c3f3edeafd9d3acf5bca2f2735117a273c70345d3b7e289b9948edda3d5ebb8adde7de2451fe2d942d92ab383dc9a9adb6fb9e8cbc0b73d852f176e0265a6107e6a59746e2f2e7203fd445e5fe278d02cd5dac1b7988e205c37bc5f35dc27572743810453b9b27e55cc5000000010852e6d0c4f5f177b700404600eae9a1449fd711efdca846f59b6c9975a2189c1430f740e0e0eb4d50be086b798a47e037ee6afe9785b45df6302cfb87d39b9c03d0712f11793adab33e74904249722c4a1544894fb2a6f735bea394066ea4ab5d3ef6f419c4bb099bcea8fd7e36d8ec14fae027d0c84ba8c38a5ea1e2e9ff24ec334dee8085d1fa9d88df2c5ce3dddd981f7b2a68011f2d4034f8e4c6f43e645fc71e19927aec56045f0ef72d9da1942dbe3e42248c2f695525c8d8fcf6dbc970c2eb5d607e07dce5c8c66d4de07de6b2c35bd3bfa12cd4e4fcd4cdda3e0b2ff7575d406db96502f1ec4d9b5748215fe2a4018c7dbdb5ead1ddfc06da5233ce359e4f3924f2af1b80c7af9d13437e0107f7e240e485458a3a656e31a543bc5a80f6a598dcbbd87ff9cb4ce6842abadb72d62ae03e6d12b7f43ac1805e408d738148fa3a5c34deae7378d7d7309a7e09f5bac848f4c031693fbe3382a2de66a9d007420512e24b8a78a9489b30794c7cc51dd751c1cdeb2870b7c4a9b9606547f843c1a16d9be986b2f3e3d2f73f89c8e15da9de3dfdf7a667ced977332c5c62fd83d3c9a5e9718643e716db8f1b6fb58904ca17c24c9a4a191ec42be4c405fb00e443429582ff712dfdbadf1bb7e2d2abb0cc14f39c13a3b7013c62fd99488f043a6aec3ed7998b943ee24905fc915e3144c54393fa67ff34fdce2e48bba044f13ee1cd9c4f59a1a41f7fb7bdd8daee73461234f7cce5d54b078c3ad2b0aa37850ed4bb24f4d310d4ce75ede546ed6e73c0ee495ff8ce4b7256e8f43949539bf9df6e8d0fbf066fb506020c5a72e5e8b686641b78b365474c65fe9d8dd41133e27326fe82744b45b6ad1170433f3746647a68b824e94a213cda4c02f78465acbccdccb5bad1c70806d5a5c98c94338f88bf980b05b72cb82a4fd5b2a8586a5e5c5f2760115df595091809cfd12829f09622b53ca1d3809060aa7ab5d1f3640b3c2792a55c58fc3e80ac5d7f39ab5774a80fa1fc70461d396fa70dad1f90598244ce4197cb3ea42dce4afd61ca8ef93c8bc254d347872db21edcc3857bcb8dbe627508aa4856bd7d46e512db071905b3db100f425ba9f7181f0cce005cee2a95ffc190ddc1939e7049e58791b0e186433b0409f5a49e4e3262690a5160f8267c9099afdc58182236833fe7f825dfca34b08801345c1592bbab4964b34d7efa6c9d92e0106ede9a10fbf2a1be32f61f914211c3caa8b4c14edec5f9c139ee14789fe7d6634ede9bf9789caa60f5bf30b092e65ff95d3b32cbdc3e5842e3b16b935d31a3a0963bb0fe60f41efb6590f24eaf5e84006b28b3c755203113237e43fa70a37a009f71da49ea3f8097914d6128ee2b18adac49b5111fd3d18db9fd61ef8a2202fac5cfce646ccbea7eaaa81df0f1b7243465de15a3900143f479852f0e40bfad434b96eea3941f527b0d31c3d8f43188b911140766b5d7146feb93bf4da1ec47023dcd8f89863e487ba25c3105a4e43c4ea90f479eb0f774f3aa044b817f7e69b5dd1b3954e7dcdb2a6c4d191d5b9178262449413310f3876dd93145716781cb077025deb37d23c35e6fdf867d5353d10303b9e60efa50e9ecd013cd3f5270fc0e117a19ffb63038c594190018bd9c1c18a799f548c08a3e6f768de0de344cff160689fed73aa7fc4edc26f77413145775745c25fc2c9da5b62e24eab9b21895cfcda6e6457ae9fdfa6c54b49b0d160dad0aa7f8cbd3820a3098\",\n\t\t\tdomain:        \"ogads-pa.clients6.google.com\",\n\t\t\twantErr:       false,\n\t\t\tneedsMoreData: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpkt, err := hex.DecodeString(tt.hexData)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to decode hex string: %v\", err)\n\t\t\t}\n\t\t\tquicHdr, err := quic.SniffQUIC(pkt)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SniffQUIC() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (errors.Is(err, protocol.ErrProtoNeedMoreData)) != tt.needsMoreData {\n\t\t\t\tt.Errorf(\"SniffQUIC() error = %v, expectsNoClue %v\", err, tt.needsMoreData)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && quicHdr.Domain() != tt.domain {\n\t\t\t\tt.Errorf(\"SniffQUIC() domain = %v, want %v\", quicHdr.Domain(), tt.domain)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSniffQUICIncompleteServerName(t *testing.T) {\n\t// 2 packets\n\tpkts, err := hex.DecodeString(\"ca0000000108d799f48baf472fc3004047005411dcdaa24bd4c3cdbef653940a1ea0a671bb8c23222a8502bfcbd0a335af016df330b1190e4b1efd420aa82c4414a7dfd7b1aa63aa7bd0269ec8ed0d24895a467b737fcc514488a8142241b81c4952fb32e9f123f3f8d1be9eecc1340d7fa747c6f4838810209d51ad453fcec084ffaa382f9fed010a0585f8ce73c36dc6c628df940f72fb5b8e3e06f4612128d3b8f87725770d81305489a3a23cd443fcc668fece0ec9a909c82cfdb66819ce031422c3edd49ea72b6216efb940b8c186e2510b0f93d4cf3b6434ed9f5817c5a5a19fa861d24883bd48ba296df81e887fcb9ef18013ab1070df268049f3d35e8ddfffd2b100e4eb687176a02e7975085caa138e9df7cb6f9862356faafba3300f586df09af3b4e3eec15b6d1f14b34fe4eeb8e762ff482f3b7b0595c16d3eb20cf9c892b3f7c6449e51ffa33f3dc877d32e572e10ba4d5a320117110b150bec4a9434103de963aa2de1b22eaa992b59a8583c7a2d4e5743ef98a1b1f5f4aaeb2b49dbe441c331e7cb028e70432ce0890f78d40671f13d5feccc4ab264ae903affaa951897128d598ed8e8542294aa03abb0a2a7ec970ab874387b8777827c4847bb29ff93d0427c1fcc581407465611e4400bfeab39de4c4f65572365156421503706d66138317e514214db27303ad8b5b17eb443e959faad1ccb3d3def5f3843b241dd9a60378cd907a02cc5c6e8527715c7734079cf9143586ed471587690816133fa8f09b8421f2de6ff1f5cecc134ac404e53a367b57fd4f3c85fe7e121e7094fbf93c06d427cec30cd519ee43a6018a8bb8717c05d7660b379101a182bfd3018d485d5b46e81d39445ccd4d8baefdd7590e664f2289f11bbad3d35ea6d70e157aaa7f9e91a692794b85c5b86db33863151468d32792ecfdb2aec52784ded5aa431fa9a7337ddd9e95313a897fe5ca6ef3c7e0cf3d41f7f104506f8695f2ba307715022e5e98472d6ff24125004d14db2c7a4cc71ca39a9874bfb93b64dd54ef451097059bbdfa96aca6c6c01e1f000b8d35bdead1502874e5d80ac00a79593b6d7e2fbee406c2212b4abd7b7e9f0037518de232cd6559443f4e3f0f03c17cef616a74992f65754bb6699b08c0eb2ec59508086e496e070c6239a73f11a9d14a727b10188ca97ebf04851fa475ed1774836a94cfb6543c33888dfc250100e9992b0cca8bd27ae8552541f1b3d81546f15b740e4f07b41af864769fe17290eb7076c0c1f938ce56a45ead6deb6c89164f829543414a1b8cd2361c3d9b22cf85d4ad0d4221dcc87533506e32c1cfdc8240636b669f97f7c6e150ea3753fcb63a7b9eedb5615b257651e090b567a20fdc5c167ad58fa940985572db004db2b14709a6663c1fb79dc045e45cb7228f11eab4c60a48fffc702cd74dcf5aabded8355a5ae1a5e6325a3afff608e53af3aa944437139c438e6200c75bb27c8a8ce7ce9f9ec9723c4423e9e49c807b12484783ed79fc501b544f03e4b29bdf0c74eb055dbac9b78fe87a66efc819f9d2ea637dd478b920b7196232b970ed092050ba0621329aa2b3e26184176137a9c6283e4cc2c03c07e28d2da7b0485da51f131686f4dd6b23735c66316142f48781885c35c9940054a4b9187a20914bedd1c48e94af358994da5c6060dd224b0ba84ddd5e95f395fe941d464e2cccd857a180de756d32407540c3611b649c7a71cbf5a492bf5c8d4e112a743498ce83ea3e94bcaa6c00000000108d799f48baf472fc3004047005411dcdaa24bd4c3cdbef653940a1ea0a671bb8c23222a8502bfcbd0a335af016df330b1190e4b1efd420aa82c4414a7dfd7b1aa63aa7bd0269ec8ed0d24895a467b737fcc514488e7151df796764293639665547941eaaa7d83dfc9d42952583a821611921cba9e0276f9244f197e011b6c2d898255802c81c8792dd68d03cd8982ee6146c8734814e9640e0999312f97e898db60642ceca21700be9e698b5e577062148d1ac840e737f7bc4f18f9ffd13e656fca1517c96d0e0c607b67d7e75795f62131255b44c2178d896192348d5f053060fa21c855236401e853e435fbc71a428c8e126cd8393cb860193fd6304d9576e8a8ce7f6959739afa7d7ffaf86516afb061ade1eef603e8e5c66f002cacbef25546e84b59be707c547837f7b49471a59325ad58a4f189a525a3a354a4be5ffd3db34ce0585ec470f78f0ff6eb807989d5998e2fccc80bd651de654da3581b6c8a858b56c4df47c3041c50d74f0b3a3a5a325882cd356d0c46d6db74559cef934a1655c65a06daff218244f5f4e95b96794e45d371a03afee2c89768fe3d7bc134844f45440620a88d6ddb5d07b7f6c9b2adfbf5743c5c586520504febf14ee4b216f8186ee1bf9802ccebd4dac519bd337a554940a3873cd21ef32f3d4d4fdfd3eaabb172b5314490b3587a9ca2dc12637fa80dcd526859bb83d54f67dedeb173470e052beec9edba4b41444d31253accb3381237179a5381d437fc4d47c0a82f1c9da8c76ad3eee9417bf69acb8d530ac824e65d796b84bee40b14daa9e80c11e466f93233e1768ecc6c0237e71b901f1faa093bbd9e5324b2a160d02f09d4590392279a15c73edc4d1d7501ebad992fc8f1c9dce96c190879225dbbb09d20532f9dcbb845e3484c87cb8c3b06fe892b07bdc60926e3d4b5c9024acde5abed0a72686d546da535a2122cfd6d6488a491dc9612b90f3f6709835e83f59fd612c2259c3ac944fdae87ffd744176a92fbea1efd037565a39d5bc38991cfe6617135b43cd6c6484a3b228e10d3f8184227a7ee63121be20f099c4264461d52aecf4b8ee1a4d0ec41717f9a68e1394544cf3be0a3a171ece35c879b67afdaac49c12731e6dc5ccaa49fa72610901036a7f0752951077cd9f1d77eb6e4f12c16dab08e1a9655fda3ba0e80c34c767f40e61491f706ab9d1b50f48273ca4e37708d075909a3e2c4b173510114d8377b5300994f9a4f794db2781b98c2e9d0056fdbe87b249beb3e7301c86efe6b32242b4c7fabef280a1ca7bdd6abb38624cbebb01b6b75b07d0cd9090b2c296d0e330b9008788dd6f7e1af1dd63b02408f1a67b57be3316152270bf90c36e9f74322c9caba5adab3309e5bd7c44d3c2ac77329e167dafda7395e37313723fb550a6ab96729a9fa8f9e4a3e5957bffa3302617d48fd38196635309928c491ca974a112768de16043fc618001780b49a0bac2ea9fc2607ebd1f25b8ef65a6cab42d16c9184e23af496ac5cfab274d9557006d9c52bb56cd8c019ddf085f260a0582ef465c73f97fedc02271dda53eec20e6aa50503357ed1847f7d976899cb24e5dd42dd87ae48db3c9335b38957fc0f86a6ceee590b1cbee41fc6ace3189886bd86f9b082e66fb263f1a98dd8e564bff9ec412ae79e29fd519ac96e9c5b446b436b78a6ca555c496ca5c5e73c2c1c5837b13f7a953d57738440d4ed60efd4701bbbbcad79e1697b5724695f52dab8bce02e7f\")\n\tcommon.Must(err)\n\t// The first packet ServerName is incomplete\n\tpkt1 := make([]byte, 1250)\n\tcopy(pkt1, pkts)\n\tquicHdr, err := quic.SniffQUIC(pkt1)\n\tif !errors.Is(err, protocol.ErrProtoNeedMoreData) {\n\t\tt.Error(\"failed\")\n\t} else {\n\t\tquicHdr, err = quic.SniffQUIC(pkts)\n\t}\n\tif err != nil || quicHdr.Domain() != \"play.google.com\" {\n\t\tt.Error(\"failed\")\n\t}\n}\n\nfunc TestSniffQUICPacketNumberLength4(t *testing.T) {\n\t// packetNumberLength = 4\n\tpkt, err := hex.DecodeString(\"c60000000108fa6cc47e912693590000449e5ea65fc129945b27f59f0b18f737a53e44f322681ecec36beeb943293db0ed751bd86413a840f9b9e9be718f3e2dba1100a8bab3a21e1541580ffd98e79aa89fa723e8d3a46f7876504c82b9ef3a081b0f8cb551370d9801ee86da77f09eec5aa19b7bf580a80ded3c9c83378285177115fd30b350c2a596ae265b3b538a81c183c0cfd13eabecfbbeb38416a5b19259731b838842c0eb33e646b9bb1f672043e90de33c3442151ee8db7d9cd66238f769f4486432ac28785a5083c616539f8320321060f64f9a0dc6af718754d645892397ff32956c4c1c97d0d9e44cdfa8d1a0ad90c3bbb7810b2196d638fd772a172a9510ea12ef12fe4050c5678851be26ec6ea6ab11824cc86ce071d110f72816166c01622c0207e9d97f8867ec7c63149e974c5a81db9cb5e0061cff2713538daa1c9ad1382ab7d883ecc85158dc76587793b258b4b0aded3f4c12b515a9183ee419b304cf748fc321f15b3f80cc53da1b889d1ac06b996d35e8d01306885851ad253083f37d0d588c9f619da25f6eb8360b846bc26913af616e2c3eabce9dbb61f7dc96b6dcb79e19905ac9ba8f3938d03f8a3647403dc919f37bb585a0d67b7ce955547d15c82eed6d94b04dfa009eaf8b30448e1450043c48845acb4fefcf29bdd55ec14e395d0e8cd8400ffdda5a58747c6e8a66d0dc5fb25f3615081b2b546e004079573e99290f5daf72705ca495707468dd26d94c7f04d7e6f89d8148ecbab67c8c0062984e0ba02539527370a2a157a58eab342ad671641812ed35b4a52ba07d244d9b5d64e29f012133d21fa4afa31645c21f6d836ad937bb75f7177768b5f94bb77e95aaf9a85f8fb7e599d482724f694cf5d7d3f61bfca892794bdcb3de7a5f321db8120560bc32b8839c0a5994917c151cc6bd4c1614c5f117e637c19dab7cff28c4848c3b328eb97e49cefded2d44a824f2705807770c2ef9dd07f0fe0198659ad062e1889e280e5f3d1c52a92ef27d4565ebae9b9a18a803b70f38e5db237ed99583d8952c79492e35e1f1c41664f1f45566126a7ec44a90004b015b893aa805fcd772737fb8dbbe7af56b9eac288ef6cab7cbb6f7a3c0b29a43bc84b6280def0f7d727b3238cba3eda2e2d110de87ad0f10e25a60783cdb0c05116df5359b19b40007812b898d03dc1d697690761856d785b83ac95778db69c3df7a8f0e092ee6ed2c9c189ebe40734b02cee2d02599e931d4cc560d38a7ec355b9f339b932613ee18f8162e8d3cb81301bfc6d726b7c26ec96d5edcaf0de171563482ed2f2de3001dbca2aee1029fdaece4340fd2d5ae8333819d5ece7c9d3f77f99a81fccd1fbcc3ea585c1538e0363141e0fd20338a193695377987afacd0baf1f0dce11b4bbf29965109bddb508e8a0974c08906d4ea340d51af376c3dda55bf97e3ba5d688533980e12704679bb18d0ef4ddd5b3af1b7676528c4bf4a84c84d40892715c0a8808ad51d6ebcc6469da708d6953e9ddcfbd19bae90e9b078f1b6641401b979304b0ef52b1441e1797ed366bb0519ca8bf9c6eba72518647d0a1400ca66a20fdd8e3ff06ee52c199bd8f941b1722a0bbf8c15447452788ba81a68431f735d99e8a80691ef64d1bf470350c8878aed3e2421223d0ba6a3d84928c8e6db0972263df9da49b8f5\")\n\tcommon.Must(err)\n\tquicHdr, err := quic.SniffQUIC(pkt)\n\tif err != nil || quicHdr.Domain() != \"www.google.com\" {\n\t\tt.Error(\"failed\")\n\t}\n}\n\nfunc TestSniffFakeQUICPacketWithInvalidPacketNumberLength(t *testing.T) {\n\tpkt, err := hex.DecodeString(\"cb00000001081c8c6d5aeb53d54400000090709b8600000000000000000000000000000000\")\n\tcommon.Must(err)\n\t_, err = quic.SniffQUIC(pkt)\n\tif err == nil {\n\t\tt.Error(\"failed\")\n\t}\n}\n\nfunc TestSniffFakeQUICPacketWithTooShortData(t *testing.T) {\n\tpkt, err := hex.DecodeString(\"cb00000001081c8c6d5aeb53d54400000090709b86\")\n\tcommon.Must(err)\n\t_, err = quic.SniffQUIC(pkt)\n\tif err == nil {\n\t\tt.Error(\"failed\")\n\t}\n}\n"
  },
  {
    "path": "common/protocol/server_spec.go",
    "content": "package protocol\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\ntype ServerSpec struct {\n\tDestination  net.Destination\n\tUser         *MemoryUser\n}\n\nfunc NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {\n\treturn &ServerSpec{\n\t\tDestination: dest,\n\t\tUser:        user,\n\t}\n}\n\nfunc NewServerSpecFromPB(spec *ServerEndpoint) (*ServerSpec, error) {\n\tdest := net.TCPDestination(spec.Address.AsAddress(), net.Port(spec.Port))\n\tvar dUser *MemoryUser\n\tif spec.User != nil {\n\t\tuser, err := spec.User.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdUser = user\n\t}\n\treturn NewServerSpec(dest, dUser), nil\n}\n"
  },
  {
    "path": "common/protocol/server_spec.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/protocol/server_spec.proto\n\npackage protocol\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ServerEndpoint struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddress       *net.IPOrDomain        `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tPort          uint32                 `protobuf:\"varint,2,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tUser          *User                  `protobuf:\"bytes,3,opt,name=user,proto3\" json:\"user,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServerEndpoint) Reset() {\n\t*x = ServerEndpoint{}\n\tmi := &file_common_protocol_server_spec_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerEndpoint) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerEndpoint) ProtoMessage() {}\n\nfunc (x *ServerEndpoint) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_protocol_server_spec_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerEndpoint.ProtoReflect.Descriptor instead.\nfunc (*ServerEndpoint) Descriptor() ([]byte, []int) {\n\treturn file_common_protocol_server_spec_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ServerEndpoint) GetAddress() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ServerEndpoint) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nfunc (x *ServerEndpoint) GetUser() *User {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nvar File_common_protocol_server_spec_proto protoreflect.FileDescriptor\n\nconst file_common_protocol_server_spec_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!common/protocol/server_spec.proto\\x12\\x14xray.common.protocol\\x1a\\x18common/net/address.proto\\x1a\\x1acommon/protocol/user.proto\\\"\\x8b\\x01\\n\" +\n\t\"\\x0eServerEndpoint\\x125\\n\" +\n\t\"\\aaddress\\x18\\x01 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x02 \\x01(\\rR\\x04port\\x12.\\n\" +\n\t\"\\x04user\\x18\\x03 \\x01(\\v2\\x1a.xray.common.protocol.UserR\\x04userB^\\n\" +\n\t\"\\x18com.xray.common.protocolP\\x01Z)github.com/xtls/xray-core/common/protocol\\xaa\\x02\\x14Xray.Common.Protocolb\\x06proto3\"\n\nvar (\n\tfile_common_protocol_server_spec_proto_rawDescOnce sync.Once\n\tfile_common_protocol_server_spec_proto_rawDescData []byte\n)\n\nfunc file_common_protocol_server_spec_proto_rawDescGZIP() []byte {\n\tfile_common_protocol_server_spec_proto_rawDescOnce.Do(func() {\n\t\tfile_common_protocol_server_spec_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_protocol_server_spec_proto_rawDesc), len(file_common_protocol_server_spec_proto_rawDesc)))\n\t})\n\treturn file_common_protocol_server_spec_proto_rawDescData\n}\n\nvar file_common_protocol_server_spec_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_protocol_server_spec_proto_goTypes = []any{\n\t(*ServerEndpoint)(nil), // 0: xray.common.protocol.ServerEndpoint\n\t(*net.IPOrDomain)(nil), // 1: xray.common.net.IPOrDomain\n\t(*User)(nil),           // 2: xray.common.protocol.User\n}\nvar file_common_protocol_server_spec_proto_depIdxs = []int32{\n\t1, // 0: xray.common.protocol.ServerEndpoint.address:type_name -> xray.common.net.IPOrDomain\n\t2, // 1: xray.common.protocol.ServerEndpoint.user:type_name -> xray.common.protocol.User\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_common_protocol_server_spec_proto_init() }\nfunc file_common_protocol_server_spec_proto_init() {\n\tif File_common_protocol_server_spec_proto != nil {\n\t\treturn\n\t}\n\tfile_common_protocol_user_proto_init()\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_protocol_server_spec_proto_rawDesc), len(file_common_protocol_server_spec_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_protocol_server_spec_proto_goTypes,\n\t\tDependencyIndexes: file_common_protocol_server_spec_proto_depIdxs,\n\t\tMessageInfos:      file_common_protocol_server_spec_proto_msgTypes,\n\t}.Build()\n\tFile_common_protocol_server_spec_proto = out.File\n\tfile_common_protocol_server_spec_proto_goTypes = nil\n\tfile_common_protocol_server_spec_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/protocol/server_spec.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.protocol;\noption csharp_namespace = \"Xray.Common.Protocol\";\noption go_package = \"github.com/xtls/xray-core/common/protocol\";\noption java_package = \"com.xray.common.protocol\";\noption java_multiple_files = true;\n\nimport \"common/net/address.proto\";\nimport \"common/protocol/user.proto\";\n\nmessage ServerEndpoint {\n  xray.common.net.IPOrDomain address = 1;\n  uint32 port = 2;\n  xray.common.protocol.User user = 3;\n}\n"
  },
  {
    "path": "common/protocol/time.go",
    "content": "package protocol\n\nimport (\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n)\n\ntype Timestamp int64\n\ntype TimestampGenerator func() Timestamp\n\nfunc NowTime() Timestamp {\n\treturn Timestamp(time.Now().Unix())\n}\n\nfunc NewTimestampGenerator(base Timestamp, delta int) TimestampGenerator {\n\treturn func() Timestamp {\n\t\trangeInDelta := dice.Roll(delta*2) - delta\n\t\treturn base + Timestamp(rangeInDelta)\n\t}\n}\n"
  },
  {
    "path": "common/protocol/time_test.go",
    "content": "package protocol_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/xtls/xray-core/common/protocol\"\n)\n\nfunc TestGenerateRandomInt64InRange(t *testing.T) {\n\tbase := time.Now().Unix()\n\tdelta := 100\n\tgenerator := NewTimestampGenerator(Timestamp(base), delta)\n\n\tfor i := 0; i < 100; i++ {\n\t\tval := int64(generator())\n\t\tif val > base+int64(delta) || val < base-int64(delta) {\n\t\t\tt.Error(val, \" not between \", base-int64(delta), \" and \", base+int64(delta))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/protocol/tls/cert/cert.go",
    "content": "package cert\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/asn1\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype Certificate struct {\n\t// certificate in ASN.1 DER format\n\tCertificate []byte\n\t// Private key in ASN.1 DER format\n\tPrivateKey []byte\n}\n\nfunc ParseCertificate(certPEM []byte, keyPEM []byte) (*Certificate, error) {\n\tcertBlock, _ := pem.Decode(certPEM)\n\tif certBlock == nil {\n\t\treturn nil, errors.New(\"failed to decode certificate\")\n\t}\n\tkeyBlock, _ := pem.Decode(keyPEM)\n\tif keyBlock == nil {\n\t\treturn nil, errors.New(\"failed to decode key\")\n\t}\n\treturn &Certificate{\n\t\tCertificate: certBlock.Bytes,\n\t\tPrivateKey:  keyBlock.Bytes,\n\t}, nil\n}\n\nfunc (c *Certificate) ToPEM() ([]byte, []byte) {\n\treturn pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: c.Certificate}),\n\t\tpem.EncodeToMemory(&pem.Block{Type: \"RSA PRIVATE KEY\", Bytes: c.PrivateKey})\n}\n\ntype Option func(*x509.Certificate)\n\nfunc Authority(isCA bool) Option {\n\treturn func(cert *x509.Certificate) {\n\t\tcert.IsCA = isCA\n\t}\n}\n\nfunc NotBefore(t time.Time) Option {\n\treturn func(c *x509.Certificate) {\n\t\tc.NotBefore = t\n\t}\n}\n\nfunc NotAfter(t time.Time) Option {\n\treturn func(c *x509.Certificate) {\n\t\tc.NotAfter = t\n\t}\n}\n\nfunc DNSNames(names ...string) Option {\n\treturn func(c *x509.Certificate) {\n\t\tc.DNSNames = names\n\t}\n}\n\nfunc CommonName(name string) Option {\n\treturn func(c *x509.Certificate) {\n\t\tc.Subject.CommonName = name\n\t}\n}\n\nfunc KeyUsage(usage x509.KeyUsage) Option {\n\treturn func(c *x509.Certificate) {\n\t\tc.KeyUsage = usage\n\t}\n}\n\nfunc Organization(org string) Option {\n\treturn func(c *x509.Certificate) {\n\t\tc.Subject.Organization = []string{org}\n\t}\n}\n\nfunc MustGenerate(parent *Certificate, opts ...Option) (*Certificate, [32]byte) {\n\tcert, err := Generate(parent, opts...)\n\tcommon.Must(err)\n\treturn cert, sha256.Sum256(cert.Certificate)\n}\n\nfunc publicKey(priv interface{}) interface{} {\n\tswitch k := priv.(type) {\n\tcase *rsa.PrivateKey:\n\t\treturn &k.PublicKey\n\tcase *ecdsa.PrivateKey:\n\t\treturn &k.PublicKey\n\tcase ed25519.PrivateKey:\n\t\treturn k.Public().(ed25519.PublicKey)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc Generate(parent *Certificate, opts ...Option) (*Certificate, error) {\n\tvar (\n\t\tpKey      interface{}\n\t\tparentKey interface{}\n\t\terr       error\n\t)\n\t// higher signing performance than RSA2048\n\tselfKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to generate self private key\").Base(err)\n\t}\n\tparentKey = selfKey\n\tif parent != nil {\n\t\tif _, e := asn1.Unmarshal(parent.PrivateKey, &ecPrivateKey{}); e == nil {\n\t\t\tpKey, err = x509.ParseECPrivateKey(parent.PrivateKey)\n\t\t} else if _, e := asn1.Unmarshal(parent.PrivateKey, &pkcs8{}); e == nil {\n\t\t\tpKey, err = x509.ParsePKCS8PrivateKey(parent.PrivateKey)\n\t\t} else if _, e := asn1.Unmarshal(parent.PrivateKey, &pkcs1PrivateKey{}); e == nil {\n\t\t\tpKey, err = x509.ParsePKCS1PrivateKey(parent.PrivateKey)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to parse parent private key\").Base(err)\n\t\t}\n\t\tparentKey = pKey\n\t}\n\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to generate serial number\").Base(err)\n\t}\n\n\ttemplate := &x509.Certificate{\n\t\tSerialNumber:          serialNumber,\n\t\tNotBefore:             time.Now().Add(time.Hour * -1),\n\t\tNotAfter:              time.Now().Add(time.Hour),\n\t\tKeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tparentCert := template\n\tif parent != nil {\n\t\tpCert, err := x509.ParseCertificate(parent.Certificate)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to parse parent certificate\").Base(err)\n\t\t}\n\t\tparentCert = pCert\n\t}\n\n\tif parentCert.NotAfter.Before(template.NotAfter) {\n\t\ttemplate.NotAfter = parentCert.NotAfter\n\t}\n\tif parentCert.NotBefore.After(template.NotBefore) {\n\t\ttemplate.NotBefore = parentCert.NotBefore\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(template)\n\t}\n\n\tderBytes, err := x509.CreateCertificate(rand.Reader, template, parentCert, publicKey(selfKey), parentKey)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to create certificate\").Base(err)\n\t}\n\n\tprivateKey, err := x509.MarshalPKCS8PrivateKey(selfKey)\n\tif err != nil {\n\t\treturn nil, errors.New(\"Unable to marshal private key\").Base(err)\n\t}\n\n\treturn &Certificate{\n\t\tCertificate: derBytes,\n\t\tPrivateKey:  privateKey,\n\t}, nil\n}\n"
  },
  {
    "path": "common/protocol/tls/cert/cert_test.go",
    "content": "package cert\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/task\"\n)\n\nfunc TestGenerate(t *testing.T) {\n\terr := generate(nil, true, true, \"ca\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc generate(domainNames []string, isCA bool, jsonOutput bool, fileOutput string) error {\n\tcommonName := \"Xray Root CA\"\n\torganization := \"Xray Inc\"\n\n\texpire := time.Hour * 3\n\n\tvar opts []Option\n\tif isCA {\n\t\topts = append(opts, Authority(isCA))\n\t\topts = append(opts, KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))\n\t}\n\n\topts = append(opts, NotAfter(time.Now().Add(expire)))\n\topts = append(opts, CommonName(commonName))\n\tif len(domainNames) > 0 {\n\t\topts = append(opts, DNSNames(domainNames...))\n\t}\n\topts = append(opts, Organization(organization))\n\n\tcert, err := Generate(nil, opts...)\n\tif err != nil {\n\t\treturn errors.New(\"failed to generate TLS certificate\").Base(err)\n\t}\n\n\tif jsonOutput {\n\t\tprintJSON(cert)\n\t}\n\n\tif len(fileOutput) > 0 {\n\t\tif err := printFile(cert, fileOutput); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype jsonCert struct {\n\tCertificate []string `json:\"certificate\"`\n\tKey         []string `json:\"key\"`\n}\n\nfunc printJSON(certificate *Certificate) {\n\tcertPEM, keyPEM := certificate.ToPEM()\n\tjCert := &jsonCert{\n\t\tCertificate: strings.Split(strings.TrimSpace(string(certPEM)), \"\\n\"),\n\t\tKey:         strings.Split(strings.TrimSpace(string(keyPEM)), \"\\n\"),\n\t}\n\tcontent, err := json.MarshalIndent(jCert, \"\", \"  \")\n\tcommon.Must(err)\n\tos.Stdout.Write(content)\n\tos.Stdout.WriteString(\"\\n\")\n}\n\nfunc printFile(certificate *Certificate, name string) error {\n\tcertPEM, keyPEM := certificate.ToPEM()\n\treturn task.Run(context.Background(), func() error {\n\t\treturn writeFile(certPEM, name+\".crt\")\n\t}, func() error {\n\t\treturn writeFile(keyPEM, name+\".key\")\n\t})\n}\n\nfunc writeFile(content []byte, name string) error {\n\tf, err := os.Create(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\treturn common.Error2(f.Write(content))\n}\n"
  },
  {
    "path": "common/protocol/tls/cert/privateKey.go",
    "content": "package cert\n\nimport (\n\t\"crypto/x509/pkix\"\n\t\"encoding/asn1\"\n\t\"math/big\"\n)\n\ntype ecPrivateKey struct {\n\tVersion       int\n\tPrivateKey    []byte\n\tNamedCurveOID asn1.ObjectIdentifier `asn1:\"optional,explicit,tag:0\"`\n\tPublicKey     asn1.BitString        `asn1:\"optional,explicit,tag:1\"`\n}\n\ntype pkcs8 struct {\n\tVersion    int\n\tAlgo       pkix.AlgorithmIdentifier\n\tPrivateKey []byte\n\t// Optional attributes omitted.\n}\n\ntype pkcs1AdditionalRSAPrime struct {\n\tPrime *big.Int\n\n\t// We ignore these values because rsa will calculate them.\n\tExp   *big.Int\n\tCoeff *big.Int\n}\n\ntype pkcs1PrivateKey struct {\n\tVersion int\n\tN       *big.Int\n\tE       int\n\tD       *big.Int\n\tP       *big.Int\n\tQ       *big.Int\n\t// We ignore these values, if present, because rsa will calculate them.\n\tDp   *big.Int `asn1:\"optional\"`\n\tDq   *big.Int `asn1:\"optional\"`\n\tQinv *big.Int `asn1:\"optional\"`\n\n\tAdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:\"optional,omitempty\"`\n}\n"
  },
  {
    "path": "common/protocol/tls/sniff.go",
    "content": "package tls\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\ntype SniffHeader struct {\n\tdomain string\n}\n\nfunc (h *SniffHeader) Protocol() string {\n\treturn \"tls\"\n}\n\nfunc (h *SniffHeader) Domain() string {\n\treturn h.domain\n}\n\nvar (\n\terrNotTLS         = errors.New(\"not TLS header\")\n\terrNotClientHello = errors.New(\"not client hello\")\n)\n\nfunc IsValidTLSVersion(major, minor byte) bool {\n\treturn major == 3\n}\n\n// ReadClientHello returns server name (if any) from TLS client hello message.\n// https://github.com/golang/go/blob/master/src/crypto/tls/handshake_messages.go#L300\nfunc ReadClientHello(data []byte, h *SniffHeader) error {\n\tif len(data) < 42 {\n\t\treturn common.ErrNoClue\n\t}\n\tsessionIDLen := int(data[38])\n\tif sessionIDLen > 32 || len(data) < 39+sessionIDLen {\n\t\treturn common.ErrNoClue\n\t}\n\tdata = data[39+sessionIDLen:]\n\tif len(data) < 2 {\n\t\treturn common.ErrNoClue\n\t}\n\t// cipherSuiteLen is the number of bytes of cipher suite numbers. Since\n\t// they are uint16s, the number must be even.\n\tcipherSuiteLen := int(data[0])<<8 | int(data[1])\n\tif cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {\n\t\treturn errNotClientHello\n\t}\n\tdata = data[2+cipherSuiteLen:]\n\tif len(data) < 1 {\n\t\treturn common.ErrNoClue\n\t}\n\tcompressionMethodsLen := int(data[0])\n\tif len(data) < 1+compressionMethodsLen {\n\t\treturn common.ErrNoClue\n\t}\n\tdata = data[1+compressionMethodsLen:]\n\n\tif len(data) < 2 {\n\t\treturn errNotClientHello\n\t}\n\n\textensionsLength := int(data[0])<<8 | int(data[1])\n\tdata = data[2:]\n\tif extensionsLength != len(data) {\n\t\treturn errNotClientHello\n\t}\n\n\tfor len(data) != 0 {\n\t\tif len(data) < 4 {\n\t\t\treturn errNotClientHello\n\t\t}\n\t\textension := uint16(data[0])<<8 | uint16(data[1])\n\t\tlength := int(data[2])<<8 | int(data[3])\n\t\tdata = data[4:]\n\t\tif len(data) < length {\n\t\t\treturn errNotClientHello\n\t\t}\n\n\t\tif extension == 0x00 { /* extensionServerName */\n\t\t\td := data[:length]\n\t\t\tif len(d) < 2 {\n\t\t\t\treturn errNotClientHello\n\t\t\t}\n\t\t\tnamesLen := int(d[0])<<8 | int(d[1])\n\t\t\td = d[2:]\n\t\t\tif len(d) != namesLen {\n\t\t\t\treturn errNotClientHello\n\t\t\t}\n\t\t\tfor len(d) > 0 {\n\t\t\t\tif len(d) < 3 {\n\t\t\t\t\treturn errNotClientHello\n\t\t\t\t}\n\t\t\t\tnameType := d[0]\n\t\t\t\tnameLen := int(d[1])<<8 | int(d[2])\n\t\t\t\td = d[3:]\n\t\t\t\tif len(d) < nameLen {\n\t\t\t\t\treturn errNotClientHello\n\t\t\t\t}\n\t\t\t\tif nameType == 0 {\n\t\t\t\t\t// QUIC separated across packets\n\t\t\t\t\t// May cause the serverName to be incomplete\n\t\t\t\t\tb := byte(0)\n\t\t\t\t\tfor _, b = range d[:nameLen] {\n\t\t\t\t\t\tif b <= ' ' {\n\t\t\t\t\t\t\treturn protocol.ErrProtoNeedMoreData\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// An SNI value may not include a\n\t\t\t\t\t// trailing dot. See\n\t\t\t\t\t// https://tools.ietf.org/html/rfc6066#section-3.\n\t\t\t\t\tif b == '.' {\n\t\t\t\t\t\treturn errNotClientHello\n\t\t\t\t\t}\n\t\t\t\t\tserverName := string(d[:nameLen])\n\t\t\t\t\th.domain = serverName\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\td = d[nameLen:]\n\t\t\t}\n\t\t}\n\t\tdata = data[length:]\n\t}\n\n\treturn errNotTLS\n}\n\nfunc SniffTLS(b []byte) (*SniffHeader, error) {\n\tif len(b) < 5 {\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\tif b[0] != 0x16 /* TLS Handshake */ {\n\t\treturn nil, errNotTLS\n\t}\n\tif !IsValidTLSVersion(b[1], b[2]) {\n\t\treturn nil, errNotTLS\n\t}\n\theaderLen := int(binary.BigEndian.Uint16(b[3:5]))\n\tif 5+headerLen > len(b) {\n\t\treturn nil, common.ErrNoClue\n\t}\n\n\th := &SniffHeader{}\n\terr := ReadClientHello(b[5:5+headerLen], h)\n\tif err == nil {\n\t\treturn h, nil\n\t}\n\treturn nil, err\n}\n"
  },
  {
    "path": "common/protocol/tls/sniff_test.go",
    "content": "package tls_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/protocol/tls\"\n)\n\nfunc TestTLSHeaders(t *testing.T) {\n\tcases := []struct {\n\t\tinput  []byte\n\t\tdomain string\n\t\terr    bool\n\t}{\n\t\t{\n\t\t\tinput: []byte{\n\t\t\t\t0x16, 0x03, 0x01, 0x00, 0xc8, 0x01, 0x00, 0x00,\n\t\t\t\t0xc4, 0x03, 0x03, 0x1a, 0xac, 0xb2, 0xa8, 0xfe,\n\t\t\t\t0xb4, 0x96, 0x04, 0x5b, 0xca, 0xf7, 0xc1, 0xf4,\n\t\t\t\t0x2e, 0x53, 0x24, 0x6e, 0x34, 0x0c, 0x58, 0x36,\n\t\t\t\t0x71, 0x97, 0x59, 0xe9, 0x41, 0x66, 0xe2, 0x43,\n\t\t\t\t0xa0, 0x13, 0xb6, 0x00, 0x00, 0x20, 0x1a, 0x1a,\n\t\t\t\t0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,\n\t\t\t\t0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,\n\t\t\t\t0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,\n\t\t\t\t0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,\n\t\t\t\t0x00, 0x7b, 0xba, 0xba, 0x00, 0x00, 0xff, 0x01,\n\t\t\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,\n\t\t\t\t0x14, 0x00, 0x00, 0x11, 0x63, 0x2e, 0x73, 0x2d,\n\t\t\t\t0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,\n\t\t\t\t0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00,\n\t\t\t\t0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00,\n\t\t\t\t0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04,\n\t\t\t\t0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08,\n\t\t\t\t0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x05, 0x00,\n\t\t\t\t0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,\n\t\t\t\t0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c,\n\t\t\t\t0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70,\n\t\t\t\t0x2f, 0x31, 0x2e, 0x31, 0x00, 0x0b, 0x00, 0x02,\n\t\t\t\t0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,\n\t\t\t\t0xaa, 0xaa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,\n\t\t\t\t0xaa, 0xaa, 0x00, 0x01, 0x00,\n\t\t\t},\n\t\t\tdomain: \"c.s-microsoft.com\",\n\t\t\terr:    false,\n\t\t},\n\t\t{\n\t\t\tinput: []byte{\n\t\t\t\t0x16, 0x03, 0x01, 0x00, 0xee, 0x01, 0x00, 0x00,\n\t\t\t\t0xea, 0x03, 0x03, 0xe7, 0x91, 0x9e, 0x93, 0xca,\n\t\t\t\t0x78, 0x1b, 0x3c, 0xe0, 0x65, 0x25, 0x58, 0xb5,\n\t\t\t\t0x93, 0xe1, 0x0f, 0x85, 0xec, 0x9a, 0x66, 0x8e,\n\t\t\t\t0x61, 0x82, 0x88, 0xc8, 0xfc, 0xae, 0x1e, 0xca,\n\t\t\t\t0xd7, 0xa5, 0x63, 0x20, 0xbd, 0x1c, 0x00, 0x00,\n\t\t\t\t0x8b, 0xee, 0x09, 0xe3, 0x47, 0x6a, 0x0e, 0x74,\n\t\t\t\t0xb0, 0xbc, 0xa3, 0x02, 0xa7, 0x35, 0xe8, 0x85,\n\t\t\t\t0x70, 0x7c, 0x7a, 0xf0, 0x00, 0xdf, 0x4a, 0xea,\n\t\t\t\t0x87, 0x01, 0x14, 0x91, 0x00, 0x20, 0xea, 0xea,\n\t\t\t\t0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,\n\t\t\t\t0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13,\n\t\t\t\t0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d,\n\t\t\t\t0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,\n\t\t\t\t0x00, 0x81, 0x9a, 0x9a, 0x00, 0x00, 0xff, 0x01,\n\t\t\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,\n\t\t\t\t0x16, 0x00, 0x00, 0x13, 0x77, 0x77, 0x77, 0x30,\n\t\t\t\t0x37, 0x2e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x74,\n\t\t\t\t0x61, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x00,\n\t\t\t\t0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,\n\t\t\t\t0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,\n\t\t\t\t0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,\n\t\t\t\t0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00,\n\t\t\t\t0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e,\n\t\t\t\t0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74,\n\t\t\t\t0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x75, 0x50,\n\t\t\t\t0x00, 0x00, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,\n\t\t\t\t0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x9a, 0x9a,\n\t\t\t\t0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x8a, 0x8a,\n\t\t\t\t0x00, 0x01, 0x00,\n\t\t\t},\n\t\t\tdomain: \"www07.clicktale.net\",\n\t\t\terr:    false,\n\t\t},\n\t\t{\n\t\t\tinput: []byte{\n\t\t\t\t0x16, 0x03, 0x01, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe2, 0x03, 0x03, 0x81, 0x47, 0xc1,\n\t\t\t\t0x66, 0xd5, 0x1b, 0xfa, 0x4b, 0xb5, 0xe0, 0x2a, 0xe1, 0xa7, 0x87, 0x13, 0x1d, 0x11, 0xaa, 0xc6,\n\t\t\t\t0xce, 0xfc, 0x7f, 0xab, 0x94, 0xc8, 0x62, 0xad, 0xc8, 0xab, 0x0c, 0xdd, 0xcb, 0x20, 0x6f, 0x9d,\n\t\t\t\t0x07, 0xf1, 0x95, 0x3e, 0x99, 0xd8, 0xf3, 0x6d, 0x97, 0xee, 0x19, 0x0b, 0x06, 0x1b, 0xf4, 0x84,\n\t\t\t\t0x0b, 0xb6, 0x8f, 0xcc, 0xde, 0xe2, 0xd0, 0x2d, 0x6b, 0x0c, 0x1f, 0x52, 0x53, 0x13, 0x00, 0x08,\n\t\t\t\t0x13, 0x02, 0x13, 0x03, 0x13, 0x01, 0x00, 0xff, 0x01, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x0c,\n\t\t\t\t0x00, 0x0a, 0x00, 0x00, 0x07, 0x64, 0x6f, 0x67, 0x66, 0x69, 0x73, 0x68, 0x00, 0x0b, 0x00, 0x04,\n\t\t\t\t0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x1e,\n\t\t\t\t0x00, 0x19, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00,\n\t\t\t\t0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x07, 0x08, 0x08,\n\t\t\t\t0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01,\n\t\t\t\t0x06, 0x01, 0x00, 0x2b, 0x00, 0x07, 0x06, 0x7f, 0x1c, 0x7f, 0x1b, 0x7f, 0x1a, 0x00, 0x2d, 0x00,\n\t\t\t\t0x02, 0x01, 0x01, 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x2f, 0x35, 0x0c,\n\t\t\t\t0xb6, 0x90, 0x0a, 0xb7, 0xd5, 0xc4, 0x1b, 0x2f, 0x60, 0xaa, 0x56, 0x7b, 0x3f, 0x71, 0xc8, 0x01,\n\t\t\t\t0x7e, 0x86, 0xd3, 0xb7, 0x0c, 0x29, 0x1a, 0x9e, 0x5b, 0x38, 0x3f, 0x01, 0x72,\n\t\t\t},\n\t\t\tdomain: \"dogfish\",\n\t\t\terr:    false,\n\t\t},\n\t\t{\n\t\t\tinput: []byte{\n\t\t\t\t0x16, 0x03, 0x01, 0x01, 0x03, 0x01, 0x00, 0x00,\n\t\t\t\t0xff, 0x03, 0x03, 0x3d, 0x89, 0x52, 0x9e, 0xee,\n\t\t\t\t0xbe, 0x17, 0x63, 0x75, 0xef, 0x29, 0xbd, 0x14,\n\t\t\t\t0x6a, 0x49, 0xe0, 0x2c, 0x37, 0x57, 0x71, 0x62,\n\t\t\t\t0x82, 0x44, 0x94, 0x8f, 0x6e, 0x94, 0x08, 0x45,\n\t\t\t\t0x7f, 0xdb, 0xc1, 0x00, 0x00, 0x3e, 0xc0, 0x2c,\n\t\t\t\t0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8,\n\t\t\t\t0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e,\n\t\t\t\t0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23,\n\t\t\t\t0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14,\n\t\t\t\t0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33,\n\t\t\t\t0x00, 0x9d, 0x00, 0x9c, 0x13, 0x02, 0x13, 0x03,\n\t\t\t\t0x13, 0x01, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35,\n\t\t\t\t0x00, 0x2f, 0x00, 0xff, 0x01, 0x00, 0x00, 0x98,\n\t\t\t\t0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00,\n\t\t\t\t0x0b, 0x31, 0x30, 0x2e, 0x34, 0x32, 0x2e, 0x30,\n\t\t\t\t0x2e, 0x32, 0x34, 0x33, 0x00, 0x0b, 0x00, 0x04,\n\t\t\t\t0x03, 0x00, 0x01, 0x02, 0x00, 0x0a, 0x00, 0x0a,\n\t\t\t\t0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19,\n\t\t\t\t0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0d,\n\t\t\t\t0x00, 0x20, 0x00, 0x1e, 0x04, 0x03, 0x05, 0x03,\n\t\t\t\t0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06,\n\t\t\t\t0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03,\n\t\t\t\t0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x05, 0x02,\n\t\t\t\t0x06, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17,\n\t\t\t\t0x00, 0x00, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x7f,\n\t\t\t\t0x14, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00,\n\t\t\t\t0x2d, 0x00, 0x03, 0x02, 0x01, 0x00, 0x00, 0x28,\n\t\t\t\t0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,\n\t\t\t\t0x13, 0x7c, 0x6e, 0x97, 0xc4, 0xfd, 0x09, 0x2e,\n\t\t\t\t0x70, 0x2f, 0x73, 0x5a, 0x9b, 0x57, 0x4d, 0x5f,\n\t\t\t\t0x2b, 0x73, 0x2c, 0xa5, 0x4a, 0x98, 0x40, 0x3d,\n\t\t\t\t0x75, 0x6e, 0xb4, 0x76, 0xf9, 0x48, 0x8f, 0x36,\n\t\t\t},\n\t\t\tdomain: \"10.42.0.243\",\n\t\t\terr:    false,\n\t\t},\n\t}\n\n\tfor _, test := range cases {\n\t\theader, err := SniffTLS(test.input)\n\t\tif test.err {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Expect error but nil in test %v\", test)\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expect no error but actually %s in test %v\", err.Error(), test)\n\t\t\t}\n\t\t\tif header.Domain() != test.domain {\n\t\t\t\tt.Error(\"expect domain \", test.domain, \" but got \", header.Domain())\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/protocol/udp/packet.go",
    "content": "package udp\n\nimport (\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// Packet is a UDP packet together with its source and destination address.\ntype Packet struct {\n\tPayload *buf.Buffer\n\tSource  net.Destination\n\tTarget  net.Destination\n}\n"
  },
  {
    "path": "common/protocol/udp/udp.go",
    "content": "package udp\n"
  },
  {
    "path": "common/protocol/user.go",
    "content": "package protocol\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\nfunc (u *User) GetTypedAccount() (Account, error) {\n\tif u.GetAccount() == nil {\n\t\treturn nil, errors.New(\"Account is missing\").AtWarning()\n\t}\n\n\trawAccount, err := u.Account.GetInstance()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif asAccount, ok := rawAccount.(AsAccount); ok {\n\t\treturn asAccount.AsAccount()\n\t}\n\tif account, ok := rawAccount.(Account); ok {\n\t\treturn account, nil\n\t}\n\treturn nil, errors.New(\"Unknown account type: \", u.Account.Type)\n}\n\nfunc (u *User) ToMemoryUser() (*MemoryUser, error) {\n\taccount, err := u.GetTypedAccount()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &MemoryUser{\n\t\tAccount: account,\n\t\tEmail:   u.Email,\n\t\tLevel:   u.Level,\n\t}, nil\n}\n\nfunc ToProtoUser(mu *MemoryUser) *User {\n\tif mu == nil {\n\t\treturn nil\n\t}\n\treturn &User{\n\t\tAccount: serial.ToTypedMessage(mu.Account.ToProto()),\n\t\tEmail:   mu.Email,\n\t\tLevel:   mu.Level,\n\t}\n}\n\n// MemoryUser is a parsed form of User, to reduce number of parsing of Account proto.\ntype MemoryUser struct {\n\t// Account is the parsed account of the protocol.\n\tAccount Account\n\tEmail   string\n\tLevel   uint32\n}\n"
  },
  {
    "path": "common/protocol/user.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/protocol/user.proto\n\npackage protocol\n\nimport (\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// User is a generic user for all protocols.\ntype User struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\tLevel uint32                 `protobuf:\"varint,1,opt,name=level,proto3\" json:\"level,omitempty\"`\n\tEmail string                 `protobuf:\"bytes,2,opt,name=email,proto3\" json:\"email,omitempty\"`\n\t// Protocol specific account information. Must be the account proto in one of\n\t// the proxies.\n\tAccount       *serial.TypedMessage `protobuf:\"bytes,3,opt,name=account,proto3\" json:\"account,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *User) Reset() {\n\t*x = User{}\n\tmi := &file_common_protocol_user_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *User) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*User) ProtoMessage() {}\n\nfunc (x *User) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_protocol_user_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use User.ProtoReflect.Descriptor instead.\nfunc (*User) Descriptor() ([]byte, []int) {\n\treturn file_common_protocol_user_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *User) GetLevel() uint32 {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn 0\n}\n\nfunc (x *User) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *User) GetAccount() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Account\n\t}\n\treturn nil\n}\n\nvar File_common_protocol_user_proto protoreflect.FileDescriptor\n\nconst file_common_protocol_user_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1acommon/protocol/user.proto\\x12\\x14xray.common.protocol\\x1a!common/serial/typed_message.proto\\\"n\\n\" +\n\t\"\\x04User\\x12\\x14\\n\" +\n\t\"\\x05level\\x18\\x01 \\x01(\\rR\\x05level\\x12\\x14\\n\" +\n\t\"\\x05email\\x18\\x02 \\x01(\\tR\\x05email\\x12:\\n\" +\n\t\"\\aaccount\\x18\\x03 \\x01(\\v2 .xray.common.serial.TypedMessageR\\aaccountB^\\n\" +\n\t\"\\x18com.xray.common.protocolP\\x01Z)github.com/xtls/xray-core/common/protocol\\xaa\\x02\\x14Xray.Common.Protocolb\\x06proto3\"\n\nvar (\n\tfile_common_protocol_user_proto_rawDescOnce sync.Once\n\tfile_common_protocol_user_proto_rawDescData []byte\n)\n\nfunc file_common_protocol_user_proto_rawDescGZIP() []byte {\n\tfile_common_protocol_user_proto_rawDescOnce.Do(func() {\n\t\tfile_common_protocol_user_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_protocol_user_proto_rawDesc), len(file_common_protocol_user_proto_rawDesc)))\n\t})\n\treturn file_common_protocol_user_proto_rawDescData\n}\n\nvar file_common_protocol_user_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_protocol_user_proto_goTypes = []any{\n\t(*User)(nil),                // 0: xray.common.protocol.User\n\t(*serial.TypedMessage)(nil), // 1: xray.common.serial.TypedMessage\n}\nvar file_common_protocol_user_proto_depIdxs = []int32{\n\t1, // 0: xray.common.protocol.User.account:type_name -> xray.common.serial.TypedMessage\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_common_protocol_user_proto_init() }\nfunc file_common_protocol_user_proto_init() {\n\tif File_common_protocol_user_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_protocol_user_proto_rawDesc), len(file_common_protocol_user_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_protocol_user_proto_goTypes,\n\t\tDependencyIndexes: file_common_protocol_user_proto_depIdxs,\n\t\tMessageInfos:      file_common_protocol_user_proto_msgTypes,\n\t}.Build()\n\tFile_common_protocol_user_proto = out.File\n\tfile_common_protocol_user_proto_goTypes = nil\n\tfile_common_protocol_user_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/protocol/user.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.protocol;\noption csharp_namespace = \"Xray.Common.Protocol\";\noption go_package = \"github.com/xtls/xray-core/common/protocol\";\noption java_package = \"com.xray.common.protocol\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\n\n// User is a generic user for all protocols.\nmessage User {\n  uint32 level = 1;\n  string email = 2;\n\n  // Protocol specific account information. Must be the account proto in one of\n  // the proxies.\n  xray.common.serial.TypedMessage account = 3;\n}\n"
  },
  {
    "path": "common/reflect/marshal.go",
    "content": "package reflect\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\tcnet \"github.com/xtls/xray-core/common/net\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n)\n\nfunc MarshalToJson(v interface{}, insertTypeInfo bool) (string, bool) {\n\tif itf := marshalInterface(v, true, insertTypeInfo); itf != nil {\n\t\tif b, err := JSONMarshalWithoutEscape(itf); err == nil {\n\t\t\treturn string(b[:]), true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc JSONMarshalWithoutEscape(t interface{}) ([]byte, error) {\n\tbuffer := &bytes.Buffer{}\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetIndent(\"\", \"    \")\n\tencoder.SetEscapeHTML(false)\n\terr := encoder.Encode(t)\n\treturn buffer.Bytes(), err\n}\n\nfunc marshalTypedMessage(v *cserial.TypedMessage, ignoreNullValue bool, insertTypeInfo bool) interface{} {\n\tif v == nil {\n\t\treturn nil\n\t}\n\ttmsg, err := v.GetInstance()\n\tif err != nil {\n\t\treturn nil\n\t}\n\tr := marshalInterface(tmsg, ignoreNullValue, insertTypeInfo)\n\tif msg, ok := r.(map[string]interface{}); ok && insertTypeInfo {\n\t\tmsg[\"_TypedMessage_\"] = v.Type\n\t}\n\treturn r\n}\n\nfunc marshalSlice(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {\n\tr := make([]interface{}, 0)\n\tfor i := 0; i < v.Len(); i++ {\n\t\trv := v.Index(i)\n\t\tif rv.CanInterface() {\n\t\t\tvalue := rv.Interface()\n\t\t\tr = append(r, marshalInterface(value, ignoreNullValue, insertTypeInfo))\n\t\t}\n\t}\n\treturn r\n}\n\nfunc isNullValue(f reflect.StructField, rv reflect.Value) bool {\n\tif rv.Kind() == reflect.Struct {\n\t\treturn false\n\t} else if rv.Kind() == reflect.String && rv.Len() == 0 {\n\t\treturn true\n\t} else if !isValueKind(rv.Kind()) && rv.IsNil() {\n\t\treturn true\n\t} else if tag := f.Tag.Get(\"json\"); strings.Contains(tag, \"omitempty\") {\n\t\tif !rv.IsValid() || rv.IsZero() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc toJsonName(f reflect.StructField) string {\n\tif tags := f.Tag.Get(\"protobuf\"); len(tags) > 0 {\n\t\tfor _, tag := range strings.Split(tags, \",\") {\n\t\t\tif before, after, ok := strings.Cut(tag, \"=\"); ok && before == \"json\" {\n\t\t\t\treturn after\n\t\t\t}\n\t\t}\n\t}\n\tif tag := f.Tag.Get(\"json\"); len(tag) > 0 {\n\t\tif before, _, ok := strings.Cut(tag, \",\"); ok {\n\t\t\treturn before\n\t\t} else {\n\t\t\treturn tag\n\t\t}\n\t}\n\treturn f.Name\n}\n\nfunc marshalStruct(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {\n\tr := make(map[string]interface{})\n\tt := v.Type()\n\tfor i := 0; i < v.NumField(); i++ {\n\t\trv := v.Field(i)\n\t\tif rv.CanInterface() {\n\t\t\tft := t.Field(i)\n\t\t\tif !ignoreNullValue || !isNullValue(ft, rv) {\n\t\t\t\tname := toJsonName(ft)\n\t\t\t\tvalue := rv.Interface()\n\t\t\t\ttv := marshalInterface(value, ignoreNullValue, insertTypeInfo)\n\t\t\t\tr[name] = tv\n\t\t\t}\n\t\t}\n\t}\n\treturn r\n}\n\nfunc marshalMap(v reflect.Value, ignoreNullValue bool, insertTypeInfo bool) interface{} {\n\t// policy.level is map[uint32] *struct\n\tkt := v.Type().Key()\n\tvt := reflect.TypeOf((*interface{})(nil))\n\tmt := reflect.MapOf(kt, vt)\n\tr := reflect.MakeMap(mt)\n\tfor _, key := range v.MapKeys() {\n\t\trv := v.MapIndex(key)\n\t\tif rv.CanInterface() {\n\t\t\tiv := rv.Interface()\n\t\t\ttv := marshalInterface(iv, ignoreNullValue, insertTypeInfo)\n\t\t\tif tv != nil || !ignoreNullValue {\n\t\t\t\tr.SetMapIndex(key, reflect.ValueOf(&tv))\n\t\t\t}\n\t\t}\n\t}\n\treturn r.Interface()\n}\n\nfunc marshalIString(v interface{}) (r string, ok bool) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tr = \"\"\n\t\t\tok = false\n\t\t}\n\t}()\n\tif iStringFn, ok := v.(interface{ String() string }); ok {\n\t\treturn iStringFn.String(), true\n\t}\n\treturn \"\", false\n}\n\nfunc serializePortList(portList *cnet.PortList) (interface{}, bool) {\n\tif portList == nil {\n\t\treturn nil, false\n\t}\n\n\tn := len(portList.Range)\n\tif n == 1 {\n\t\tif first := portList.Range[0]; first.From == first.To {\n\t\t\treturn first.From, true\n\t\t}\n\t}\n\n\tr := make([]string, 0, n)\n\tfor _, pr := range portList.Range {\n\t\tif pr.From == pr.To {\n\t\t\tr = append(r, pr.FromPort().String())\n\t\t} else {\n\t\t\tr = append(r, fmt.Sprintf(\"%d-%d\", pr.From, pr.To))\n\t\t}\n\t}\n\treturn strings.Join(r, \",\"), true\n}\n\nfunc marshalKnownType(v interface{}, ignoreNullValue bool, insertTypeInfo bool) (interface{}, bool) {\n\tswitch ty := v.(type) {\n\tcase cserial.TypedMessage:\n\t\treturn marshalTypedMessage(&ty, ignoreNullValue, insertTypeInfo), true\n\tcase *cserial.TypedMessage:\n\t\treturn marshalTypedMessage(ty, ignoreNullValue, insertTypeInfo), true\n\tcase map[string]json.RawMessage:\n\t\treturn ty, true\n\tcase []json.RawMessage:\n\t\treturn ty, true\n\tcase *json.RawMessage, json.RawMessage:\n\t\treturn ty, true\n\tcase *cnet.IPOrDomain:\n\t\tif domain := v.(*cnet.IPOrDomain); domain != nil {\n\t\t\treturn domain.AsAddress().String(), true\n\t\t}\n\t\treturn nil, false\n\tcase *cnet.PortList:\n\t\tnpl := v.(*cnet.PortList)\n\t\treturn serializePortList(npl)\n\tcase *conf.PortList:\n\t\tcpl := v.(*conf.PortList)\n\t\treturn serializePortList(cpl.Build())\n\tcase conf.Int32Range:\n\t\ti32rng := v.(conf.Int32Range)\n\t\tif i32rng.Left == i32rng.Right {\n\t\t\treturn i32rng.Left, true\n\t\t}\n\t\treturn i32rng.String(), true\n\tcase cnet.Address:\n\t\tif addr := v.(cnet.Address); addr != nil {\n\t\t\treturn addr.String(), true\n\t\t}\n\t\treturn nil, false\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc isValueKind(kind reflect.Kind) bool {\n\tswitch kind {\n\tcase reflect.Bool,\n\t\treflect.Int,\n\t\treflect.Int8,\n\t\treflect.Int16,\n\t\treflect.Int32,\n\t\treflect.Int64,\n\t\treflect.Uint,\n\t\treflect.Uint8,\n\t\treflect.Uint16,\n\t\treflect.Uint32,\n\t\treflect.Uint64,\n\t\treflect.Uintptr,\n\t\treflect.Float32,\n\t\treflect.Float64,\n\t\treflect.Complex64,\n\t\treflect.Complex128,\n\t\treflect.String:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {\n\n\tif r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {\n\t\treturn r\n\t}\n\n\trv := reflect.ValueOf(v)\n\tif rv.Kind() == reflect.Ptr {\n\t\trv = rv.Elem()\n\t}\n\tk := rv.Kind()\n\tif k == reflect.Invalid {\n\t\treturn nil\n\t}\n\n\tif ty := rv.Type().Name(); isValueKind(k) {\n\t\tif k.String() != ty {\n\t\t\tif s, ok := marshalIString(v); ok {\n\t\t\t\treturn s\n\t\t\t}\n\t\t}\n\t\treturn v\n\t}\n\n\t// fmt.Println(\"kind:\", k, \"type:\", rv.Type().Name())\n\n\tswitch k {\n\tcase reflect.Struct:\n\t\treturn marshalStruct(rv, ignoreNullValue, insertTypeInfo)\n\tcase reflect.Slice:\n\t\treturn marshalSlice(rv, ignoreNullValue, insertTypeInfo)\n\tcase reflect.Array:\n\t\treturn marshalSlice(rv, ignoreNullValue, insertTypeInfo)\n\tcase reflect.Map:\n\t\treturn marshalMap(rv, ignoreNullValue, insertTypeInfo)\n\tdefault:\n\t\tbreak\n\t}\n\n\tif str, ok := marshalIString(v); ok {\n\t\treturn str\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/reflect/marshal_test.go",
    "content": "package reflect_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t. \"github.com/xtls/xray-core/common/reflect\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\tiserial \"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks\"\n)\n\nfunc TestMashalAccount(t *testing.T) {\n\taccount := &shadowsocks.Account{\n\t\tPassword:   \"shadowsocks-password\",\n\t\tCipherType: shadowsocks.CipherType_CHACHA20_POLY1305,\n\t}\n\n\tuser := &protocol.User{\n\t\tLevel:   0,\n\t\tEmail:   \"love@v2ray.com\",\n\t\tAccount: cserial.ToTypedMessage(account),\n\t}\n\n\tj, ok := MarshalToJson(user, false)\n\tif !ok || strings.Contains(j, \"_TypedMessage_\") {\n\n\t\tt.Error(\"marshal account failed\")\n\t}\n\n\tkws := []string{\"CHACHA20_POLY1305\", \"cipherType\", \"shadowsocks-password\"}\n\tfor _, kw := range kws {\n\t\tif !strings.Contains(j, kw) {\n\t\t\tt.Error(\"marshal account failed\")\n\t\t}\n\t}\n\t// t.Log(j)\n}\n\nfunc TestMashalStruct(t *testing.T) {\n\ttype Foo = struct {\n\t\tN   int                             `json:\"n\"`\n\t\tNp  *int                            `json:\"np\"`\n\t\tS   string                          `json:\"s\"`\n\t\tArr *[]map[string]map[string]string `json:\"arr\"`\n\t}\n\n\tn := 1\n\tnp := &n\n\tarr := make([]map[string]map[string]string, 0)\n\tm1 := make(map[string]map[string]string, 0)\n\tm2 := make(map[string]string, 0)\n\tm2[\"hello\"] = \"world\"\n\tm1[\"foo\"] = m2\n\n\tarr = append(arr, m1)\n\n\tf1 := Foo{\n\t\tN:   n,\n\t\tNp:  np,\n\t\tS:   \"hello\",\n\t\tArr: &arr,\n\t}\n\n\ts, ok1 := MarshalToJson(f1, true)\n\tsp, ok2 := MarshalToJson(&f1, true)\n\n\tif !ok1 || !ok2 || s != sp {\n\t\tt.Error(\"marshal failed\")\n\t}\n\n\tf2 := Foo{}\n\tif json.Unmarshal([]byte(s), &f2) != nil {\n\t\tt.Error(\"json unmarshal failed\")\n\t}\n\n\tv := (*f2.Arr)[0][\"foo\"][\"hello\"]\n\n\tif f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != \"world\" {\n\t\tt.Error(\"f1 not equal to f2\")\n\t}\n}\n\nfunc TestMarshalConfigJson(t *testing.T) {\n\n\tbuf := bytes.NewBufferString(getConfig())\n\tconfig, err := iserial.DecodeJSONConfig(buf)\n\tif err != nil {\n\t\tt.Error(\"decode JSON config failed\")\n\t}\n\n\tbc, err := config.Build()\n\tif err != nil {\n\t\tt.Error(\"build core config failed\")\n\t}\n\n\ttmsg := cserial.ToTypedMessage(bc)\n\ttc, ok := MarshalToJson(tmsg, true)\n\tif !ok {\n\t\tt.Error(\"marshal config failed\")\n\t}\n\n\t// t.Log(tc)\n\n\tkeywords := []string{\n\t\t\"4784f9b8-a879-4fec-9718-ebddefa47750\",\n\t\t\"bing.com\",\n\t\t\"inboundTag\",\n\t\t\"level\",\n\t\t\"stats\",\n\t\t\"userDownlink\",\n\t\t\"userUplink\",\n\t\t\"system\",\n\t\t\"inboundDownlink\",\n\t\t\"outboundUplink\",\n\t\t\"XHTTP_IN\",\n\t\t\"\\\"host\\\": \\\"bing.com\\\"\",\n\t\t\"scMaxEachPostBytes\",\n\t\t\"\\\"from\\\": 100\",\n\t\t\"\\\"to\\\": 1000\",\n\t\t\"\\\"from\\\": 1000000\",\n\t\t\"\\\"to\\\": 1000000\",\n\t}\n\tfor _, kw := range keywords {\n\t\tif !strings.Contains(tc, kw) {\n\t\t\tt.Log(\"config.json:\", tc)\n\t\t\tt.Error(\"keyword not found:\", kw)\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc getConfig() string {\n\treturn `{\n  \"log\": {\n    \"loglevel\": \"debug\"\n  },\n  \"stats\": {},\n  \"policy\": {\n    \"levels\": {\n      \"0\": {\n        \"statsUserUplink\": true,\n        \"statsUserDownlink\": true\n      }\n    },\n    \"system\": {\n      \"statsInboundUplink\": true,\n      \"statsInboundDownlink\": true,\n      \"statsOutboundUplink\": true,\n      \"statsOutboundDownlink\": true\n    }\n  },\n  \"inbounds\": [\n    {\n      \"tag\": \"agentin\",\n      \"protocol\": \"http\",\n      \"port\": 18080,\n      \"listen\": \"127.0.0.1\",\n      \"settings\": {}\n    },\n    {\n      \"listen\": \"127.0.0.1\",\n      \"port\": 10085,\n      \"protocol\": \"dokodemo-door\",\n      \"settings\": {\n        \"address\": \"127.0.0.1\"\n      },\n      \"tag\": \"api-in\"\n    }\n  ],\n  \"api\": {\n    \"tag\": \"api\",\n    \"services\": [\n      \"HandlerService\",\n      \"StatsService\"\n    ]\n  },\n  \"routing\": {\n    \"rules\": [\n      {\n        \"inboundTag\": [\n          \"api-in\"\n        ],\n        \"outboundTag\": \"api\"\n      }\n    ],\n    \"domainStrategy\": \"AsIs\"\n  },\n  \"outbounds\": [\n    {\n      \"protocol\": \"vless\",\n      \"settings\": {\n        \"vnext\": [\n          {\n            \"address\": \"1.2.3.4\",\n            \"port\": 1234,\n            \"users\": [\n              {\n                \"id\": \"4784f9b8-a879-4fec-9718-ebddefa47750\",\n                \"encryption\": \"none\"\n              }\n            ]\n          }\n        ]\n      },\n      \"tag\": \"XHTTP_IN\",\n      \"streamSettings\": {\n        \"network\": \"xhttp\",\n        \"xhttpSettings\": {\n          \"host\": \"bing.com\",\n          \"path\": \"/xhttp_client_upload\",\n          \"mode\": \"auto\",\n          \"extra\": {\n            \"noSSEHeader\": false,\n            \"scMaxEachPostBytes\": 1000000,\n            \"scMaxBufferedPosts\": 30,\n            \"xPaddingBytes\": \"100-1000\"\n          }\n        },\n        \"sockopt\": {\n          \"tcpFastOpen\": true,\n          \"acceptProxyProtocol\": false,\n          \"tcpcongestion\": \"bbr\",\n          \"tcpMptcp\": true\n        }\n      },\n      \"sniffing\": {\n        \"enabled\": true,\n        \"destOverride\": [\n          \"http\",\n          \"tls\",\n          \"quic\"\n        ],\n        \"metadataOnly\": false,\n        \"routeOnly\": true\n      }\n    }\n  ]\n}`\n}\n"
  },
  {
    "path": "common/retry/retry.go",
    "content": "package retry // import \"github.com/xtls/xray-core/common/retry\"\n\nimport (\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nvar ErrRetryFailed = errors.New(\"all retry attempts failed\")\n\n// Strategy is a way to retry on a specific function.\ntype Strategy interface {\n\t// On performs a retry on a specific function, until it doesn't return any error.\n\tOn(func() error) error\n}\n\ntype retryer struct {\n\ttotalAttempt int\n\tnextDelay    func() uint32\n}\n\n// On implements Strategy.On.\nfunc (r *retryer) On(method func() error) error {\n\tattempt := 0\n\taccumulatedError := make([]error, 0, r.totalAttempt)\n\tfor attempt < r.totalAttempt {\n\t\terr := method()\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tnumErrors := len(accumulatedError)\n\t\tif numErrors == 0 || err.Error() != accumulatedError[numErrors-1].Error() {\n\t\t\taccumulatedError = append(accumulatedError, err)\n\t\t}\n\t\tdelay := r.nextDelay()\n\t\ttime.Sleep(time.Duration(delay) * time.Millisecond)\n\t\tattempt++\n\t}\n\treturn errors.New(accumulatedError).Base(ErrRetryFailed)\n}\n\n// Timed returns a retry strategy with fixed interval.\nfunc Timed(attempts int, delay uint32) Strategy {\n\treturn &retryer{\n\t\ttotalAttempt: attempts,\n\t\tnextDelay: func() uint32 {\n\t\t\treturn delay\n\t\t},\n\t}\n}\n\nfunc ExponentialBackoff(attempts int, delay uint32) Strategy {\n\tnextDelay := uint32(0)\n\treturn &retryer{\n\t\ttotalAttempt: attempts,\n\t\tnextDelay: func() uint32 {\n\t\t\tr := nextDelay\n\t\t\tnextDelay += delay\n\t\t\treturn r\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "common/retry/retry_test.go",
    "content": "package retry_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t. \"github.com/xtls/xray-core/common/retry\"\n)\n\nvar errorTestOnly = errors.New(\"this is a fake error\")\n\nfunc TestNoRetry(t *testing.T) {\n\tstartTime := time.Now().Unix()\n\terr := Timed(10, 100000).On(func() error {\n\t\treturn nil\n\t})\n\tendTime := time.Now().Unix()\n\n\tcommon.Must(err)\n\tif endTime < startTime {\n\t\tt.Error(\"endTime < startTime: \", startTime, \" -> \", endTime)\n\t}\n}\n\nfunc TestRetryOnce(t *testing.T) {\n\tstartTime := time.Now()\n\tcalled := 0\n\terr := Timed(10, 1000).On(func() error {\n\t\tif called == 0 {\n\t\t\tcalled++\n\t\t\treturn errorTestOnly\n\t\t}\n\t\treturn nil\n\t})\n\tduration := time.Since(startTime)\n\n\tcommon.Must(err)\n\tif v := int64(duration / time.Millisecond); v < 900 {\n\t\tt.Error(\"duration: \", v)\n\t}\n}\n\nfunc TestRetryMultiple(t *testing.T) {\n\tstartTime := time.Now()\n\tcalled := 0\n\terr := Timed(10, 1000).On(func() error {\n\t\tif called < 5 {\n\t\t\tcalled++\n\t\t\treturn errorTestOnly\n\t\t}\n\t\treturn nil\n\t})\n\tduration := time.Since(startTime)\n\n\tcommon.Must(err)\n\tif v := int64(duration / time.Millisecond); v < 4900 {\n\t\tt.Error(\"duration: \", v)\n\t}\n}\n\nfunc TestRetryExhausted(t *testing.T) {\n\tstartTime := time.Now()\n\tcalled := 0\n\terr := Timed(2, 1000).On(func() error {\n\t\tcalled++\n\t\treturn errorTestOnly\n\t})\n\tduration := time.Since(startTime)\n\n\tif errors.Cause(err) != ErrRetryFailed {\n\t\tt.Error(\"cause: \", err)\n\t}\n\n\tif v := int64(duration / time.Millisecond); v < 1900 {\n\t\tt.Error(\"duration: \", v)\n\t}\n}\n\nfunc TestExponentialBackoff(t *testing.T) {\n\tstartTime := time.Now()\n\tcalled := 0\n\terr := ExponentialBackoff(10, 100).On(func() error {\n\t\tcalled++\n\t\treturn errorTestOnly\n\t})\n\tduration := time.Since(startTime)\n\n\tif errors.Cause(err) != ErrRetryFailed {\n\t\tt.Error(\"cause: \", err)\n\t}\n\tif v := int64(duration / time.Millisecond); v < 4000 {\n\t\tt.Error(\"duration: \", v)\n\t}\n}\n"
  },
  {
    "path": "common/serial/serial.go",
    "content": "package serial\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n)\n\n// ReadUint16 reads first two bytes from the reader, and then converts them to an uint16 value.\nfunc ReadUint16(reader io.Reader) (uint16, error) {\n\tvar b [2]byte\n\tif _, err := io.ReadFull(reader, b[:]); err != nil {\n\t\treturn 0, err\n\t}\n\treturn binary.BigEndian.Uint16(b[:]), nil\n}\n\n// WriteUint16 writes an uint16 value into writer.\nfunc WriteUint16(writer io.Writer, value uint16) (int, error) {\n\tvar b [2]byte\n\tbinary.BigEndian.PutUint16(b[:], value)\n\treturn writer.Write(b[:])\n}\n\n// WriteUint64 writes an uint64 value into writer.\nfunc WriteUint64(writer io.Writer, value uint64) (int, error) {\n\tvar b [8]byte\n\tbinary.BigEndian.PutUint64(b[:], value)\n\treturn writer.Write(b[:])\n}\n"
  },
  {
    "path": "common/serial/serial_test.go",
    "content": "package serial_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\nfunc TestUint16Serial(t *testing.T) {\n\tb := buf.New()\n\tdefer b.Release()\n\n\tn, err := serial.WriteUint16(b, 10)\n\tcommon.Must(err)\n\tif n != 2 {\n\t\tt.Error(\"expect 2 bytes writtng, but actually \", n)\n\t}\n\tif diff := cmp.Diff(b.Bytes(), []byte{0, 10}); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n}\n\nfunc TestUint64Serial(t *testing.T) {\n\tb := buf.New()\n\tdefer b.Release()\n\n\tn, err := serial.WriteUint64(b, 10)\n\tcommon.Must(err)\n\tif n != 8 {\n\t\tt.Error(\"expect 8 bytes writtng, but actually \", n)\n\t}\n\tif diff := cmp.Diff(b.Bytes(), []byte{0, 0, 0, 0, 0, 0, 0, 10}); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n}\n\nfunc TestReadUint16(t *testing.T) {\n\ttestCases := []struct {\n\t\tInput  []byte\n\t\tOutput uint16\n\t}{\n\t\t{\n\t\t\tInput:  []byte{0, 1},\n\t\t\tOutput: 1,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tv, err := serial.ReadUint16(bytes.NewReader(testCase.Input))\n\t\tcommon.Must(err)\n\t\tif v != testCase.Output {\n\t\t\tt.Error(\"for input \", testCase.Input, \" expect output \", testCase.Output, \" but got \", v)\n\t\t}\n\t}\n}\n\nfunc BenchmarkReadUint16(b *testing.B) {\n\treader := buf.New()\n\tdefer reader.Release()\n\n\tcommon.Must2(reader.Write([]byte{0, 1}))\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := serial.ReadUint16(reader)\n\t\tcommon.Must(err)\n\t\treader.Clear()\n\t\treader.Extend(2)\n\t}\n}\n\nfunc BenchmarkWriteUint64(b *testing.B) {\n\twriter := buf.New()\n\tdefer writer.Release()\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := serial.WriteUint64(writer, 8)\n\t\tcommon.Must(err)\n\t\twriter.Clear()\n\t}\n}\n"
  },
  {
    "path": "common/serial/string.go",
    "content": "package serial\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ToString serializes an arbitrary value into string.\nfunc ToString(v interface{}) string {\n\tif v == nil {\n\t\treturn \"\"\n\t}\n\n\tswitch value := v.(type) {\n\tcase string:\n\t\treturn value\n\tcase *string:\n\t\treturn *value\n\tcase fmt.Stringer:\n\t\treturn value.String()\n\tcase error:\n\t\treturn value.Error()\n\tdefault:\n\t\treturn fmt.Sprintf(\"%+v\", value)\n\t}\n}\n\n// Concat concatenates all input into a single string.\nfunc Concat(v ...interface{}) string {\n\tbuilder := strings.Builder{}\n\tfor _, value := range v {\n\t\tbuilder.WriteString(ToString(value))\n\t}\n\treturn builder.String()\n}\n"
  },
  {
    "path": "common/serial/string_test.go",
    "content": "package serial_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t. \"github.com/xtls/xray-core/common/serial\"\n)\n\nfunc TestToString(t *testing.T) {\n\ts := \"a\"\n\tdata := []struct {\n\t\tValue  interface{}\n\t\tString string\n\t}{\n\t\t{Value: s, String: s},\n\t\t{Value: &s, String: s},\n\t\t{Value: errors.New(\"t\"), String: \"t\"},\n\t\t{Value: []byte{'b', 'c'}, String: \"[98 99]\"},\n\t}\n\n\tfor _, c := range data {\n\t\tif r := cmp.Diff(ToString(c.Value), c.String); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t}\n}\n\nfunc TestConcat(t *testing.T) {\n\ttestCases := []struct {\n\t\tInput  []interface{}\n\t\tOutput string\n\t}{\n\t\t{\n\t\t\tInput: []interface{}{\n\t\t\t\t\"a\", \"b\",\n\t\t\t},\n\t\t\tOutput: \"ab\",\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tactual := Concat(testCase.Input...)\n\t\tif actual != testCase.Output {\n\t\t\tt.Error(\"Unexpected output: \", actual, \" but want: \", testCase.Output)\n\t\t}\n\t}\n}\n\nfunc BenchmarkConcat(b *testing.B) {\n\tinput := []interface{}{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\"}\n\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = Concat(input...)\n\t}\n}\n"
  },
  {
    "path": "common/serial/typed_message.go",
    "content": "package serial\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/reflect/protoreflect\"\n\t\"google.golang.org/protobuf/reflect/protoregistry\"\n)\n\n// ToTypedMessage converts a proto Message into TypedMessage.\nfunc ToTypedMessage(message proto.Message) *TypedMessage {\n\tif message == nil {\n\t\treturn nil\n\t}\n\tsettings, _ := proto.Marshal(message)\n\treturn &TypedMessage{\n\t\tType:  GetMessageType(message),\n\t\tValue: settings,\n\t}\n}\n\n// GetMessageType returns the name of this proto Message.\nfunc GetMessageType(message proto.Message) string {\n\treturn string(message.ProtoReflect().Descriptor().FullName())\n}\n\n// GetInstance creates a new instance of the message with messageType.\nfunc GetInstance(messageType string) (interface{}, error) {\n\tmessageTypeDescriptor := protoreflect.FullName(messageType)\n\tmType, err := protoregistry.GlobalTypes.FindMessageByName(messageTypeDescriptor)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn mType.New().Interface(), nil\n}\n\n// GetInstance converts current TypedMessage into a proto Message.\nfunc (v *TypedMessage) GetInstance() (proto.Message, error) {\n\tinstance, err := GetInstance(v.Type)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprotoMessage := instance.(proto.Message)\n\tif err := proto.Unmarshal(v.Value, protoMessage); err != nil {\n\t\treturn nil, err\n\t}\n\treturn protoMessage, nil\n}\n"
  },
  {
    "path": "common/serial/typed_message.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: common/serial/typed_message.proto\n\npackage serial\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// TypedMessage is a serialized proto message along with its type name.\ntype TypedMessage struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The name of the message type, retrieved from protobuf API.\n\tType string `protobuf:\"bytes,1,opt,name=type,proto3\" json:\"type,omitempty\"`\n\t// Serialized proto message.\n\tValue         []byte `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TypedMessage) Reset() {\n\t*x = TypedMessage{}\n\tmi := &file_common_serial_typed_message_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TypedMessage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TypedMessage) ProtoMessage() {}\n\nfunc (x *TypedMessage) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_serial_typed_message_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TypedMessage.ProtoReflect.Descriptor instead.\nfunc (*TypedMessage) Descriptor() ([]byte, []int) {\n\treturn file_common_serial_typed_message_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *TypedMessage) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *TypedMessage) GetValue() []byte {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\nvar File_common_serial_typed_message_proto protoreflect.FileDescriptor\n\nconst file_common_serial_typed_message_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!common/serial/typed_message.proto\\x12\\x12xray.common.serial\\\"8\\n\" +\n\t\"\\fTypedMessage\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\tR\\x04type\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\fR\\x05valueBX\\n\" +\n\t\"\\x16com.xray.common.serialP\\x01Z'github.com/xtls/xray-core/common/serial\\xaa\\x02\\x12Xray.Common.Serialb\\x06proto3\"\n\nvar (\n\tfile_common_serial_typed_message_proto_rawDescOnce sync.Once\n\tfile_common_serial_typed_message_proto_rawDescData []byte\n)\n\nfunc file_common_serial_typed_message_proto_rawDescGZIP() []byte {\n\tfile_common_serial_typed_message_proto_rawDescOnce.Do(func() {\n\t\tfile_common_serial_typed_message_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_serial_typed_message_proto_rawDesc), len(file_common_serial_typed_message_proto_rawDesc)))\n\t})\n\treturn file_common_serial_typed_message_proto_rawDescData\n}\n\nvar file_common_serial_typed_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_serial_typed_message_proto_goTypes = []any{\n\t(*TypedMessage)(nil), // 0: xray.common.serial.TypedMessage\n}\nvar file_common_serial_typed_message_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_common_serial_typed_message_proto_init() }\nfunc file_common_serial_typed_message_proto_init() {\n\tif File_common_serial_typed_message_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_serial_typed_message_proto_rawDesc), len(file_common_serial_typed_message_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_serial_typed_message_proto_goTypes,\n\t\tDependencyIndexes: file_common_serial_typed_message_proto_depIdxs,\n\t\tMessageInfos:      file_common_serial_typed_message_proto_msgTypes,\n\t}.Build()\n\tFile_common_serial_typed_message_proto = out.File\n\tfile_common_serial_typed_message_proto_goTypes = nil\n\tfile_common_serial_typed_message_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "common/serial/typed_message.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.common.serial;\noption csharp_namespace = \"Xray.Common.Serial\";\noption go_package = \"github.com/xtls/xray-core/common/serial\";\noption java_package = \"com.xray.common.serial\";\noption java_multiple_files = true;\n\n// TypedMessage is a serialized proto message along with its type name.\nmessage TypedMessage {\n  // The name of the message type, retrieved from protobuf API.\n  string type = 1;\n  // Serialized proto message.\n  bytes value = 2;\n}\n"
  },
  {
    "path": "common/serial/typed_message_test.go",
    "content": "package serial_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/serial\"\n)\n\nfunc TestGetInstance(t *testing.T) {\n\tp, err := GetInstance(\"\")\n\tif p != nil {\n\t\tt.Error(\"expected nil instance, but got \", p)\n\t}\n\tif err == nil {\n\t\tt.Error(\"expect non-nil error, but got nil\")\n\t}\n}\n\nfunc TestConvertingNilMessage(t *testing.T) {\n\tx := ToTypedMessage(nil)\n\tif x != nil {\n\t\tt.Error(\"expect nil, but actually not\")\n\t}\n}\n"
  },
  {
    "path": "common/session/context.go",
    "content": "package session\n\nimport (\n\t\"context\"\n\t_ \"unsafe\"\n\n\t\"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\n//go:linkname IndependentCancelCtx context.newCancelCtx\nfunc IndependentCancelCtx(parent context.Context) context.Context\n\nconst (\n\tinboundSessionKey         ctx.SessionKey = 1\n\toutboundSessionKey        ctx.SessionKey = 2\n\tcontentSessionKey         ctx.SessionKey = 3\n\tisReverseMuxKey           ctx.SessionKey = 4  // is reverse mux\n\tsockoptSessionKey         ctx.SessionKey = 5  // used by dokodemo to only receive sockopt.Mark\n\ttrackedConnectionErrorKey ctx.SessionKey = 6  // used by observer to get outbound error\n\tdispatcherKey             ctx.SessionKey = 7  // used by ss2022 inbounds to get dispatcher\n\ttimeoutOnlyKey            ctx.SessionKey = 8  // mux context's child contexts to only cancel when its own traffic times out\n\tallowedNetworkKey         ctx.SessionKey = 9  // muxcool server control incoming request tcp/udp\n\tfullHandlerKey            ctx.SessionKey = 10 // outbound gets full handler\n\tmitmAlpn11Key             ctx.SessionKey = 11 // used by TLS dialer\n\tmitmServerNameKey         ctx.SessionKey = 12 // used by TLS dialer\n)\n\nfunc ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {\n\treturn context.WithValue(ctx, inboundSessionKey, inbound)\n}\n\nfunc InboundFromContext(ctx context.Context) *Inbound {\n\tif inbound, ok := ctx.Value(inboundSessionKey).(*Inbound); ok {\n\t\treturn inbound\n\t}\n\treturn nil\n}\n\nfunc ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Context {\n\treturn context.WithValue(ctx, outboundSessionKey, outbounds)\n}\n\nfunc SubContextFromMuxInbound(ctx context.Context) context.Context {\n\tnewOutbounds := []*Outbound{{}}\n\n\tcontent := ContentFromContext(ctx)\n\tnewContent := Content{}\n\tif content != nil {\n\t\tnewContent = *content\n\t\tif content.Attributes != nil {\n\t\t\tpanic(\"content.Attributes != nil\")\n\t\t}\n\t}\n\treturn ContextWithContent(ContextWithOutbounds(ctx, newOutbounds), &newContent)\n}\n\nfunc OutboundsFromContext(ctx context.Context) []*Outbound {\n\tif outbounds, ok := ctx.Value(outboundSessionKey).([]*Outbound); ok {\n\t\treturn outbounds\n\t}\n\treturn nil\n}\n\nfunc ContextWithContent(ctx context.Context, content *Content) context.Context {\n\treturn context.WithValue(ctx, contentSessionKey, content)\n}\n\nfunc ContentFromContext(ctx context.Context) *Content {\n\tif content, ok := ctx.Value(contentSessionKey).(*Content); ok {\n\t\treturn content\n\t}\n\treturn nil\n}\n\nfunc ContextWithIsReverseMux(ctx context.Context, isReverseMux bool) context.Context {\n\treturn context.WithValue(ctx, isReverseMuxKey, isReverseMux)\n}\n\nfunc IsReverseMuxFromContext(ctx context.Context) bool {\n\tif val, ok := ctx.Value(isReverseMuxKey).(bool); ok {\n\t\treturn val\n\t}\n\treturn false\n}\n\nfunc ContextWithSockopt(ctx context.Context, s *Sockopt) context.Context {\n\treturn context.WithValue(ctx, sockoptSessionKey, s)\n}\n\nfunc SockoptFromContext(ctx context.Context) *Sockopt {\n\tif sockopt, ok := ctx.Value(sockoptSessionKey).(*Sockopt); ok {\n\t\treturn sockopt\n\t}\n\treturn nil\n}\n\nfunc GetForcedOutboundTagFromContext(ctx context.Context) string {\n\tif ContentFromContext(ctx) == nil {\n\t\treturn \"\"\n\t}\n\treturn ContentFromContext(ctx).Attribute(\"forcedOutboundTag\")\n}\n\nfunc SetForcedOutboundTagToContext(ctx context.Context, tag string) context.Context {\n\tif contentFromContext := ContentFromContext(ctx); contentFromContext == nil {\n\t\tctx = ContextWithContent(ctx, &Content{})\n\t}\n\tContentFromContext(ctx).SetAttribute(\"forcedOutboundTag\", tag)\n\treturn ctx\n}\n\ntype TrackedRequestErrorFeedback interface {\n\tSubmitError(err error)\n}\n\nfunc SubmitOutboundErrorToOriginator(ctx context.Context, err error) {\n\tif errorTracker := ctx.Value(trackedConnectionErrorKey); errorTracker != nil {\n\t\terrorTracker := errorTracker.(TrackedRequestErrorFeedback)\n\t\terrorTracker.SubmitError(err)\n\t}\n}\n\nfunc TrackedConnectionError(ctx context.Context, tracker TrackedRequestErrorFeedback) context.Context {\n\treturn context.WithValue(ctx, trackedConnectionErrorKey, tracker)\n}\n\nfunc ContextWithDispatcher(ctx context.Context, dispatcher routing.Dispatcher) context.Context {\n\treturn context.WithValue(ctx, dispatcherKey, dispatcher)\n}\n\nfunc DispatcherFromContext(ctx context.Context) routing.Dispatcher {\n\tif dispatcher, ok := ctx.Value(dispatcherKey).(routing.Dispatcher); ok {\n\t\treturn dispatcher\n\t}\n\treturn nil\n}\n\nfunc ContextWithTimeoutOnly(ctx context.Context, only bool) context.Context {\n\treturn context.WithValue(ctx, timeoutOnlyKey, only)\n}\n\nfunc TimeoutOnlyFromContext(ctx context.Context) bool {\n\tif val, ok := ctx.Value(timeoutOnlyKey).(bool); ok {\n\t\treturn val\n\t}\n\treturn false\n}\n\nfunc ContextWithAllowedNetwork(ctx context.Context, network net.Network) context.Context {\n\treturn context.WithValue(ctx, allowedNetworkKey, network)\n}\n\nfunc AllowedNetworkFromContext(ctx context.Context) net.Network {\n\tif val, ok := ctx.Value(allowedNetworkKey).(net.Network); ok {\n\t\treturn val\n\t}\n\treturn net.Network_Unknown\n}\n\nfunc ContextWithFullHandler(ctx context.Context, handler outbound.Handler) context.Context {\n\treturn context.WithValue(ctx, fullHandlerKey, handler)\n}\n\nfunc FullHandlerFromContext(ctx context.Context) outbound.Handler {\n\tif val, ok := ctx.Value(fullHandlerKey).(outbound.Handler); ok {\n\t\treturn val\n\t}\n\treturn nil\n}\n\nfunc ContextWithMitmAlpn11(ctx context.Context, alpn11 bool) context.Context {\n\treturn context.WithValue(ctx, mitmAlpn11Key, alpn11)\n}\n\nfunc MitmAlpn11FromContext(ctx context.Context) bool {\n\tif val, ok := ctx.Value(mitmAlpn11Key).(bool); ok {\n\t\treturn val\n\t}\n\treturn false\n}\n\nfunc ContextWithMitmServerName(ctx context.Context, serverName string) context.Context {\n\treturn context.WithValue(ctx, mitmServerNameKey, serverName)\n}\n\nfunc MitmServerNameFromContext(ctx context.Context) string {\n\tif val, ok := ctx.Value(mitmServerNameKey).(string); ok {\n\t\treturn val\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "common/session/session.go",
    "content": "// Package session provides functions for sessions of incoming requests.\npackage session // import \"github.com/xtls/xray-core/common/session\"\n\nimport (\n\t\"context\"\n\t\"math/rand\"\n\n\tc \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/signal\"\n)\n\n// NewID generates a new ID. The generated ID is high likely to be unique, but not cryptographically secure.\n// The generated ID will never be 0.\nfunc NewID() c.ID {\n\tfor {\n\t\tid := c.ID(rand.Uint32())\n\t\tif id != 0 {\n\t\t\treturn id\n\t\t}\n\t}\n}\n\n// ExportIDToError transfers session.ID into an error object, for logging purpose.\n// This can be used with error.WriteToLog().\nfunc ExportIDToError(ctx context.Context) errors.ExportOption {\n\tid := c.IDFromContext(ctx)\n\treturn func(h *errors.ExportOptionHolder) {\n\t\th.SessionID = uint32(id)\n\t}\n}\n\n// Inbound is the metadata of an inbound connection.\ntype Inbound struct {\n\t// Source address of the inbound connection.\n\tSource net.Destination\n\t// Local address of the inbound connection.\n\tLocal net.Destination\n\t// Gateway address.\n\tGateway net.Destination\n\t// Tag of the inbound proxy that handles the connection.\n\tTag string\n\t// Name of the inbound proxy that handles the connection.\n\tName string\n\t// User is the user that authenticates for the inbound. May be nil if the protocol allows anonymous traffic.\n\tUser *protocol.MemoryUser\n\t// VlessRoute is the user-sent VLESS UUID's 7th<<8 | 8th bytes.\n\tVlessRoute net.Port\n\t// Used by splice copy. Conn is actually internet.Connection. May be nil.\n\tConn net.Conn\n\t// Used by splice copy. Timer of the inbound buf copier. May be nil.\n\tTimer *signal.ActivityTimer\n\t// CanSpliceCopy is a property for this connection\n\t// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot\n\tCanSpliceCopy int\n}\n\n// Outbound is the metadata of an outbound connection.\ntype Outbound struct {\n\t// Target address of the outbound connection.\n\tOriginalTarget net.Destination\n\tTarget         net.Destination\n\tRouteTarget    net.Destination\n\t// Gateway address\n\tGateway net.Address\n\t// Tag of the outbound proxy that handles the connection.\n\tTag string\n\t// Name of the outbound proxy that handles the connection.\n\tName string\n\t// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings\n\tConn net.Conn\n\t// CanSpliceCopy is a property for this connection\n\t// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot\n\tCanSpliceCopy int\n}\n\n// SniffingRequest controls the behavior of content sniffing. They are from inbound config. Read-only\ntype SniffingRequest struct {\n\tExcludeForDomain               []string\n\tOverrideDestinationForProtocol []string\n\tEnabled                        bool\n\tMetadataOnly                   bool\n\tRouteOnly                      bool\n}\n\n// Content is the metadata of the connection content. Mainly used for routing.\ntype Content struct {\n\t// Protocol of current content.\n\tProtocol string\n\n\tSniffingRequest SniffingRequest\n\n\t// HTTP traffic sniffed headers\n\tAttributes map[string]string\n\n\t// SkipDNSResolve is set from DNS module. the DOH remote server maybe a domain name, this prevents cycle resolving dead loop\n\tSkipDNSResolve bool\n}\n\n// Sockopt is the settings for socket connection.\ntype Sockopt struct {\n\t// Mark of the socket connection.\n\tMark int32\n}\n\n// SetAttribute attaches additional string attributes to content.\nfunc (c *Content) SetAttribute(name string, value string) {\n\tif c.Attributes == nil {\n\t\tc.Attributes = make(map[string]string)\n\t}\n\tc.Attributes[name] = value\n}\n\n// Attribute retrieves additional string attributes from content.\nfunc (c *Content) Attribute(name string) string {\n\tif c.Attributes == nil {\n\t\treturn \"\"\n\t}\n\treturn c.Attributes[name]\n}\n"
  },
  {
    "path": "common/signal/done/done.go",
    "content": "package done\n\nimport (\n\t\"sync\"\n)\n\n// Instance is a utility for notifications of something being done.\ntype Instance struct {\n\taccess sync.Mutex\n\tc      chan struct{}\n\tclosed bool\n}\n\n// New returns a new Done.\nfunc New() *Instance {\n\treturn &Instance{\n\t\tc: make(chan struct{}),\n\t}\n}\n\n// Done returns true if Close() is called.\nfunc (d *Instance) Done() bool {\n\tselect {\n\tcase <-d.Wait():\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// Wait returns a channel for waiting for done.\nfunc (d *Instance) Wait() <-chan struct{} {\n\treturn d.c\n}\n\n// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status.\nfunc (d *Instance) Close() error {\n\td.access.Lock()\n\tdefer d.access.Unlock()\n\n\tif d.closed {\n\t\treturn nil\n\t}\n\n\td.closed = true\n\tclose(d.c)\n\n\treturn nil\n}\n"
  },
  {
    "path": "common/signal/notifier.go",
    "content": "package signal\n\n// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.\ntype Notifier struct {\n\tc chan struct{}\n}\n\n// NewNotifier creates a new Notifier.\nfunc NewNotifier() *Notifier {\n\treturn &Notifier{\n\t\tc: make(chan struct{}, 1),\n\t}\n}\n\n// Signal signals a change, usually by producer. This method never blocks.\nfunc (n *Notifier) Signal() {\n\tselect {\n\tcase n.c <- struct{}{}:\n\tdefault:\n\t}\n}\n\n// Wait returns a channel for waiting for changes. The returned channel never gets closed.\nfunc (n *Notifier) Wait() <-chan struct{} {\n\treturn n.c\n}\n"
  },
  {
    "path": "common/signal/notifier_test.go",
    "content": "package signal_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/signal\"\n)\n\nfunc TestNotifierSignal(t *testing.T) {\n\tn := NewNotifier()\n\n\tw := n.Wait()\n\tn.Signal()\n\n\tselect {\n\tcase <-w:\n\tdefault:\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "common/signal/pubsub/pubsub.go",
    "content": "package pubsub\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/common/task\"\n)\n\ntype Subscriber struct {\n\tbuffer chan interface{}\n\tdone   *done.Instance\n}\n\nfunc (s *Subscriber) push(msg interface{}) {\n\tselect {\n\tcase s.buffer <- msg:\n\tdefault:\n\t}\n}\n\nfunc (s *Subscriber) Wait() <-chan interface{} {\n\treturn s.buffer\n}\n\nfunc (s *Subscriber) Close() error {\n\treturn s.done.Close()\n}\n\nfunc (s *Subscriber) IsClosed() bool {\n\treturn s.done.Done()\n}\n\ntype Service struct {\n\tsync.RWMutex\n\tsubs  map[string][]*Subscriber\n\tctask *task.Periodic\n}\n\nfunc NewService() *Service {\n\ts := &Service{\n\t\tsubs: make(map[string][]*Subscriber),\n\t}\n\ts.ctask = &task.Periodic{\n\t\tExecute:  s.Cleanup,\n\t\tInterval: time.Second * 30,\n\t}\n\treturn s\n}\n\n// Cleanup cleans up internal caches of subscribers.\n// Visible for testing only.\nfunc (s *Service) Cleanup() error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tif len(s.subs) == 0 {\n\t\treturn errors.New(\"nothing to do\")\n\t}\n\n\tfor name, subs := range s.subs {\n\t\tnewSub := make([]*Subscriber, 0, len(s.subs))\n\t\tfor _, sub := range subs {\n\t\t\tif !sub.IsClosed() {\n\t\t\t\tnewSub = append(newSub, sub)\n\t\t\t}\n\t\t}\n\t\tif len(newSub) == 0 {\n\t\t\tdelete(s.subs, name)\n\t\t} else {\n\t\t\ts.subs[name] = newSub\n\t\t}\n\t}\n\n\tif len(s.subs) == 0 {\n\t\ts.subs = make(map[string][]*Subscriber)\n\t}\n\treturn nil\n}\n\nfunc (s *Service) Subscribe(name string) *Subscriber {\n\tsub := &Subscriber{\n\t\tbuffer: make(chan interface{}, 16),\n\t\tdone:   done.New(),\n\t}\n\ts.Lock()\n\ts.subs[name] = append(s.subs[name], sub)\n\ts.Unlock()\n\tcommon.Must(s.ctask.Start())\n\treturn sub\n}\n\nfunc (s *Service) Publish(name string, message interface{}) {\n\ts.RLock()\n\tdefer s.RUnlock()\n\n\tfor _, sub := range s.subs[name] {\n\t\tif !sub.IsClosed() {\n\t\t\tsub.push(message)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/signal/pubsub/pubsub_test.go",
    "content": "package pubsub_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/signal/pubsub\"\n)\n\nfunc TestPubsub(t *testing.T) {\n\tservice := NewService()\n\n\tsub := service.Subscribe(\"a\")\n\tservice.Publish(\"a\", 1)\n\n\tselect {\n\tcase v := <-sub.Wait():\n\t\tif v != 1 {\n\t\t\tt.Error(\"expected subscribed value 1, but got \", v)\n\t\t}\n\tdefault:\n\t\tt.Fail()\n\t}\n\n\tsub.Close()\n\tservice.Publish(\"a\", 2)\n\n\tselect {\n\tcase <-sub.Wait():\n\t\tt.Fail()\n\tdefault:\n\t}\n\n\tservice.Cleanup()\n}\n"
  },
  {
    "path": "common/signal/semaphore/semaphore.go",
    "content": "package semaphore\n\n// Instance is an implementation of semaphore.\ntype Instance struct {\n\ttoken chan struct{}\n}\n\n// New create a new Semaphore with n permits.\nfunc New(n int) *Instance {\n\ts := &Instance{\n\t\ttoken: make(chan struct{}, n),\n\t}\n\tfor i := 0; i < n; i++ {\n\t\ts.token <- struct{}{}\n\t}\n\treturn s\n}\n\n// Wait returns a channel for acquiring a permit.\nfunc (s *Instance) Wait() <-chan struct{} {\n\treturn s.token\n}\n\n// Signal releases a permit into the semaphore.\nfunc (s *Instance) Signal() {\n\ts.token <- struct{}{}\n}\n"
  },
  {
    "path": "common/signal/timer.go",
    "content": "package signal\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/task\"\n)\n\ntype ActivityUpdater interface {\n\tUpdate()\n}\n\ntype ActivityTimer struct {\n\tmu        sync.RWMutex\n\tupdated   chan struct{}\n\tcheckTask *task.Periodic\n\tonTimeout func()\n\tconsumed  atomic.Bool\n\tonce      sync.Once\n}\n\nfunc (t *ActivityTimer) Update() {\n\tselect {\n\tcase t.updated <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (t *ActivityTimer) check() error {\n\tselect {\n\tcase <-t.updated:\n\tdefault:\n\t\tt.finish()\n\t}\n\treturn nil\n}\n\nfunc (t *ActivityTimer) finish() {\n\tt.once.Do(func() {\n\t\tt.consumed.Store(true)\n\t\tt.mu.Lock()\n\t\tdefer t.mu.Unlock()\n\n\t\tcommon.CloseIfExists(t.checkTask)\n\t\tt.onTimeout()\n\t})\n}\n\nfunc (t *ActivityTimer) SetTimeout(timeout time.Duration) {\n\tif t.consumed.Load() {\n\t\treturn\n\t}\n\tif timeout == 0 {\n\t\tt.finish()\n\t\treturn\n\t}\n\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\t// double check, just in case\n\tif t.consumed.Load() {\n\t\treturn\n\t}\n\tnewCheckTask := &task.Periodic{\n\t\tInterval: timeout,\n\t\tExecute:  t.check,\n\t}\n\tcommon.CloseIfExists(t.checkTask)\n\tt.checkTask = newCheckTask\n\tt.Update()\n\tcommon.Must(newCheckTask.Start())\n}\n\nfunc CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {\n\ttimer := &ActivityTimer{\n\t\tupdated:   make(chan struct{}, 1),\n\t\tonTimeout: cancel,\n\t}\n\ttimer.SetTimeout(timeout)\n\treturn timer\n}\n"
  },
  {
    "path": "common/signal/timer_test.go",
    "content": "package signal_test\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/xtls/xray-core/common/signal\"\n)\n\nfunc TestActivityTimer(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\ttimer := CancelAfterInactivity(ctx, cancel, time.Second*4)\n\ttime.Sleep(time.Second * 6)\n\tif ctx.Err() == nil {\n\t\tt.Error(\"expected some error, but got nil\")\n\t}\n\truntime.KeepAlive(timer)\n}\n\nfunc TestActivityTimerUpdate(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\ttimer := CancelAfterInactivity(ctx, cancel, time.Second*10)\n\ttime.Sleep(time.Second * 3)\n\tif ctx.Err() != nil {\n\t\tt.Error(\"expected nil, but got \", ctx.Err().Error())\n\t}\n\ttimer.SetTimeout(time.Second * 1)\n\ttime.Sleep(time.Second * 2)\n\tif ctx.Err() == nil {\n\t\tt.Error(\"expected some error, but got nil\")\n\t}\n\truntime.KeepAlive(timer)\n}\n\nfunc TestActivityTimerNonBlocking(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\ttimer := CancelAfterInactivity(ctx, cancel, 0)\n\ttime.Sleep(time.Second * 1)\n\tselect {\n\tcase <-ctx.Done():\n\tdefault:\n\t\tt.Error(\"context not done\")\n\t}\n\ttimer.SetTimeout(0)\n\ttimer.SetTimeout(1)\n\ttimer.SetTimeout(2)\n}\n\nfunc TestActivityTimerZeroTimeout(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\ttimer := CancelAfterInactivity(ctx, cancel, 0)\n\tselect {\n\tcase <-ctx.Done():\n\tdefault:\n\t\tt.Error(\"context not done\")\n\t}\n\truntime.KeepAlive(timer)\n}\n"
  },
  {
    "path": "common/singbridge/destination.go",
    "content": "package singbridge\n\nimport (\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nfunc ToNetwork(network string) net.Network {\n\tswitch N.NetworkName(network) {\n\tcase N.NetworkTCP:\n\t\treturn net.Network_TCP\n\tcase N.NetworkUDP:\n\t\treturn net.Network_UDP\n\tdefault:\n\t\treturn net.Network_Unknown\n\t}\n}\n\nfunc ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {\n\t// IsFqdn() implicitly checks if the domain name is valid\n\tif socksaddr.IsFqdn() {\n\t\treturn net.Destination{\n\t\t\tNetwork: network,\n\t\t\tAddress: net.DomainAddress(socksaddr.Fqdn),\n\t\t\tPort:    net.Port(socksaddr.Port),\n\t\t}\n\t}\n\n\t// IsIP() implicitly checks if the IP address is valid\n\tif socksaddr.IsIP() {\n\t\treturn net.Destination{\n\t\t\tNetwork: network,\n\t\t\tAddress: net.IPAddress(socksaddr.Addr.AsSlice()),\n\t\t\tPort:    net.Port(socksaddr.Port),\n\t\t}\n\t}\n\n\treturn net.Destination{}\n}\n\nfunc ToSocksaddr(destination net.Destination) M.Socksaddr {\n\tvar addr M.Socksaddr\n\tswitch destination.Address.Family() {\n\tcase net.AddressFamilyDomain:\n\t\taddr.Fqdn = destination.Address.Domain()\n\tdefault:\n\t\taddr.Addr = M.AddrFromIP(destination.Address.IP())\n\t}\n\taddr.Port = uint16(destination.Port)\n\treturn addr\n}\n"
  },
  {
    "path": "common/singbridge/dialer.go",
    "content": "package singbridge\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nvar _ N.Dialer = (*XrayDialer)(nil)\n\ntype XrayDialer struct {\n\tinternet.Dialer\n}\n\nfunc NewDialer(dialer internet.Dialer) *XrayDialer {\n\treturn &XrayDialer{dialer}\n}\n\nfunc (d *XrayDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\treturn d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network)))\n}\n\nfunc (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n\ntype XrayOutboundDialer struct {\n\toutbound proxy.Outbound\n\tdialer   internet.Dialer\n}\n\nfunc NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOutboundDialer {\n\treturn &XrayOutboundDialer{outbound, dialer}\n}\n\nfunc (d *XrayOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tif len(outbounds) == 0 {\n\t\toutbounds = []*session.Outbound{{}}\n\t\tctx = session.ContextWithOutbounds(ctx, outbounds)\n\t}\n\tob := outbounds[len(outbounds)-1]\n\tob.Target = ToDestination(destination, ToNetwork(network))\n\n\topts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}\n\tuplinkReader, uplinkWriter := pipe.New(opts...)\n\tdownlinkReader, downlinkWriter := pipe.New(opts...)\n\tconn := cnc.NewConnection(cnc.ConnectionInputMulti(downlinkWriter), cnc.ConnectionOutputMulti(uplinkReader))\n\tgo d.outbound.Process(ctx, &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, d.dialer)\n\treturn conn, nil\n}\n\nfunc (d *XrayOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {\n\treturn nil, os.ErrInvalid\n}\n"
  },
  {
    "path": "common/singbridge/error.go",
    "content": "package singbridge\n\nimport E \"github.com/sagernet/sing/common/exceptions\"\n\nfunc ReturnError(err error) error {\n\tif E.IsClosedOrCanceled(err) {\n\t\treturn nil\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "common/singbridge/handler.go",
    "content": "package singbridge\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\nvar (\n\t_ N.TCPConnectionHandler = (*Dispatcher)(nil)\n\t_ N.UDPConnectionHandler = (*Dispatcher)(nil)\n)\n\ntype Dispatcher struct {\n\tupstream     routing.Dispatcher\n\tnewErrorFunc func(values ...any) *errors.Error\n}\n\nfunc NewDispatcher(dispatcher routing.Dispatcher, newErrorFunc func(values ...any) *errors.Error) *Dispatcher {\n\treturn &Dispatcher{\n\t\tupstream:     dispatcher,\n\t\tnewErrorFunc: newErrorFunc,\n\t}\n}\n\nfunc (d *Dispatcher) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\txConn := NewConn(conn)\n\treturn d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{\n\t\tReader: xConn,\n\t\tWriter: xConn,\n\t})\n}\n\nfunc (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\treturn d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{\n\t\tReader: buf.NewPacketReader(conn.(io.Reader)),\n\t\tWriter: buf.NewWriter(conn.(io.Writer)),\n\t})\n}\n\nfunc (d *Dispatcher) NewError(ctx context.Context, err error) {\n\terrors.LogInfo(ctx, err.Error())\n}\n"
  },
  {
    "path": "common/singbridge/logger.go",
    "content": "package singbridge\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing/common/logger\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nvar _ logger.ContextLogger = (*XrayLogger)(nil)\n\ntype XrayLogger struct {\n\tnewError func(values ...any) *errors.Error\n}\n\nfunc NewLogger(newErrorFunc func(values ...any) *errors.Error) *XrayLogger {\n\treturn &XrayLogger{\n\t\tnewErrorFunc,\n\t}\n}\n\nfunc (l *XrayLogger) Trace(args ...any) {\n}\n\nfunc (l *XrayLogger) Debug(args ...any) {\n\terrors.LogDebug(context.Background(), args...)\n}\n\nfunc (l *XrayLogger) Info(args ...any) {\n\terrors.LogInfo(context.Background(), args...)\n}\n\nfunc (l *XrayLogger) Warn(args ...any) {\n\terrors.LogWarning(context.Background(), args...)\n}\n\nfunc (l *XrayLogger) Error(args ...any) {\n\terrors.LogError(context.Background(), args...)\n}\n\nfunc (l *XrayLogger) Fatal(args ...any) {\n}\n\nfunc (l *XrayLogger) Panic(args ...any) {\n}\n\nfunc (l *XrayLogger) TraceContext(ctx context.Context, args ...any) {\n}\n\nfunc (l *XrayLogger) DebugContext(ctx context.Context, args ...any) {\n\terrors.LogDebug(ctx, args...)\n}\n\nfunc (l *XrayLogger) InfoContext(ctx context.Context, args ...any) {\n\terrors.LogInfo(ctx, args...)\n}\n\nfunc (l *XrayLogger) WarnContext(ctx context.Context, args ...any) {\n\terrors.LogWarning(ctx, args...)\n}\n\nfunc (l *XrayLogger) ErrorContext(ctx context.Context, args ...any) {\n\terrors.LogError(ctx, args...)\n}\n\nfunc (l *XrayLogger) FatalContext(ctx context.Context, args ...any) {\n}\n\nfunc (l *XrayLogger) PanicContext(ctx context.Context, args ...any) {\n}\n"
  },
  {
    "path": "common/singbridge/packet.go",
    "content": "package singbridge\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tB \"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\nfunc CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error {\n\tconn := &PacketConnWrapper{\n\t\tReader: link.Reader,\n\t\tWriter: link.Writer,\n\t\tDest:   destination,\n\t\tConn:   inboundConn,\n\t}\n\treturn ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)))\n}\n\ntype PacketConnWrapper struct {\n\tbuf.Reader\n\tbuf.Writer\n\tnet.Conn\n\tDest   net.Destination\n\tcached buf.MultiBuffer\n}\n\n// This ReadPacket implemented a timeout to avoid goroutine leak like PipeConnWrapper.Read()\n// as a temporarily solution\nfunc (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {\n\tif w.cached != nil {\n\t\tmb, bb := buf.SplitFirst(w.cached)\n\t\tif bb == nil {\n\t\t\tw.cached = nil\n\t\t} else {\n\t\t\tbuffer.Write(bb.Bytes())\n\t\t\tw.cached = mb\n\t\t\tvar destination net.Destination\n\t\t\tif bb.UDP != nil {\n\t\t\t\tdestination = *bb.UDP\n\t\t\t} else {\n\t\t\t\tdestination = w.Dest\n\t\t\t}\n\t\t\tbb.Release()\n\t\t\treturn ToSocksaddr(destination), nil\n\t\t}\n\t}\n\n\t// timeout\n\ttype readResult struct {\n\t\tmb  buf.MultiBuffer\n\t\terr error\n\t}\n\tc := make(chan readResult, 1)\n\tgo func() {\n\t\tmb, err := w.ReadMultiBuffer()\n\t\tc <- readResult{mb: mb, err: err}\n\t}()\n\tvar mb buf.MultiBuffer\n\tselect {\n\tcase <-time.After(60 * time.Second):\n\t\tcommon.Close(w.Reader)\n\t\tcommon.Interrupt(w.Reader)\n\t\treturn M.Socksaddr{}, buf.ErrReadTimeout\n\tcase result := <-c:\n\t\tif result.err != nil {\n\t\t\treturn M.Socksaddr{}, result.err\n\t\t}\n\t\tmb = result.mb\n\t}\n\n\tnb, bb := buf.SplitFirst(mb)\n\tif bb == nil {\n\t\treturn M.Socksaddr{}, nil\n\t} else {\n\t\tbuffer.Write(bb.Bytes())\n\t\tw.cached = nb\n\t\tvar destination net.Destination\n\t\tif bb.UDP != nil {\n\t\t\tdestination = *bb.UDP\n\t\t} else {\n\t\t\tdestination = w.Dest\n\t\t}\n\t\tbb.Release()\n\t\treturn ToSocksaddr(destination), nil\n\t}\n}\n\nfunc (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error {\n\tvBuf := buf.New()\n\tvBuf.Write(buffer.Bytes())\n\tendpoint := ToDestination(destination, net.Network_UDP)\n\tvBuf.UDP = &endpoint\n\treturn w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf})\n}\n\nfunc (w *PacketConnWrapper) Close() error {\n\tbuf.ReleaseMulti(w.cached)\n\treturn nil\n}\n"
  },
  {
    "path": "common/singbridge/pipe.go",
    "content": "package singbridge\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/bufio\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\nfunc CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, serverConn net.Conn) error {\n\tconn := &PipeConnWrapper{\n\t\tW:    link.Writer,\n\t\tConn: inboundConn,\n\t}\n\tif ir, ok := link.Reader.(io.Reader); ok {\n\t\tconn.R = ir\n\t} else {\n\t\tconn.R = &buf.BufferedReader{Reader: link.Reader}\n\t}\n\treturn ReturnError(bufio.CopyConn(ctx, conn, serverConn))\n}\n\ntype PipeConnWrapper struct {\n\tR io.Reader\n\tW buf.Writer\n\tnet.Conn\n}\n\nfunc (w *PipeConnWrapper) Close() error {\n\treturn nil\n}\n\n// This Read implemented a timeout to avoid goroutine leak.\n// as a temporarily solution\nfunc (w *PipeConnWrapper) Read(b []byte) (n int, err error) {\n\ttype readResult struct {\n\t\tn   int\n\t\terr error\n\t}\n\tc := make(chan readResult, 1)\n\tgo func() {\n\t\tn, err := w.R.Read(b)\n\t\tc <- readResult{n: n, err: err}\n\t}()\n\tselect {\n\tcase result := <-c:\n\t\treturn result.n, result.err\n\tcase <-time.After(300 * time.Second):\n\t\tcommon.Close(w.R)\n\t\tcommon.Interrupt(w.R)\n\t\treturn 0, buf.ErrReadTimeout\n\t}\n}\n\nfunc (w *PipeConnWrapper) Write(p []byte) (n int, err error) {\n\tn = len(p)\n\tvar mb buf.MultiBuffer\n\tpLen := len(p)\n\tfor pLen > 0 {\n\t\tbuffer := buf.New()\n\t\tif pLen > buf.Size {\n\t\t\t_, err = buffer.Write(p[:buf.Size])\n\t\t\tp = p[buf.Size:]\n\t\t} else {\n\t\t\tbuffer.Write(p)\n\t\t}\n\t\tpLen -= int(buffer.Len())\n\t\tmb = append(mb, buffer)\n\t}\n\terr = w.W.WriteMultiBuffer(mb)\n\tif err != nil {\n\t\tn = 0\n\t\tbuf.ReleaseMulti(mb)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "common/singbridge/reader.go",
    "content": "package singbridge\n\nimport (\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nvar (\n\t_ buf.Reader        = (*Conn)(nil)\n\t_ buf.TimeoutReader = (*Conn)(nil)\n\t_ buf.Writer        = (*Conn)(nil)\n)\n\ntype Conn struct {\n\tnet.Conn\n\twriter N.VectorisedWriter\n}\n\nfunc NewConn(conn net.Conn) *Conn {\n\twriter, _ := bufio.CreateVectorisedWriter(conn)\n\treturn &Conn{\n\t\tConn:   conn,\n\t\twriter: writer,\n\t}\n}\n\nfunc (c *Conn) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tbuffer, err := buf.ReadBuffer(c.Conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.MultiBuffer{buffer}, nil\n}\n\nfunc (c *Conn) ReadMultiBufferTimeout(duration time.Duration) (buf.MultiBuffer, error) {\n\terr := c.SetReadDeadline(time.Now().Add(duration))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer c.SetReadDeadline(time.Time{})\n\treturn c.ReadMultiBuffer()\n}\n\nfunc (c *Conn) WriteMultiBuffer(bufferList buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(bufferList)\n\tif c.writer != nil {\n\t\tbytesList := make([][]byte, len(bufferList))\n\t\tfor i, buffer := range bufferList {\n\t\t\tbytesList[i] = buffer.Bytes()\n\t\t}\n\t\treturn common.Error(bufio.WriteVectorised(c.writer, bytesList))\n\t}\n\t// Since this conn is only used by tun, we don't force buffer writes to merge.\n\tfor _, buffer := range bufferList {\n\t\t_, err := c.Conn.Write(buffer.Bytes())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/strmatcher/ac_automaton_matcher.go",
    "content": "package strmatcher\n\nimport (\n\t\"container/list\"\n)\n\nconst validCharCount = 53\n\ntype MatchType struct {\n\tType  Type\n\tExist bool\n}\n\nconst (\n\tTrieEdge bool = true\n\tFailEdge bool = false\n)\n\ntype Edge struct {\n\tType     bool\n\tNextNode int\n}\n\ntype ACAutomaton struct {\n\tTrie   [][validCharCount]Edge\n\tFail   []int\n\tExists []MatchType\n\tCount  int\n}\n\nfunc newNode() [validCharCount]Edge {\n\tvar s [validCharCount]Edge\n\tfor i := range s {\n\t\ts[i] = Edge{\n\t\t\tType:     FailEdge,\n\t\t\tNextNode: 0,\n\t\t}\n\t}\n\treturn s\n}\n\nvar char2Index = []int{\n\t'A':  0,\n\t'a':  0,\n\t'B':  1,\n\t'b':  1,\n\t'C':  2,\n\t'c':  2,\n\t'D':  3,\n\t'd':  3,\n\t'E':  4,\n\t'e':  4,\n\t'F':  5,\n\t'f':  5,\n\t'G':  6,\n\t'g':  6,\n\t'H':  7,\n\t'h':  7,\n\t'I':  8,\n\t'i':  8,\n\t'J':  9,\n\t'j':  9,\n\t'K':  10,\n\t'k':  10,\n\t'L':  11,\n\t'l':  11,\n\t'M':  12,\n\t'm':  12,\n\t'N':  13,\n\t'n':  13,\n\t'O':  14,\n\t'o':  14,\n\t'P':  15,\n\t'p':  15,\n\t'Q':  16,\n\t'q':  16,\n\t'R':  17,\n\t'r':  17,\n\t'S':  18,\n\t's':  18,\n\t'T':  19,\n\t't':  19,\n\t'U':  20,\n\t'u':  20,\n\t'V':  21,\n\t'v':  21,\n\t'W':  22,\n\t'w':  22,\n\t'X':  23,\n\t'x':  23,\n\t'Y':  24,\n\t'y':  24,\n\t'Z':  25,\n\t'z':  25,\n\t'!':  26,\n\t'$':  27,\n\t'&':  28,\n\t'\\'': 29,\n\t'(':  30,\n\t')':  31,\n\t'*':  32,\n\t'+':  33,\n\t',':  34,\n\t';':  35,\n\t'=':  36,\n\t':':  37,\n\t'%':  38,\n\t'-':  39,\n\t'.':  40,\n\t'_':  41,\n\t'~':  42,\n\t'0':  43,\n\t'1':  44,\n\t'2':  45,\n\t'3':  46,\n\t'4':  47,\n\t'5':  48,\n\t'6':  49,\n\t'7':  50,\n\t'8':  51,\n\t'9':  52,\n}\n\nfunc NewACAutomaton() *ACAutomaton {\n\tac := new(ACAutomaton)\n\tac.Trie = append(ac.Trie, newNode())\n\tac.Fail = append(ac.Fail, 0)\n\tac.Exists = append(ac.Exists, MatchType{\n\t\tType:  Full,\n\t\tExist: false,\n\t})\n\treturn ac\n}\n\nfunc (ac *ACAutomaton) Add(domain string, t Type) {\n\tnode := 0\n\tfor i := len(domain) - 1; i >= 0; i-- {\n\t\tidx := char2Index[domain[i]]\n\t\tif ac.Trie[node][idx].NextNode == 0 {\n\t\t\tac.Count++\n\t\t\tif len(ac.Trie) < ac.Count+1 {\n\t\t\t\tac.Trie = append(ac.Trie, newNode())\n\t\t\t\tac.Fail = append(ac.Fail, 0)\n\t\t\t\tac.Exists = append(ac.Exists, MatchType{\n\t\t\t\t\tType:  Full,\n\t\t\t\t\tExist: false,\n\t\t\t\t})\n\t\t\t}\n\t\t\tac.Trie[node][idx] = Edge{\n\t\t\t\tType:     TrieEdge,\n\t\t\t\tNextNode: ac.Count,\n\t\t\t}\n\t\t}\n\t\tnode = ac.Trie[node][idx].NextNode\n\t}\n\tac.Exists[node] = MatchType{\n\t\tType:  t,\n\t\tExist: true,\n\t}\n\tswitch t {\n\tcase Domain:\n\t\tac.Exists[node] = MatchType{\n\t\t\tType:  Full,\n\t\t\tExist: true,\n\t\t}\n\t\tidx := char2Index['.']\n\t\tif ac.Trie[node][idx].NextNode == 0 {\n\t\t\tac.Count++\n\t\t\tif len(ac.Trie) < ac.Count+1 {\n\t\t\t\tac.Trie = append(ac.Trie, newNode())\n\t\t\t\tac.Fail = append(ac.Fail, 0)\n\t\t\t\tac.Exists = append(ac.Exists, MatchType{\n\t\t\t\t\tType:  Full,\n\t\t\t\t\tExist: false,\n\t\t\t\t})\n\t\t\t}\n\t\t\tac.Trie[node][idx] = Edge{\n\t\t\t\tType:     TrieEdge,\n\t\t\t\tNextNode: ac.Count,\n\t\t\t}\n\t\t}\n\t\tnode = ac.Trie[node][idx].NextNode\n\t\tac.Exists[node] = MatchType{\n\t\t\tType:  t,\n\t\t\tExist: true,\n\t\t}\n\tdefault:\n\t\tbreak\n\t}\n}\n\nfunc (ac *ACAutomaton) Build() {\n\tqueue := list.New()\n\tfor i := 0; i < validCharCount; i++ {\n\t\tif ac.Trie[0][i].NextNode != 0 {\n\t\t\tqueue.PushBack(ac.Trie[0][i])\n\t\t}\n\t}\n\tfor {\n\t\tfront := queue.Front()\n\t\tif front == nil {\n\t\t\tbreak\n\t\t} else {\n\t\t\tnode := front.Value.(Edge).NextNode\n\t\t\tqueue.Remove(front)\n\t\t\tfor i := 0; i < validCharCount; i++ {\n\t\t\t\tif ac.Trie[node][i].NextNode != 0 {\n\t\t\t\t\tac.Fail[ac.Trie[node][i].NextNode] = ac.Trie[ac.Fail[node]][i].NextNode\n\t\t\t\t\tqueue.PushBack(ac.Trie[node][i])\n\t\t\t\t} else {\n\t\t\t\t\tac.Trie[node][i] = Edge{\n\t\t\t\t\t\tType:     FailEdge,\n\t\t\t\t\t\tNextNode: ac.Trie[ac.Fail[node]][i].NextNode,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (ac *ACAutomaton) Match(s string) bool {\n\tnode := 0\n\tfullMatch := true\n\t// 1. the match string is all through trie edge. FULL MATCH or DOMAIN\n\t// 2. the match string is through a fail edge. NOT FULL MATCH\n\t// 2.1 Through a fail edge, but there exists a valid node. SUBSTR\n\tfor i := len(s) - 1; i >= 0; i-- {\n\t\tchr := int(s[i])\n\t\tif chr >= len(char2Index) {\n\t\t\treturn false\n\t\t}\n\t\tidx := char2Index[chr]\n\t\tfullMatch = fullMatch && ac.Trie[node][idx].Type\n\t\tnode = ac.Trie[node][idx].NextNode\n\t\tswitch ac.Exists[node].Type {\n\t\tcase Substr:\n\t\t\treturn true\n\t\tcase Domain:\n\t\t\tif fullMatch {\n\t\t\t\treturn true\n\t\t\t}\n\t\tdefault:\n\t\t\tbreak\n\t\t}\n\t}\n\treturn fullMatch && ac.Exists[node].Exist\n}\n"
  },
  {
    "path": "common/strmatcher/benchmark_test.go",
    "content": "package strmatcher_test\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/strmatcher\"\n)\n\nfunc BenchmarkACAutomaton(b *testing.B) {\n\tac := NewACAutomaton()\n\tfor i := 1; i <= 1024; i++ {\n\t\tac.Add(strconv.Itoa(i)+\".xray.com\", Domain)\n\t}\n\tac.Build()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = ac.Match(\"0.xray.com\")\n\t}\n}\n\nfunc BenchmarkDomainMatcherGroup(b *testing.B) {\n\tg := new(DomainMatcherGroup)\n\n\tfor i := 1; i <= 1024; i++ {\n\t\tg.Add(strconv.Itoa(i)+\".example.com\", uint32(i))\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = g.Match(\"0.example.com\")\n\t}\n}\n\nfunc BenchmarkFullMatcherGroup(b *testing.B) {\n\tg := new(FullMatcherGroup)\n\n\tfor i := 1; i <= 1024; i++ {\n\t\tg.Add(strconv.Itoa(i)+\".example.com\", uint32(i))\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = g.Match(\"0.example.com\")\n\t}\n}\n\nfunc BenchmarkMarchGroup(b *testing.B) {\n\tg := new(MatcherGroup)\n\tfor i := 1; i <= 1024; i++ {\n\t\tm, err := Domain.New(strconv.Itoa(i) + \".example.com\")\n\t\tcommon.Must(err)\n\t\tg.Add(m)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = g.Match(\"0.example.com\")\n\t}\n}\n"
  },
  {
    "path": "common/strmatcher/domain_matcher.go",
    "content": "package strmatcher\n\nimport \"strings\"\n\nfunc breakDomain(domain string) []string {\n\treturn strings.Split(domain, \".\")\n}\n\ntype node struct {\n\tvalues []uint32\n\tsub    map[string]*node\n}\n\n// DomainMatcherGroup is a IndexMatcher for a large set of Domain matchers.\n// Visible for testing only.\ntype DomainMatcherGroup struct {\n\troot *node\n}\n\nfunc (g *DomainMatcherGroup) Add(domain string, value uint32) {\n\tif g.root == nil {\n\t\tg.root = new(node)\n\t}\n\n\tcurrent := g.root\n\tparts := breakDomain(domain)\n\tfor i := len(parts) - 1; i >= 0; i-- {\n\t\tpart := parts[i]\n\t\tif current.sub == nil {\n\t\t\tcurrent.sub = make(map[string]*node)\n\t\t}\n\t\tnext := current.sub[part]\n\t\tif next == nil {\n\t\t\tnext = new(node)\n\t\t\tcurrent.sub[part] = next\n\t\t}\n\t\tcurrent = next\n\t}\n\n\tcurrent.values = append(current.values, value)\n}\n\nfunc (g *DomainMatcherGroup) addMatcher(m domainMatcher, value uint32) {\n\tg.Add(string(m), value)\n}\n\nfunc (g *DomainMatcherGroup) Match(domain string) []uint32 {\n\tif domain == \"\" {\n\t\treturn nil\n\t}\n\n\tcurrent := g.root\n\tif current == nil {\n\t\treturn nil\n\t}\n\n\tnextPart := func(idx int) int {\n\t\tfor i := idx - 1; i >= 0; i-- {\n\t\t\tif domain[i] == '.' {\n\t\t\t\treturn i\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n\n\tmatches := [][]uint32{}\n\tidx := len(domain)\n\tfor {\n\t\tif idx == -1 || current.sub == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tnidx := nextPart(idx)\n\t\tpart := domain[nidx+1 : idx]\n\t\tnext := current.sub[part]\n\t\tif next == nil {\n\t\t\tbreak\n\t\t}\n\t\tcurrent = next\n\t\tidx = nidx\n\t\tif len(current.values) > 0 {\n\t\t\tmatches = append(matches, current.values)\n\t\t}\n\t}\n\tswitch len(matches) {\n\tcase 0:\n\t\treturn nil\n\tcase 1:\n\t\treturn matches[0]\n\tdefault:\n\t\tresult := []uint32{}\n\t\tfor idx := range matches {\n\t\t\t// Insert reversely, the subdomain that matches further ranks higher\n\t\t\tresult = append(result, matches[len(matches)-1-idx]...)\n\t\t}\n\t\treturn result\n\t}\n}\n"
  },
  {
    "path": "common/strmatcher/domain_matcher_test.go",
    "content": "package strmatcher_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/strmatcher\"\n)\n\nfunc TestDomainMatcherGroup(t *testing.T) {\n\tg := new(DomainMatcherGroup)\n\tg.Add(\"example.com\", 1)\n\tg.Add(\"google.com\", 2)\n\tg.Add(\"x.a.com\", 3)\n\tg.Add(\"a.b.com\", 4)\n\tg.Add(\"c.a.b.com\", 5)\n\tg.Add(\"x.y.com\", 4)\n\tg.Add(\"x.y.com\", 6)\n\n\ttestCases := []struct {\n\t\tDomain string\n\t\tResult []uint32\n\t}{\n\t\t{\n\t\t\tDomain: \"x.example.com\",\n\t\t\tResult: []uint32{1},\n\t\t},\n\t\t{\n\t\t\tDomain: \"y.com\",\n\t\t\tResult: nil,\n\t\t},\n\t\t{\n\t\t\tDomain: \"a.b.com\",\n\t\t\tResult: []uint32{4},\n\t\t},\n\t\t{ // Matches [c.a.b.com, a.b.com]\n\t\t\tDomain: \"c.a.b.com\",\n\t\t\tResult: []uint32{5, 4},\n\t\t},\n\t\t{\n\t\t\tDomain: \"c.a..b.com\",\n\t\t\tResult: nil,\n\t\t},\n\t\t{\n\t\t\tDomain: \".com\",\n\t\t\tResult: nil,\n\t\t},\n\t\t{\n\t\t\tDomain: \"com\",\n\t\t\tResult: nil,\n\t\t},\n\t\t{\n\t\t\tDomain: \"\",\n\t\t\tResult: nil,\n\t\t},\n\t\t{\n\t\t\tDomain: \"x.y.com\",\n\t\t\tResult: []uint32{4, 6},\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tr := g.Match(testCase.Domain)\n\t\tif !reflect.DeepEqual(r, testCase.Result) {\n\t\t\tt.Error(\"Failed to match domain: \", testCase.Domain, \", expect \", testCase.Result, \", but got \", r)\n\t\t}\n\t}\n}\n\nfunc TestEmptyDomainMatcherGroup(t *testing.T) {\n\tg := new(DomainMatcherGroup)\n\tr := g.Match(\"example.com\")\n\tif len(r) != 0 {\n\t\tt.Error(\"Expect [], but \", r)\n\t}\n}\n"
  },
  {
    "path": "common/strmatcher/full_matcher.go",
    "content": "package strmatcher\n\ntype FullMatcherGroup struct {\n\tmatchers map[string][]uint32\n}\n\nfunc (g *FullMatcherGroup) Add(domain string, value uint32) {\n\tif g.matchers == nil {\n\t\tg.matchers = make(map[string][]uint32)\n\t}\n\n\tg.matchers[domain] = append(g.matchers[domain], value)\n}\n\nfunc (g *FullMatcherGroup) addMatcher(m fullMatcher, value uint32) {\n\tg.Add(string(m), value)\n}\n\nfunc (g *FullMatcherGroup) Match(str string) []uint32 {\n\tif g.matchers == nil {\n\t\treturn nil\n\t}\n\n\treturn g.matchers[str]\n}\n"
  },
  {
    "path": "common/strmatcher/full_matcher_test.go",
    "content": "package strmatcher_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common/strmatcher\"\n)\n\nfunc TestFullMatcherGroup(t *testing.T) {\n\tg := new(FullMatcherGroup)\n\tg.Add(\"example.com\", 1)\n\tg.Add(\"google.com\", 2)\n\tg.Add(\"x.a.com\", 3)\n\tg.Add(\"x.y.com\", 4)\n\tg.Add(\"x.y.com\", 6)\n\n\ttestCases := []struct {\n\t\tDomain string\n\t\tResult []uint32\n\t}{\n\t\t{\n\t\t\tDomain: \"example.com\",\n\t\t\tResult: []uint32{1},\n\t\t},\n\t\t{\n\t\t\tDomain: \"y.com\",\n\t\t\tResult: nil,\n\t\t},\n\t\t{\n\t\t\tDomain: \"x.y.com\",\n\t\t\tResult: []uint32{4, 6},\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tr := g.Match(testCase.Domain)\n\t\tif !reflect.DeepEqual(r, testCase.Result) {\n\t\t\tt.Error(\"Failed to match domain: \", testCase.Domain, \", expect \", testCase.Result, \", but got \", r)\n\t\t}\n\t}\n}\n\nfunc TestEmptyFullMatcherGroup(t *testing.T) {\n\tg := new(FullMatcherGroup)\n\tr := g.Match(\"example.com\")\n\tif len(r) != 0 {\n\t\tt.Error(\"Expect [], but \", r)\n\t}\n}\n"
  },
  {
    "path": "common/strmatcher/matchers.go",
    "content": "package strmatcher\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype fullMatcher string\n\nfunc (m fullMatcher) Match(s string) bool {\n\treturn string(m) == s\n}\n\nfunc (m fullMatcher) String() string {\n\treturn \"full:\" + string(m)\n}\n\ntype substrMatcher string\n\nfunc (m substrMatcher) Match(s string) bool {\n\treturn strings.Contains(s, string(m))\n}\n\nfunc (m substrMatcher) String() string {\n\treturn \"keyword:\" + string(m)\n}\n\ntype domainMatcher string\n\nfunc (m domainMatcher) Match(s string) bool {\n\tpattern := string(m)\n\tif !strings.HasSuffix(s, pattern) {\n\t\treturn false\n\t}\n\treturn len(s) == len(pattern) || s[len(s)-len(pattern)-1] == '.'\n}\n\nfunc (m domainMatcher) String() string {\n\treturn \"domain:\" + string(m)\n}\n\ntype RegexMatcher struct {\n\tPattern string\n\treg     *regexp.Regexp\n}\n\nfunc (m *RegexMatcher) Match(s string) bool {\n\tif m.reg == nil {\n\t\tm.reg = regexp.MustCompile(m.Pattern)\n\t}\n\treturn m.reg.MatchString(s)\n}\n\nfunc (m *RegexMatcher) String() string {\n\treturn \"regexp:\" + m.Pattern\n}\n"
  },
  {
    "path": "common/strmatcher/matchers_test.go",
    "content": "package strmatcher_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/strmatcher\"\n)\n\nfunc TestMatcher(t *testing.T) {\n\tcases := []struct {\n\t\tpattern string\n\t\tmType   Type\n\t\tinput   string\n\t\toutput  bool\n\t}{\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"www.example.com\",\n\t\t\toutput:  true,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"example.com\",\n\t\t\toutput:  true,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"www.fxample.com\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"xample.com\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"xexample.com\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Full,\n\t\t\tinput:   \"example.com\",\n\t\t\toutput:  true,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Full,\n\t\t\tinput:   \"xexample.com\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"example.com\",\n\t\t\tmType:   Regex,\n\t\t\tinput:   \"examplexcom\",\n\t\t\toutput:  true,\n\t\t},\n\t}\n\tfor _, test := range cases {\n\t\tmatcher, err := test.mType.New(test.pattern)\n\t\tcommon.Must(err)\n\t\tif m := matcher.Match(test.input); m != test.output {\n\t\t\tt.Error(\"unexpected output: \", m, \" for test case \", test)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/strmatcher/mph_matcher.go",
    "content": "package strmatcher\n\nimport (\n\t\"math/bits\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"unsafe\"\n)\n\n// PrimeRK is the prime base used in Rabin-Karp algorithm.\nconst PrimeRK = 16777619\n\n// calculate the rolling murmurHash of given string\nfunc RollingHash(s string) uint32 {\n\th := uint32(0)\n\tfor i := len(s) - 1; i >= 0; i-- {\n\t\th = h*PrimeRK + uint32(s[i])\n\t}\n\treturn h\n}\n\n// A MphMatcherGroup is divided into three parts:\n// 1. `full` and `domain` patterns are matched by Rabin-Karp algorithm and minimal perfect hash table;\n// 2. `substr` patterns are matched by ac automaton;\n// 3. `regex` patterns are matched with the regex library.\ntype MphMatcherGroup struct {\n\tAc            *ACAutomaton\n\tOtherMatchers []MatcherEntry\n\tRules         []string\n\tLevel0        []uint32\n\tLevel0Mask    int\n\tLevel1        []uint32\n\tLevel1Mask    int\n\tCount         uint32\n\tRuleMap       *map[string]uint32\n}\n\nfunc (g *MphMatcherGroup) AddFullOrDomainPattern(pattern string, t Type) {\n\th := RollingHash(pattern)\n\tswitch t {\n\tcase Domain:\n\t\t(*g.RuleMap)[\".\"+pattern] = h*PrimeRK + uint32('.')\n\t\tfallthrough\n\tcase Full:\n\t\t(*g.RuleMap)[pattern] = h\n\tdefault:\n\t}\n}\n\nfunc NewMphMatcherGroup() *MphMatcherGroup {\n\treturn &MphMatcherGroup{\n\t\tAc:            nil,\n\t\tOtherMatchers: nil,\n\t\tRules:         nil,\n\t\tLevel0:        nil,\n\t\tLevel0Mask:    0,\n\t\tLevel1:        nil,\n\t\tLevel1Mask:    0,\n\t\tCount:         1,\n\t\tRuleMap:       &map[string]uint32{},\n\t}\n}\n\n// AddPattern adds a pattern to MphMatcherGroup\nfunc (g *MphMatcherGroup) AddPattern(pattern string, t Type) (uint32, error) {\n\tswitch t {\n\tcase Substr:\n\t\tif g.Ac == nil {\n\t\t\tg.Ac = NewACAutomaton()\n\t\t}\n\t\tg.Ac.Add(pattern, t)\n\tcase Full, Domain:\n\t\tpattern = strings.ToLower(pattern)\n\t\tg.AddFullOrDomainPattern(pattern, t)\n\tcase Regex:\n\t\tr, err := regexp.Compile(pattern)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tg.OtherMatchers = append(g.OtherMatchers, MatcherEntry{\n\t\t\tM:  &RegexMatcher{Pattern: pattern, reg: r},\n\t\t\tId: g.Count,\n\t\t})\n\tdefault:\n\t\tpanic(\"Unknown type\")\n\t}\n\treturn g.Count, nil\n}\n\n// Build builds a minimal perfect hash table and ac automaton from insert rules\nfunc (g *MphMatcherGroup) Build() {\n\tif g.Ac != nil {\n\t\tg.Ac.Build()\n\t}\n\tkeyLen := len(*g.RuleMap)\n\tif keyLen == 0 {\n\t\tkeyLen = 1\n\t\t(*g.RuleMap)[\"empty___\"] = RollingHash(\"empty___\")\n\t}\n\tg.Level0 = make([]uint32, nextPow2(keyLen/4))\n\tg.Level0Mask = len(g.Level0) - 1\n\tg.Level1 = make([]uint32, nextPow2(keyLen))\n\tg.Level1Mask = len(g.Level1) - 1\n\tsparseBuckets := make([][]int, len(g.Level0))\n\tvar ruleIdx int\n\tfor rule, hash := range *g.RuleMap {\n\t\tn := int(hash) & g.Level0Mask\n\t\tg.Rules = append(g.Rules, rule)\n\t\tsparseBuckets[n] = append(sparseBuckets[n], ruleIdx)\n\t\truleIdx++\n\t}\n\tg.RuleMap = nil\n\tvar buckets []indexBucket\n\tfor n, vals := range sparseBuckets {\n\t\tif len(vals) > 0 {\n\t\t\tbuckets = append(buckets, indexBucket{n, vals})\n\t\t}\n\t}\n\tsort.Sort(bySize(buckets))\n\n\tocc := make([]bool, len(g.Level1))\n\tvar tmpOcc []int\n\tfor _, bucket := range buckets {\n\t\tseed := uint32(0)\n\t\tfor {\n\t\t\tfindSeed := true\n\t\t\ttmpOcc = tmpOcc[:0]\n\t\t\tfor _, i := range bucket.vals {\n\t\t\t\tn := int(strhashFallback(unsafe.Pointer(&g.Rules[i]), uintptr(seed))) & g.Level1Mask\n\t\t\t\tif occ[n] {\n\t\t\t\t\tfor _, n := range tmpOcc {\n\t\t\t\t\t\tocc[n] = false\n\t\t\t\t\t}\n\t\t\t\t\tseed++\n\t\t\t\t\tfindSeed = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tocc[n] = true\n\t\t\t\ttmpOcc = append(tmpOcc, n)\n\t\t\t\tg.Level1[n] = uint32(i)\n\t\t\t}\n\t\t\tif findSeed {\n\t\t\t\tg.Level0[bucket.n] = seed\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc nextPow2(v int) int {\n\tif v <= 1 {\n\t\treturn 1\n\t}\n\tconst MaxUInt = ^uint(0)\n\tn := (MaxUInt >> bits.LeadingZeros(uint(v))) + 1\n\treturn int(n)\n}\n\n// Lookup searches for s in t and returns its index and whether it was found.\nfunc (g *MphMatcherGroup) Lookup(h uint32, s string) bool {\n\ti0 := int(h) & g.Level0Mask\n\tseed := g.Level0[i0]\n\ti1 := int(strhashFallback(unsafe.Pointer(&s), uintptr(seed))) & g.Level1Mask\n\tn := g.Level1[i1]\n\treturn s == g.Rules[int(n)]\n}\n\n// Match implements IndexMatcher.Match.\nfunc (g *MphMatcherGroup) Match(pattern string) []uint32 {\n\tresult := []uint32{}\n\thash := uint32(0)\n\tfor i := len(pattern) - 1; i >= 0; i-- {\n\t\thash = hash*PrimeRK + uint32(pattern[i])\n\t\tif pattern[i] == '.' {\n\t\t\tif g.Lookup(hash, pattern[i:]) {\n\t\t\t\tresult = append(result, 1)\n\t\t\t\treturn result\n\t\t\t}\n\t\t}\n\t}\n\tif g.Lookup(hash, pattern) {\n\t\tresult = append(result, 1)\n\t\treturn result\n\t}\n\tif g.Ac != nil && g.Ac.Match(pattern) {\n\t\tresult = append(result, 1)\n\t\treturn result\n\t}\n\tfor _, e := range g.OtherMatchers {\n\t\tif e.M.Match(pattern) {\n\t\t\tresult = append(result, e.Id)\n\t\t\treturn result\n\t\t}\n\t}\n\treturn nil\n}\n\ntype indexBucket struct {\n\tn    int\n\tvals []int\n}\n\ntype bySize []indexBucket\n\nfunc (s bySize) Len() int           { return len(s) }\nfunc (s bySize) Less(i, j int) bool { return len(s[i].vals) > len(s[j].vals) }\nfunc (s bySize) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }\n\ntype stringStruct struct {\n\tstr unsafe.Pointer\n\tlen int\n}\n\nfunc strhashFallback(a unsafe.Pointer, h uintptr) uintptr {\n\tx := (*stringStruct)(a)\n\treturn memhashFallback(x.str, h, uintptr(x.len))\n}\n\nconst (\n\t// Constants for multiplication: four random odd 64-bit numbers.\n\tm1 = 16877499708836156737\n\tm2 = 2820277070424839065\n\tm3 = 9497967016996688599\n\tm4 = 15839092249703872147\n)\n\nvar hashkey = [4]uintptr{1, 1, 1, 1}\n\nfunc memhashFallback(p unsafe.Pointer, seed, s uintptr) uintptr {\n\th := uint64(seed + s*hashkey[0])\ntail:\n\tswitch {\n\tcase s == 0:\n\tcase s < 4:\n\t\th ^= uint64(*(*byte)(p))\n\t\th ^= uint64(*(*byte)(add(p, s>>1))) << 8\n\t\th ^= uint64(*(*byte)(add(p, s-1))) << 16\n\t\th = rotl31(h*m1) * m2\n\tcase s <= 8:\n\t\th ^= uint64(readUnaligned32(p))\n\t\th ^= uint64(readUnaligned32(add(p, s-4))) << 32\n\t\th = rotl31(h*m1) * m2\n\tcase s <= 16:\n\t\th ^= readUnaligned64(p)\n\t\th = rotl31(h*m1) * m2\n\t\th ^= readUnaligned64(add(p, s-8))\n\t\th = rotl31(h*m1) * m2\n\tcase s <= 32:\n\t\th ^= readUnaligned64(p)\n\t\th = rotl31(h*m1) * m2\n\t\th ^= readUnaligned64(add(p, 8))\n\t\th = rotl31(h*m1) * m2\n\t\th ^= readUnaligned64(add(p, s-16))\n\t\th = rotl31(h*m1) * m2\n\t\th ^= readUnaligned64(add(p, s-8))\n\t\th = rotl31(h*m1) * m2\n\tdefault:\n\t\tv1 := h\n\t\tv2 := uint64(seed * hashkey[1])\n\t\tv3 := uint64(seed * hashkey[2])\n\t\tv4 := uint64(seed * hashkey[3])\n\t\tfor s >= 32 {\n\t\t\tv1 ^= readUnaligned64(p)\n\t\t\tv1 = rotl31(v1*m1) * m2\n\t\t\tp = add(p, 8)\n\t\t\tv2 ^= readUnaligned64(p)\n\t\t\tv2 = rotl31(v2*m2) * m3\n\t\t\tp = add(p, 8)\n\t\t\tv3 ^= readUnaligned64(p)\n\t\t\tv3 = rotl31(v3*m3) * m4\n\t\t\tp = add(p, 8)\n\t\t\tv4 ^= readUnaligned64(p)\n\t\t\tv4 = rotl31(v4*m4) * m1\n\t\t\tp = add(p, 8)\n\t\t\ts -= 32\n\t\t}\n\t\th = v1 ^ v2 ^ v3 ^ v4\n\t\tgoto tail\n\t}\n\n\th ^= h >> 29\n\th *= m3\n\th ^= h >> 32\n\treturn uintptr(h)\n}\n\nfunc add(p unsafe.Pointer, x uintptr) unsafe.Pointer {\n\treturn unsafe.Pointer(uintptr(p) + x)\n}\n\nfunc readUnaligned32(p unsafe.Pointer) uint32 {\n\tq := (*[4]byte)(p)\n\treturn uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24\n}\n\nfunc rotl31(x uint64) uint64 {\n\treturn (x << 31) | (x >> (64 - 31))\n}\n\nfunc readUnaligned64(p unsafe.Pointer) uint64 {\n\tq := (*[8]byte)(p)\n\treturn uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 | uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56\n}\n\nfunc (g *MphMatcherGroup) Size() uint32 {\n\treturn g.Count\n}\n"
  },
  {
    "path": "common/strmatcher/mph_matcher_compact.go",
    "content": "package strmatcher\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"io\"\n)\n\nfunc init() {\n\tgob.Register(&RegexMatcher{})\n\tgob.Register(fullMatcher(\"\"))\n\tgob.Register(substrMatcher(\"\"))\n\tgob.Register(domainMatcher(\"\"))\n}\n\nfunc (g *MphMatcherGroup) Serialize(w io.Writer) error {\n\tdata := MphMatcherGroup{\n\t\tAc:            g.Ac,\n\t\tOtherMatchers: g.OtherMatchers,\n\t\tRules:         g.Rules,\n\t\tLevel0:        g.Level0,\n\t\tLevel0Mask:    g.Level0Mask,\n\t\tLevel1:        g.Level1,\n\t\tLevel1Mask:    g.Level1Mask,\n\t\tCount:         g.Count,\n\t}\n\treturn gob.NewEncoder(w).Encode(data)\n}\n\nfunc NewMphMatcherGroupFromBuffer(data []byte) (*MphMatcherGroup, error) {\n\tvar gData MphMatcherGroup\n\tif err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gData); err != nil {\n\t\treturn nil, err\n\t}\n\n\tg := NewMphMatcherGroup()\n\tg.Ac = gData.Ac\n\tg.OtherMatchers = gData.OtherMatchers\n\tg.Rules = gData.Rules\n\tg.Level0 = gData.Level0\n\tg.Level0Mask = gData.Level0Mask\n\tg.Level1 = gData.Level1\n\tg.Level1Mask = gData.Level1Mask\n\tg.Count = gData.Count\n\n\treturn g, nil\n}\n"
  },
  {
    "path": "common/strmatcher/strmatcher.go",
    "content": "package strmatcher\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n)\n\n// Matcher is the interface to determine a string matches a pattern.\ntype Matcher interface {\n\t// Match returns true if the given string matches a predefined pattern.\n\tMatch(string) bool\n\tString() string\n}\n\n// Type is the type of the matcher.\ntype Type byte\n\nconst (\n\t// Full is the type of matcher that the input string must exactly equal to the pattern.\n\tFull Type = iota\n\t// Substr is the type of matcher that the input string must contain the pattern as a sub-string.\n\tSubstr\n\t// Domain is the type of matcher that the input string must be a sub-domain or itself of the pattern.\n\tDomain\n\t// Regex is the type of matcher that the input string must matches the regular-expression pattern.\n\tRegex\n)\n\n// New creates a new Matcher based on the given pattern.\nfunc (t Type) New(pattern string) (Matcher, error) {\n\t// 1. regex matching is case-sensitive\n\tswitch t {\n\tcase Full:\n\t\treturn fullMatcher(pattern), nil\n\tcase Substr:\n\t\treturn substrMatcher(pattern), nil\n\tcase Domain:\n\t\treturn domainMatcher(pattern), nil\n\tcase Regex:\n\t\tr, err := regexp.Compile(pattern)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &RegexMatcher{\n\t\t\tPattern: pattern,\n\t\t\treg:     r,\n\t\t}, nil\n\tdefault:\n\t\treturn nil, errors.New(\"unk type\")\n\t}\n}\n\n// IndexMatcher is the interface for matching with a group of matchers.\ntype IndexMatcher interface {\n\t// Match returns the index of a matcher that matches the input. It returns empty array if no such matcher exists.\n\tMatch(input string) []uint32\n\t// Size returns the number of matchers in the group.\n\tSize() uint32\n}\n\ntype MatcherEntry struct {\n\tM  Matcher\n\tId uint32\n}\n\n// MatcherGroup is an implementation of IndexMatcher.\n// Empty initialization works.\ntype MatcherGroup struct {\n\tcount         uint32\n\tfullMatcher   FullMatcherGroup\n\tdomainMatcher DomainMatcherGroup\n\totherMatchers []MatcherEntry\n}\n\n// Add adds a new Matcher into the MatcherGroup, and returns its index. The index will never be 0.\nfunc (g *MatcherGroup) Add(m Matcher) uint32 {\n\tg.count++\n\tc := g.count\n\n\tswitch tm := m.(type) {\n\tcase fullMatcher:\n\t\tg.fullMatcher.addMatcher(tm, c)\n\tcase domainMatcher:\n\t\tg.domainMatcher.addMatcher(tm, c)\n\tdefault:\n\t\tg.otherMatchers = append(g.otherMatchers, MatcherEntry{\n\t\t\tM:  m,\n\t\t\tId: c,\n\t\t})\n\t}\n\n\treturn c\n}\n\n// Match implements IndexMatcher.Match.\nfunc (g *MatcherGroup) Match(pattern string) []uint32 {\n\tresult := []uint32{}\n\tresult = append(result, g.fullMatcher.Match(pattern)...)\n\tresult = append(result, g.domainMatcher.Match(pattern)...)\n\tfor _, e := range g.otherMatchers {\n\t\tif e.M.Match(pattern) {\n\t\t\tresult = append(result, e.Id)\n\t\t}\n\t}\n\treturn result\n}\n\n// Size returns the number of matchers in the MatcherGroup.\nfunc (g *MatcherGroup) Size() uint32 {\n\treturn g.count\n}\n\ntype IndexMatcherGroup struct {\n\tMatchers []IndexMatcher\n}\n\nfunc (g *IndexMatcherGroup) Match(input string) []uint32 {\n\tvar offset uint32\n\tfor _, m := range g.Matchers {\n\t\tif res := m.Match(input); len(res) > 0 {\n\t\t\tif offset == 0 {\n\t\t\t\treturn res\n\t\t\t}\n\t\t\tshifted := make([]uint32, len(res))\n\t\t\tfor i, id := range res {\n\t\t\t\tshifted[i] = id + offset\n\t\t\t}\n\t\t\treturn shifted\n\t\t}\n\t\toffset += m.Size()\n\t}\n\treturn nil\n}\n\nfunc (g *IndexMatcherGroup) Size() uint32 {\n\tvar count uint32\n\tfor _, m := range g.Matchers {\n\t\tcount += m.Size()\n\t}\n\treturn count\n}\n"
  },
  {
    "path": "common/strmatcher/strmatcher_test.go",
    "content": "package strmatcher_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/strmatcher\"\n)\n\nfunc TestMatcherGroup(t *testing.T) {\n\trules := []struct {\n\t\tType   Type\n\t\tDomain string\n\t}{\n\t\t{\n\t\t\tType:   Regex,\n\t\t\tDomain: \"apis\\\\.us$\",\n\t\t},\n\t\t{\n\t\t\tType:   Substr,\n\t\t\tDomain: \"apis\",\n\t\t},\n\t\t{\n\t\t\tType:   Domain,\n\t\t\tDomain: \"googleapis.com\",\n\t\t},\n\t\t{\n\t\t\tType:   Domain,\n\t\t\tDomain: \"com\",\n\t\t},\n\t\t{\n\t\t\tType:   Full,\n\t\t\tDomain: \"www.baidu.com\",\n\t\t},\n\t\t{\n\t\t\tType:   Substr,\n\t\t\tDomain: \"apis\",\n\t\t},\n\t\t{\n\t\t\tType:   Domain,\n\t\t\tDomain: \"googleapis.com\",\n\t\t},\n\t\t{\n\t\t\tType:   Full,\n\t\t\tDomain: \"fonts.googleapis.com\",\n\t\t},\n\t\t{\n\t\t\tType:   Full,\n\t\t\tDomain: \"www.baidu.com\",\n\t\t},\n\t\t{\n\t\t\tType:   Domain,\n\t\t\tDomain: \"example.com\",\n\t\t},\n\t}\n\tcases := []struct {\n\t\tInput  string\n\t\tOutput []uint32\n\t}{\n\t\t{\n\t\t\tInput:  \"www.baidu.com\",\n\t\t\tOutput: []uint32{5, 9, 4},\n\t\t},\n\t\t{\n\t\t\tInput:  \"fonts.googleapis.com\",\n\t\t\tOutput: []uint32{8, 3, 7, 4, 2, 6},\n\t\t},\n\t\t{\n\t\t\tInput:  \"example.googleapis.com\",\n\t\t\tOutput: []uint32{3, 7, 4, 2, 6},\n\t\t},\n\t\t{\n\t\t\tInput:  \"testapis.us\",\n\t\t\tOutput: []uint32{1, 2, 6},\n\t\t},\n\t\t{\n\t\t\tInput:  \"example.com\",\n\t\t\tOutput: []uint32{10, 4},\n\t\t},\n\t}\n\tmatcherGroup := &MatcherGroup{}\n\tfor _, rule := range rules {\n\t\tmatcher, err := rule.Type.New(rule.Domain)\n\t\tcommon.Must(err)\n\t\tmatcherGroup.Add(matcher)\n\t}\n\tfor _, test := range cases {\n\t\tif m := matcherGroup.Match(test.Input); !reflect.DeepEqual(m, test.Output) {\n\t\t\tt.Error(\"unexpected output: \", m, \" for test case \", test)\n\t\t}\n\t}\n}\n\nfunc TestACAutomaton(t *testing.T) {\n\tcases1 := []struct {\n\t\tpattern string\n\t\tmType   Type\n\t\tinput   string\n\t\toutput  bool\n\t}{\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"www.xtls.github.io\",\n\t\t\toutput:  true,\n\t\t},\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"xtls.github.io\",\n\t\t\toutput:  true,\n\t\t},\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"www.xtis.github.io\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"tls.github.io\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Domain,\n\t\t\tinput:   \"xxtls.github.io\",\n\t\t\toutput:  false,\n\t\t},\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Full,\n\t\t\tinput:   \"xtls.github.io\",\n\t\t\toutput:  true,\n\t\t},\n\t\t{\n\t\t\tpattern: \"xtls.github.io\",\n\t\t\tmType:   Full,\n\t\t\tinput:   \"xxtls.github.io\",\n\t\t\toutput:  false,\n\t\t},\n\t}\n\tfor _, test := range cases1 {\n\t\tac := NewACAutomaton()\n\t\tac.Add(test.pattern, test.mType)\n\t\tac.Build()\n\t\tif m := ac.Match(test.input); m != test.output {\n\t\t\tt.Error(\"unexpected output: \", m, \" for test case \", test)\n\t\t}\n\t}\n\t{\n\t\tcases2Input := []struct {\n\t\t\tpattern string\n\t\t\tmType   Type\n\t\t}{\n\t\t\t{\n\t\t\t\tpattern: \"163.com\",\n\t\t\t\tmType:   Domain,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"m.126.com\",\n\t\t\t\tmType:   Full,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"3.com\",\n\t\t\t\tmType:   Full,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"google.com\",\n\t\t\t\tmType:   Substr,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"vgoogle.com\",\n\t\t\t\tmType:   Substr,\n\t\t\t},\n\t\t}\n\t\tac := NewACAutomaton()\n\t\tfor _, test := range cases2Input {\n\t\t\tac.Add(test.pattern, test.mType)\n\t\t}\n\t\tac.Build()\n\t\tcases2Output := []struct {\n\t\t\tpattern string\n\t\t\tres     bool\n\t\t}{\n\t\t\t{\n\t\t\t\tpattern: \"126.com\",\n\t\t\t\tres:     false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"m.163.com\",\n\t\t\t\tres:     true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"mm163.com\",\n\t\t\t\tres:     false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"m.126.com\",\n\t\t\t\tres:     true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"163.com\",\n\t\t\t\tres:     true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"63.com\",\n\t\t\t\tres:     false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"oogle.com\",\n\t\t\t\tres:     false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"vvgoogle.com\",\n\t\t\t\tres:     true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"½\",\n\t\t\t\tres:     false,\n\t\t\t},\n\t\t}\n\t\tfor _, test := range cases2Output {\n\t\t\tif m := ac.Match(test.pattern); m != test.res {\n\t\t\t\tt.Error(\"unexpected output: \", m, \" for test case \", test)\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\tcases3Input := []struct {\n\t\t\tpattern string\n\t\t\tmType   Type\n\t\t}{\n\t\t\t{\n\t\t\t\tpattern: \"video.google.com\",\n\t\t\t\tmType:   Domain,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"gle.com\",\n\t\t\t\tmType:   Domain,\n\t\t\t},\n\t\t}\n\t\tac := NewACAutomaton()\n\t\tfor _, test := range cases3Input {\n\t\t\tac.Add(test.pattern, test.mType)\n\t\t}\n\t\tac.Build()\n\t\tcases3Output := []struct {\n\t\t\tpattern string\n\t\t\tres     bool\n\t\t}{\n\t\t\t{\n\t\t\t\tpattern: \"google.com\",\n\t\t\t\tres:     false,\n\t\t\t},\n\t\t}\n\t\tfor _, test := range cases3Output {\n\t\t\tif m := ac.Match(test.pattern); m != test.res {\n\t\t\t\tt.Error(\"unexpected output: \", m, \" for test case \", test)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "common/task/common.go",
    "content": "package task\n\nimport \"github.com/xtls/xray-core/common\"\n\n// Close returns a func() that closes v.\nfunc Close(v interface{}) func() error {\n\treturn func() error {\n\t\treturn common.Close(v)\n\t}\n}\n"
  },
  {
    "path": "common/task/periodic.go",
    "content": "package task\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// Periodic is a task that runs periodically.\ntype Periodic struct {\n\t// Interval of the task being run\n\tInterval time.Duration\n\t// Execute is the task function\n\tExecute func() error\n\n\taccess  sync.Mutex\n\ttimer   *time.Timer\n\trunning bool\n}\n\nfunc (t *Periodic) hasClosed() bool {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\n\treturn !t.running\n}\n\nfunc (t *Periodic) checkedExecute() error {\n\tif t.hasClosed() {\n\t\treturn nil\n\t}\n\n\tif err := t.Execute(); err != nil {\n\t\tt.access.Lock()\n\t\tt.running = false\n\t\tt.access.Unlock()\n\t\treturn err\n\t}\n\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\n\tif !t.running {\n\t\treturn nil\n\t}\n\n\tt.timer = time.AfterFunc(t.Interval, func() {\n\t\tt.checkedExecute()\n\t})\n\n\treturn nil\n}\n\n// Start implements common.Runnable.\nfunc (t *Periodic) Start() error {\n\tt.access.Lock()\n\tif t.running {\n\t\tt.access.Unlock()\n\t\treturn nil\n\t}\n\tt.running = true\n\tt.access.Unlock()\n\n\tif err := t.checkedExecute(); err != nil {\n\t\tt.access.Lock()\n\t\tt.running = false\n\t\tt.access.Unlock()\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (t *Periodic) Close() error {\n\tt.access.Lock()\n\tdefer t.access.Unlock()\n\n\tt.running = false\n\tif t.timer != nil {\n\t\tt.timer.Stop()\n\t\tt.timer = nil\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "common/task/periodic_test.go",
    "content": "package task_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/task\"\n)\n\nfunc TestPeriodicTaskStop(t *testing.T) {\n\tvalue := 0\n\ttask := &Periodic{\n\t\tInterval: time.Second * 2,\n\t\tExecute: func() error {\n\t\t\tvalue++\n\t\t\treturn nil\n\t\t},\n\t}\n\tcommon.Must(task.Start())\n\ttime.Sleep(time.Second * 5)\n\tcommon.Must(task.Close())\n\tif value != 3 {\n\t\tt.Fatal(\"expected 3, but got \", value)\n\t}\n\ttime.Sleep(time.Second * 4)\n\tif value != 3 {\n\t\tt.Fatal(\"expected 3, but got \", value)\n\t}\n\tcommon.Must(task.Start())\n\ttime.Sleep(time.Second * 3)\n\tif value != 5 {\n\t\tt.Fatal(\"Expected 5, but \", value)\n\t}\n\tcommon.Must(task.Close())\n}\n"
  },
  {
    "path": "common/task/task.go",
    "content": "package task\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/signal/semaphore\"\n)\n\n// OnSuccess executes g() after f() returns nil.\nfunc OnSuccess(f func() error, g func() error) func() error {\n\treturn func() error {\n\t\tif err := f(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn g()\n\t}\n}\n\n// Run executes a list of tasks in parallel, returns the first error encountered or nil if all tasks pass.\nfunc Run(ctx context.Context, tasks ...func() error) error {\n\tn := len(tasks)\n\ts := semaphore.New(n)\n\tdone := make(chan error, 1)\n\n\tfor _, task := range tasks {\n\t\t<-s.Wait()\n\t\tgo func(f func() error) {\n\t\t\terr := f()\n\t\t\tif err == nil {\n\t\t\t\ts.Signal()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase done <- err:\n\t\t\tdefault:\n\t\t\t}\n\t\t}(task)\n\t}\n\n\t/*\n\t\tif altctx := ctx.Value(\"altctx\"); altctx != nil {\n\t\t\tctx = altctx.(context.Context)\n\t\t}\n\t*/\n\n\tfor i := 0; i < n; i++ {\n\t\tselect {\n\t\tcase err := <-done:\n\t\t\treturn err\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase <-s.Wait():\n\t\t}\n\t}\n\n\t/*\n\t\tif cancel := ctx.Value(\"cancel\"); cancel != nil {\n\t\t\tcancel.(context.CancelFunc)()\n\t\t}\n\t*/\n\n\treturn nil\n}\n"
  },
  {
    "path": "common/task/task_test.go",
    "content": "package task_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/task\"\n)\n\nfunc TestExecuteParallel(t *testing.T) {\n\terr := Run(context.Background(),\n\t\tfunc() error {\n\t\t\ttime.Sleep(time.Millisecond * 200)\n\t\t\treturn errors.New(\"test\")\n\t\t}, func() error {\n\t\t\ttime.Sleep(time.Millisecond * 500)\n\t\t\treturn errors.New(\"test2\")\n\t\t})\n\n\tif r := cmp.Diff(err.Error(), \"test\"); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestExecuteParallelContextCancel(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\terr := Run(ctx, func() error {\n\t\ttime.Sleep(time.Millisecond * 2000)\n\t\treturn errors.New(\"test\")\n\t}, func() error {\n\t\ttime.Sleep(time.Millisecond * 5000)\n\t\treturn errors.New(\"test2\")\n\t}, func() error {\n\t\tcancel()\n\t\treturn nil\n\t})\n\n\terrStr := err.Error()\n\tif !strings.Contains(errStr, \"canceled\") {\n\t\tt.Error(\"expected error string to contain 'canceled', but actually not: \", errStr)\n\t}\n}\n\nfunc BenchmarkExecuteOne(b *testing.B) {\n\tnoop := func() error {\n\t\treturn nil\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(Run(context.Background(), noop))\n\t}\n}\n\nfunc BenchmarkExecuteTwo(b *testing.B) {\n\tnoop := func() error {\n\t\treturn nil\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(Run(context.Background(), noop, noop))\n\t}\n}\n"
  },
  {
    "path": "common/type.go",
    "content": "package common\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\n// ConfigCreator is a function to create an object by a config.\ntype ConfigCreator func(ctx context.Context, config interface{}) (interface{}, error)\n\nvar typeCreatorRegistry = make(map[reflect.Type]ConfigCreator)\n\n// RegisterConfig registers a global config creator. The config can be nil but must have a type.\nfunc RegisterConfig(config interface{}, configCreator ConfigCreator) error {\n\tconfigType := reflect.TypeOf(config)\n\tif _, found := typeCreatorRegistry[configType]; found {\n\t\treturn errors.New(configType.Name() + \" is already registered\").AtError()\n\t}\n\ttypeCreatorRegistry[configType] = configCreator\n\treturn nil\n}\n\n// CreateObject creates an object by its config. The config type must be registered through RegisterConfig().\nfunc CreateObject(ctx context.Context, config interface{}) (interface{}, error) {\n\tconfigType := reflect.TypeOf(config)\n\tcreator, found := typeCreatorRegistry[configType]\n\tif !found {\n\t\treturn nil, errors.New(configType.String() + \" is not registered\").AtError()\n\t}\n\treturn creator(ctx, config)\n}\n"
  },
  {
    "path": "common/type_test.go",
    "content": "package common_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/common\"\n)\n\ntype TConfig struct {\n\tvalue int\n}\n\ntype YConfig struct {\n\tvalue string\n}\n\nfunc TestObjectCreation(t *testing.T) {\n\tf := func(ctx context.Context, t interface{}) (interface{}, error) {\n\t\treturn func() int {\n\t\t\treturn t.(*TConfig).value\n\t\t}, nil\n\t}\n\n\tMust(RegisterConfig((*TConfig)(nil), f))\n\terr := RegisterConfig((*TConfig)(nil), f)\n\tif err == nil {\n\t\tt.Error(\"expect non-nil error, but got nil\")\n\t}\n\n\tg, err := CreateObject(context.Background(), &TConfig{value: 2})\n\tMust(err)\n\tif v := g.(func() int)(); v != 2 {\n\t\tt.Error(\"expect return value 2, but got \", v)\n\t}\n\n\t_, err = CreateObject(context.Background(), &YConfig{value: \"T\"})\n\tif err == nil {\n\t\tt.Error(\"expect non-nil error, but got nil\")\n\t}\n}\n"
  },
  {
    "path": "common/units/bytesize.go",
    "content": "package units\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n)\n\nvar (\n\terrInvalidSize = errors.New(\"invalid size\")\n\terrInvalidUnit = errors.New(\"invalid or unsupported unit\")\n)\n\n// ByteSize is the size of bytes\ntype ByteSize uint64\n\nconst (\n\t_ = iota\n\t// KB = 1KB\n\tKB ByteSize = 1 << (10 * iota)\n\t// MB = 1MB\n\tMB\n\t// GB = 1GB\n\tGB\n\t// TB = 1TB\n\tTB\n\t// PB = 1PB\n\tPB\n\t// EB = 1EB\n\tEB\n)\n\nfunc (b ByteSize) String() string {\n\tunit := \"\"\n\tvalue := float64(0)\n\tswitch {\n\tcase b == 0:\n\t\treturn \"0\"\n\tcase b < KB:\n\t\tunit = \"B\"\n\t\tvalue = float64(b)\n\tcase b < MB:\n\t\tunit = \"KB\"\n\t\tvalue = float64(b) / float64(KB)\n\tcase b < GB:\n\t\tunit = \"MB\"\n\t\tvalue = float64(b) / float64(MB)\n\tcase b < TB:\n\t\tunit = \"GB\"\n\t\tvalue = float64(b) / float64(GB)\n\tcase b < PB:\n\t\tunit = \"TB\"\n\t\tvalue = float64(b) / float64(TB)\n\tcase b < EB:\n\t\tunit = \"PB\"\n\t\tvalue = float64(b) / float64(PB)\n\tdefault:\n\t\tunit = \"EB\"\n\t\tvalue = float64(b) / float64(EB)\n\t}\n\tresult := strconv.FormatFloat(value, 'f', 2, 64)\n\tresult = strings.TrimSuffix(result, \".0\")\n\treturn result + unit\n}\n\n// Parse parses ByteSize from string\nfunc (b *ByteSize) Parse(s string) error {\n\ts = strings.TrimSpace(s)\n\ts = strings.ToUpper(s)\n\ti := strings.IndexFunc(s, unicode.IsLetter)\n\tif i == -1 {\n\t\treturn errInvalidUnit\n\t}\n\n\tbytesString, multiple := s[:i], s[i:]\n\tbytes, err := strconv.ParseFloat(bytesString, 64)\n\tif err != nil || bytes <= 0 {\n\t\treturn errInvalidSize\n\t}\n\tswitch multiple {\n\tcase \"B\":\n\t\t*b = ByteSize(bytes)\n\tcase \"K\", \"KB\", \"KIB\":\n\t\t*b = ByteSize(bytes * float64(KB))\n\tcase \"M\", \"MB\", \"MIB\":\n\t\t*b = ByteSize(bytes * float64(MB))\n\tcase \"G\", \"GB\", \"GIB\":\n\t\t*b = ByteSize(bytes * float64(GB))\n\tcase \"T\", \"TB\", \"TIB\":\n\t\t*b = ByteSize(bytes * float64(TB))\n\tcase \"P\", \"PB\", \"PIB\":\n\t\t*b = ByteSize(bytes * float64(PB))\n\tcase \"E\", \"EB\", \"EIB\":\n\t\t*b = ByteSize(bytes * float64(EB))\n\tdefault:\n\t\treturn errInvalidUnit\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "common/units/bytesize_test.go",
    "content": "package units_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/units\"\n)\n\nfunc TestByteSizes(t *testing.T) {\n\tsize := units.ByteSize(0)\n\tassertSizeString(t, size, \"0\")\n\tsize++\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00B\"),\n\t\tsize,\n\t)\n\tsize <<= 10\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00KB\"),\n\t\tsize,\n\t)\n\tsize <<= 10\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00MB\"),\n\t\tsize,\n\t)\n\tsize <<= 10\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00GB\"),\n\t\tsize,\n\t)\n\tsize <<= 10\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00TB\"),\n\t\tsize,\n\t)\n\tsize <<= 10\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00PB\"),\n\t\tsize,\n\t)\n\tsize <<= 10\n\tassertSizeValue(t,\n\t\tassertSizeString(t, size, \"1.00EB\"),\n\t\tsize,\n\t)\n}\n\nfunc assertSizeValue(t *testing.T, size string, expected units.ByteSize) {\n\tactual := units.ByteSize(0)\n\terr := actual.Parse(size)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif actual != expected {\n\t\tt.Errorf(\"expect %s, but got %s\", expected, actual)\n\t}\n}\n\nfunc assertSizeString(t *testing.T, size units.ByteSize, expected string) string {\n\tactual := size.String()\n\tif actual != expected {\n\t\tt.Errorf(\"expect %s, but got %s\", expected, actual)\n\t}\n\treturn expected\n}\n"
  },
  {
    "path": "common/utils/access_field.go",
    "content": "package utils\n\nimport (\n\t\"reflect\"\n\t\"unsafe\"\n)\n\n// AccessField can used to access unexported field of a struct\n// valueType must be the exact type of the field or it will panic\nfunc AccessField[valueType any](obj any, fieldName string) *valueType {\n\tfield := reflect.ValueOf(obj).Elem().FieldByName(fieldName)\n\tif field.Type() != reflect.TypeOf(*new(valueType)) {\n\t\tpanic(\"field type: \" + field.Type().String() + \", valueType: \" + reflect.TypeOf(*new(valueType)).String())\n\t}\n\tv := (*valueType)(unsafe.Pointer(field.UnsafeAddr()))\n\treturn v\n}\n"
  },
  {
    "path": "common/utils/browser.go",
    "content": "package utils\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/klauspost/cpuid/v2\"\n)\n\nfunc ChromeVersion() int {\n\t// Use only CPU info as seed for PRNG\n\tseed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine)\n\trng := rand.New(rand.NewSource(seed))\n\t// Start from Chrome 144 released on 2026.1.13\n\treleaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC)\n\tversion := 144\n\tnow := time.Now()\n\t// Each version has random 25-45 day interval\n\tfor releaseDate.Before(now) {\n\t\treleaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25)\n\t\tversion++\n\t}\n\treturn version - 1\n}\n\n// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG.\nvar ChromeUA = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/\" + strconv.Itoa(ChromeVersion()) + \".0.0.0 Safari/537.36\"\n"
  },
  {
    "path": "common/utils/padding.go",
    "content": "package utils\n\nimport (\n\t\"math/rand/v2\"\n)\n\nvar (\n\t// 8 ÷ (397/62)\n\th2packCorrectionFactor = 1.2493702770780857\n\tbase62TotalCharsNum    = 62\n\tbase62Chars            = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"\n)\n\n// H2Base62Pad generates a base62 padding string for HTTP/2 header\n// The total len will be slightly longer than the input to match the length after h2(h3 also) header huffman encoding\nfunc H2Base62Pad[T int32 | int64 | int](expectedLen T) string {\n\tactualLenFloat := float64(expectedLen) * h2packCorrectionFactor\n\tactualLen := int(actualLenFloat)\n\tresult := make([]byte, actualLen)\n\tfor i := range actualLen {\n\t\tresult[i] = base62Chars[rand.N(base62TotalCharsNum)]\n\t}\n\treturn string(result)\n}\n"
  },
  {
    "path": "common/utils/typed_sync_map.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n)\n\n// TypedSyncMap is a wrapper of sync.Map that provides type-safe for keys and values.\n// No need to use type assertions every time, so you can have more time to enjoy other things like GochiUsa\n// If sync.Map methods returned nil, it will return the zero value of the type V.\ntype TypedSyncMap[K, V any] struct {\n\tsyncMap *sync.Map\n}\n\n// NewTypedSyncMap creates a new TypedSyncMap\n// K is key type, V is value type\n// It is recommended to use pointer types for V because sync.Map might return nil\n// If sync.Map methods really returned nil, it will return the zero value of the type V\nfunc NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] {\n\treturn &TypedSyncMap[K, V]{\n\t\tsyncMap: &sync.Map{},\n\t}\n}\n\n// Clear deletes all the entries, resulting in an empty Map.\nfunc (m *TypedSyncMap[K, V]) Clear() {\n\tm.syncMap.Clear()\n}\n\n// CompareAndDelete deletes the entry for key if its value is equal to old.\n// The old value must be of a comparable type.\n//\n// If there is no current value for key in the map, CompareAndDelete\n// returns false (even if the old value is the nil interface value).\nfunc (m *TypedSyncMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) {\n\treturn m.syncMap.CompareAndDelete(key, old)\n}\n\n// CompareAndSwap swaps the old and new values for key\n// if the value stored in the map is equal to old.\n// The old value must be of a comparable type.\nfunc (m *TypedSyncMap[K, V]) CompareAndSwap(key K, old V, new V) (swapped bool) {\n\treturn m.syncMap.CompareAndSwap(key, old, new)\n}\n\n// Delete deletes the value for a key.\nfunc (m *TypedSyncMap[K, V]) Delete(key K) {\n\tm.syncMap.Delete(key)\n}\n\n// Load returns the value stored in the map for a key, or nil if no\n// value is present.\n// The ok result indicates whether value was found in the map.\nfunc (m *TypedSyncMap[K, V]) Load(key K) (value V, ok bool) {\n\tanyValue, ok := m.syncMap.Load(key)\n\t// anyValue might be nil\n\tif anyValue != nil {\n\t\tvalue = anyValue.(V)\n\t}\n\treturn value, ok\n}\n\n// LoadAndDelete deletes the value for a key, returning the previous value if any.\n// The loaded result reports whether the key was present.\nfunc (m *TypedSyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {\n\tanyValue, loaded := m.syncMap.LoadAndDelete(key)\n\tif anyValue != nil {\n\t\tvalue = anyValue.(V)\n\t}\n\treturn value, loaded\n}\n\n// LoadOrStore returns the existing value for the key if present.\n// Otherwise, it stores and returns the given value.\n// The loaded result is true if the value was loaded, false if stored.\nfunc (m *TypedSyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {\n\tanyActual, loaded := m.syncMap.LoadOrStore(key, value)\n\tif anyActual != nil {\n\t\tactual = anyActual.(V)\n\t}\n\treturn actual, loaded\n}\n\n// Range calls f sequentially for each key and value present in the map.\n// If f returns false, range stops the iteration.\n//\n// Range does not necessarily correspond to any consistent snapshot of the Map's\n// contents: no key will be visited more than once, but if the value for any key\n// is stored or deleted concurrently (including by f), Range may reflect any\n// mapping for that key from any point during the Range call. Range does not\n// block other methods on the receiver; even f itself may call any method on m.\n//\n// Range may be O(N) with the number of elements in the map even if f returns\n// false after a constant number of calls.\nfunc (m *TypedSyncMap[K, V]) Range(f func(key K, value V) bool) {\n\tm.syncMap.Range(func(key, value any) bool {\n\t\treturn f(key.(K), value.(V))\n\t})\n}\n\n// Store sets the value for a key.\nfunc (m *TypedSyncMap[K, V]) Store(key K, value V) {\n\tm.syncMap.Store(key, value)\n}\n\n// Swap swaps the value for a key and returns the previous value if any. The loaded result reports whether the key was present.\nfunc (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {\n\tanyPrevious, loaded := m.syncMap.Swap(key, value)\n\tif anyPrevious != nil {\n\t\tprevious = anyPrevious.(V)\n\t}\n\treturn previous, loaded\n}"
  },
  {
    "path": "common/uuid/uuid.go",
    "content": "package uuid // import \"github.com/xtls/xray-core/common/uuid\"\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/sha1\"\n\t\"encoding/hex\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nvar byteGroups = []int{8, 4, 4, 4, 12}\n\ntype UUID [16]byte\n\n// String returns the string representation of this UUID.\nfunc (u *UUID) String() string {\n\tbytes := u.Bytes()\n\tresult := hex.EncodeToString(bytes[0 : byteGroups[0]/2])\n\tstart := byteGroups[0] / 2\n\tfor i := 1; i < len(byteGroups); i++ {\n\t\tnBytes := byteGroups[i] / 2\n\t\tresult += \"-\"\n\t\tresult += hex.EncodeToString(bytes[start : start+nBytes])\n\t\tstart += nBytes\n\t}\n\treturn result\n}\n\n// Bytes returns the bytes representation of this UUID.\nfunc (u *UUID) Bytes() []byte {\n\treturn u[:]\n}\n\n// Equals returns true if this UUID equals another UUID by value.\nfunc (u *UUID) Equals(another *UUID) bool {\n\tif u == nil && another == nil {\n\t\treturn true\n\t}\n\tif u == nil || another == nil {\n\t\treturn false\n\t}\n\treturn bytes.Equal(u.Bytes(), another.Bytes())\n}\n\n// New creates a UUID with random value.\nfunc New() UUID {\n\tvar uuid UUID\n\tcommon.Must2(rand.Read(uuid.Bytes()))\n\tuuid[6] = (uuid[6] & 0x0f) | (4 << 4)\n\tuuid[8] = (uuid[8]&(0xff>>2) | (0x02 << 6))\n\treturn uuid\n}\n\n// ParseBytes converts a UUID in byte form to object.\nfunc ParseBytes(b []byte) (UUID, error) {\n\tvar uuid UUID\n\tif len(b) != 16 {\n\t\treturn uuid, errors.New(\"invalid UUID: \", b)\n\t}\n\tcopy(uuid[:], b)\n\treturn uuid, nil\n}\n\n// ParseString converts a UUID in string form to object.\nfunc ParseString(str string) (UUID, error) {\n\tvar uuid UUID\n\n\ttext := []byte(str)\n\tif l := len(text); l < 32 || l > 36 {\n\t\tif l == 0 || l > 30 {\n\t\t\treturn uuid, errors.New(\"invalid UUID: \", str)\n\t\t}\n\t\th := sha1.New()\n\t\th.Write(uuid[:])\n\t\th.Write(text)\n\t\tu := h.Sum(nil)[:16]\n\t\tu[6] = (u[6] & 0x0f) | (5 << 4)\n\t\tu[8] = (u[8]&(0xff>>2) | (0x02 << 6))\n\t\tcopy(uuid[:], u)\n\t\treturn uuid, nil\n\t}\n\n\tb := uuid.Bytes()\n\n\tfor _, byteGroup := range byteGroups {\n\t\tif len(text) > 0 && text[0] == '-' {\n\t\t\ttext = text[1:]\n\t\t}\n\n\t\tif len(text) < byteGroup {\n\t\t\treturn uuid, errors.New(\"invalid UUID: \", str)\n\t\t}\n\n\t\tif _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil {\n\t\t\treturn uuid, err\n\t\t}\n\n\t\ttext = text[byteGroup:]\n\t\tb = b[byteGroup/2:]\n\t}\n\n\treturn uuid, nil\n}\n"
  },
  {
    "path": "common/uuid/uuid_test.go",
    "content": "package uuid_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/common/uuid\"\n)\n\nfunc TestParseBytes(t *testing.T) {\n\tstr := \"2418d087-648d-4990-86e8-19dca1d006d3\"\n\tbytes := []byte{0x24, 0x18, 0xd0, 0x87, 0x64, 0x8d, 0x49, 0x90, 0x86, 0xe8, 0x19, 0xdc, 0xa1, 0xd0, 0x06, 0xd3}\n\n\tuuid, err := ParseBytes(bytes)\n\tcommon.Must(err)\n\tif diff := cmp.Diff(uuid.String(), str); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n\n\t_, err = ParseBytes([]byte{1, 3, 2, 4})\n\tif err == nil {\n\t\tt.Fatal(\"Expect error but nil\")\n\t}\n}\n\nfunc TestParseString(t *testing.T) {\n\tstr := \"2418d087-648d-4990-86e8-19dca1d006d3\"\n\texpectedBytes := []byte{0x24, 0x18, 0xd0, 0x87, 0x64, 0x8d, 0x49, 0x90, 0x86, 0xe8, 0x19, 0xdc, 0xa1, 0xd0, 0x06, 0xd3}\n\n\tuuid, err := ParseString(str)\n\tcommon.Must(err)\n\tif r := cmp.Diff(expectedBytes, uuid.Bytes()); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n\n\tu0, _ := ParseString(\"example\")\n\tu5, _ := ParseString(\"feb54431-301b-52bb-a6dd-e1e93e81bb9e\")\n\tif r := cmp.Diff(u0, u5); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n\n\t_, err = ParseString(\"2418d087-648k-4990-86e8-19dca1d006d3\")\n\tif err == nil {\n\t\tt.Fatal(\"Expect error but nil\")\n\t}\n\n\t_, err = ParseString(\"2418d087-648d-4990-86e8-19dca1d0\")\n\tif err == nil {\n\t\tt.Fatal(\"Expect error but nil\")\n\t}\n}\n\nfunc TestNewUUID(t *testing.T) {\n\tuuid := New()\n\tuuid2, err := ParseString(uuid.String())\n\n\tcommon.Must(err)\n\tif uuid.String() != uuid2.String() {\n\t\tt.Error(\"uuid string: \", uuid.String(), \" != \", uuid2.String())\n\t}\n\tif r := cmp.Diff(uuid.Bytes(), uuid2.Bytes()); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestRandom(t *testing.T) {\n\tuuid := New()\n\tuuid2 := New()\n\n\tif uuid.String() == uuid2.String() {\n\t\tt.Error(\"duplicated uuid\")\n\t}\n}\n\nfunc TestEquals(t *testing.T) {\n\tvar uuid *UUID\n\tvar uuid2 *UUID\n\tif !uuid.Equals(uuid2) {\n\t\tt.Error(\"empty uuid should equal\")\n\t}\n\n\tuuid3 := New()\n\tif uuid.Equals(&uuid3) {\n\t\tt.Error(\"nil uuid equals non-nil uuid\")\n\t}\n}\n"
  },
  {
    "path": "common/xudp/xudp.go",
    "content": "package xudp\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"lukechampine.com/blake3\"\n)\n\nvar AddrParser = protocol.NewAddressParser(\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6),\n\tprotocol.PortThenAddress(),\n)\n\nvar (\n\tShow    bool\n\tBaseKey []byte\n)\n\nfunc init() {\n\tif strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return \"\" })) == \"true\" {\n\t\tShow = true\n\t}\n\tBaseKey = make([]byte, 32)\n\trand.Read(BaseKey)\n\tgo func() {\n\t\ttime.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV\n\t\tif raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return \"\" }); raw != \"\" {\n\t\t\tif BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpanic(platform.XUDPBaseKey + \": invalid value (BaseKey must be 32 bytes): \" + raw + \" len \" + strconv.Itoa(len(BaseKey)))\n\t\t}\n\t}()\n}\n\nfunc GetGlobalID(ctx context.Context) (globalID [8]byte) {\n\tif cone := ctx.Value(\"cone\"); cone == nil || !cone.(bool) { // cone is nil only in some unit tests\n\t\treturn\n\t}\n\tif inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&\n\t\t(inbound.Name == \"dokodemo-door\" || inbound.Name == \"socks\" || inbound.Name == \"shadowsocks\" || inbound.Name == \"tun\") {\n\t\th := blake3.New(8, BaseKey)\n\t\th.Write([]byte(inbound.Source.String()))\n\t\tcopy(globalID[:], h.Sum(nil))\n\t\tif Show {\n\t\t\terrors.LogInfo(ctx, fmt.Sprintf(\"XUDP inbound.Source.String(): %v\\tglobalID: %v\\n\", inbound.Source.String(), globalID))\n\t\t}\n\t}\n\treturn\n}\n\nfunc NewPacketWriter(writer buf.Writer, dest net.Destination, globalID [8]byte) *PacketWriter {\n\treturn &PacketWriter{\n\t\tWriter:   writer,\n\t\tDest:     dest,\n\t\tGlobalID: globalID,\n\t}\n}\n\ntype PacketWriter struct {\n\tWriter   buf.Writer\n\tDest     net.Destination\n\tGlobalID [8]byte\n}\n\nfunc (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\tmb2Write := make(buf.MultiBuffer, 0, len(mb))\n\tfor _, b := range mb {\n\t\tlength := b.Len()\n\t\tif length == 0 || length+666 > buf.Size {\n\t\t\tcontinue\n\t\t}\n\n\t\teb := buf.New()\n\t\teb.Write([]byte{0, 0, 0, 0}) // Meta data length; Mux Session ID\n\t\tif w.Dest.Network == net.Network_UDP {\n\t\t\teb.WriteByte(1) // New\n\t\t\teb.WriteByte(1) // Opt\n\t\t\teb.WriteByte(2) // UDP\n\t\t\tAddrParser.WriteAddressPort(eb, w.Dest.Address, w.Dest.Port)\n\t\t\tif b.UDP != nil { // make sure it's user's proxy request\n\t\t\t\teb.Write(w.GlobalID[:]) // no need to check whether it's empty\n\t\t\t}\n\t\t\tw.Dest.Network = net.Network_Unknown\n\t\t} else {\n\t\t\teb.WriteByte(2) // Keep\n\t\t\teb.WriteByte(1) // Opt\n\t\t\tif b.UDP != nil {\n\t\t\t\teb.WriteByte(2) // UDP\n\t\t\t\tAddrParser.WriteAddressPort(eb, b.UDP.Address, b.UDP.Port)\n\t\t\t}\n\t\t}\n\t\tl := eb.Len() - 2\n\t\teb.SetByte(0, byte(l>>8))\n\t\teb.SetByte(1, byte(l))\n\t\teb.WriteByte(byte(length >> 8))\n\t\teb.WriteByte(byte(length))\n\t\teb.Write(b.Bytes())\n\n\t\tmb2Write = append(mb2Write, eb)\n\t}\n\tif mb2Write.IsEmpty() {\n\t\treturn nil\n\t}\n\treturn w.Writer.WriteMultiBuffer(mb2Write)\n}\n\nfunc NewPacketReader(reader io.Reader) *PacketReader {\n\treturn &PacketReader{\n\t\tReader: reader,\n\t\tcache:  make([]byte, 2),\n\t}\n}\n\ntype PacketReader struct {\n\tReader io.Reader\n\tcache  []byte\n}\n\nfunc (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tfor {\n\t\tif _, err := io.ReadFull(r.Reader, r.cache); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tl := int32(r.cache[0])<<8 | int32(r.cache[1])\n\t\tif l < 4 {\n\t\t\treturn nil, io.EOF\n\t\t}\n\t\tb := buf.New()\n\t\tif _, err := b.ReadFullFrom(r.Reader, l); err != nil {\n\t\t\tb.Release()\n\t\t\treturn nil, err\n\t\t}\n\t\tdiscard := false\n\t\tswitch b.Byte(2) {\n\t\tcase 2:\n\t\t\tif l > 4 && b.Byte(4) == 2 { // MUST check the flag first\n\t\t\t\tb.Advance(5)\n\t\t\t\t// b.Clear() will be called automatically if all data had been read.\n\t\t\t\taddr, port, err := AddrParser.ReadAddressPort(nil, b)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Release()\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tb.UDP = &net.Destination{\n\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\tAddress: addr,\n\t\t\t\t\tPort:    port,\n\t\t\t\t}\n\t\t\t}\n\t\tcase 4:\n\t\t\tdiscard = true\n\t\tdefault:\n\t\t\tb.Release()\n\t\t\treturn nil, io.EOF\n\t\t}\n\t\tb.Clear() // in case there is padding (empty bytes) attached\n\t\tif b.Byte(3) == 1 {\n\t\t\tif _, err := io.ReadFull(r.Reader, r.cache); err != nil {\n\t\t\t\tb.Release()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tlength := int32(r.cache[0])<<8 | int32(r.cache[1])\n\t\t\tif length > 0 {\n\t\t\t\tif _, err := b.ReadFullFrom(r.Reader, length); err != nil {\n\t\t\t\t\tb.Release()\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif !discard {\n\t\t\t\t\treturn buf.MultiBuffer{b}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tb.Release()\n\t}\n}\n"
  },
  {
    "path": "common/xudp/xudp_test.go",
    "content": "package xudp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nfunc TestXudpReadWrite(t *testing.T) {\n\taddr, _ := net.ParseDestination(\"tcp:127.0.0.1:1345\")\n\tmb := make(buf.MultiBuffer, 0, 16)\n\tm := buf.MultiBufferContainer{\n\t\tMultiBuffer: mb,\n\t}\n\tvar arr [8]byte\n\twriter := NewPacketWriter(&m, addr, arr)\n\n\tsource := make(buf.MultiBuffer, 0, 16)\n\tb := buf.New()\n\tb.WriteByte('a')\n\tb.UDP = &addr\n\tsource = append(source, b)\n\twriter.WriteMultiBuffer(source)\n\n\treader := NewPacketReader(&m)\n\tdest, err := reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif dest[0].Byte(0) != 'a' {\n\t\tt.Error(\"failed to parse xudp buffer\")\n\t}\n\tif dest[0].UDP.Port != 1345 {\n\t\tt.Error(\"failed to parse xudp buffer\")\n\t}\n}\n"
  },
  {
    "path": "core/annotations.go",
    "content": "package core\n\n// Annotation is a concept in Xray. This struct is only for documentation. It is not used anywhere.\n// Annotations begin with \"xray:\" in comment, as metadata of functions or types.\ntype Annotation struct {\n\t// API is for types or functions that can be used in other libs. Possible values are:\n\t//\n\t// * xray:api:beta for types or functions that are ready for use, but maybe changed in the future.\n\t// * xray:api:stable for types or functions with guarantee of backward compatibility.\n\t// * xray:api:deprecated for types or functions that should not be used anymore.\n\t//\n\t// Types or functions without api annotation should not be used externally.\n\tAPI string\n}\n"
  },
  {
    "path": "core/config.go",
    "content": "package core\n\nimport (\n\t\"io\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// ConfigFormat is a configurable format of Xray config file.\ntype ConfigFormat struct {\n\tName      string\n\tExtension []string\n\tLoader    ConfigLoader\n}\n\ntype ConfigSource struct {\n\tName   string\n\tFormat string\n}\n\n// ConfigLoader is a utility to load Xray config from external source.\ntype ConfigLoader func(input interface{}) (*Config, error)\n\n// ConfigBuilder is a builder to build core.Config from filenames and formats\ntype ConfigBuilder func(files []*ConfigSource) (*Config, error)\n\n// ConfigsMerger merges multiple json configs into a single one\ntype ConfigsMerger func(files []*ConfigSource) (string, error)\n\nvar (\n\tconfigLoaderByName    = make(map[string]*ConfigFormat)\n\tconfigLoaderByExt     = make(map[string]*ConfigFormat)\n\tConfigBuilderForFiles ConfigBuilder\n\tConfigMergedFormFiles ConfigsMerger\n)\n\n// RegisterConfigLoader add a new ConfigLoader.\nfunc RegisterConfigLoader(format *ConfigFormat) error {\n\tname := strings.ToLower(format.Name)\n\tif _, found := configLoaderByName[name]; found {\n\t\treturn errors.New(format.Name, \" already registered.\")\n\t}\n\tconfigLoaderByName[name] = format\n\n\tfor _, ext := range format.Extension {\n\t\tlext := strings.ToLower(ext)\n\t\tif f, found := configLoaderByExt[lext]; found {\n\t\t\treturn errors.New(ext, \" already registered to \", f.Name)\n\t\t}\n\t\tconfigLoaderByExt[lext] = format\n\t}\n\n\treturn nil\n}\n\nfunc GetMergedConfig(args cmdarg.Arg) (string, error) {\n\tvar files []*ConfigSource\n\tsupported := []string{\"json\", \"yaml\", \"toml\"}\n\tfor _, file := range args {\n\t\tformat := \"json\"\n\t\tif file != \"stdin:\" {\n\t\t\tformat = GetFormat(file)\n\t\t}\n\n\t\tif slices.Contains(supported, format) {\n\t\t\tfiles = append(files, &ConfigSource{\n\t\t\t\tName:   file,\n\t\t\t\tFormat: format,\n\t\t\t})\n\t\t}\n\t}\n\treturn ConfigMergedFormFiles(files)\n}\n\nfunc GetFormatByExtension(ext string) string {\n\tswitch strings.ToLower(ext) {\n\tcase \"pb\", \"protobuf\":\n\t\treturn \"protobuf\"\n\tcase \"yaml\", \"yml\":\n\t\treturn \"yaml\"\n\tcase \"toml\":\n\t\treturn \"toml\"\n\tcase \"json\", \"jsonc\":\n\t\treturn \"json\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc getExtension(filename string) string {\n\tidx := strings.LastIndexByte(filename, '.')\n\tif idx == -1 {\n\t\treturn \"\"\n\t}\n\treturn filename[idx+1:]\n}\n\nfunc GetFormat(filename string) string {\n\treturn GetFormatByExtension(getExtension(filename))\n}\n\nfunc LoadConfig(formatName string, input interface{}) (*Config, error) {\n\tswitch v := input.(type) {\n\tcase cmdarg.Arg:\n\t\tfiles := make([]*ConfigSource, len(v))\n\t\thasProtobuf := false\n\t\tfor i, file := range v {\n\t\t\tvar f string\n\n\t\t\tif formatName == \"auto\" {\n\t\t\t\tif file != \"stdin:\" {\n\t\t\t\t\tf = GetFormat(file)\n\t\t\t\t} else {\n\t\t\t\t\tf = \"json\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tf = formatName\n\t\t\t}\n\n\t\t\tif f == \"\" {\n\t\t\t\treturn nil, errors.New(\"Failed to get format of \", file).AtWarning()\n\t\t\t}\n\n\t\t\tif f == \"protobuf\" {\n\t\t\t\thasProtobuf = true\n\t\t\t}\n\t\t\tfiles[i] = &ConfigSource{\n\t\t\t\tName:   file,\n\t\t\t\tFormat: f,\n\t\t\t}\n\t\t}\n\n\t\t// only one protobuf config file is allowed\n\t\tif hasProtobuf {\n\t\t\tif len(v) == 1 {\n\t\t\t\treturn configLoaderByName[\"protobuf\"].Loader(v)\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"Only one protobuf config file is allowed\").AtWarning()\n\t\t\t}\n\t\t}\n\n\t\t// to avoid import cycle\n\t\treturn ConfigBuilderForFiles(files)\n\tcase io.Reader:\n\t\tif f, found := configLoaderByName[formatName]; found {\n\t\t\treturn f.Loader(v)\n\t\t} else {\n\t\t\treturn nil, errors.New(\"Unable to load config in\", formatName).AtWarning()\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"Unable to load config\").AtWarning()\n}\n\nfunc loadProtobufConfig(data []byte) (*Config, error) {\n\tconfig := new(Config)\n\tif err := proto.Unmarshal(data, config); err != nil {\n\t\treturn nil, err\n\t}\n\treturn config, nil\n}\n\nfunc init() {\n\tcommon.Must(RegisterConfigLoader(&ConfigFormat{\n\t\tName:      \"Protobuf\",\n\t\tExtension: []string{\"pb\"},\n\t\tLoader: func(input interface{}) (*Config, error) {\n\t\t\tswitch v := input.(type) {\n\t\t\tcase cmdarg.Arg:\n\t\t\t\tr, err := confloader.LoadConfig(v[0])\n\t\t\t\tcommon.Must(err)\n\t\t\t\tdata, err := buf.ReadAllToBytes(r)\n\t\t\t\tcommon.Must(err)\n\t\t\t\treturn loadProtobufConfig(data)\n\t\t\tcase io.Reader:\n\t\t\t\tdata, err := buf.ReadAllToBytes(v)\n\t\t\t\tcommon.Must(err)\n\t\t\t\treturn loadProtobufConfig(data)\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"unknown type\")\n\t\t\t}\n\t\t},\n\t}))\n}\n"
  },
  {
    "path": "core/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: core/config.proto\n\npackage core\n\nimport (\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Config is the master config of Xray. Xray takes this config as input and\n// functions accordingly.\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Inbound handler configurations. Must have at least one item.\n\tInbound []*InboundHandlerConfig `protobuf:\"bytes,1,rep,name=inbound,proto3\" json:\"inbound,omitempty\"`\n\t// Outbound handler configurations. Must have at least one item. The first\n\t// item is used as default for routing.\n\tOutbound []*OutboundHandlerConfig `protobuf:\"bytes,2,rep,name=outbound,proto3\" json:\"outbound,omitempty\"`\n\t// App is for configurations of all features in Xray. A feature must\n\t// implement the Feature interface, and its config type must be registered\n\t// through common.RegisterConfig.\n\tApp []*serial.TypedMessage `protobuf:\"bytes,4,rep,name=app,proto3\" json:\"app,omitempty\"`\n\t// Configuration for extensions. The config may not work if corresponding\n\t// extension is not loaded into Xray. Xray will ignore such config during\n\t// initialization.\n\tExtension     []*serial.TypedMessage `protobuf:\"bytes,6,rep,name=extension,proto3\" json:\"extension,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_core_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_core_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetInbound() []*InboundHandlerConfig {\n\tif x != nil {\n\t\treturn x.Inbound\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetOutbound() []*OutboundHandlerConfig {\n\tif x != nil {\n\t\treturn x.Outbound\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetApp() []*serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.App\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetExtension() []*serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Extension\n\t}\n\treturn nil\n}\n\n// InboundHandlerConfig is the configuration for inbound handler.\ntype InboundHandlerConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Tag of the inbound handler. The tag must be unique among all inbound\n\t// handlers\n\tTag string `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\t// Settings for how this inbound proxy is handled.\n\tReceiverSettings *serial.TypedMessage `protobuf:\"bytes,2,opt,name=receiver_settings,json=receiverSettings,proto3\" json:\"receiver_settings,omitempty\"`\n\t// Settings for inbound proxy. Must be one of the inbound proxies.\n\tProxySettings *serial.TypedMessage `protobuf:\"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3\" json:\"proxy_settings,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *InboundHandlerConfig) Reset() {\n\t*x = InboundHandlerConfig{}\n\tmi := &file_core_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InboundHandlerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InboundHandlerConfig) ProtoMessage() {}\n\nfunc (x *InboundHandlerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InboundHandlerConfig.ProtoReflect.Descriptor instead.\nfunc (*InboundHandlerConfig) Descriptor() ([]byte, []int) {\n\treturn file_core_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *InboundHandlerConfig) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *InboundHandlerConfig) GetReceiverSettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.ReceiverSettings\n\t}\n\treturn nil\n}\n\nfunc (x *InboundHandlerConfig) GetProxySettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.ProxySettings\n\t}\n\treturn nil\n}\n\n// OutboundHandlerConfig is the configuration for outbound handler.\ntype OutboundHandlerConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Tag of this outbound handler.\n\tTag string `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\t// Settings for how to dial connection for this outbound handler.\n\tSenderSettings *serial.TypedMessage `protobuf:\"bytes,2,opt,name=sender_settings,json=senderSettings,proto3\" json:\"sender_settings,omitempty\"`\n\t// Settings for this outbound proxy. Must be one of the outbound proxies.\n\tProxySettings *serial.TypedMessage `protobuf:\"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3\" json:\"proxy_settings,omitempty\"`\n\t// If not zero, this outbound will be expired in seconds. Not used for now.\n\tExpire int64 `protobuf:\"varint,4,opt,name=expire,proto3\" json:\"expire,omitempty\"`\n\t// Comment of this outbound handler. Not used for now.\n\tComment       string `protobuf:\"bytes,5,opt,name=comment,proto3\" json:\"comment,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *OutboundHandlerConfig) Reset() {\n\t*x = OutboundHandlerConfig{}\n\tmi := &file_core_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *OutboundHandlerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*OutboundHandlerConfig) ProtoMessage() {}\n\nfunc (x *OutboundHandlerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_core_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use OutboundHandlerConfig.ProtoReflect.Descriptor instead.\nfunc (*OutboundHandlerConfig) Descriptor() ([]byte, []int) {\n\treturn file_core_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *OutboundHandlerConfig) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *OutboundHandlerConfig) GetSenderSettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.SenderSettings\n\t}\n\treturn nil\n}\n\nfunc (x *OutboundHandlerConfig) GetProxySettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.ProxySettings\n\t}\n\treturn nil\n}\n\nfunc (x *OutboundHandlerConfig) GetExpire() int64 {\n\tif x != nil {\n\t\treturn x.Expire\n\t}\n\treturn 0\n}\n\nfunc (x *OutboundHandlerConfig) GetComment() string {\n\tif x != nil {\n\t\treturn x.Comment\n\t}\n\treturn \"\"\n}\n\nvar File_core_config_proto protoreflect.FileDescriptor\n\nconst file_core_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x11core/config.proto\\x12\\txray.core\\x1a!common/serial/typed_message.proto\\\"\\xfb\\x01\\n\" +\n\t\"\\x06Config\\x129\\n\" +\n\t\"\\ainbound\\x18\\x01 \\x03(\\v2\\x1f.xray.core.InboundHandlerConfigR\\ainbound\\x12<\\n\" +\n\t\"\\boutbound\\x18\\x02 \\x03(\\v2 .xray.core.OutboundHandlerConfigR\\boutbound\\x122\\n\" +\n\t\"\\x03app\\x18\\x04 \\x03(\\v2 .xray.common.serial.TypedMessageR\\x03app\\x12>\\n\" +\n\t\"\\textension\\x18\\x06 \\x03(\\v2 .xray.common.serial.TypedMessageR\\textensionJ\\x04\\b\\x03\\x10\\x04\\\"\\xc0\\x01\\n\" +\n\t\"\\x14InboundHandlerConfig\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12M\\n\" +\n\t\"\\x11receiver_settings\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\x10receiverSettings\\x12G\\n\" +\n\t\"\\x0eproxy_settings\\x18\\x03 \\x01(\\v2 .xray.common.serial.TypedMessageR\\rproxySettings\\\"\\xef\\x01\\n\" +\n\t\"\\x15OutboundHandlerConfig\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x12I\\n\" +\n\t\"\\x0fsender_settings\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\x0esenderSettings\\x12G\\n\" +\n\t\"\\x0eproxy_settings\\x18\\x03 \\x01(\\v2 .xray.common.serial.TypedMessageR\\rproxySettings\\x12\\x16\\n\" +\n\t\"\\x06expire\\x18\\x04 \\x01(\\x03R\\x06expire\\x12\\x18\\n\" +\n\t\"\\acomment\\x18\\x05 \\x01(\\tR\\acommentB=\\n\" +\n\t\"\\rcom.xray.coreP\\x01Z\\x1egithub.com/xtls/xray-core/core\\xaa\\x02\\tXray.Coreb\\x06proto3\"\n\nvar (\n\tfile_core_config_proto_rawDescOnce sync.Once\n\tfile_core_config_proto_rawDescData []byte\n)\n\nfunc file_core_config_proto_rawDescGZIP() []byte {\n\tfile_core_config_proto_rawDescOnce.Do(func() {\n\t\tfile_core_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_core_config_proto_rawDesc), len(file_core_config_proto_rawDesc)))\n\t})\n\treturn file_core_config_proto_rawDescData\n}\n\nvar file_core_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_core_config_proto_goTypes = []any{\n\t(*Config)(nil),                // 0: xray.core.Config\n\t(*InboundHandlerConfig)(nil),  // 1: xray.core.InboundHandlerConfig\n\t(*OutboundHandlerConfig)(nil), // 2: xray.core.OutboundHandlerConfig\n\t(*serial.TypedMessage)(nil),   // 3: xray.common.serial.TypedMessage\n}\nvar file_core_config_proto_depIdxs = []int32{\n\t1, // 0: xray.core.Config.inbound:type_name -> xray.core.InboundHandlerConfig\n\t2, // 1: xray.core.Config.outbound:type_name -> xray.core.OutboundHandlerConfig\n\t3, // 2: xray.core.Config.app:type_name -> xray.common.serial.TypedMessage\n\t3, // 3: xray.core.Config.extension:type_name -> xray.common.serial.TypedMessage\n\t3, // 4: xray.core.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage\n\t3, // 5: xray.core.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage\n\t3, // 6: xray.core.OutboundHandlerConfig.sender_settings:type_name -> xray.common.serial.TypedMessage\n\t3, // 7: xray.core.OutboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage\n\t8, // [8:8] is the sub-list for method output_type\n\t8, // [8:8] is the sub-list for method input_type\n\t8, // [8:8] is the sub-list for extension type_name\n\t8, // [8:8] is the sub-list for extension extendee\n\t0, // [0:8] is the sub-list for field type_name\n}\n\nfunc init() { file_core_config_proto_init() }\nfunc file_core_config_proto_init() {\n\tif File_core_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_core_config_proto_rawDesc), len(file_core_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_core_config_proto_goTypes,\n\t\tDependencyIndexes: file_core_config_proto_depIdxs,\n\t\tMessageInfos:      file_core_config_proto_msgTypes,\n\t}.Build()\n\tFile_core_config_proto = out.File\n\tfile_core_config_proto_goTypes = nil\n\tfile_core_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "core/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.core;\noption csharp_namespace = \"Xray.Core\";\noption go_package = \"github.com/xtls/xray-core/core\";\noption java_package = \"com.xray.core\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\n\n// Config is the master config of Xray. Xray takes this config as input and\n// functions accordingly.\nmessage Config {\n  // Inbound handler configurations. Must have at least one item.\n  repeated InboundHandlerConfig inbound = 1;\n\n  // Outbound handler configurations. Must have at least one item. The first\n  // item is used as default for routing.\n  repeated OutboundHandlerConfig outbound = 2;\n\n  reserved 3;\n\n  // App is for configurations of all features in Xray. A feature must\n  // implement the Feature interface, and its config type must be registered\n  // through common.RegisterConfig.\n  repeated xray.common.serial.TypedMessage app = 4;\n\n  // Configuration for extensions. The config may not work if corresponding\n  // extension is not loaded into Xray. Xray will ignore such config during\n  // initialization.\n  repeated xray.common.serial.TypedMessage extension = 6;\n}\n\n// InboundHandlerConfig is the configuration for inbound handler.\nmessage InboundHandlerConfig {\n  // Tag of the inbound handler. The tag must be unique among all inbound\n  // handlers\n  string tag = 1;\n  // Settings for how this inbound proxy is handled.\n  xray.common.serial.TypedMessage receiver_settings = 2;\n  // Settings for inbound proxy. Must be one of the inbound proxies.\n  xray.common.serial.TypedMessage proxy_settings = 3;\n}\n\n// OutboundHandlerConfig is the configuration for outbound handler.\nmessage OutboundHandlerConfig {\n  // Tag of this outbound handler.\n  string tag = 1;\n  // Settings for how to dial connection for this outbound handler.\n  xray.common.serial.TypedMessage sender_settings = 2;\n  // Settings for this outbound proxy. Must be one of the outbound proxies.\n  xray.common.serial.TypedMessage proxy_settings = 3;\n  // If not zero, this outbound will be expired in seconds. Not used for now.\n  int64 expire = 4;\n  // Comment of this outbound handler. Not used for now.\n  string comment = 5;\n}\n"
  },
  {
    "path": "core/context.go",
    "content": "package core\n\nimport (\n\t\"context\"\n)\n\n// XrayKey is the key type of Instance in Context, exported for test.\ntype XrayKey int\n\nconst xrayKey XrayKey = 1\n\n// FromContext returns an Instance from the given context, or nil if the context doesn't contain one.\nfunc FromContext(ctx context.Context) *Instance {\n\tif s, ok := ctx.Value(xrayKey).(*Instance); ok {\n\t\treturn s\n\t}\n\treturn nil\n}\n\n// MustFromContext returns an Instance from the given context, or panics if not present.\nfunc MustFromContext(ctx context.Context) *Instance {\n\tx := FromContext(ctx)\n\tif x == nil {\n\t\tpanic(\"X is not in context.\")\n\t}\n\treturn x\n}\n\n/*\n\ttoContext returns ctx from the given context, or creates an Instance if the context doesn't find that.\n\nIt is unsupported to use this function to create a context that is suitable to invoke Xray's internal component\nin third party code, you shouldn't use //go:linkname to alias of this function into your own package and\nuse this function in your third party code.\n\nFor third party code, usage enabled by creating a context to interact with Xray's internal component is unsupported,\nand may break at any time.\n*/\nfunc toContext(ctx context.Context, v *Instance) context.Context {\n\tif FromContext(ctx) != v {\n\t\tctx = context.WithValue(ctx, xrayKey, v)\n\t}\n\treturn ctx\n}\n\n/*\nToBackgroundDetachedContext create a detached context from another context\nInternal API\n*/\nfunc ToBackgroundDetachedContext(ctx context.Context) context.Context {\n\tinstance := MustFromContext(ctx)\n\treturn toContext(context.Background(), instance)\n}\n"
  },
  {
    "path": "core/context_test.go",
    "content": "package core_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t_ \"unsafe\"\n\n\t. \"github.com/xtls/xray-core/core\"\n)\n\nfunc TestFromContextPanic(t *testing.T) {\n\tdefer func() {\n\t\tr := recover()\n\t\tif r == nil {\n\t\t\tt.Error(\"expect panic, but nil\")\n\t\t}\n\t}()\n\n\tMustFromContext(context.Background())\n}\n"
  },
  {
    "path": "core/core.go",
    "content": "// Package core provides an entry point to use Xray core functionalities.\n//\n// Xray makes it possible to accept incoming network connections with certain\n// protocol, process the data, and send them through another connection with\n// the same or a difference protocol on demand.\n//\n// It may be configured to work with multiple protocols at the same time, and\n// uses the internal router to tunnel through different inbound and outbound\n// connections.\npackage core\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\nvar (\n\tVersion_x byte = 26\n\tVersion_y byte = 2\n\tVersion_z byte = 6\n)\n\nvar (\n\tbuild    = \"Custom\"\n\tcodename = \"Xray, Penetrates Everything.\"\n\tintro    = \"A unified platform for anti-censorship.\"\n)\n\nfunc init() {\n\t// Manually injected\n\tif build != \"Custom\" {\n\t\treturn\n\t}\n\tinfo, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn\n\t}\n\tvar isDirty bool\n\tvar foundBuild bool\n\tfor _, setting := range info.Settings {\n\t\tswitch setting.Key {\n\t\tcase \"vcs.revision\":\n\t\t\tif len(setting.Value) < 7 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbuild = setting.Value[:7]\n\t\t\tfoundBuild = true\n\t\tcase \"vcs.modified\":\n\t\t\tisDirty = setting.Value == \"true\"\n\t\t}\n\t}\n\tif isDirty && foundBuild {\n\t\tbuild += \"-dirty\"\n\t}\n}\n\n// Version returns Xray's version as a string, in the form of \"x.y.z\" where x, y and z are numbers.\n// \".z\" part may be omitted in regular releases.\nfunc Version() string {\n\treturn fmt.Sprintf(\"%v.%v.%v\", Version_x, Version_y, Version_z)\n}\n\n// VersionStatement returns a list of strings representing the full version info.\nfunc VersionStatement() []string {\n\treturn []string{\n\t\tserial.Concat(\"Xray \", Version(), \" (\", codename, \") \", build, \" (\", runtime.Version(), \" \", runtime.GOOS, \"/\", runtime.GOARCH, \")\"),\n\t\tintro,\n\t}\n}\n"
  },
  {
    "path": "core/format.go",
    "content": "package core\n\n//go:generate go install -v github.com/daixiang0/gci@latest\n//go:generate go install -v mvdan.cc/gofumpt@latest\n//go:generate go run ../infra/vformat/main.go -pwd ./..\n"
  },
  {
    "path": "core/functions.go",
    "content": "package core\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n)\n\n// CreateObject creates a new object based on the given Xray instance and config. The Xray instance may be nil.\nfunc CreateObject(v *Instance, config interface{}) (interface{}, error) {\n\tctx := v.ctx\n\tif v != nil {\n\t\tctx = toContext(v.ctx, v)\n\t}\n\treturn common.CreateObject(ctx, config)\n}\n\n// StartInstance starts a new Xray instance with given serialized config.\n// By default Xray only support config in protobuf format, i.e., configFormat = \"protobuf\". Caller need to load other packages to add JSON support.\n//\n// xray:api:stable\nfunc StartInstance(configFormat string, configBytes []byte) (*Instance, error) {\n\tconfig, err := LoadConfig(configFormat, bytes.NewReader(configBytes))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinstance, err := New(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := instance.Start(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn instance, nil\n}\n\n// Dial provides an easy way for upstream caller to create net.Conn through Xray.\n// It dispatches the request to the given destination by the given Xray instance.\n// Since it is under a proxy context, the LocalAddr() and RemoteAddr() in returned net.Conn\n// will not show real addresses being used for communication.\n//\n// xray:api:stable\nfunc Dial(ctx context.Context, v *Instance, dest net.Destination) (net.Conn, error) {\n\tctx = toContext(ctx, v)\n\n\tdispatcher := v.GetFeature(routing.DispatcherType())\n\tif dispatcher == nil {\n\t\treturn nil, errors.New(\"routing.Dispatcher is not registered in Xray core\")\n\t}\n\n\tr, err := dispatcher.(routing.Dispatcher).Dispatch(ctx, dest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar readerOpt cnc.ConnectionOption\n\tif dest.Network == net.Network_TCP {\n\t\treaderOpt = cnc.ConnectionOutputMulti(r.Reader)\n\t} else {\n\t\treaderOpt = cnc.ConnectionOutputMultiUDP(r.Reader)\n\t}\n\treturn cnc.NewConnection(cnc.ConnectionInputMulti(r.Writer), readerOpt), nil\n}\n\n// DialUDP provides a way to exchange UDP packets through Xray instance to remote servers.\n// Since it is under a proxy context, the LocalAddr() in returned PacketConn will not show the real address.\n//\n// TODO: SetDeadline() / SetReadDeadline() / SetWriteDeadline() are not implemented.\n//\n// xray:api:beta\nfunc DialUDP(ctx context.Context, v *Instance) (net.PacketConn, error) {\n\tctx = toContext(ctx, v)\n\n\tdispatcher := v.GetFeature(routing.DispatcherType())\n\tif dispatcher == nil {\n\t\treturn nil, errors.New(\"routing.Dispatcher is not registered in Xray core\")\n\t}\n\treturn udp.DialDispatcher(ctx, dispatcher.(routing.Dispatcher))\n}\n"
  },
  {
    "path": "core/functions_test.go",
    "content": "package core_test\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc xor(b []byte) []byte {\n\tr := make([]byte, len(b))\n\tfor i, v := range b {\n\t\tr[i] = v ^ 'c'\n\t}\n\treturn r\n}\n\nfunc xor2(b []byte) []byte {\n\tr := make([]byte, len(b))\n\tfor i, v := range b {\n\t\tr[i] = v ^ 'd'\n\t}\n\treturn r\n}\n\nfunc TestXrayDial(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tcfgBytes, err := proto.Marshal(config)\n\tcommon.Must(err)\n\n\tserver, err := core.StartInstance(\"protobuf\", cfgBytes)\n\tcommon.Must(err)\n\tdefer server.Close()\n\n\tconn, err := core.Dial(context.Background(), server, dest)\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\tconst size = 10240 * 1024\n\tpayload := make([]byte, size)\n\tcommon.Must2(rand.Read(payload))\n\n\tif _, err := conn.Write(payload); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treceive := make([]byte, size)\n\tif _, err := io.ReadFull(conn, receive); err != nil {\n\t\tt.Fatal(\"failed to read all response: \", err)\n\t}\n\n\tif r := cmp.Diff(xor(receive), payload); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestXrayDialUDPConn(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tcfgBytes, err := proto.Marshal(config)\n\tcommon.Must(err)\n\n\tserver, err := core.StartInstance(\"protobuf\", cfgBytes)\n\tcommon.Must(err)\n\tdefer server.Close()\n\n\tconn, err := core.Dial(context.Background(), server, dest)\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\tconst size = 1024\n\tpayload := make([]byte, size)\n\tcommon.Must2(rand.Read(payload))\n\n\tfor i := 0; i < 2; i++ {\n\t\tif _, err := conn.Write(payload); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\ttime.Sleep(time.Millisecond * 500)\n\n\treceive := make([]byte, size*2)\n\tfor i := 0; i < 2; i++ {\n\t\tn, err := conn.Read(receive)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"expect no error, but got \", err)\n\t\t}\n\t\tif n != size {\n\t\t\tt.Fatal(\"expect read size \", size, \" but got \", n)\n\t\t}\n\n\t\tif r := cmp.Diff(xor(receive[:n]), payload); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n}\n\nfunc TestXrayDialUDP(t *testing.T) {\n\tudpServer1 := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest1, err := udpServer1.Start()\n\tcommon.Must(err)\n\tdefer udpServer1.Close()\n\n\tudpServer2 := udp.Server{\n\t\tMsgProcessor: xor2,\n\t}\n\tdest2, err := udpServer2.Start()\n\tcommon.Must(err)\n\tdefer udpServer2.Close()\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tcfgBytes, err := proto.Marshal(config)\n\tcommon.Must(err)\n\n\tserver, err := core.StartInstance(\"protobuf\", cfgBytes)\n\tcommon.Must(err)\n\tdefer server.Close()\n\n\tconn, err := core.DialUDP(context.Background(), server)\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\tconst size = 1024\n\t{\n\t\tpayload := make([]byte, size)\n\t\tcommon.Must2(rand.Read(payload))\n\n\t\tif _, err := conn.WriteTo(payload, &net.UDPAddr{\n\t\t\tIP:   dest1.Address.IP(),\n\t\t\tPort: int(dest1.Port),\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\treceive := make([]byte, size)\n\t\tif _, _, err := conn.ReadFrom(receive); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif r := cmp.Diff(xor(receive), payload); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t}\n\n\t{\n\t\tpayload := make([]byte, size)\n\t\tcommon.Must2(rand.Read(payload))\n\n\t\tif _, err := conn.WriteTo(payload, &net.UDPAddr{\n\t\t\tIP:   dest2.Address.IP(),\n\t\t\tPort: int(dest2.Port),\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\treceive := make([]byte, size)\n\t\tif _, _, err := conn.ReadFrom(receive); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif r := cmp.Diff(xor2(receive), payload); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "core/mocks.go",
    "content": "package core\n\n//go:generate go run github.com/golang/mock/mockgen -package mocks -destination ../testing/mocks/io.go -mock_names Reader=Reader,Writer=Writer io Reader,Writer\n//go:generate go run github.com/golang/mock/mockgen -package mocks -destination ../testing/mocks/log.go -mock_names Handler=LogHandler github.com/xtls/xray-core/common/log Handler\n//go:generate go run github.com/golang/mock/mockgen -package mocks -destination ../testing/mocks/mux.go -mock_names ClientWorkerFactory=MuxClientWorkerFactory github.com/xtls/xray-core/common/mux ClientWorkerFactory\n//go:generate go run github.com/golang/mock/mockgen -package mocks -destination ../testing/mocks/dns.go -mock_names Client=DNSClient github.com/xtls/xray-core/features/dns Client\n//go:generate go run github.com/golang/mock/mockgen -package mocks -destination ../testing/mocks/outbound.go -mock_names Manager=OutboundManager,HandlerSelector=OutboundHandlerSelector github.com/xtls/xray-core/features/outbound Manager,HandlerSelector\n//go:generate go run github.com/golang/mock/mockgen -package mocks -destination ../testing/mocks/proxy.go -mock_names Inbound=ProxyInbound,Outbound=ProxyOutbound github.com/xtls/xray-core/proxy Inbound,Outbound\n"
  },
  {
    "path": "core/proto.go",
    "content": "package core\n\n//go:generate go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest\n//go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest\n//go:generate go run ../infra/vprotogen/main.go -pwd ./..\n"
  },
  {
    "path": "core/xray.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/features\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/dns/localdns\"\n\t\"github.com/xtls/xray-core/features/inbound\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// Server is an instance of Xray. At any time, there must be at most one Server instance running.\ntype Server interface {\n\tcommon.Runnable\n}\n\n// ServerType returns the type of the server.\nfunc ServerType() interface{} {\n\treturn (*Instance)(nil)\n}\n\ntype resolution struct {\n\tdeps     []reflect.Type\n\tcallback interface{}\n}\n\nfunc getFeature(allFeatures []features.Feature, t reflect.Type) features.Feature {\n\tfor _, f := range allFeatures {\n\t\tif reflect.TypeOf(f.Type()) == t {\n\t\t\treturn f\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *resolution) callbackResolution(allFeatures []features.Feature) error {\n\tcallback := reflect.ValueOf(r.callback)\n\tvar input []reflect.Value\n\tcallbackType := callback.Type()\n\tfor i := 0; i < callbackType.NumIn(); i++ {\n\t\tpt := callbackType.In(i)\n\t\tfor _, f := range allFeatures {\n\t\t\tif reflect.TypeOf(f).AssignableTo(pt) {\n\t\t\t\tinput = append(input, reflect.ValueOf(f))\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(input) != callbackType.NumIn() {\n\t\tpanic(\"Can't get all input parameters\")\n\t}\n\n\tvar err error\n\tret := callback.Call(input)\n\terrInterface := reflect.TypeOf((*error)(nil)).Elem()\n\tfor i := len(ret) - 1; i >= 0; i-- {\n\t\tif ret[i].Type() == errInterface {\n\t\t\tv := ret[i].Interface()\n\t\t\tif v != nil {\n\t\t\t\terr = v.(error)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn err\n}\n\n// Instance combines all Xray features.\ntype Instance struct {\n\tstatusLock                 sync.Mutex\n\tfeatures                   []features.Feature\n\tpendingResolutions         []resolution\n\tpendingOptionalResolutions []resolution\n\trunning                    bool\n\tresolveLock                sync.Mutex\n\n\tctx context.Context\n}\n\n// Instance state\nfunc (server *Instance) IsRunning() bool {\n\treturn server.running\n}\n\nfunc AddInboundHandler(server *Instance, config *InboundHandlerConfig) error {\n\tinboundManager := server.GetFeature(inbound.ManagerType()).(inbound.Manager)\n\trawHandler, err := CreateObject(server, config)\n\tif err != nil {\n\t\treturn err\n\t}\n\thandler, ok := rawHandler.(inbound.Handler)\n\tif !ok {\n\t\treturn errors.New(\"not an InboundHandler\")\n\t}\n\tif err := inboundManager.AddHandler(server.ctx, handler); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc addInboundHandlers(server *Instance, configs []*InboundHandlerConfig) error {\n\tfor _, inboundConfig := range configs {\n\t\tif err := AddInboundHandler(server, inboundConfig); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc AddOutboundHandler(server *Instance, config *OutboundHandlerConfig) error {\n\toutboundManager := server.GetFeature(outbound.ManagerType()).(outbound.Manager)\n\trawHandler, err := CreateObject(server, config)\n\tif err != nil {\n\t\treturn err\n\t}\n\thandler, ok := rawHandler.(outbound.Handler)\n\tif !ok {\n\t\treturn errors.New(\"not an OutboundHandler\")\n\t}\n\tif err := outboundManager.AddHandler(server.ctx, handler); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc addOutboundHandlers(server *Instance, configs []*OutboundHandlerConfig) error {\n\tfor _, outboundConfig := range configs {\n\t\tif err := AddOutboundHandler(server, outboundConfig); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// RequireFeatures is a helper function to require features from Instance in context.\n// See Instance.RequireFeatures for more information.\nfunc RequireFeatures(ctx context.Context, callback interface{}) error {\n\tv := MustFromContext(ctx)\n\treturn v.RequireFeatures(callback, false)\n}\n\n// OptionalFeatures is a helper function to aquire features from Instance in context.\n// See Instance.RequireFeatures for more information.\nfunc OptionalFeatures(ctx context.Context, callback interface{}) error {\n\tv := MustFromContext(ctx)\n\treturn v.RequireFeatures(callback, true)\n}\n\n// New returns a new Xray instance based on given configuration.\n// The instance is not started at this point.\n// To ensure Xray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional.\nfunc New(config *Config) (*Instance, error) {\n\tserver := &Instance{ctx: context.Background()}\n\n\tdone, err := initInstanceWithConfig(config, server)\n\tif done {\n\t\treturn nil, err\n\t}\n\n\treturn server, nil\n}\n\nfunc NewWithContext(ctx context.Context, config *Config) (*Instance, error) {\n\tserver := &Instance{ctx: ctx}\n\n\tdone, err := initInstanceWithConfig(config, server)\n\tif done {\n\t\treturn nil, err\n\t}\n\n\treturn server, nil\n}\n\nfunc initInstanceWithConfig(config *Config, server *Instance) (bool, error) {\n\tserver.ctx = context.WithValue(server.ctx, \"cone\",\n\t\tplatform.NewEnvFlag(platform.UseCone).GetValue(func() string { return \"\" }) != \"true\")\n\n\tfor _, appSettings := range config.App {\n\t\tsettings, err := appSettings.GetInstance()\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\tobj, err := CreateObject(server, settings)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\tif feature, ok := obj.(features.Feature); ok {\n\t\t\tif err := server.AddFeature(feature); err != nil {\n\t\t\t\treturn true, err\n\t\t\t}\n\t\t}\n\t}\n\n\tessentialFeatures := []struct {\n\t\tType     interface{}\n\t\tInstance features.Feature\n\t}{\n\t\t{dns.ClientType(), localdns.New()},\n\t\t{policy.ManagerType(), policy.DefaultManager{}},\n\t\t{routing.RouterType(), routing.DefaultRouter{}},\n\t\t{stats.ManagerType(), stats.NoopManager{}},\n\t}\n\n\tfor _, f := range essentialFeatures {\n\t\tif server.GetFeature(f.Type) == nil {\n\t\t\tif err := server.AddFeature(f.Instance); err != nil {\n\t\t\t\treturn true, err\n\t\t\t}\n\t\t}\n\t}\n\n\tinternet.InitSystemDialer(\n\t\tserver.GetFeature(dns.ClientType()).(dns.Client),\n\t\tfunc() outbound.Manager {\n\t\t\tobm, _ := server.GetFeature(outbound.ManagerType()).(outbound.Manager)\n\t\t\treturn obm\n\t\t}(),\n\t)\n\n\tserver.resolveLock.Lock()\n\tif server.pendingResolutions != nil {\n\t\tserver.resolveLock.Unlock()\n\t\treturn true, errors.New(\"not all dependencies are resolved.\")\n\t}\n\tserver.resolveLock.Unlock()\n\n\tif err := addInboundHandlers(server, config.Inbound); err != nil {\n\t\treturn true, err\n\t}\n\n\tif err := addOutboundHandlers(server, config.Outbound); err != nil {\n\t\treturn true, err\n\t}\n\treturn false, nil\n}\n\n// Type implements common.HasType.\nfunc (s *Instance) Type() interface{} {\n\treturn ServerType()\n}\n\n// Close shutdown the Xray instance.\nfunc (s *Instance) Close() error {\n\ts.statusLock.Lock()\n\tdefer s.statusLock.Unlock()\n\n\ts.running = false\n\n\tvar errs []interface{}\n\tfor _, f := range s.features {\n\t\tif err := f.Close(); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) > 0 {\n\t\treturn errors.New(\"failed to close all features\").Base(errors.New(serial.Concat(errs...)))\n\t}\n\n\treturn nil\n}\n\n// RequireFeatures registers a callback, which will be called when all dependent features are registered.\n// The callback must be a func(). All its parameters must be features.Feature.\nfunc (s *Instance) RequireFeatures(callback interface{}, optional bool) error {\n\tcallbackType := reflect.TypeOf(callback)\n\tif callbackType.Kind() != reflect.Func {\n\t\tpanic(\"not a function\")\n\t}\n\n\tvar featureTypes []reflect.Type\n\tfor i := 0; i < callbackType.NumIn(); i++ {\n\t\tfeatureTypes = append(featureTypes, reflect.PtrTo(callbackType.In(i)))\n\t}\n\n\tr := resolution{\n\t\tdeps:     featureTypes,\n\t\tcallback: callback,\n\t}\n\n\ts.resolveLock.Lock()\n\tfoundAll := true\n\tfor _, d := range r.deps {\n\t\tf := getFeature(s.features, d)\n\t\tif f == nil {\n\t\t\tfoundAll = false\n\t\t\tbreak\n\t\t}\n\t}\n\tif foundAll {\n\t\ts.resolveLock.Unlock()\n\t\treturn r.callbackResolution(s.features)\n\t} else {\n\t\tif optional {\n\t\t\ts.pendingOptionalResolutions = append(s.pendingOptionalResolutions, r)\n\t\t} else {\n\t\t\ts.pendingResolutions = append(s.pendingResolutions, r)\n\t\t}\n\t\ts.resolveLock.Unlock()\n\t\treturn nil\n\t}\n}\n\n// AddFeature registers a feature into current Instance.\nfunc (s *Instance) AddFeature(feature features.Feature) error {\n\tif s.running {\n\t\tif err := feature.Start(); err != nil {\n\t\t\terrors.LogInfoInner(s.ctx, err, \"failed to start feature\")\n\t\t}\n\t\treturn nil\n\t}\n\n\ts.resolveLock.Lock()\n\ts.features = append(s.features, feature)\n\n\tvar availableResolution []resolution\n\tvar pending []resolution\n\tfor _, r := range s.pendingResolutions {\n\t\tfoundAll := true\n\t\tfor _, d := range r.deps {\n\t\t\tf := getFeature(s.features, d)\n\t\t\tif f == nil {\n\t\t\t\tfoundAll = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif foundAll {\n\t\t\tavailableResolution = append(availableResolution, r)\n\t\t} else {\n\t\t\tpending = append(pending, r)\n\t\t}\n\t}\n\ts.pendingResolutions = pending\n\n\tvar pendingOptional []resolution\n\tfor _, r := range s.pendingOptionalResolutions {\n\t\tfoundAll := true\n\t\tfor _, d := range r.deps {\n\t\t\tf := getFeature(s.features, d)\n\t\t\tif f == nil {\n\t\t\t\tfoundAll = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif foundAll {\n\t\t\tavailableResolution = append(availableResolution, r)\n\t\t} else {\n\t\t\tpendingOptional = append(pendingOptional, r)\n\t\t}\n\t}\n\ts.pendingOptionalResolutions = pendingOptional\n\ts.resolveLock.Unlock()\n\n\tvar err error\n\tfor _, r := range availableResolution {\n\t\terr = r.callbackResolution(s.features) // only return the last error for now\n\t}\n\treturn err\n}\n\n// GetFeature returns a feature of the given type, or nil if such feature is not registered.\nfunc (s *Instance) GetFeature(featureType interface{}) features.Feature {\n\treturn getFeature(s.features, reflect.TypeOf(featureType))\n}\n\n// Start starts the Xray instance, including all registered features. When Start returns error, the state of the instance is unknown.\n// A Xray instance can be started only once. Upon closing, the instance is not guaranteed to start again.\n//\n// xray:api:stable\nfunc (s *Instance) Start() error {\n\ts.statusLock.Lock()\n\tdefer s.statusLock.Unlock()\n\n\ts.running = true\n\tfor _, f := range s.features {\n\t\tif err := f.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terrors.LogWarning(s.ctx, \"Xray \", Version(), \" started\")\n\n\treturn nil\n}\n"
  },
  {
    "path": "core/xray_test.go",
    "content": "package core_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t. \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/dns/localdns\"\n\t_ \"github.com/xtls/xray-core/main/distro/all\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestXrayDependency(t *testing.T) {\n\tinstance := new(Instance)\n\n\twait := make(chan bool, 1)\n\tinstance.RequireFeatures(func(d dns.Client) {\n\t\tif d == nil {\n\t\t\tt.Error(\"expected dns client fulfilled, but actually nil\")\n\t\t}\n\t\twait <- true\n\t}, false)\n\tinstance.AddFeature(localdns.New())\n\t<-wait\n}\n\nfunc TestXrayClose(t *testing.T) {\n\tport := tcp.PickPort()\n\n\tuserID := uuid.New()\n\tconfig := &Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t\tInbound: []*InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{\n\t\t\t\t\t\tRange: []*net.PortRange{net.SinglePortRange(port)},\n\t\t\t\t\t},\n\t\t\t\t\tListen: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:     uint32(0),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(0),\n\t\t\t\t\t\tUser:  &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tcfgBytes, err := proto.Marshal(config)\n\tcommon.Must(err)\n\n\tserver, err := StartInstance(\"protobuf\", cfgBytes)\n\tcommon.Must(err)\n\tserver.Close()\n}\n"
  },
  {
    "path": "features/dns/client.go",
    "content": "package dns\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/features\"\n)\n\n// IPOption is an object for IP query options.\ntype IPOption struct {\n\tIPv4Enable bool\n\tIPv6Enable bool\n\tFakeEnable bool\n}\n\n// Client is a Xray feature for querying DNS information.\n//\n// xray:api:stable\ntype Client interface {\n\tfeatures.Feature\n\n\t// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.\n\tLookupIP(domain string, option IPOption) ([]net.IP, uint32, error)\n}\n\n// ClientType returns the type of Client interface. Can be used for implementing common.HasType.\n//\n// xray:api:beta\nfunc ClientType() interface{} {\n\treturn (*Client)(nil)\n}\n\n// ErrEmptyResponse indicates that DNS query succeeded but no answer was returned.\nvar ErrEmptyResponse = errors.New(\"empty response\")\n\nconst DefaultTTL = 300\n\ntype RCodeError uint16\n\nfunc (e RCodeError) Error() string {\n\treturn serial.Concat(\"rcode: \", uint16(e))\n}\n\nfunc (RCodeError) IP() net.IP {\n\tpanic(\"Calling IP() on a RCodeError.\")\n}\n\nfunc (RCodeError) Domain() string {\n\tpanic(\"Calling Domain() on a RCodeError.\")\n}\n\nfunc (RCodeError) Family() net.AddressFamily {\n\tpanic(\"Calling Family() on a RCodeError.\")\n}\n\nfunc (e RCodeError) String() string {\n\treturn e.Error()\n}\n\nvar _ net.Address = (*RCodeError)(nil)\n\nfunc RCodeFromError(err error) uint16 {\n\tif err == nil {\n\t\treturn 0\n\t}\n\tcause := errors.Cause(err)\n\tif r, ok := cause.(RCodeError); ok {\n\t\treturn uint16(r)\n\t}\n\treturn 0\n}\n"
  },
  {
    "path": "features/dns/fakedns.go",
    "content": "package dns\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features\"\n)\n\ntype FakeDNSEngine interface {\n\tfeatures.Feature\n\tGetFakeIPForDomain(domain string) []net.Address\n\tGetDomainFromFakeDNS(ip net.Address) string\n}\n\nvar (\n\tFakeIPv4Pool = \"198.18.0.0/15\"\n\tFakeIPv6Pool = \"fc00::/18\"\n)\n\ntype FakeDNSEngineRev0 interface {\n\tFakeDNSEngine\n\tIsIPInIPPool(ip net.Address) bool\n\tGetFakeIPForDomain3(domain string, IPv4, IPv6 bool) []net.Address\n}\n"
  },
  {
    "path": "features/dns/localdns/client.go",
    "content": "package localdns\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\n// Client is an implementation of dns.Client, which queries localhost for DNS.\ntype Client struct{}\n\n// Type implements common.HasType.\nfunc (*Client) Type() interface{} {\n\treturn dns.ClientType()\n}\n\n// Start implements common.Runnable.\nfunc (*Client) Start() error { return nil }\n\n// Close implements common.Closable.\nfunc (*Client) Close() error { return nil }\n\n// LookupIP implements Client.\nfunc (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) {\n\tips, err := net.LookupIP(host)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\tparsedIPs := make([]net.IP, 0, len(ips))\n\tipv4 := make([]net.IP, 0, len(ips))\n\tipv6 := make([]net.IP, 0, len(ips))\n\tfor _, ip := range ips {\n\t\tparsed := net.IPAddress(ip)\n\t\tif parsed == nil {\n\t\t\tcontinue\n\t\t}\n\t\tparsedIP := parsed.IP()\n\t\tparsedIPs = append(parsedIPs, parsedIP)\n\n\t\tif len(parsedIP) == net.IPv4len {\n\t\t\tipv4 = append(ipv4, parsedIP)\n\t\t} else {\n\t\t\tipv6 = append(ipv6, parsedIP)\n\t\t}\n\t}\n\n\tswitch {\n\tcase option.IPv4Enable && option.IPv6Enable:\n\t\tif len(parsedIPs) > 0 {\n\t\t\treturn parsedIPs, dns.DefaultTTL, nil\n\t\t}\n\tcase option.IPv4Enable:\n\t\tif len(ipv4) > 0 {\n\t\t\treturn ipv4, dns.DefaultTTL, nil\n\t\t}\n\tcase option.IPv6Enable:\n\t\tif len(ipv6) > 0 {\n\t\t\treturn ipv6, dns.DefaultTTL, nil\n\t\t}\n\t}\n\treturn nil, 0, dns.ErrEmptyResponse\n}\n\n// New create a new dns.Client that queries localhost for DNS.\nfunc New() *Client {\n\treturn &Client{}\n}\n"
  },
  {
    "path": "features/extension/contextreceiver.go",
    "content": "package extension\n\nimport \"context\"\n\ntype ContextReceiver interface {\n\tInjectContext(ctx context.Context)\n}\n"
  },
  {
    "path": "features/extension/observatory.go",
    "content": "package extension\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/features\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype Observatory interface {\n\tfeatures.Feature\n\n\tGetObservation(ctx context.Context) (proto.Message, error)\n}\n\nfunc ObservatoryType() interface{} {\n\treturn (*Observatory)(nil)\n}\n"
  },
  {
    "path": "features/feature.go",
    "content": "package features\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n)\n\n// Feature is the interface for Xray features. All features must implement this interface.\n// All existing features have an implementation in app directory. These features can be replaced by third-party ones.\ntype Feature interface {\n\tcommon.HasType\n\tcommon.Runnable\n}\n"
  },
  {
    "path": "features/inbound/inbound.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/features\"\n)\n\n// Handler is the interface for handlers that process inbound connections.\n//\n// xray:api:stable\ntype Handler interface {\n\tcommon.Runnable\n\t// The tag of this handler.\n\tTag() string\n\t// Returns the active receiver settings.\n\tReceiverSettings() *serial.TypedMessage\n\t// Returns the active proxy settings.\n\tProxySettings() *serial.TypedMessage\n}\n\n// Manager is a feature that manages InboundHandlers.\n//\n// xray:api:stable\ntype Manager interface {\n\tfeatures.Feature\n\t// GetHandler returns an InboundHandler for the given tag.\n\tGetHandler(ctx context.Context, tag string) (Handler, error)\n\t// AddHandler adds the given handler into this Manager.\n\tAddHandler(ctx context.Context, handler Handler) error\n\n\t// RemoveHandler removes a handler from Manager.\n\tRemoveHandler(ctx context.Context, tag string) error\n\n\t// ListHandlers returns a list of inbound.Handler.\n\tListHandlers(ctx context.Context) []Handler\n}\n\n// ManagerType returns the type of Manager interface. Can be used for implementing common.HasType.\n//\n// xray:api:stable\nfunc ManagerType() interface{} {\n\treturn (*Manager)(nil)\n}\n"
  },
  {
    "path": "features/outbound/outbound.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/features\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\n// Handler is the interface for handlers that process outbound connections.\n//\n// xray:api:stable\ntype Handler interface {\n\tcommon.Runnable\n\tTag() string\n\tDispatch(ctx context.Context, link *transport.Link)\n\tSenderSettings() *serial.TypedMessage\n\tProxySettings() *serial.TypedMessage\n}\n\ntype HandlerSelector interface {\n\tSelect([]string) []string\n}\n\n// Manager is a feature that manages outbound.Handlers.\n//\n// xray:api:stable\ntype Manager interface {\n\tfeatures.Feature\n\t// GetHandler returns an outbound.Handler for the given tag.\n\tGetHandler(tag string) Handler\n\t// GetDefaultHandler returns the default outbound.Handler. It is usually the first outbound.Handler specified in the configuration.\n\tGetDefaultHandler() Handler\n\t// AddHandler adds a handler into this outbound.Manager.\n\tAddHandler(ctx context.Context, handler Handler) error\n\n\t// RemoveHandler removes a handler from outbound.Manager.\n\tRemoveHandler(ctx context.Context, tag string) error\n\n\t// ListHandlers returns a list of outbound.Handler.\n\tListHandlers(ctx context.Context) []Handler\n}\n\n// ManagerType returns the type of Manager interface. Can be used to implement common.HasType.\n//\n// xray:api:stable\nfunc ManagerType() interface{} {\n\treturn (*Manager)(nil)\n}\n"
  },
  {
    "path": "features/policy/default.go",
    "content": "package policy\n\nimport (\n\t\"time\"\n)\n\n// DefaultManager is the implementation of the Manager.\ntype DefaultManager struct{}\n\n// Type implements common.HasType.\nfunc (DefaultManager) Type() interface{} {\n\treturn ManagerType()\n}\n\n// ForLevel implements Manager.\nfunc (DefaultManager) ForLevel(level uint32) Session {\n\tp := SessionDefault()\n\tif level == 1 {\n\t\tp.Timeouts.ConnectionIdle = time.Second * 600\n\t}\n\treturn p\n}\n\n// ForSystem implements Manager.\nfunc (DefaultManager) ForSystem() System {\n\treturn System{}\n}\n\n// Start implements common.Runnable.\nfunc (DefaultManager) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (DefaultManager) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "features/policy/policy.go",
    "content": "package policy\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/features\"\n)\n\n// Timeout contains limits for connection timeout.\ntype Timeout struct {\n\t// Timeout for handshake phase in a connection.\n\tHandshake time.Duration\n\t// Timeout for connection being idle, i.e., there is no egress or ingress traffic in this connection.\n\tConnectionIdle time.Duration\n\t// Timeout for an uplink only connection, i.e., the downlink of the connection has been closed.\n\tUplinkOnly time.Duration\n\t// Timeout for an downlink only connection, i.e., the uplink of the connection has been closed.\n\tDownlinkOnly time.Duration\n}\n\n// Stats contains settings for stats counters.\ntype Stats struct {\n\t// Whether or not to enable stat counter for user uplink traffic.\n\tUserUplink bool\n\t// Whether or not to enable stat counter for user downlink traffic.\n\tUserDownlink bool\n\t// Whether or not to enable online map for user.\n\tUserOnline bool\n}\n\n// Buffer contains settings for internal buffer.\ntype Buffer struct {\n\t// Size of buffer per connection, in bytes. -1 for unlimited buffer.\n\tPerConnection int32\n}\n\n// SystemStats contains stat policy settings on system level.\ntype SystemStats struct {\n\t// Whether or not to enable stat counter for uplink traffic in inbound handlers.\n\tInboundUplink bool\n\t// Whether or not to enable stat counter for downlink traffic in inbound handlers.\n\tInboundDownlink bool\n\t// Whether or not to enable stat counter for uplink traffic in outbound handlers.\n\tOutboundUplink bool\n\t// Whether or not to enable stat counter for downlink traffic in outbound handlers.\n\tOutboundDownlink bool\n}\n\n// System contains policy settings at system level.\ntype System struct {\n\tStats  SystemStats\n\tBuffer Buffer\n}\n\n// Session is session based settings for controlling Xray requests. It contains various settings (or limits) that may differ for different users in the context.\ntype Session struct {\n\tTimeouts Timeout // Timeout settings\n\tStats    Stats\n\tBuffer   Buffer\n}\n\n// Manager is a feature that provides Policy for the given user by its id or level.\n//\n// xray:api:stable\ntype Manager interface {\n\tfeatures.Feature\n\n\t// ForLevel returns the Session policy for the given user level.\n\tForLevel(level uint32) Session\n\n\t// ForSystem returns the System policy for Xray system.\n\tForSystem() System\n}\n\n// ManagerType returns the type of Manager interface. Can be used to implement common.HasType.\n//\n// xray:api:stable\nfunc ManagerType() interface{} {\n\treturn (*Manager)(nil)\n}\n\nvar defaultBufferSize int32\n\nfunc init() {\n\tconst defaultValue = -17\n\tsize := platform.NewEnvFlag(platform.BufferSize).GetValueAsInt(defaultValue)\n\n\tswitch size {\n\tcase 0:\n\t\tdefaultBufferSize = -1 // For pipe to use unlimited size\n\tcase defaultValue: // Env flag not defined. Use default values per CPU-arch.\n\t\tswitch runtime.GOARCH {\n\t\tcase \"arm\", \"mips\", \"mipsle\":\n\t\t\tdefaultBufferSize = 0\n\t\tcase \"arm64\", \"mips64\", \"mips64le\":\n\t\t\tdefaultBufferSize = 4 * 1024 // 4k cache for low-end devices\n\t\tdefault:\n\t\t\tdefaultBufferSize = 512 * 1024\n\t\t}\n\tdefault:\n\t\tdefaultBufferSize = int32(size) * 1024 * 1024\n\t}\n}\n\nfunc defaultBufferPolicy() Buffer {\n\treturn Buffer{\n\t\tPerConnection: defaultBufferSize,\n\t}\n}\n\n// SessionDefault returns the Policy when user is not specified.\nfunc SessionDefault() Session {\n\treturn Session{\n\t\tTimeouts: Timeout{\n\t\t\t// Align Handshake timeout with nginx client_header_timeout\n\t\t\t// So that this value will not indicate server identity\n\t\t\tHandshake:      time.Second * 60,\n\t\t\tConnectionIdle: time.Second * 300,\n\t\t\tUplinkOnly:     time.Second * 1,\n\t\t\tDownlinkOnly:   time.Second * 1,\n\t\t},\n\t\tStats: Stats{\n\t\t\tUserUplink:   false,\n\t\t\tUserDownlink: false,\n\t\t\tUserOnline:   false,\n\t\t},\n\t\tBuffer: defaultBufferPolicy(),\n\t}\n}\n\ntype policyKey int32\n\nconst (\n\tbufferPolicyKey policyKey = 0\n)\n\nfunc ContextWithBufferPolicy(ctx context.Context, p Buffer) context.Context {\n\treturn context.WithValue(ctx, bufferPolicyKey, p)\n}\n\nfunc BufferPolicyFromContext(ctx context.Context) Buffer {\n\tpPolicy := ctx.Value(bufferPolicyKey)\n\tif pPolicy == nil {\n\t\treturn defaultBufferPolicy()\n\t}\n\treturn pPolicy.(Buffer)\n}\n"
  },
  {
    "path": "features/routing/balancer.go",
    "content": "package routing\n\ntype BalancerOverrider interface {\n\tSetOverrideTarget(tag, target string) error\n\tGetOverrideTarget(tag string) (string, error)\n}\n\ntype BalancerPrincipleTarget interface {\n\tGetPrincipleTarget(tag string) ([]string, error)\n}\n"
  },
  {
    "path": "features/routing/context.go",
    "content": "package routing\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// Context is a feature to store connection information for routing.\n//\n// xray:api:stable\ntype Context interface {\n\t// GetInboundTag returns the tag of the inbound the connection was from.\n\tGetInboundTag() string\n\n\t// GetSourceIPs returns the source IPs bound to the connection.\n\tGetSourceIPs() []net.IP\n\n\t// GetSourcePort returns the source port of the connection.\n\tGetSourcePort() net.Port\n\n\t// GetTargetIPs returns the target IP of the connection or resolved IPs of target domain.\n\tGetTargetIPs() []net.IP\n\n\t// GetTargetPort returns the target port of the connection.\n\tGetTargetPort() net.Port\n\n\t// GetLocalIPs returns the local IPs bound to the connection.\n\tGetLocalIPs() []net.IP\n\n\t// GetLocalPort returns the local port of the connection.\n\tGetLocalPort() net.Port\n\n\t// GetTargetDomain returns the target domain of the connection, if exists.\n\tGetTargetDomain() string\n\n\t// GetNetwork returns the network type of the connection.\n\tGetNetwork() net.Network\n\n\t// GetProtocol returns the protocol from the connection content, if sniffed out.\n\tGetProtocol() string\n\n\t// GetUser returns the user email from the connection content, if exists.\n\tGetUser() string\n\n\t// GetVlessRoute returns the user-sent VLESS UUID's 7th<<8 | 8th bytes, if exists.\n\tGetVlessRoute() net.Port\n\n\t// GetAttributes returns extra attributes from the conneciont content.\n\tGetAttributes() map[string]string\n\n\t// GetSkipDNSResolve returns a flag switch for weather skip dns resolve during route pick.\n\tGetSkipDNSResolve() bool\n}\n"
  },
  {
    "path": "features/routing/dispatcher.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\n// Dispatcher is a feature that dispatches inbound requests to outbound handlers based on rules.\n// Dispatcher is required to be registered in a Xray instance to make Xray function properly.\n//\n// xray:api:stable\ntype Dispatcher interface {\n\tfeatures.Feature\n\n\t// Dispatch returns a Ray for transporting data for the given request.\n\tDispatch(ctx context.Context, dest net.Destination) (*transport.Link, error)\n\tDispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error\n}\n\n// DispatcherType returns the type of Dispatcher interface. Can be used to implement common.HasType.\n//\n// xray:api:stable\nfunc DispatcherType() interface{} {\n\treturn (*Dispatcher)(nil)\n}\n"
  },
  {
    "path": "features/routing/dns/context.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\n// ResolvableContext is an implementation of routing.Context, with domain resolving capability.\ntype ResolvableContext struct {\n\trouting.Context\n\tdnsClient dns.Client\n\tcacheIPs  []net.IP\n\thasError  bool\n}\n\n// GetTargetIPs overrides original routing.Context's implementation.\nfunc (ctx *ResolvableContext) GetTargetIPs() []net.IP {\n\tif len(ctx.cacheIPs) > 0 {\n\t\treturn ctx.cacheIPs\n\t}\n\n\tif ctx.hasError {\n\t\treturn nil\n\t}\n\n\tif domain := ctx.GetTargetDomain(); len(domain) != 0 {\n\t\tips, _, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: false,\n\t\t})\n\t\tif err == nil {\n\t\t\tctx.cacheIPs = ips\n\t\t\treturn ips\n\t\t}\n\t\terrors.LogInfoInner(context.Background(), err, \"resolve ip for \", domain)\n\t}\n\n\tif ips := ctx.Context.GetTargetIPs(); len(ips) != 0 {\n\t\tctx.cacheIPs = ips\n\t\treturn ips\n\t}\n\n\tctx.hasError = true\n\treturn nil\n}\n\n// ContextWithDNSClient creates a new routing context with domain resolving capability.\n// Resolved domain IPs can be retrieved by GetTargetIPs().\nfunc ContextWithDNSClient(ctx routing.Context, client dns.Client) routing.Context {\n\treturn &ResolvableContext{Context: ctx, dnsClient: client}\n}\n"
  },
  {
    "path": "features/routing/router.go",
    "content": "package routing\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/features\"\n)\n\n// Router is a feature to choose an outbound tag for the given request.\n//\n// xray:api:stable\ntype Router interface {\n\tfeatures.Feature\n\n\t// PickRoute returns a route decision based on the given routing context.\n\tPickRoute(ctx Context) (Route, error)\n\tAddRule(config *serial.TypedMessage, shouldAppend bool) error\n\tRemoveRule(tag string) error\n\tListRule() []Route\n}\n\n// Route is the routing result of Router feature.\n//\n// xray:api:stable\ntype Route interface {\n\t// A Route is also a routing context.\n\tContext\n\n\t// GetOutboundGroupTags returns the detoured outbound group tags in sequence before a final outbound is chosen.\n\tGetOutboundGroupTags() []string\n\n\t// GetOutboundTag returns the tag of the outbound the connection was dispatched to.\n\tGetOutboundTag() string\n\n\t// GetRuleTag returns the matching rule tag for debugging if exists\n\tGetRuleTag() string\n}\n\n// RouterType return the type of Router interface. Can be used to implement common.HasType.\n//\n// xray:api:stable\nfunc RouterType() interface{} {\n\treturn (*Router)(nil)\n}\n\n// DefaultRouter is an implementation of Router, which always returns ErrNoClue for routing decisions.\ntype DefaultRouter struct{}\n\n// Type implements common.HasType.\nfunc (DefaultRouter) Type() interface{} {\n\treturn RouterType()\n}\n\n// PickRoute implements Router.\nfunc (DefaultRouter) PickRoute(ctx Context) (Route, error) {\n\treturn nil, common.ErrNoClue\n}\n\n// AddRule implements Router.\nfunc (DefaultRouter) AddRule(config *serial.TypedMessage, shouldAppend bool) error {\n\treturn common.ErrNoClue\n}\n\n// RemoveRule implements Router.\nfunc (DefaultRouter) RemoveRule(tag string) error {\n\treturn common.ErrNoClue\n}\n\n// ListRule implements Router.\nfunc (DefaultRouter) ListRule() []Route {\n\treturn nil\n}\n\n// Start implements common.Runnable.\nfunc (DefaultRouter) Start() error {\n\treturn nil\n}\n\n// Close implements common.Closable.\nfunc (DefaultRouter) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "features/routing/session/context.go",
    "content": "package session\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\n// Context is an implementation of routing.Context, which is a wrapper of context.context with session info.\ntype Context struct {\n\tInbound  *session.Inbound\n\tOutbound *session.Outbound\n\tContent  *session.Content\n}\n\n// GetInboundTag implements routing.Context.\nfunc (ctx *Context) GetInboundTag() string {\n\tif ctx.Inbound == nil {\n\t\treturn \"\"\n\t}\n\treturn ctx.Inbound.Tag\n}\n\n// GetSourceIPs implements routing.Context.\nfunc (ctx *Context) GetSourceIPs() []net.IP {\n\tif ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() {\n\t\treturn nil\n\t}\n\n\tif ctx.Inbound.Source.Address.Family().IsIP() {\n\t\treturn []net.IP{ctx.Inbound.Source.Address.IP()}\n\t}\n\n\treturn nil\n\n}\n\n// GetSourcePort implements routing.Context.\nfunc (ctx *Context) GetSourcePort() net.Port {\n\tif ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() {\n\t\treturn 0\n\t}\n\treturn ctx.Inbound.Source.Port\n}\n\n// GetTargetIPs implements routing.Context.\nfunc (ctx *Context) GetTargetIPs() []net.IP {\n\tif ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {\n\t\treturn nil\n\t}\n\n\tif ctx.Outbound.Target.Address.Family().IsIP() {\n\t\treturn []net.IP{ctx.Outbound.Target.Address.IP()}\n\t}\n\n\treturn nil\n}\n\n// GetTargetPort implements routing.Context.\nfunc (ctx *Context) GetTargetPort() net.Port {\n\tif ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {\n\t\treturn 0\n\t}\n\treturn ctx.Outbound.Target.Port\n}\n\n// GetLocalIPs implements routing.Context.\nfunc (ctx *Context) GetLocalIPs() []net.IP {\n\tif ctx.Inbound == nil || !ctx.Inbound.Local.IsValid() {\n\t\treturn nil\n\t}\n\n\tif ctx.Inbound.Local.Address.Family().IsIP() {\n\t\treturn []net.IP{ctx.Inbound.Local.Address.IP()}\n\t}\n\n\treturn nil\n}\n\n// GetLocalPort implements routing.Context.\nfunc (ctx *Context) GetLocalPort() net.Port {\n\tif ctx.Inbound == nil || !ctx.Inbound.Local.IsValid() {\n\t\treturn 0\n\t}\n\treturn ctx.Inbound.Local.Port\n}\n\n// GetTargetDomain implements routing.Context.\nfunc (ctx *Context) GetTargetDomain() string {\n\tif ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() {\n\t\treturn \"\"\n\t}\n\tdest := ctx.Outbound.RouteTarget\n\tif dest.IsValid() && dest.Address.Family().IsDomain() {\n\t\treturn dest.Address.Domain()\n\t}\n\n\tdest = ctx.Outbound.Target\n\tif !dest.Address.Family().IsDomain() {\n\t\treturn \"\"\n\t}\n\treturn dest.Address.Domain()\n}\n\n// GetNetwork implements routing.Context.\nfunc (ctx *Context) GetNetwork() net.Network {\n\tif ctx.Outbound == nil {\n\t\treturn net.Network_Unknown\n\t}\n\treturn ctx.Outbound.Target.Network\n}\n\n// GetProtocol implements routing.Context.\nfunc (ctx *Context) GetProtocol() string {\n\tif ctx.Content == nil {\n\t\treturn \"\"\n\t}\n\treturn ctx.Content.Protocol\n}\n\n// GetUser implements routing.Context.\nfunc (ctx *Context) GetUser() string {\n\tif ctx.Inbound == nil || ctx.Inbound.User == nil {\n\t\treturn \"\"\n\t}\n\treturn ctx.Inbound.User.Email\n}\n\n// GetVlessRoute implements routing.Context.\nfunc (ctx *Context) GetVlessRoute() net.Port {\n\tif ctx.Inbound == nil {\n\t\treturn 0\n\t}\n\treturn ctx.Inbound.VlessRoute\n}\n\n// GetAttributes implements routing.Context.\nfunc (ctx *Context) GetAttributes() map[string]string {\n\tif ctx.Content == nil {\n\t\treturn nil\n\t}\n\treturn ctx.Content.Attributes\n}\n\n// GetSkipDNSResolve implements routing.Context.\nfunc (ctx *Context) GetSkipDNSResolve() bool {\n\tif ctx.Content == nil {\n\t\treturn false\n\t}\n\treturn ctx.Content.SkipDNSResolve\n}\n\n// AsRoutingContext creates a context from context.context with session info.\nfunc AsRoutingContext(ctx context.Context) routing.Context {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\treturn &Context{\n\t\tInbound:  session.InboundFromContext(ctx),\n\t\tOutbound: ob,\n\t\tContent:  session.ContentFromContext(ctx),\n\t}\n}\n"
  },
  {
    "path": "features/stats/stats.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features\"\n)\n\n// Counter is the interface for stats counters.\n//\n// xray:api:stable\ntype Counter interface {\n\t// Value is the current value of the counter.\n\tValue() int64\n\t// Set sets a new value to the counter, and returns the previous one.\n\tSet(int64) int64\n\t// Add adds a value to the current counter value, and returns the previous value.\n\tAdd(int64) int64\n}\n\n// OnlineMap is the interface for stats.\n//\n// xray:api:stable\ntype OnlineMap interface {\n\t// Count returns the number of unique online IPs.\n\tCount() int\n\t// AddIP increments the reference count for the given IP.\n\tAddIP(string)\n\t// RemoveIP decrements the reference count for the given IP. Deletes at zero.\n\tRemoveIP(string)\n\t// List returns all currently online IPs.\n\tList() []string\n\t// IPTimeMap returns a snapshot copy of IPs to their last-seen times.\n\tIPTimeMap() map[string]time.Time\n}\n\n// Channel is the interface for stats channel.\n//\n// xray:api:stable\ntype Channel interface {\n\t// Runnable implies that Channel is a runnable unit.\n\tcommon.Runnable\n\t// Publish broadcasts a message through the channel with a controlling context.\n\tPublish(context.Context, interface{})\n\t// Subscribers returns all subscribers.\n\tSubscribers() []chan interface{}\n\t// Subscribe registers for listening to channel stream and returns a new listener channel.\n\tSubscribe() (chan interface{}, error)\n\t// Unsubscribe unregisters a listener channel from current Channel object.\n\tUnsubscribe(chan interface{}) error\n}\n\n// SubscribeRunnableChannel subscribes the channel and starts it if there is first subscriber coming.\nfunc SubscribeRunnableChannel(c Channel) (chan interface{}, error) {\n\tif len(c.Subscribers()) == 0 {\n\t\tif err := c.Start(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn c.Subscribe()\n}\n\n// UnsubscribeClosableChannel unsubscribes the channel and close it if there is no more subscriber.\nfunc UnsubscribeClosableChannel(c Channel, sub chan interface{}) error {\n\tif err := c.Unsubscribe(sub); err != nil {\n\t\treturn err\n\t}\n\tif len(c.Subscribers()) == 0 {\n\t\treturn c.Close()\n\t}\n\treturn nil\n}\n\n// Manager is the interface for stats manager.\n//\n// xray:api:stable\ntype Manager interface {\n\tfeatures.Feature\n\n\t// RegisterCounter registers a new counter to the manager. The identifier string must not be empty, and unique among other counters.\n\tRegisterCounter(string) (Counter, error)\n\t// UnregisterCounter unregisters a counter from the manager by its identifier.\n\tUnregisterCounter(string) error\n\t// GetCounter returns a counter by its identifier.\n\tGetCounter(string) Counter\n\n\t// RegisterOnlineMap registers a new onlinemap to the manager. The identifier string must not be empty, and unique among other onlinemaps.\n\tRegisterOnlineMap(string) (OnlineMap, error)\n\t// UnregisterOnlineMap unregisters a onlinemap from the manager by its identifier.\n\tUnregisterOnlineMap(string) error\n\t// GetOnlineMap returns a onlinemap by its identifier.\n\tGetOnlineMap(string) OnlineMap\n\n\t// RegisterChannel registers a new channel to the manager. The identifier string must not be empty, and unique among other channels.\n\tRegisterChannel(string) (Channel, error)\n\t// UnregisterChannel unregisters a channel from the manager by its identifier.\n\tUnregisterChannel(string) error\n\t// GetChannel returns a channel by its identifier.\n\tGetChannel(string) Channel\n\n\t// GetAllOnlineUsers returns all online users from all OnlineMaps.\n\tGetAllOnlineUsers() []string\n}\n\n// GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter.\nfunc GetOrRegisterCounter(m Manager, name string) (Counter, error) {\n\tcounter := m.GetCounter(name)\n\tif counter != nil {\n\t\treturn counter, nil\n\t}\n\n\treturn m.RegisterCounter(name)\n}\n\n// GetOrRegisterOnlineMap tries to get the OnlineMap first. If not exist, it then tries to create a new onlinemap.\nfunc GetOrRegisterOnlineMap(m Manager, name string) (OnlineMap, error) {\n\tonlineMap := m.GetOnlineMap(name)\n\tif onlineMap != nil {\n\t\treturn onlineMap, nil\n\t}\n\n\treturn m.RegisterOnlineMap(name)\n}\n\n// GetOrRegisterChannel tries to get the StatChannel first. If not exist, it then tries to create a new channel.\nfunc GetOrRegisterChannel(m Manager, name string) (Channel, error) {\n\tchannel := m.GetChannel(name)\n\tif channel != nil {\n\t\treturn channel, nil\n\t}\n\n\treturn m.RegisterChannel(name)\n}\n\n// ManagerType returns the type of Manager interface. Can be used to implement common.HasType.\n//\n// xray:api:stable\nfunc ManagerType() interface{} {\n\treturn (*Manager)(nil)\n}\n\n// NoopManager is an implementation of Manager, which doesn't has actual functionalities.\ntype NoopManager struct{}\n\n// Type implements common.HasType.\nfunc (NoopManager) Type() interface{} {\n\treturn ManagerType()\n}\n\n// RegisterCounter implements Manager.\nfunc (NoopManager) RegisterCounter(string) (Counter, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// UnregisterCounter implements Manager.\nfunc (NoopManager) UnregisterCounter(string) error {\n\treturn nil\n}\n\n// GetCounter implements Manager.\nfunc (NoopManager) GetCounter(string) Counter {\n\treturn nil\n}\n\n// RegisterOnlineMap implements Manager.\nfunc (NoopManager) RegisterOnlineMap(string) (OnlineMap, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// UnregisterOnlineMap implements Manager.\nfunc (NoopManager) UnregisterOnlineMap(string) error {\n\treturn nil\n}\n\n// GetOnlineMap implements Manager.\nfunc (NoopManager) GetOnlineMap(string) OnlineMap {\n\treturn nil\n}\n\n// RegisterChannel implements Manager.\nfunc (NoopManager) RegisterChannel(string) (Channel, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// UnregisterChannel implements Manager.\nfunc (NoopManager) UnregisterChannel(string) error {\n\treturn nil\n}\n\n// GetChannel implements Manager.\nfunc (NoopManager) GetChannel(string) Channel {\n\treturn nil\n}\n\n// GetAllOnlineUsers implements Manager.\nfunc (NoopManager) GetAllOnlineUsers() []string {\n\treturn nil\n}\n\n// Start implements common.Runnable.\nfunc (NoopManager) Start() error { return nil }\n\n// Close implements common.Closable.\nfunc (NoopManager) Close() error { return nil }\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/xtls/xray-core\n\ngo 1.26\n\nrequire (\n\tgithub.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22\n\tgithub.com/cloudflare/circl v1.6.3\n\tgithub.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344\n\tgithub.com/golang/mock v1.7.0-rc.1\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/gorilla/websocket v1.5.3\n\tgithub.com/klauspost/cpuid/v2 v2.3.0\n\tgithub.com/miekg/dns v1.1.72\n\tgithub.com/pelletier/go-toml v1.9.5\n\tgithub.com/pires/go-proxyproto v0.11.0\n\tgithub.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af\n\tgithub.com/sagernet/sing v0.5.1\n\tgithub.com/sagernet/sing-shadowsocks v0.2.7\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/vishvananda/netlink v1.3.1\n\tgithub.com/xtls/reality v0.0.0-20251014195629-e4eec4520535\n\tgo4.org/netipx v0.0.0-20231129151722-fdeea329fbba\n\tgolang.org/x/crypto v0.49.0\n\tgolang.org/x/exp v0.0.0-20240506185415-9bf2ced13842\n\tgolang.org/x/net v0.52.0\n\tgolang.org/x/sync v0.20.0\n\tgolang.org/x/sys v0.42.0\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2\n\tgolang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173\n\tgoogle.golang.org/grpc v1.79.3\n\tgoogle.golang.org/protobuf v1.36.11\n\tgvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0\n\th12.io/socks v1.0.3\n\tlukechampine.com/blake3 v1.4.1\n)\n\nrequire (\n\tgithub.com/andybalholm/brotli v1.0.6 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/google/btree v1.1.2 // indirect\n\tgithub.com/juju/ratelimit v1.0.2 // indirect\n\tgithub.com/klauspost/compress v1.17.4 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/quic-go/qpack v0.6.0 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=\ngithub.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 h1:00ziBGnLWQEcR9LThDwvxOznJJquJ9bYUdmBFnawLMU=\ngithub.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=\ngithub.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=\ngithub.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=\ngithub.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=\ngithub.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=\ngithub.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=\ngithub.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=\ngithub.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=\ngithub.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=\ngithub.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=\ngithub.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=\ngithub.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=\ngithub.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=\ngithub.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=\ngithub.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=\ngithub.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=\ngithub.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=\ngithub.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=\ngithub.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=\ngithub.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=\ngithub.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU=\ngithub.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=\ngo.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\ngolang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=\ngolang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=\ngolang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngolang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=\ngolang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=\ngvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=\nh12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=\nh12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=\nlukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=\nlukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=\n"
  },
  {
    "path": "infra/conf/api.go",
    "content": "package conf\n\nimport (\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/commander\"\n\tloggerservice \"github.com/xtls/xray-core/app/log/command\"\n\tobservatoryservice \"github.com/xtls/xray-core/app/observatory/command\"\n\thandlerservice \"github.com/xtls/xray-core/app/proxyman/command\"\n\trouterservice \"github.com/xtls/xray-core/app/router/command\"\n\tstatsservice \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\ntype APIConfig struct {\n\tTag      string   `json:\"tag\"`\n\tListen   string   `json:\"listen\"`\n\tServices []string `json:\"services\"`\n}\n\nfunc (c *APIConfig) Build() (*commander.Config, error) {\n\tif c.Tag == \"\" {\n\t\treturn nil, errors.New(\"API tag can't be empty.\")\n\t}\n\n\tservices := make([]*serial.TypedMessage, 0, 16)\n\tfor _, s := range c.Services {\n\t\tswitch strings.ToLower(s) {\n\t\tcase \"reflectionservice\":\n\t\t\tservices = append(services, serial.ToTypedMessage(&commander.ReflectionConfig{}))\n\t\tcase \"handlerservice\":\n\t\t\tservices = append(services, serial.ToTypedMessage(&handlerservice.Config{}))\n\t\tcase \"loggerservice\":\n\t\t\tservices = append(services, serial.ToTypedMessage(&loggerservice.Config{}))\n\t\tcase \"statsservice\":\n\t\t\tservices = append(services, serial.ToTypedMessage(&statsservice.Config{}))\n\t\tcase \"observatoryservice\":\n\t\t\tservices = append(services, serial.ToTypedMessage(&observatoryservice.Config{}))\n\t\tcase \"routingservice\":\n\t\t\tservices = append(services, serial.ToTypedMessage(&routerservice.Config{}))\n\t\t}\n\t}\n\n\treturn &commander.Config{\n\t\tTag:     c.Tag,\n\t\tListen:  c.Listen,\n\t\tService: services,\n\t}, nil\n}\n"
  },
  {
    "path": "infra/conf/blackhole.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype NoneResponse struct{}\n\nfunc (*NoneResponse) Build() (proto.Message, error) {\n\treturn new(blackhole.NoneResponse), nil\n}\n\ntype HTTPResponse struct{}\n\nfunc (*HTTPResponse) Build() (proto.Message, error) {\n\treturn new(blackhole.HTTPResponse), nil\n}\n\ntype BlackholeConfig struct {\n\tResponse json.RawMessage `json:\"response\"`\n}\n\nfunc (v *BlackholeConfig) Build() (proto.Message, error) {\n\tconfig := new(blackhole.Config)\n\tif v.Response != nil {\n\t\tresponse, _, err := configLoader.Load(v.Response)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Config: Failed to parse Blackhole response config.\").Base(err)\n\t\t}\n\t\tresponseSettings, err := response.(Buildable).Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.Response = serial.ToTypedMessage(responseSettings)\n\t}\n\n\treturn config, nil\n}\n\nvar configLoader = NewJSONConfigLoader(\n\tConfigCreatorCache{\n\t\t\"none\": func() interface{} { return new(NoneResponse) },\n\t\t\"http\": func() interface{} { return new(HTTPResponse) },\n\t},\n\t\"type\",\n\t\"\")\n"
  },
  {
    "path": "infra/conf/blackhole_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/serial\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n)\n\nfunc TestHTTPResponseJSON(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(BlackholeConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"response\": {\n\t\t\t\t\t\"type\": \"http\"\n\t\t\t\t}\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &blackhole.Config{\n\t\t\t\tResponse: serial.ToTypedMessage(&blackhole.HTTPResponse{}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput:  `{}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &blackhole.Config{},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/buildable.go",
    "content": "package conf\n\nimport \"google.golang.org/protobuf/proto\"\n\ntype Buildable interface {\n\tBuild() (proto.Message, error)\n}\n"
  },
  {
    "path": "infra/conf/cfgcommon/duration/duration.go",
    "content": "package duration\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n)\n\ntype Duration int64\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (d *Duration) MarshalJSON() ([]byte, error) {\n\tdr := time.Duration(*d)\n\treturn json.Marshal(dr.String())\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (d *Duration) UnmarshalJSON(b []byte) error {\n\tvar v interface{}\n\tif err := json.Unmarshal(b, &v); err != nil {\n\t\treturn err\n\t}\n\tswitch value := v.(type) {\n\tcase string:\n\t\tvar err error\n\t\tdr, err := time.ParseDuration(value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*d = Duration(dr)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid duration: %v\", v)\n\t}\n}\n"
  },
  {
    "path": "infra/conf/cfgcommon/duration/duration_test.go",
    "content": "package duration_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/infra/conf/cfgcommon/duration\"\n)\n\ntype testWithDuration struct {\n\tDuration duration.Duration\n}\n\nfunc TestDurationJSON(t *testing.T) {\n\texpected := &testWithDuration{\n\t\tDuration: duration.Duration(time.Hour),\n\t}\n\tdata, err := json.Marshal(expected)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tactual := &testWithDuration{}\n\terr = json.Unmarshal(data, &actual)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\tif actual.Duration != expected.Duration {\n\t\tt.Errorf(\"expected: %s, actual: %s\", time.Duration(expected.Duration), time.Duration(actual.Duration))\n\t}\n}\n"
  },
  {
    "path": "infra/conf/common.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\ntype StringList []string\n\nfunc NewStringList(raw []string) *StringList {\n\tlist := StringList(raw)\n\treturn &list\n}\n\nfunc (v StringList) Len() int {\n\treturn len(v)\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (v *StringList) UnmarshalJSON(data []byte) error {\n\tvar strarray []string\n\tif err := json.Unmarshal(data, &strarray); err == nil {\n\t\t*v = *NewStringList(strarray)\n\t\treturn nil\n\t}\n\n\tvar rawstr string\n\tif err := json.Unmarshal(data, &rawstr); err == nil {\n\t\tstrlist := strings.Split(rawstr, \",\")\n\t\t*v = *NewStringList(strlist)\n\t\treturn nil\n\t}\n\treturn errors.New(\"unknown format of a string list: \" + string(data))\n}\n\ntype Address struct {\n\tnet.Address\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (v *Address) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(v.Address.String())\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (v *Address) UnmarshalJSON(data []byte) error {\n\tvar rawStr string\n\tif err := json.Unmarshal(data, &rawStr); err != nil {\n\t\treturn errors.New(\"invalid address: \", string(data)).Base(err)\n\t}\n\tif strings.HasPrefix(rawStr, \"env:\") {\n\t\trawStr = platform.NewEnvFlag(rawStr[4:]).GetValue(func() string { return \"\" })\n\t}\n\tv.Address = net.ParseAddress(rawStr)\n\n\treturn nil\n}\n\nfunc (v *Address) Build() *net.IPOrDomain {\n\treturn net.NewIPOrDomain(v.Address)\n}\n\ntype Network string\n\nfunc (v Network) Build() net.Network {\n\tswitch strings.ToLower(string(v)) {\n\tcase \"tcp\":\n\t\treturn net.Network_TCP\n\tcase \"udp\":\n\t\treturn net.Network_UDP\n\tcase \"unix\":\n\t\treturn net.Network_UNIX\n\tdefault:\n\t\treturn net.Network_Unknown\n\t}\n}\n\ntype NetworkList []Network\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (v *NetworkList) UnmarshalJSON(data []byte) error {\n\tvar strarray []Network\n\tif err := json.Unmarshal(data, &strarray); err == nil {\n\t\tnl := NetworkList(strarray)\n\t\t*v = nl\n\t\treturn nil\n\t}\n\n\tvar rawstr Network\n\tif err := json.Unmarshal(data, &rawstr); err == nil {\n\t\tstrlist := strings.Split(string(rawstr), \",\")\n\t\tnl := make([]Network, len(strlist))\n\t\tfor idx, network := range strlist {\n\t\t\tnl[idx] = Network(network)\n\t\t}\n\t\t*v = nl\n\t\treturn nil\n\t}\n\treturn errors.New(\"unknown format of a string list: \" + string(data))\n}\n\nfunc (v *NetworkList) Build() []net.Network {\n\tif v == nil {\n\t\treturn []net.Network{net.Network_TCP}\n\t}\n\n\tlist := make([]net.Network, 0, len(*v))\n\tfor _, network := range *v {\n\t\tlist = append(list, network.Build())\n\t}\n\treturn list\n}\n\nfunc parseIntPort(data []byte) (net.Port, error) {\n\tvar intPort uint32\n\terr := json.Unmarshal(data, &intPort)\n\tif err != nil {\n\t\treturn net.Port(0), err\n\t}\n\treturn net.PortFromInt(intPort)\n}\n\nfunc parseStringPort(s string) (net.Port, net.Port, error) {\n\tif strings.HasPrefix(s, \"env:\") {\n\t\ts = platform.NewEnvFlag(s[4:]).GetValue(func() string { return \"\" })\n\t}\n\n\tpair := strings.SplitN(s, \"-\", 2)\n\tif len(pair) == 0 {\n\t\treturn net.Port(0), net.Port(0), errors.New(\"invalid port range: \", s)\n\t}\n\tif len(pair) == 1 {\n\t\tport, err := net.PortFromString(pair[0])\n\t\treturn port, port, err\n\t}\n\n\tfromPort, err := net.PortFromString(pair[0])\n\tif err != nil {\n\t\treturn net.Port(0), net.Port(0), err\n\t}\n\ttoPort, err := net.PortFromString(pair[1])\n\tif err != nil {\n\t\treturn net.Port(0), net.Port(0), err\n\t}\n\treturn fromPort, toPort, nil\n}\n\nfunc parseJSONStringPort(data []byte) (net.Port, net.Port, error) {\n\tvar s string\n\terr := json.Unmarshal(data, &s)\n\tif err != nil {\n\t\treturn net.Port(0), net.Port(0), err\n\t}\n\treturn parseStringPort(s)\n}\n\ntype PortRange struct {\n\tFrom uint32\n\tTo   uint32\n}\n\nfunc (v *PortRange) Build() *net.PortRange {\n\treturn &net.PortRange{\n\t\tFrom: v.From,\n\t\tTo:   v.To,\n\t}\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (v *PortRange) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(v.String())\n}\n\nfunc (port *PortRange) String() string {\n\tif port.From == port.To {\n\t\treturn strconv.Itoa(int(port.From))\n\t} else {\n\t\treturn fmt.Sprintf(\"%d-%d\", port.From, port.To)\n\t}\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (v *PortRange) UnmarshalJSON(data []byte) error {\n\tport, err := parseIntPort(data)\n\tif err == nil {\n\t\tv.From = uint32(port)\n\t\tv.To = uint32(port)\n\t\treturn nil\n\t}\n\n\tfrom, to, err := parseJSONStringPort(data)\n\tif err == nil {\n\t\tv.From = uint32(from)\n\t\tv.To = uint32(to)\n\t\tif v.From > v.To {\n\t\t\treturn errors.New(\"invalid port range \", v.From, \" -> \", v.To)\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"invalid port range: \", string(data))\n}\n\ntype PortList struct {\n\tRange []PortRange\n}\n\nfunc (list *PortList) Build() *net.PortList {\n\tportList := new(net.PortList)\n\tfor _, r := range list.Range {\n\t\tportList.Range = append(portList.Range, r.Build())\n\t}\n\treturn portList\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (v *PortList) MarshalJSON() ([]byte, error) {\n\tportStr := v.String()\n\tport, err := strconv.Atoi(portStr)\n\tif err == nil {\n\t\treturn json.Marshal(port)\n\t} else {\n\t\treturn json.Marshal(portStr)\n\t}\n}\n\nfunc (v PortList) String() string {\n\tports := []string{}\n\tfor _, port := range v.Range {\n\t\tports = append(ports, port.String())\n\t}\n\treturn strings.Join(ports, \",\")\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (list *PortList) UnmarshalJSON(data []byte) error {\n\tvar listStr string\n\tvar number uint32\n\tif err := json.Unmarshal(data, &listStr); err != nil {\n\t\tif err2 := json.Unmarshal(data, &number); err2 != nil {\n\t\t\treturn errors.New(\"invalid port: \", string(data)).Base(err2)\n\t\t}\n\t}\n\trangelist := strings.Split(listStr, \",\")\n\tfor _, rangeStr := range rangelist {\n\t\ttrimmed := strings.TrimSpace(rangeStr)\n\t\tif len(trimmed) > 0 {\n\t\t\tif strings.Contains(trimmed, \"-\") || strings.Contains(trimmed, \"env:\") {\n\t\t\t\tfrom, to, err := parseStringPort(trimmed)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.New(\"invalid port range: \", trimmed).Base(err)\n\t\t\t\t}\n\t\t\t\tlist.Range = append(list.Range, PortRange{From: uint32(from), To: uint32(to)})\n\t\t\t} else {\n\t\t\t\tport, err := parseIntPort([]byte(trimmed))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.New(\"invalid port: \", trimmed).Base(err)\n\t\t\t\t}\n\t\t\t\tlist.Range = append(list.Range, PortRange{From: uint32(port), To: uint32(port)})\n\t\t\t}\n\t\t}\n\t}\n\tif number != 0 {\n\t\tlist.Range = append(list.Range, PortRange{From: number, To: number})\n\t}\n\treturn nil\n}\n\ntype User struct {\n\tEmailString string `json:\"email\"`\n\tLevelByte   byte   `json:\"level\"`\n}\n\nfunc (v *User) Build() *protocol.User {\n\treturn &protocol.User{\n\t\tEmail: v.EmailString,\n\t\tLevel: uint32(v.LevelByte),\n\t}\n}\n\n// Int32Range deserializes from \"1-2\" or 1, so can deserialize from both int and number.\n// Negative integers can be passed as sentinel values, but do not parse as ranges.\n// Value will be exchanged if From > To, use .Left and .Right to get original value if need.\ntype Int32Range struct {\n\tLeft  int32\n\tRight int32\n\tFrom  int32\n\tTo    int32\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (v *Int32Range) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(v.String())\n}\n\nfunc (v Int32Range) String() string {\n\tif v.Left == v.Right {\n\t\treturn strconv.Itoa(int(v.Left))\n\t} else {\n\t\treturn fmt.Sprintf(\"%d-%d\", v.Left, v.Right)\n\t}\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (v *Int32Range) UnmarshalJSON(data []byte) error {\n\tdefer v.ensureOrder()\n\tvar str string\n\tvar rawint int32\n\tif err := json.Unmarshal(data, &str); err == nil {\n\t\tleft, right, err := ParseRangeString(str)\n\t\tif err == nil {\n\t\t\tv.Left, v.Right = int32(left), int32(right)\n\t\t\treturn nil\n\t\t}\n\t} else if err := json.Unmarshal(data, &rawint); err == nil {\n\t\tv.Left = rawint\n\t\tv.Right = rawint\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"Invalid integer range, expected either string of form \\\"1-2\\\" or plain integer.\")\n}\n\n// ensureOrder() gives value to .From & .To and make sure .From < .To\nfunc (r *Int32Range) ensureOrder() {\n\tr.From, r.To = r.Left, r.Right\n\tif r.From > r.To {\n\t\tr.From, r.To = r.To, r.From\n\t}\n}\n\n// \"-114-514\"   →  [\"-114\",\"514\"]\n// \"-1919--810\" →  [\"-1919\",\"-810\"]\nfunc splitFromSecondDash(s string) []string {\n\tparts := strings.SplitN(s, \"-\", 3)\n\tif len(parts) < 3 {\n\t\treturn []string{s}\n\t}\n\treturn []string{parts[0] + \"-\" + parts[1], parts[2]}\n}\n\n// Parse rang in string. Support negative number.\n// eg: \"114-514\" \"-114-514\" \"-1919--810\" \"114514\" \"\"(return 0)\nfunc ParseRangeString(str string) (int, int, error) {\n\t// for number in string format like \"114\" or \"-1\"\n\tif value, err := strconv.Atoi(str); err == nil {\n\t\treturn value, value, nil\n\t}\n\t// for empty \"\", we treat it as 0\n\tif str == \"\" {\n\t\treturn 0, 0, nil\n\t}\n\t// for range value, like \"114-514\"\n\tvar pair []string\n\t// Process sth like \"-114-514\" \"-1919--810\"\n\tif strings.HasPrefix(str, \"-\") {\n\t\tpair = splitFromSecondDash(str)\n\t} else {\n\t\tpair = strings.SplitN(str, \"-\", 2)\n\t}\n\tif len(pair) == 2 {\n\t\tleft, err := strconv.Atoi(pair[0])\n\t\tright, err2 := strconv.Atoi(pair[1])\n\t\tif err == nil && err2 == nil {\n\t\t\treturn left, right, nil\n\t\t}\n\t}\n\treturn 0, 0, errors.New(\"invalid range string: \", str)\n}\n"
  },
  {
    "path": "infra/conf/common_test.go",
    "content": "package conf_test\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n)\n\nfunc TestStringListUnmarshalError(t *testing.T) {\n\trawJSON := `1234`\n\tlist := new(StringList)\n\terr := json.Unmarshal([]byte(rawJSON), list)\n\tif err == nil {\n\t\tt.Error(\"expected error, but got nil\")\n\t}\n}\n\nfunc TestStringListLen(t *testing.T) {\n\trawJSON := `\"a, b, c, d\"`\n\tvar list StringList\n\terr := json.Unmarshal([]byte(rawJSON), &list)\n\tcommon.Must(err)\n\tif r := cmp.Diff([]string(list), []string{\"a\", \" b\", \" c\", \" d\"}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestIPParsing(t *testing.T) {\n\trawJSON := \"\\\"8.8.8.8\\\"\"\n\tvar address Address\n\terr := json.Unmarshal([]byte(rawJSON), &address)\n\tcommon.Must(err)\n\tif r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestDomainParsing(t *testing.T) {\n\trawJSON := \"\\\"example.com\\\"\"\n\tvar address Address\n\tcommon.Must(json.Unmarshal([]byte(rawJSON), &address))\n\tif address.Domain() != \"example.com\" {\n\t\tt.Error(\"domain: \", address.Domain())\n\t}\n}\n\nfunc TestURLParsing(t *testing.T) {\n\t{\n\t\trawJSON := \"\\\"https://dns.google/dns-query\\\"\"\n\t\tvar address Address\n\t\tcommon.Must(json.Unmarshal([]byte(rawJSON), &address))\n\t\tif address.Domain() != \"https://dns.google/dns-query\" {\n\t\t\tt.Error(\"URL: \", address.Domain())\n\t\t}\n\t}\n\t{\n\t\trawJSON := \"\\\"https+local://dns.google/dns-query\\\"\"\n\t\tvar address Address\n\t\tcommon.Must(json.Unmarshal([]byte(rawJSON), &address))\n\t\tif address.Domain() != \"https+local://dns.google/dns-query\" {\n\t\t\tt.Error(\"URL: \", address.Domain())\n\t\t}\n\t}\n}\n\nfunc TestInvalidAddressJson(t *testing.T) {\n\trawJSON := \"1234\"\n\tvar address Address\n\terr := json.Unmarshal([]byte(rawJSON), &address)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestStringNetwork(t *testing.T) {\n\tvar network Network\n\tcommon.Must(json.Unmarshal([]byte(`\"tcp\"`), &network))\n\tif v := network.Build(); v != net.Network_TCP {\n\t\tt.Error(\"network: \", v)\n\t}\n}\n\nfunc TestArrayNetworkList(t *testing.T) {\n\tvar list NetworkList\n\tcommon.Must(json.Unmarshal([]byte(\"[\\\"Tcp\\\"]\"), &list))\n\n\tnlist := list.Build()\n\tif !net.HasNetwork(nlist, net.Network_TCP) {\n\t\tt.Error(\"no tcp network\")\n\t}\n\tif net.HasNetwork(nlist, net.Network_UDP) {\n\t\tt.Error(\"has udp network\")\n\t}\n}\n\nfunc TestStringNetworkList(t *testing.T) {\n\tvar list NetworkList\n\tcommon.Must(json.Unmarshal([]byte(\"\\\"TCP, ip\\\"\"), &list))\n\n\tnlist := list.Build()\n\tif !net.HasNetwork(nlist, net.Network_TCP) {\n\t\tt.Error(\"no tcp network\")\n\t}\n\tif net.HasNetwork(nlist, net.Network_UDP) {\n\t\tt.Error(\"has udp network\")\n\t}\n}\n\nfunc TestInvalidNetworkJson(t *testing.T) {\n\tvar list NetworkList\n\terr := json.Unmarshal([]byte(\"0\"), &list)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestIntPort(t *testing.T) {\n\tvar portRange PortRange\n\tcommon.Must(json.Unmarshal([]byte(\"1234\"), &portRange))\n\n\tif r := cmp.Diff(portRange, PortRange{\n\t\tFrom: 1234, To: 1234,\n\t}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestOverRangeIntPort(t *testing.T) {\n\tvar portRange PortRange\n\terr := json.Unmarshal([]byte(\"70000\"), &portRange)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n\n\terr = json.Unmarshal([]byte(\"-1\"), &portRange)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestEnvPort(t *testing.T) {\n\tcommon.Must(os.Setenv(\"PORT\", \"1234\"))\n\n\tvar portRange PortRange\n\tcommon.Must(json.Unmarshal([]byte(\"\\\"env:PORT\\\"\"), &portRange))\n\n\tif r := cmp.Diff(portRange, PortRange{\n\t\tFrom: 1234, To: 1234,\n\t}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestSingleStringPort(t *testing.T) {\n\tvar portRange PortRange\n\tcommon.Must(json.Unmarshal([]byte(\"\\\"1234\\\"\"), &portRange))\n\n\tif r := cmp.Diff(portRange, PortRange{\n\t\tFrom: 1234, To: 1234,\n\t}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestStringPairPort(t *testing.T) {\n\tvar portRange PortRange\n\tcommon.Must(json.Unmarshal([]byte(\"\\\"1234-5678\\\"\"), &portRange))\n\n\tif r := cmp.Diff(portRange, PortRange{\n\t\tFrom: 1234, To: 5678,\n\t}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestOverRangeStringPort(t *testing.T) {\n\tvar portRange PortRange\n\terr := json.Unmarshal([]byte(\"\\\"65536\\\"\"), &portRange)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n\n\terr = json.Unmarshal([]byte(\"\\\"70000-80000\\\"\"), &portRange)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n\n\terr = json.Unmarshal([]byte(\"\\\"1-90000\\\"\"), &portRange)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n\n\terr = json.Unmarshal([]byte(\"\\\"700-600\\\"\"), &portRange)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestUserParsing(t *testing.T) {\n\tuser := new(User)\n\tcommon.Must(json.Unmarshal([]byte(`{\n    \"id\": \"96edb838-6d68-42ef-a933-25f7ac3a9d09\",\n    \"email\": \"love@example.com\",\n    \"level\": 1\n  }`), user))\n\n\tnUser := user.Build()\n\tif r := cmp.Diff(nUser, &protocol.User{\n\t\tLevel: 1,\n\t\tEmail: \"love@example.com\",\n\t}, cmpopts.IgnoreUnexported(protocol.User{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestInvalidUserJson(t *testing.T) {\n\tuser := new(User)\n\terr := json.Unmarshal([]byte(`{\"email\": 1234}`), user)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n"
  },
  {
    "path": "infra/conf/conf.go",
    "content": "package conf\n"
  },
  {
    "path": "infra/conf/dns.go",
    "content": "package conf\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\ntype NameServerConfig struct {\n\tAddress         *Address   `json:\"address\"`\n\tClientIP        *Address   `json:\"clientIp\"`\n\tPort            uint16     `json:\"port\"`\n\tSkipFallback    bool       `json:\"skipFallback\"`\n\tDomains         []string   `json:\"domains\"`\n\tExpectedIPs     StringList `json:\"expectedIPs\"`\n\tExpectIPs       StringList `json:\"expectIPs\"`\n\tQueryStrategy   string     `json:\"queryStrategy\"`\n\tTag             string     `json:\"tag\"`\n\tTimeoutMs       uint64     `json:\"timeoutMs\"`\n\tDisableCache    *bool      `json:\"disableCache\"`\n\tServeStale      *bool      `json:\"serveStale\"`\n\tServeExpiredTTL *uint32    `json:\"serveExpiredTTL\"`\n\tFinalQuery      bool       `json:\"finalQuery\"`\n\tUnexpectedIPs   StringList `json:\"unexpectedIPs\"`\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (c *NameServerConfig) UnmarshalJSON(data []byte) error {\n\tvar address Address\n\tif err := json.Unmarshal(data, &address); err == nil {\n\t\tc.Address = &address\n\t\treturn nil\n\t}\n\n\tvar advanced struct {\n\t\tAddress         *Address   `json:\"address\"`\n\t\tClientIP        *Address   `json:\"clientIp\"`\n\t\tPort            uint16     `json:\"port\"`\n\t\tSkipFallback    bool       `json:\"skipFallback\"`\n\t\tDomains         []string   `json:\"domains\"`\n\t\tExpectedIPs     StringList `json:\"expectedIPs\"`\n\t\tExpectIPs       StringList `json:\"expectIPs\"`\n\t\tQueryStrategy   string     `json:\"queryStrategy\"`\n\t\tTag             string     `json:\"tag\"`\n\t\tTimeoutMs       uint64     `json:\"timeoutMs\"`\n\t\tDisableCache    *bool      `json:\"disableCache\"`\n\t\tServeStale      *bool      `json:\"serveStale\"`\n\t\tServeExpiredTTL *uint32    `json:\"serveExpiredTTL\"`\n\t\tFinalQuery      bool       `json:\"finalQuery\"`\n\t\tUnexpectedIPs   StringList `json:\"unexpectedIPs\"`\n\t}\n\tif err := json.Unmarshal(data, &advanced); err == nil {\n\t\tc.Address = advanced.Address\n\t\tc.ClientIP = advanced.ClientIP\n\t\tc.Port = advanced.Port\n\t\tc.SkipFallback = advanced.SkipFallback\n\t\tc.Domains = advanced.Domains\n\t\tc.ExpectedIPs = advanced.ExpectedIPs\n\t\tc.ExpectIPs = advanced.ExpectIPs\n\t\tc.QueryStrategy = advanced.QueryStrategy\n\t\tc.Tag = advanced.Tag\n\t\tc.TimeoutMs = advanced.TimeoutMs\n\t\tc.DisableCache = advanced.DisableCache\n\t\tc.ServeStale = advanced.ServeStale\n\t\tc.ServeExpiredTTL = advanced.ServeExpiredTTL\n\t\tc.FinalQuery = advanced.FinalQuery\n\t\tc.UnexpectedIPs = advanced.UnexpectedIPs\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"failed to parse name server: \", string(data))\n}\n\nfunc toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {\n\tswitch t {\n\tcase router.Domain_Domain:\n\t\treturn dns.DomainMatchingType_Subdomain\n\tcase router.Domain_Full:\n\t\treturn dns.DomainMatchingType_Full\n\tcase router.Domain_Plain:\n\t\treturn dns.DomainMatchingType_Keyword\n\tcase router.Domain_Regex:\n\t\treturn dns.DomainMatchingType_Regex\n\tdefault:\n\t\tpanic(\"unknown domain type\")\n\t}\n}\n\nfunc (c *NameServerConfig) Build() (*dns.NameServer, error) {\n\tif c.Address == nil {\n\t\treturn nil, errors.New(\"NameServer address is not specified.\")\n\t}\n\n\tvar domains []*dns.NameServer_PriorityDomain\n\tvar originalRules []*dns.NameServer_OriginalRule\n\n\tfor _, rule := range c.Domains {\n\t\tparsedDomain, err := parseDomainRule(rule)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid domain rule: \", rule).Base(err)\n\t\t}\n\n\t\tfor _, pd := range parsedDomain {\n\t\t\tdomains = append(domains, &dns.NameServer_PriorityDomain{\n\t\t\t\tType:   toDomainMatchingType(pd.Type),\n\t\t\t\tDomain: pd.Value,\n\t\t\t})\n\t\t}\n\t\toriginalRules = append(originalRules, &dns.NameServer_OriginalRule{\n\t\t\tRule: rule,\n\t\t\tSize: uint32(len(parsedDomain)),\n\t\t})\n\t}\n\n\tif len(c.ExpectedIPs) == 0 {\n\t\tc.ExpectedIPs = c.ExpectIPs\n\t}\n\n\tactPrior := false\n\tvar newExpectedIPs StringList\n\tfor _, s := range c.ExpectedIPs {\n\t\tif s == \"*\" {\n\t\t\tactPrior = true\n\t\t} else {\n\t\t\tnewExpectedIPs = append(newExpectedIPs, s)\n\t\t}\n\t}\n\n\tactUnprior := false\n\tvar newUnexpectedIPs StringList\n\tfor _, s := range c.UnexpectedIPs {\n\t\tif s == \"*\" {\n\t\t\tactUnprior = true\n\t\t} else {\n\t\t\tnewUnexpectedIPs = append(newUnexpectedIPs, s)\n\t\t}\n\t}\n\n\texpectedGeoipList, err := ToCidrList(newExpectedIPs)\n\tif err != nil {\n\t\treturn nil, errors.New(\"invalid expected IP rule: \", c.ExpectedIPs).Base(err)\n\t}\n\n\tunexpectedGeoipList, err := ToCidrList(newUnexpectedIPs)\n\tif err != nil {\n\t\treturn nil, errors.New(\"invalid unexpected IP rule: \", c.UnexpectedIPs).Base(err)\n\t}\n\n\tvar myClientIP []byte\n\tif c.ClientIP != nil {\n\t\tif !c.ClientIP.Family().IsIP() {\n\t\t\treturn nil, errors.New(\"not an IP address:\", c.ClientIP.String())\n\t\t}\n\t\tmyClientIP = []byte(c.ClientIP.IP())\n\t}\n\n\treturn &dns.NameServer{\n\t\tAddress: &net.Endpoint{\n\t\t\tNetwork: net.Network_UDP,\n\t\t\tAddress: c.Address.Build(),\n\t\t\tPort:    uint32(c.Port),\n\t\t},\n\t\tClientIp:          myClientIP,\n\t\tSkipFallback:      c.SkipFallback,\n\t\tPrioritizedDomain: domains,\n\t\tExpectedGeoip:     expectedGeoipList,\n\t\tOriginalRules:     originalRules,\n\t\tQueryStrategy:     resolveQueryStrategy(c.QueryStrategy),\n\t\tActPrior:          actPrior,\n\t\tTag:               c.Tag,\n\t\tTimeoutMs:         c.TimeoutMs,\n\t\tDisableCache:      c.DisableCache,\n\t\tServeStale:        c.ServeStale,\n\t\tServeExpiredTTL:   c.ServeExpiredTTL,\n\t\tFinalQuery:        c.FinalQuery,\n\t\tUnexpectedGeoip:   unexpectedGeoipList,\n\t\tActUnprior:        actUnprior,\n\t}, nil\n}\n\nvar typeMap = map[router.Domain_Type]dns.DomainMatchingType{\n\trouter.Domain_Full:   dns.DomainMatchingType_Full,\n\trouter.Domain_Domain: dns.DomainMatchingType_Subdomain,\n\trouter.Domain_Plain:  dns.DomainMatchingType_Keyword,\n\trouter.Domain_Regex:  dns.DomainMatchingType_Regex,\n}\n\n// DNSConfig is a JSON serializable object for dns.Config.\ntype DNSConfig struct {\n\tServers                []*NameServerConfig `json:\"servers\"`\n\tHosts                  *HostsWrapper       `json:\"hosts\"`\n\tClientIP               *Address            `json:\"clientIp\"`\n\tTag                    string              `json:\"tag\"`\n\tQueryStrategy          string              `json:\"queryStrategy\"`\n\tDisableCache           bool                `json:\"disableCache\"`\n\tServeStale             bool                `json:\"serveStale\"`\n\tServeExpiredTTL        uint32              `json:\"serveExpiredTTL\"`\n\tDisableFallback        bool                `json:\"disableFallback\"`\n\tDisableFallbackIfMatch bool                `json:\"disableFallbackIfMatch\"`\n\tEnableParallelQuery    bool                `json:\"enableParallelQuery\"`\n\tUseSystemHosts         bool                `json:\"useSystemHosts\"`\n}\n\ntype HostAddress struct {\n\taddr  *Address\n\taddrs []*Address\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (h *HostAddress) MarshalJSON() ([]byte, error) {\n\tif (h.addr != nil) != (h.addrs != nil) {\n\t\tif h.addr != nil {\n\t\t\treturn json.Marshal(h.addr)\n\t\t} else if h.addrs != nil {\n\t\t\treturn json.Marshal(h.addrs)\n\t\t}\n\t}\n\treturn nil, errors.New(\"unexpected config state\")\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (h *HostAddress) UnmarshalJSON(data []byte) error {\n\taddr := new(Address)\n\tvar addrs []*Address\n\tswitch {\n\tcase json.Unmarshal(data, &addr) == nil:\n\t\th.addr = addr\n\tcase json.Unmarshal(data, &addrs) == nil:\n\t\th.addrs = addrs\n\tdefault:\n\t\treturn errors.New(\"invalid address\")\n\t}\n\treturn nil\n}\n\ntype HostsWrapper struct {\n\tHosts map[string]*HostAddress\n}\n\nfunc getHostMapping(ha *HostAddress) *dns.Config_HostMapping {\n\tif ha.addr != nil {\n\t\tif ha.addr.Family().IsDomain() {\n\t\t\treturn &dns.Config_HostMapping{\n\t\t\t\tProxiedDomain: ha.addr.Domain(),\n\t\t\t}\n\t\t}\n\t\treturn &dns.Config_HostMapping{\n\t\t\tIp: [][]byte{ha.addr.IP()},\n\t\t}\n\t}\n\n\tips := make([][]byte, 0, len(ha.addrs))\n\tfor _, addr := range ha.addrs {\n\t\tif addr.Family().IsDomain() {\n\t\t\treturn &dns.Config_HostMapping{\n\t\t\t\tProxiedDomain: addr.Domain(),\n\t\t\t}\n\t\t}\n\t\tips = append(ips, []byte(addr.IP()))\n\t}\n\treturn &dns.Config_HostMapping{\n\t\tIp: ips,\n\t}\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (m *HostsWrapper) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(m.Hosts)\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (m *HostsWrapper) UnmarshalJSON(data []byte) error {\n\thosts := make(map[string]*HostAddress)\n\terr := json.Unmarshal(data, &hosts)\n\tif err == nil {\n\t\tm.Hosts = hosts\n\t\treturn nil\n\t}\n\treturn errors.New(\"invalid DNS hosts\").Base(err)\n}\n\n// Build implements Buildable\nfunc (m *HostsWrapper) Build() ([]*dns.Config_HostMapping, error) {\n\tmappings := make([]*dns.Config_HostMapping, 0, 20)\n\n\tdomains := make([]string, 0, len(m.Hosts))\n\tfor domain := range m.Hosts {\n\t\tdomains = append(domains, domain)\n\t}\n\tsort.Strings(domains)\n\n\tfor _, domain := range domains {\n\t\tswitch {\n\t\tcase strings.HasPrefix(domain, \"domain:\"):\n\t\t\tdomainName := domain[7:]\n\t\t\tif len(domainName) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty domain type of rule: \", domain)\n\t\t\t}\n\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\tmapping.Type = dns.DomainMatchingType_Subdomain\n\t\t\tmapping.Domain = domainName\n\t\t\tmappings = append(mappings, mapping)\n\n\t\tcase strings.HasPrefix(domain, \"geosite:\"):\n\t\t\tlistName := domain[8:]\n\t\t\tif len(listName) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty geosite rule: \", domain)\n\t\t\t}\n\t\t\tgeositeList, err := loadGeositeWithAttr(\"geosite.dat\", listName)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to load geosite: \", listName).Base(err)\n\t\t\t}\n\t\t\tfor _, d := range geositeList {\n\t\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\t\tmapping.Type = typeMap[d.Type]\n\t\t\t\tmapping.Domain = d.Value\n\t\t\t\tmappings = append(mappings, mapping)\n\t\t\t}\n\n\t\tcase strings.HasPrefix(domain, \"regexp:\"):\n\t\t\tregexpVal := domain[7:]\n\t\t\tif len(regexpVal) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty regexp type of rule: \", domain)\n\t\t\t}\n\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\tmapping.Type = dns.DomainMatchingType_Regex\n\t\t\tmapping.Domain = regexpVal\n\t\t\tmappings = append(mappings, mapping)\n\n\t\tcase strings.HasPrefix(domain, \"keyword:\"):\n\t\t\tkeywordVal := domain[8:]\n\t\t\tif len(keywordVal) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty keyword type of rule: \", domain)\n\t\t\t}\n\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\tmapping.Type = dns.DomainMatchingType_Keyword\n\t\t\tmapping.Domain = keywordVal\n\t\t\tmappings = append(mappings, mapping)\n\n\t\tcase strings.HasPrefix(domain, \"full:\"):\n\t\t\tfullVal := domain[5:]\n\t\t\tif len(fullVal) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty full domain type of rule: \", domain)\n\t\t\t}\n\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\tmapping.Type = dns.DomainMatchingType_Full\n\t\t\tmapping.Domain = fullVal\n\t\t\tmappings = append(mappings, mapping)\n\n\t\tcase strings.HasPrefix(domain, \"dotless:\"):\n\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\tmapping.Type = dns.DomainMatchingType_Regex\n\t\t\tswitch substr := domain[8:]; {\n\t\t\tcase substr == \"\":\n\t\t\t\tmapping.Domain = \"^[^.]*$\"\n\t\t\tcase !strings.Contains(substr, \".\"):\n\t\t\t\tmapping.Domain = \"^[^.]*\" + substr + \"[^.]*$\"\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"substr in dotless rule should not contain a dot: \", substr)\n\t\t\t}\n\t\t\tmappings = append(mappings, mapping)\n\n\t\tcase strings.HasPrefix(domain, \"ext:\"):\n\t\t\tkv := strings.Split(domain[4:], \":\")\n\t\t\tif len(kv) != 2 {\n\t\t\t\treturn nil, errors.New(\"invalid external resource: \", domain)\n\t\t\t}\n\t\t\tfilename := kv[0]\n\t\t\tlist := kv[1]\n\t\t\tgeositeList, err := loadGeositeWithAttr(filename, list)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to load domain list: \", list, \" from \", filename).Base(err)\n\t\t\t}\n\t\t\tfor _, d := range geositeList {\n\t\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\t\tmapping.Type = typeMap[d.Type]\n\t\t\t\tmapping.Domain = d.Value\n\t\t\t\tmappings = append(mappings, mapping)\n\t\t\t}\n\n\t\tdefault:\n\t\t\tmapping := getHostMapping(m.Hosts[domain])\n\t\t\tmapping.Type = dns.DomainMatchingType_Full\n\t\t\tmapping.Domain = domain\n\t\t\tmappings = append(mappings, mapping)\n\t\t}\n\t}\n\treturn mappings, nil\n}\n\n// Build implements Buildable\nfunc (c *DNSConfig) Build() (*dns.Config, error) {\n\tconfig := &dns.Config{\n\t\tTag:                    c.Tag,\n\t\tDisableCache:           c.DisableCache,\n\t\tServeStale:             c.ServeStale,\n\t\tServeExpiredTTL:        c.ServeExpiredTTL,\n\t\tDisableFallback:        c.DisableFallback,\n\t\tDisableFallbackIfMatch: c.DisableFallbackIfMatch,\n\t\tEnableParallelQuery:    c.EnableParallelQuery,\n\t\tQueryStrategy:          resolveQueryStrategy(c.QueryStrategy),\n\t}\n\n\tif c.ClientIP != nil {\n\t\tif !c.ClientIP.Family().IsIP() {\n\t\t\treturn nil, errors.New(\"not an IP address:\", c.ClientIP.String())\n\t\t}\n\t\tconfig.ClientIp = []byte(c.ClientIP.IP())\n\t}\n\n\t// Build PolicyID\n\tpolicyMap := map[string]uint32{}\n\tnextPolicyID := uint32(1)\n\tbuildPolicyID := func(nsc *NameServerConfig) uint32 {\n\t\tvar sb strings.Builder\n\n\t\t// ClientIP\n\t\tif nsc.ClientIP != nil {\n\t\t\tsb.WriteString(\"client=\")\n\t\t\tsb.WriteString(nsc.ClientIP.String())\n\t\t\tsb.WriteByte('|')\n\t\t} else {\n\t\t\tsb.WriteString(\"client=none|\")\n\t\t}\n\n\t\t// SkipFallback\n\t\tif nsc.SkipFallback {\n\t\t\tsb.WriteString(\"skip=1|\")\n\t\t} else {\n\t\t\tsb.WriteString(\"skip=0|\")\n\t\t}\n\n\t\t// QueryStrategy\n\t\tsb.WriteString(\"qs=\")\n\t\tsb.WriteString(strings.ToLower(strings.TrimSpace(nsc.QueryStrategy)))\n\t\tsb.WriteByte('|')\n\n\t\t// Tag\n\t\tsb.WriteString(\"tag=\")\n\t\tsb.WriteString(strings.ToLower(strings.TrimSpace(nsc.Tag)))\n\t\tsb.WriteByte('|')\n\n\t\t// []string helper\n\t\twriteList := func(tag string, lst []string) {\n\t\t\tif len(lst) == 0 {\n\t\t\t\tsb.WriteString(tag)\n\t\t\t\tsb.WriteString(\"=[]|\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcp := make([]string, len(lst))\n\t\t\tfor i, s := range lst {\n\t\t\t\tcp[i] = strings.TrimSpace(strings.ToLower(s))\n\t\t\t}\n\t\t\tsort.Strings(cp)\n\t\t\tsb.WriteString(tag)\n\t\t\tsb.WriteByte('=')\n\t\t\tsb.WriteString(strings.Join(cp, \",\"))\n\t\t\tsb.WriteByte('|')\n\t\t}\n\n\t\twriteList(\"domains\", nsc.Domains)\n\t\twriteList(\"expected\", nsc.ExpectedIPs)\n\t\twriteList(\"expect\", nsc.ExpectIPs)\n\t\twriteList(\"unexpected\", nsc.UnexpectedIPs)\n\n\t\tkey := sb.String()\n\n\t\tif id, ok := policyMap[key]; ok {\n\t\t\treturn id\n\t\t}\n\t\tid := nextPolicyID\n\t\tnextPolicyID++\n\t\tpolicyMap[key] = id\n\t\treturn id\n\t}\n\n\tfor _, server := range c.Servers {\n\t\tns, err := server.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build nameserver\").Base(err)\n\t\t}\n\t\tns.PolicyID = buildPolicyID(server)\n\t\tconfig.NameServer = append(config.NameServer, ns)\n\t}\n\n\tif c.Hosts != nil {\n\t\tstaticHosts, err := c.Hosts.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build hosts\").Base(err)\n\t\t}\n\t\tconfig.StaticHosts = append(config.StaticHosts, staticHosts...)\n\t}\n\tif c.UseSystemHosts {\n\t\tsystemHosts, err := readSystemHosts()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to read system hosts\").Base(err)\n\t\t}\n\t\tfor domain, ips := range systemHosts {\n\t\t\tconfig.StaticHosts = append(config.StaticHosts, &dns.Config_HostMapping{Ip: ips, Domain: domain, Type: dns.DomainMatchingType_Full})\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\nfunc resolveQueryStrategy(queryStrategy string) dns.QueryStrategy {\n\tswitch strings.ToLower(queryStrategy) {\n\tcase \"useip\", \"use_ip\", \"use-ip\":\n\t\treturn dns.QueryStrategy_USE_IP\n\tcase \"useip4\", \"useipv4\", \"use_ip4\", \"use_ipv4\", \"use_ip_v4\", \"use-ip4\", \"use-ipv4\", \"use-ip-v4\":\n\t\treturn dns.QueryStrategy_USE_IP4\n\tcase \"useip6\", \"useipv6\", \"use_ip6\", \"use_ipv6\", \"use_ip_v6\", \"use-ip6\", \"use-ipv6\", \"use-ip-v6\":\n\t\treturn dns.QueryStrategy_USE_IP6\n\tcase \"usesys\", \"usesystem\", \"use_sys\", \"use_system\", \"use-sys\", \"use-system\":\n\t\treturn dns.QueryStrategy_USE_SYS\n\tdefault:\n\t\treturn dns.QueryStrategy_USE_IP\n\t}\n}\n\nfunc readSystemHosts() (map[string][][]byte, error) {\n\tvar hostsPath string\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\thostsPath = filepath.Join(os.Getenv(\"SystemRoot\"), \"System32\", \"drivers\", \"etc\", \"hosts\")\n\tdefault:\n\t\thostsPath = \"/etc/hosts\"\n\t}\n\n\tfile, err := os.Open(hostsPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\thostsMap := make(map[string][][]byte)\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif i := strings.IndexByte(line, '#'); i >= 0 {\n\t\t\t// Discard comments.\n\t\t\tline = line[0:i]\n\t\t}\n\t\tf := strings.Fields(line)\n\t\tif len(f) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\taddr := net.ParseAddress(f[0])\n\t\tif addr.Family().IsDomain() {\n\t\t\tcontinue\n\t\t}\n\t\tip := addr.IP()\n\t\tfor i := 1; i < len(f); i++ {\n\t\t\tdomain := strings.TrimSuffix(f[i], \".\")\n\t\t\tdomain = strings.ToLower(domain)\n\t\t\tif v, ok := hostsMap[domain]; ok {\n\t\t\t\thostsMap[domain] = append(v, ip)\n\t\t\t} else {\n\t\t\t\thostsMap[domain] = [][]byte{ip}\n\t\t\t}\n\t\t}\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn hostsMap, nil\n}\n"
  },
  {
    "path": "infra/conf/dns_proxy.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/proxy/dns\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype DNSOutboundConfig struct {\n\tNetwork    Network  `json:\"network\"`\n\tAddress    *Address `json:\"address\"`\n\tPort       uint16   `json:\"port\"`\n\tUserLevel  uint32   `json:\"userLevel\"`\n\tNonIPQuery string   `json:\"nonIPQuery\"`\n\tBlockTypes []int32  `json:\"blockTypes\"`\n}\n\nfunc (c *DNSOutboundConfig) Build() (proto.Message, error) {\n\tconfig := &dns.Config{\n\t\tServer: &net.Endpoint{\n\t\t\tNetwork: c.Network.Build(),\n\t\t\tPort:    uint32(c.Port),\n\t\t},\n\t\tUserLevel: c.UserLevel,\n\t}\n\tif c.Address != nil {\n\t\tconfig.Server.Address = c.Address.Build()\n\t}\n\tswitch c.NonIPQuery {\n\tcase \"\", \"reject\", \"drop\", \"skip\":\n\tdefault:\n\t\treturn nil, errors.New(`unknown \"nonIPQuery\": `, c.NonIPQuery)\n\t}\n\tconfig.Non_IPQuery = c.NonIPQuery\n\tconfig.BlockTypes = c.BlockTypes\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/dns_proxy_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/dns\"\n)\n\nfunc TestDnsProxyConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(DNSOutboundConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"address\": \"8.8.8.8\",\n\t\t\t\t\"port\": 53,\n\t\t\t\t\"network\": \"tcp\"\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &dns.Config{\n\t\t\t\tServer: &net.Endpoint{\n\t\t\t\t\tNetwork: net.Network_TCP,\n\t\t\t\t\tAddress: net.NewIPOrDomain(net.IPAddress([]byte{8, 8, 8, 8})),\n\t\t\t\t\tPort:    53,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/dns_test.go",
    "content": "package conf_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestDNSConfigParsing(t *testing.T) {\n\tparserCreator := func() func(string) (proto.Message, error) {\n\t\treturn func(s string) (proto.Message, error) {\n\t\t\tconfig := new(DNSConfig)\n\t\t\tif err := json.Unmarshal([]byte(s), config); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn config.Build()\n\t\t}\n\t}\n\texpectedServeStale := true\n\texpectedServeExpiredTTL := uint32(172800)\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"servers\": [{\n\t\t\t\t\t\"address\": \"8.8.8.8\",\n\t\t\t\t\t\"port\": 5353,\n\t\t\t\t\t\"skipFallback\": true,\n\t\t\t\t\t\"domains\": [\"domain:example.com\"],\n\t\t\t\t\t\"serveStale\": true,\n\t\t\t\t\t\"serveExpiredTTL\": 172800\n\t\t\t\t}],\n\t\t\t\t\"hosts\": {\n\t\t\t\t\t\"domain:example.com\": \"google.com\",\n\t\t\t\t\t\"example.com\": \"127.0.0.1\",\n\t\t\t\t\t\"keyword:google\": [\"8.8.8.8\", \"8.8.4.4\"],\n\t\t\t\t\t\"regexp:.*\\\\.com\": \"8.8.4.4\",\n\t\t\t\t\t\"www.example.org\": [\"127.0.0.1\", \"127.0.0.2\"]\n\t\t\t\t},\n\t\t\t\t\"clientIp\": \"10.0.0.1\",\n\t\t\t\t\"queryStrategy\": \"UseIPv4\",\n\t\t\t\t\"disableCache\": true,\n\t\t\t\t\"serveStale\": false,\n\t\t\t\t\"serveExpiredTTL\": 86400,\n\t\t\t\t\"disableFallback\": true\n\t\t\t}`,\n\t\t\tParser: parserCreator(),\n\t\t\tOutput: &dns.Config{\n\t\t\t\tNameServer: []*dns.NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{8, 8, 8, 8},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tPort:    5353,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSkipFallback: true,\n\t\t\t\t\t\tPrioritizedDomain: []*dns.NameServer_PriorityDomain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   dns.DomainMatchingType_Subdomain,\n\t\t\t\t\t\t\t\tDomain: \"example.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tOriginalRules: []*dns.NameServer_OriginalRule{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tRule: \"domain:example.com\",\n\t\t\t\t\t\t\t\tSize: 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tServeStale:      &expectedServeStale,\n\t\t\t\t\t\tServeExpiredTTL: &expectedServeExpiredTTL,\n\t\t\t\t\t\tPolicyID:        1, // Servers with certain identical fields share this ID, incrementing starting from 1. See: Build PolicyID\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tStaticHosts: []*dns.Config_HostMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:          dns.DomainMatchingType_Subdomain,\n\t\t\t\t\t\tDomain:        \"example.com\",\n\t\t\t\t\t\tProxiedDomain: \"google.com\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   dns.DomainMatchingType_Full,\n\t\t\t\t\t\tDomain: \"example.com\",\n\t\t\t\t\t\tIp:     [][]byte{{127, 0, 0, 1}},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   dns.DomainMatchingType_Keyword,\n\t\t\t\t\t\tDomain: \"google\",\n\t\t\t\t\t\tIp:     [][]byte{{8, 8, 8, 8}, {8, 8, 4, 4}},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   dns.DomainMatchingType_Regex,\n\t\t\t\t\t\tDomain: \".*\\\\.com\",\n\t\t\t\t\t\tIp:     [][]byte{{8, 8, 4, 4}},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   dns.DomainMatchingType_Full,\n\t\t\t\t\t\tDomain: \"www.example.org\",\n\t\t\t\t\t\tIp:     [][]byte{{127, 0, 0, 1}, {127, 0, 0, 2}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tClientIp:        []byte{10, 0, 0, 1},\n\t\t\t\tQueryStrategy:   dns.QueryStrategy_USE_IP4,\n\t\t\t\tDisableCache:    true,\n\t\t\t\tServeStale:      false,\n\t\t\t\tServeExpiredTTL: 86400,\n\t\t\t\tDisableFallback: true,\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/dokodemo.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype DokodemoConfig struct {\n\tAddress        *Address          `json:\"address\"`\n\tPort           uint16            `json:\"port\"`\n\tPortMap        map[string]string `json:\"portMap\"`\n\tNetwork        *NetworkList      `json:\"network\"`\n\tFollowRedirect bool              `json:\"followRedirect\"`\n\tUserLevel      uint32            `json:\"userLevel\"`\n}\n\nfunc (v *DokodemoConfig) Build() (proto.Message, error) {\n\tconfig := new(dokodemo.Config)\n\tif v.Address != nil {\n\t\tconfig.Address = v.Address.Build()\n\t}\n\tconfig.Port = uint32(v.Port)\n\tconfig.PortMap = v.PortMap\n\tfor _, v := range config.PortMap {\n\t\tif _, _, err := net.SplitHostPort(v); err != nil {\n\t\t\treturn nil, errors.New(\"invalid portMap: \", v).Base(err)\n\t\t}\n\t}\n\tconfig.Networks = v.Network.Build()\n\tconfig.FollowRedirect = v.FollowRedirect\n\tconfig.UserLevel = v.UserLevel\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/dokodemo_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n)\n\nfunc TestDokodemoConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(DokodemoConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"address\": \"8.8.8.8\",\n\t\t\t\t\"port\": 53,\n\t\t\t\t\"network\": \"tcp\",\n\t\t\t\t\"followRedirect\": true,\n\t\t\t\t\"userLevel\": 1\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &dokodemo.Config{\n\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\tIp: []byte{8, 8, 8, 8},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort:           53,\n\t\t\t\tNetworks:       []net.Network{net.Network_TCP},\n\t\t\t\tFollowRedirect: true,\n\t\t\t\tUserLevel:      1,\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/fakedns.go",
    "content": "package conf\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/dns/fakedns\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/features/dns\"\n)\n\ntype FakeDNSPoolElementConfig struct {\n\tIPPool  string `json:\"ipPool\"`\n\tLRUSize int64  `json:\"poolSize\"`\n}\n\ntype FakeDNSConfig struct {\n\tpool  *FakeDNSPoolElementConfig\n\tpools []*FakeDNSPoolElementConfig\n}\n\n// MarshalJSON implements encoding/json.Marshaler.MarshalJSON\nfunc (f *FakeDNSConfig) MarshalJSON() ([]byte, error) {\n\tif (f.pool != nil) != (f.pools != nil) {\n\t\tif f.pool != nil {\n\t\t\treturn json.Marshal(f.pool)\n\t\t} else if f.pools != nil {\n\t\t\treturn json.Marshal(f.pools)\n\t\t}\n\t}\n\treturn nil, errors.New(\"unexpected config state\")\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON\nfunc (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {\n\tvar pool FakeDNSPoolElementConfig\n\tvar pools []*FakeDNSPoolElementConfig\n\tswitch {\n\tcase json.Unmarshal(data, &pool) == nil:\n\t\tf.pool = &pool\n\tcase json.Unmarshal(data, &pools) == nil:\n\t\tf.pools = pools\n\tdefault:\n\t\treturn errors.New(\"invalid fakedns config\")\n\t}\n\treturn nil\n}\n\nfunc (f *FakeDNSConfig) Build() (*fakedns.FakeDnsPoolMulti, error) {\n\tfakeDNSPool := fakedns.FakeDnsPoolMulti{}\n\n\tif f.pool != nil {\n\t\tfakeDNSPool.Pools = append(fakeDNSPool.Pools, &fakedns.FakeDnsPool{\n\t\t\tIpPool:  f.pool.IPPool,\n\t\t\tLruSize: f.pool.LRUSize,\n\t\t})\n\t\treturn &fakeDNSPool, nil\n\t}\n\n\tif f.pools != nil {\n\t\tfor _, v := range f.pools {\n\t\t\tfakeDNSPool.Pools = append(fakeDNSPool.Pools, &fakedns.FakeDnsPool{IpPool: v.IPPool, LruSize: v.LRUSize})\n\t\t}\n\t\treturn &fakeDNSPool, nil\n\t}\n\n\treturn nil, errors.New(\"no valid FakeDNS config\")\n}\n\ntype FakeDNSPostProcessingStage struct{}\n\nfunc (FakeDNSPostProcessingStage) Process(config *Config) error {\n\tfakeDNSInUse := false\n\tisIPv4Enable, isIPv6Enable := true, true\n\n\tif config.DNSConfig != nil {\n\t\tfor _, v := range config.DNSConfig.Servers {\n\t\t\tif v.Address.Family().IsDomain() && strings.EqualFold(v.Address.Domain(), \"fakedns\") {\n\t\t\t\tfakeDNSInUse = true\n\t\t\t}\n\t\t}\n\n\t\tswitch strings.ToLower(config.DNSConfig.QueryStrategy) {\n\t\tcase \"useip4\", \"useipv4\", \"use_ip4\", \"use_ipv4\", \"use_ip_v4\", \"use-ip4\", \"use-ipv4\", \"use-ip-v4\":\n\t\t\tisIPv4Enable, isIPv6Enable = true, false\n\t\tcase \"useip6\", \"useipv6\", \"use_ip6\", \"use_ipv6\", \"use_ip_v6\", \"use-ip6\", \"use-ipv6\", \"use-ip-v6\":\n\t\t\tisIPv4Enable, isIPv6Enable = false, true\n\t\t}\n\t}\n\n\tif fakeDNSInUse {\n\t\t// Add a Fake DNS Config if there is none\n\t\tif config.FakeDNS == nil {\n\t\t\tconfig.FakeDNS = &FakeDNSConfig{}\n\t\t\tswitch {\n\t\t\tcase isIPv4Enable && isIPv6Enable:\n\t\t\t\tconfig.FakeDNS.pools = []*FakeDNSPoolElementConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\tIPPool:  dns.FakeIPv4Pool,\n\t\t\t\t\t\tLRUSize: 32768,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tIPPool:  dns.FakeIPv6Pool,\n\t\t\t\t\t\tLRUSize: 32768,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tcase !isIPv4Enable && isIPv6Enable:\n\t\t\t\tconfig.FakeDNS.pool = &FakeDNSPoolElementConfig{\n\t\t\t\t\tIPPool:  dns.FakeIPv6Pool,\n\t\t\t\t\tLRUSize: 65535,\n\t\t\t\t}\n\t\t\tcase isIPv4Enable && !isIPv6Enable:\n\t\t\t\tconfig.FakeDNS.pool = &FakeDNSPoolElementConfig{\n\t\t\t\t\tIPPool:  dns.FakeIPv4Pool,\n\t\t\t\t\tLRUSize: 65535,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfound := false\n\t\t// Check if there is a Outbound with necessary sniffer on\n\t\tvar inbounds []InboundDetourConfig\n\n\t\tif len(config.InboundConfigs) > 0 {\n\t\t\tinbounds = append(inbounds, config.InboundConfigs...)\n\t\t}\n\t\tfor _, v := range inbounds {\n\t\t\tif v.SniffingConfig != nil && v.SniffingConfig.Enabled && v.SniffingConfig.DestOverride != nil {\n\t\t\t\tfor _, dov := range *v.SniffingConfig.DestOverride {\n\t\t\t\t\tif strings.EqualFold(dov, \"fakedns\") || strings.EqualFold(dov, \"fakedns+others\") {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\terrors.LogWarning(context.Background(), \"Defined FakeDNS but haven't enabled FakeDNS destOverride at any inbound.\")\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "infra/conf/freedom.go",
    "content": "package conf\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\tv2net \"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype FreedomConfig struct {\n\tTargetStrategy string    `json:\"targetStrategy\"`\n\tDomainStrategy string    `json:\"domainStrategy\"`\n\tRedirect       string    `json:\"redirect\"`\n\tUserLevel      uint32    `json:\"userLevel\"`\n\tFragment       *Fragment `json:\"fragment\"`\n\tNoise          *Noise    `json:\"noise\"`\n\tNoises         []*Noise  `json:\"noises\"`\n\tProxyProtocol  uint32    `json:\"proxyProtocol\"`\n}\n\ntype Fragment struct {\n\tPackets  string      `json:\"packets\"`\n\tLength   *Int32Range `json:\"length\"`\n\tInterval *Int32Range `json:\"interval\"`\n\tMaxSplit *Int32Range `json:\"maxSplit\"`\n}\n\ntype Noise struct {\n\tType    string      `json:\"type\"`\n\tPacket  string      `json:\"packet\"`\n\tDelay   *Int32Range `json:\"delay\"`\n\tApplyTo string      `json:\"applyTo\"`\n}\n\n// Build implements Buildable\nfunc (c *FreedomConfig) Build() (proto.Message, error) {\n\tconfig := new(freedom.Config)\n\ttargetStrategy := c.TargetStrategy\n\tif targetStrategy == \"\" {\n\t\ttargetStrategy = c.DomainStrategy\n\t}\n\tswitch strings.ToLower(targetStrategy) {\n\tcase \"asis\", \"\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_AS_IS\n\tcase \"useip\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_USE_IP\n\tcase \"useipv4\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_USE_IP4\n\tcase \"useipv6\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_USE_IP6\n\tcase \"useipv4v6\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_USE_IP46\n\tcase \"useipv6v4\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_USE_IP64\n\tcase \"forceip\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_FORCE_IP\n\tcase \"forceipv4\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_FORCE_IP4\n\tcase \"forceipv6\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_FORCE_IP6\n\tcase \"forceipv4v6\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_FORCE_IP46\n\tcase \"forceipv6v4\":\n\t\tconfig.DomainStrategy = internet.DomainStrategy_FORCE_IP64\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported domain strategy: \", targetStrategy)\n\t}\n\n\tif c.Fragment != nil {\n\t\tconfig.Fragment = new(freedom.Fragment)\n\n\t\tswitch strings.ToLower(c.Fragment.Packets) {\n\t\tcase \"tlshello\":\n\t\t\t// TLS Hello Fragmentation (into multiple handshake messages)\n\t\t\tconfig.Fragment.PacketsFrom = 0\n\t\t\tconfig.Fragment.PacketsTo = 1\n\t\tcase \"\":\n\t\t\t// TCP Segmentation (all packets)\n\t\t\tconfig.Fragment.PacketsFrom = 0\n\t\t\tconfig.Fragment.PacketsTo = 0\n\t\tdefault:\n\t\t\t// TCP Segmentation (range)\n\t\t\tfrom, to, err := ParseRangeString(c.Fragment.Packets)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"Invalid PacketsFrom\").Base(err)\n\t\t\t}\n\t\t\tconfig.Fragment.PacketsFrom = uint64(from)\n\t\t\tconfig.Fragment.PacketsTo = uint64(to)\n\t\t\tif config.Fragment.PacketsFrom == 0 {\n\t\t\t\treturn nil, errors.New(\"PacketsFrom can't be 0\")\n\t\t\t}\n\t\t}\n\n\t\t{\n\t\t\tif c.Fragment.Length == nil {\n\t\t\t\treturn nil, errors.New(\"Length can't be empty\")\n\t\t\t}\n\t\t\tconfig.Fragment.LengthMin = uint64(c.Fragment.Length.From)\n\t\t\tconfig.Fragment.LengthMax = uint64(c.Fragment.Length.To)\n\t\t\tif config.Fragment.LengthMin == 0 {\n\t\t\t\treturn nil, errors.New(\"LengthMin can't be 0\")\n\t\t\t}\n\t\t}\n\n\t\t{\n\t\t\tif c.Fragment.Interval == nil {\n\t\t\t\treturn nil, errors.New(\"Interval can't be empty\")\n\t\t\t}\n\t\t\tconfig.Fragment.IntervalMin = uint64(c.Fragment.Interval.From)\n\t\t\tconfig.Fragment.IntervalMax = uint64(c.Fragment.Interval.To)\n\t\t}\n\n\t\t{\n\t\t\tif c.Fragment.MaxSplit != nil {\n\t\t\t\tconfig.Fragment.MaxSplitMin = uint64(c.Fragment.MaxSplit.From)\n\t\t\t\tconfig.Fragment.MaxSplitMax = uint64(c.Fragment.MaxSplit.To)\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.Noise != nil {\n\t\treturn nil, errors.PrintRemovedFeatureError(\"noise = { ... }\", \"noises = [ { ... } ]\")\n\t}\n\n\tif c.Noises != nil {\n\t\tfor _, n := range c.Noises {\n\t\t\tNConfig, err := ParseNoise(n)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tconfig.Noises = append(config.Noises, NConfig)\n\t\t}\n\t}\n\n\tconfig.UserLevel = c.UserLevel\n\tif len(c.Redirect) > 0 {\n\t\thost, portStr, err := net.SplitHostPort(c.Redirect)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid redirect address: \", c.Redirect, \": \", err).Base(err)\n\t\t}\n\t\tport, err := v2net.PortFromString(portStr)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid redirect port: \", c.Redirect, \": \", err).Base(err)\n\t\t}\n\t\tconfig.DestinationOverride = &freedom.DestinationOverride{\n\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\tPort: uint32(port),\n\t\t\t},\n\t\t}\n\n\t\tif len(host) > 0 {\n\t\t\tconfig.DestinationOverride.Server.Address = v2net.NewIPOrDomain(v2net.ParseAddress(host))\n\t\t}\n\t}\n\tif c.ProxyProtocol > 0 && c.ProxyProtocol <= 2 {\n\t\tconfig.ProxyProtocol = c.ProxyProtocol\n\t}\n\treturn config, nil\n}\n\nfunc ParseNoise(noise *Noise) (*freedom.Noise, error) {\n\tvar err error\n\tNConfig := new(freedom.Noise)\n\tnoise.Packet = strings.TrimSpace(noise.Packet)\n\n\tswitch noise.Type {\n\tcase \"rand\":\n\t\tmin, max, err := ParseRangeString(noise.Packet)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid value for rand Length\").Base(err)\n\t\t}\n\t\tNConfig.LengthMin = uint64(min)\n\t\tNConfig.LengthMax = uint64(max)\n\t\tif NConfig.LengthMin == 0 {\n\t\t\treturn nil, errors.New(\"rand lengthMin or lengthMax cannot be 0\")\n\t\t}\n\n\tcase \"str\":\n\t\t// user input string\n\t\tNConfig.Packet = []byte(noise.Packet)\n\n\tcase \"hex\":\n\t\t// user input hex\n\t\tNConfig.Packet, err = hex.DecodeString(noise.Packet)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Invalid hex string\").Base(err)\n\t\t}\n\n\tcase \"base64\":\n\t\t// user input base64\n\t\tNConfig.Packet, err = base64.RawURLEncoding.DecodeString(strings.NewReplacer(\"+\", \"-\", \"/\", \"_\", \"=\", \"\").Replace(noise.Packet))\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Invalid base64 string\").Base(err)\n\t\t}\n\n\tdefault:\n\t\treturn nil, errors.New(\"Invalid packet, only rand/str/hex/base64 are supported\")\n\t}\n\n\tif noise.Delay != nil {\n\t\tNConfig.DelayMin = uint64(noise.Delay.From)\n\t\tNConfig.DelayMax = uint64(noise.Delay.To)\n\t}\n\tswitch strings.ToLower(noise.ApplyTo) {\n\tcase \"\", \"ip\", \"all\":\n\t\tNConfig.ApplyTo = \"ip\"\n\tcase \"ipv4\":\n\t\tNConfig.ApplyTo = \"ipv4\"\n\tcase \"ipv6\":\n\t\tNConfig.ApplyTo = \"ipv6\"\n\tdefault:\n\t\treturn nil, errors.New(\"Invalid applyTo, only ip/ipv4/ipv6 are supported\")\n\t}\n\treturn NConfig, nil\n}\n"
  },
  {
    "path": "infra/conf/freedom_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc TestFreedomConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(FreedomConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"domainStrategy\": \"AsIs\",\n\t\t\t\t\"redirect\": \"127.0.0.1:3366\",\n\t\t\t\t\"userLevel\": 1\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &freedom.Config{\n\t\t\t\tDomainStrategy: internet.DomainStrategy_AS_IS,\n\t\t\t\tDestinationOverride: &freedom.DestinationOverride{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPort: 3366,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUserLevel: 1,\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/general_test.go",
    "content": "package conf_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc loadJSON(creator func() Buildable) func(string) (proto.Message, error) {\n\treturn func(s string) (proto.Message, error) {\n\t\tinstance := creator()\n\t\tif err := json.Unmarshal([]byte(s), instance); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn instance.Build()\n\t}\n}\n\ntype TestCase struct {\n\tInput  string\n\tParser func(string) (proto.Message, error)\n\tOutput proto.Message\n}\n\nfunc runMultiTestCase(t *testing.T, testCases []TestCase) {\n\tfor _, testCase := range testCases {\n\t\tactual, err := testCase.Parser(testCase.Input)\n\t\tcommon.Must(err)\n\t\tif !proto.Equal(actual, testCase.Output) {\n\t\t\tt.Fatalf(\"Failed in test case:\\n%s\\nActual:\\n%v\\nExpected:\\n%v\", testCase.Input, actual, testCase.Output)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "infra/conf/grpc.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/transport/internet/grpc\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype GRPCConfig struct {\n\tAuthority           string `json:\"authority\"`\n\tServiceName         string `json:\"serviceName\"`\n\tMultiMode           bool   `json:\"multiMode\"`\n\tIdleTimeout         int32  `json:\"idle_timeout\"`\n\tHealthCheckTimeout  int32  `json:\"health_check_timeout\"`\n\tPermitWithoutStream bool   `json:\"permit_without_stream\"`\n\tInitialWindowsSize  int32  `json:\"initial_windows_size\"`\n\tUserAgent           string `json:\"user_agent\"`\n}\n\nfunc (g *GRPCConfig) Build() (proto.Message, error) {\n\tif g.IdleTimeout <= 0 {\n\t\tg.IdleTimeout = 0\n\t}\n\tif g.HealthCheckTimeout <= 0 {\n\t\tg.HealthCheckTimeout = 0\n\t}\n\tif g.InitialWindowsSize < 0 {\n\t\t// default window size of gRPC-go\n\t\tg.InitialWindowsSize = 0\n\t}\n\n\treturn &grpc.Config{\n\t\tAuthority:           g.Authority,\n\t\tServiceName:         g.ServiceName,\n\t\tMultiMode:           g.MultiMode,\n\t\tIdleTimeout:         g.IdleTimeout,\n\t\tHealthCheckTimeout:  g.HealthCheckTimeout,\n\t\tPermitWithoutStream: g.PermitWithoutStream,\n\t\tInitialWindowsSize:  g.InitialWindowsSize,\n\t\tUserAgent:           g.UserAgent,\n\t}, nil\n}\n"
  },
  {
    "path": "infra/conf/http.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/proxy/http\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype HTTPAccount struct {\n\tUsername string `json:\"user\"`\n\tPassword string `json:\"pass\"`\n}\n\nfunc (v *HTTPAccount) Build() *http.Account {\n\treturn &http.Account{\n\t\tUsername: v.Username,\n\t\tPassword: v.Password,\n\t}\n}\n\ntype HTTPServerConfig struct {\n\tAccounts    []*HTTPAccount `json:\"accounts\"`\n\tTransparent bool           `json:\"allowTransparent\"`\n\tUserLevel   uint32         `json:\"userLevel\"`\n}\n\nfunc (c *HTTPServerConfig) Build() (proto.Message, error) {\n\tconfig := &http.ServerConfig{\n\t\tAllowTransparent: c.Transparent,\n\t\tUserLevel:        c.UserLevel,\n\t}\n\n\tif len(c.Accounts) > 0 {\n\t\tconfig.Accounts = make(map[string]string)\n\t\tfor _, account := range c.Accounts {\n\t\t\tconfig.Accounts[account.Username] = account.Password\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\ntype HTTPRemoteConfig struct {\n\tAddress *Address          `json:\"address\"`\n\tPort    uint16            `json:\"port\"`\n\tUsers   []json.RawMessage `json:\"users\"`\n}\n\ntype HTTPClientConfig struct {\n\tAddress  *Address          \t `json:\"address\"`\n\tPort     uint16            \t `json:\"port\"`\n\tLevel    uint32              `json:\"level\"`\n\tEmail    string              `json:\"email\"`\n\tUsername string              `json:\"user\"`\n\tPassword string              `json:\"pass\"`\n\tServers  []*HTTPRemoteConfig `json:\"servers\"`\n\tHeaders  map[string]string   `json:\"headers\"`\n}\n\nfunc (v *HTTPClientConfig) Build() (proto.Message, error) {\n\tconfig := new(http.ClientConfig)\n\tif v.Address != nil {\n\t\tv.Servers = []*HTTPRemoteConfig{\n\t\t\t{\n\t\t\t\tAddress: v.Address,\n\t\t\t\tPort:    v.Port,\n\t\t\t},\n\t\t}\n\t\tif len(v.Username) > 0 {\n\t\t\tv.Servers[0].Users = []json.RawMessage{{}}\n\t\t}\n\t}\n\tif len(v.Servers) != 1 {\n\t\treturn nil, errors.New(`HTTP settings: \"servers\" should have one and only one member. Multiple endpoints in \"servers\" should use multiple HTTP outbounds and routing balancer instead`)\n\t}\n\tfor _, serverConfig := range v.Servers {\n\t\tif len(serverConfig.Users) > 1 {\n\t\t\treturn nil, errors.New(`HTTP servers: \"users\" should have one member at most. Multiple members in \"users\" should use multiple HTTP outbounds and routing balancer instead`)\n\t\t}\n\t\tserver := &protocol.ServerEndpoint{\n\t\t\tAddress: serverConfig.Address.Build(),\n\t\t\tPort:    uint32(serverConfig.Port),\n\t\t}\n\t\tfor _, rawUser := range serverConfig.Users {\n\t\t\tuser := new(protocol.User)\n\t\t\tif v.Address != nil {\n\t\t\t\tuser.Level = v.Level\n\t\t\t\tuser.Email = v.Email\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, user); err != nil {\n\t\t\t\t\treturn nil, errors.New(\"failed to parse HTTP user\").Base(err).AtError()\n\t\t\t\t}\n\t\t\t}\n\t\t\taccount := new(HTTPAccount)\n\t\t\tif v.Address != nil {\n\t\t\t\taccount.Username = v.Username\n\t\t\t\taccount.Password = v.Password\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, account); err != nil {\n\t\t\t\t\treturn nil, errors.New(\"failed to parse HTTP account\").Base(err).AtError()\n\t\t\t\t}\n\t\t\t}\n\t\t\tuser.Account = serial.ToTypedMessage(account.Build())\n\t\t\tserver.User = user\n\t\t\tbreak\n\t\t}\n\t\tconfig.Server = server\n\t\tbreak\n\t}\n\tconfig.Header = make([]*http.Header, 0, 32)\n\tfor key, value := range v.Headers {\n\t\tconfig.Header = append(config.Header, &http.Header{\n\t\t\tKey:   key,\n\t\t\tValue: value,\n\t\t})\n\t}\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/http_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/http\"\n)\n\nfunc TestHTTPServerConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(HTTPServerConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"accounts\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"user\": \"my-username\",\n\t\t\t\t\t\t\"pass\": \"my-password\"\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"allowTransparent\": true,\n\t\t\t\t\"userLevel\": 1\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &http.ServerConfig{\n\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\"my-username\": \"my-password\",\n\t\t\t\t},\n\t\t\t\tAllowTransparent: true,\n\t\t\t\tUserLevel:        1,\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/hysteria.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/proxy/hysteria\"\n\t\"github.com/xtls/xray-core/proxy/hysteria/account\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype HysteriaClientConfig struct {\n\tVersion int32    `json:\"version\"`\n\tAddress *Address `json:\"address\"`\n\tPort    uint16   `json:\"port\"`\n}\n\nfunc (c *HysteriaClientConfig) Build() (proto.Message, error) {\n\tif c.Version != 2 {\n\t\treturn nil, errors.New(\"version != 2\")\n\t}\n\n\tconfig := &hysteria.ClientConfig{}\n\tconfig.Version = c.Version\n\tconfig.Server = &protocol.ServerEndpoint{\n\t\tAddress: c.Address.Build(),\n\t\tPort:    uint32(c.Port),\n\t}\n\n\treturn config, nil\n}\n\ntype HysteriaUserConfig struct {\n\tAuth  string `json:\"auth\"`\n\tLevel uint32 `json:\"level\"`\n\tEmail string `json:\"email\"`\n}\n\ntype HysteriaServerConfig struct {\n\tVersion int32                 `json:\"version\"`\n\tUsers   []*HysteriaUserConfig `json:\"clients\"`\n}\n\nfunc (c *HysteriaServerConfig) Build() (proto.Message, error) {\n\tconfig := new(hysteria.ServerConfig)\n\n\tif c.Users != nil {\n\t\tfor _, user := range c.Users {\n\t\t\taccount := &account.Account{\n\t\t\t\tAuth: user.Auth,\n\t\t\t}\n\t\t\tconfig.Users = append(config.Users, &protocol.User{\n\t\t\t\tEmail:   user.Email,\n\t\t\t\tLevel:   user.Level,\n\t\t\t\tAccount: serial.ToTypedMessage(account),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/init.go",
    "content": "package conf\n\nfunc init() {\n\tRegisterConfigureFilePostProcessingStage(\"FakeDNS\", &FakeDNSPostProcessingStage{})\n}\n"
  },
  {
    "path": "infra/conf/json/reader.go",
    "content": "package json\n\nimport (\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\n// State is the internal state of parser.\ntype State byte\n\nconst (\n\tStateContent State = iota\n\tStateEscape\n\tStateDoubleQuote\n\tStateDoubleQuoteEscape\n\tStateSingleQuote\n\tStateSingleQuoteEscape\n\tStateComment\n\tStateSlash\n\tStateMultilineComment\n\tStateMultilineCommentStar\n)\n\n// Reader is a reader for filtering comments.\n// It supports Java style single and multi line comment syntax, and Python style single line comment syntax.\ntype Reader struct {\n\tio.Reader\n\n\tstate State\n\tbr    *buf.BufferedReader\n}\n\n// Read implements io.Reader.Read(). Buffer must be at least 3 bytes.\nfunc (v *Reader) Read(b []byte) (int, error) {\n\tif v.br == nil {\n\t\tv.br = &buf.BufferedReader{Reader: buf.NewReader(v.Reader)}\n\t}\n\n\tp := b[:0]\n\tfor len(p) < len(b)-2 {\n\t\tx, err := v.br.ReadByte()\n\t\tif err != nil {\n\t\t\tif len(p) == 0 {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\treturn len(p), nil\n\t\t}\n\t\tswitch v.state {\n\t\tcase StateContent:\n\t\t\tswitch x {\n\t\t\tcase '\"':\n\t\t\t\tv.state = StateDoubleQuote\n\t\t\t\tp = append(p, x)\n\t\t\tcase '\\'':\n\t\t\t\tv.state = StateSingleQuote\n\t\t\t\tp = append(p, x)\n\t\t\tcase '\\\\':\n\t\t\t\tv.state = StateEscape\n\t\t\tcase '#':\n\t\t\t\tv.state = StateComment\n\t\t\tcase '/':\n\t\t\t\tv.state = StateSlash\n\t\t\tdefault:\n\t\t\t\tp = append(p, x)\n\t\t\t}\n\t\tcase StateEscape:\n\t\t\tp = append(p, '\\\\', x)\n\t\t\tv.state = StateContent\n\t\tcase StateDoubleQuote:\n\t\t\tswitch x {\n\t\t\tcase '\"':\n\t\t\t\tv.state = StateContent\n\t\t\t\tp = append(p, x)\n\t\t\tcase '\\\\':\n\t\t\t\tv.state = StateDoubleQuoteEscape\n\t\t\tdefault:\n\t\t\t\tp = append(p, x)\n\t\t\t}\n\t\tcase StateDoubleQuoteEscape:\n\t\t\tp = append(p, '\\\\', x)\n\t\t\tv.state = StateDoubleQuote\n\t\tcase StateSingleQuote:\n\t\t\tswitch x {\n\t\t\tcase '\\'':\n\t\t\t\tv.state = StateContent\n\t\t\t\tp = append(p, x)\n\t\t\tcase '\\\\':\n\t\t\t\tv.state = StateSingleQuoteEscape\n\t\t\tdefault:\n\t\t\t\tp = append(p, x)\n\t\t\t}\n\t\tcase StateSingleQuoteEscape:\n\t\t\tp = append(p, '\\\\', x)\n\t\t\tv.state = StateSingleQuote\n\t\tcase StateComment:\n\t\t\tif x == '\\n' {\n\t\t\t\tv.state = StateContent\n\t\t\t\tp = append(p, '\\n')\n\t\t\t}\n\t\tcase StateSlash:\n\t\t\tswitch x {\n\t\t\tcase '/':\n\t\t\t\tv.state = StateComment\n\t\t\tcase '*':\n\t\t\t\tv.state = StateMultilineComment\n\t\t\tdefault:\n\t\t\t\tp = append(p, '/', x)\n\t\t\t}\n\t\tcase StateMultilineComment:\n\t\t\tswitch x {\n\t\t\tcase '*':\n\t\t\t\tv.state = StateMultilineCommentStar\n\t\t\tcase '\\n':\n\t\t\t\tp = append(p, '\\n')\n\t\t\t}\n\t\tcase StateMultilineCommentStar:\n\t\t\tswitch x {\n\t\t\tcase '/':\n\t\t\t\tv.state = StateContent\n\t\t\tcase '*':\n\t\t\t\t// Stay\n\t\t\tcase '\\n':\n\t\t\t\tp = append(p, '\\n')\n\t\t\tdefault:\n\t\t\t\tv.state = StateMultilineComment\n\t\t\t}\n\t\tdefault:\n\t\t\tpanic(\"Unknown state.\")\n\t\t}\n\t}\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "infra/conf/json/reader_test.go",
    "content": "package json_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/infra/conf/json\"\n)\n\nfunc TestReader(t *testing.T) {\n\tdata := []struct {\n\t\tinput  string\n\t\toutput string\n\t}{\n\t\t{\n\t\t\t`\ncontent #comment 1\n#comment 2\ncontent 2`,\n\t\t\t`\ncontent \n\ncontent 2`,\n\t\t},\n\t\t{`content`, `content`},\n\t\t{\" \", \" \"},\n\t\t{`con/*abcd*/tent`, \"content\"},\n\t\t{`\ntext // adlkhdf /*\n//comment adfkj\ntext 2*/`, `\ntext \n\ntext 2*`},\n\t\t{`\"//\"content`, `\"//\"content`},\n\t\t{`abcd'//'abcd`, `abcd'//'abcd`},\n\t\t{`\"\\\"\"`, `\"\\\"\"`},\n\t\t{`\\\"/*abcd*/\\\"`, `\\\"\\\"`},\n\t}\n\n\tfor _, testCase := range data {\n\t\treader := &Reader{\n\t\t\tReader: bytes.NewReader([]byte(testCase.input)),\n\t\t}\n\n\t\tactual := make([]byte, 1024)\n\t\tn, err := reader.Read(actual)\n\t\tcommon.Must(err)\n\t\tif r := cmp.Diff(string(actual[:n]), testCase.output); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t}\n}\n\nfunc TestReader1(t *testing.T) {\n\ttype dataStruct struct {\n\t\tinput  string\n\t\toutput string\n\t}\n\n\tbufLen := 8\n\n\tdata := []dataStruct{\n\t\t{\"loooooooooooooooooooooooooooooooooooooooog\", \"loooooooooooooooooooooooooooooooooooooooog\"},\n\t\t{`{\"t\": \"\\/testlooooooooooooooooooooooooooooong\"}`, `{\"t\": \"\\/testlooooooooooooooooooooooooooooong\"}`},\n\t\t{`{\"t\": \"\\/test\"}`, `{\"t\": \"\\/test\"}`},\n\t\t{`\"\\// fake comment\"`, `\"\\// fake comment\"`},\n\t\t{`\"\\/\\/\\/\\/\\/\"`, `\"\\/\\/\\/\\/\\/\"`},\n\t}\n\n\tfor _, testCase := range data {\n\t\treader := &Reader{\n\t\t\tReader: bytes.NewReader([]byte(testCase.input)),\n\t\t}\n\t\ttarget := make([]byte, 0)\n\t\tbuf := make([]byte, bufLen)\n\t\tvar n int\n\t\tvar err error\n\t\tfor n, err = reader.Read(buf); err == nil; n, err = reader.Read(buf) {\n\t\t\tif n > len(buf) {\n\t\t\t\tt.Error(\"n: \", n)\n\t\t\t}\n\t\t\ttarget = append(target, buf[:n]...)\n\t\t\tbuf = make([]byte, bufLen)\n\t\t}\n\t\tif err != nil && err != io.EOF {\n\t\t\tt.Error(\"error: \", err)\n\t\t}\n\t\tif string(target) != testCase.output {\n\t\t\tt.Error(\"got \", string(target), \" want \", testCase.output)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "infra/conf/lint.go",
    "content": "package conf\n\nimport \"github.com/xtls/xray-core/common/errors\"\n\ntype ConfigureFilePostProcessingStage interface {\n\tProcess(conf *Config) error\n}\n\nvar configureFilePostProcessingStages map[string]ConfigureFilePostProcessingStage\n\nfunc RegisterConfigureFilePostProcessingStage(name string, stage ConfigureFilePostProcessingStage) {\n\tif configureFilePostProcessingStages == nil {\n\t\tconfigureFilePostProcessingStages = make(map[string]ConfigureFilePostProcessingStage)\n\t}\n\tconfigureFilePostProcessingStages[name] = stage\n}\n\nfunc PostProcessConfigureFile(conf *Config) error {\n\tfor k, v := range configureFilePostProcessingStages {\n\t\tif err := v.Process(conf); err != nil {\n\t\t\treturn errors.New(\"Rejected by Postprocessing Stage \", k).AtError().Base(err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "infra/conf/loader.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype ConfigCreator func() interface{}\n\ntype ConfigCreatorCache map[string]ConfigCreator\n\nfunc (v ConfigCreatorCache) RegisterCreator(id string, creator ConfigCreator) error {\n\tif _, found := v[id]; found {\n\t\treturn errors.New(id, \" already registered.\").AtError()\n\t}\n\n\tv[id] = creator\n\treturn nil\n}\n\nfunc (v ConfigCreatorCache) CreateConfig(id string) (interface{}, error) {\n\tcreator, found := v[id]\n\tif !found {\n\t\treturn nil, errors.New(\"unknown config id: \", id)\n\t}\n\treturn creator(), nil\n}\n\ntype JSONConfigLoader struct {\n\tcache     ConfigCreatorCache\n\tidKey     string\n\tconfigKey string\n}\n\nfunc NewJSONConfigLoader(cache ConfigCreatorCache, idKey string, configKey string) *JSONConfigLoader {\n\treturn &JSONConfigLoader{\n\t\tidKey:     idKey,\n\t\tconfigKey: configKey,\n\t\tcache:     cache,\n\t}\n}\n\nfunc (v *JSONConfigLoader) LoadWithID(raw []byte, id string) (interface{}, error) {\n\tid = strings.ToLower(id)\n\tconfig, err := v.cache.CreateConfig(id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := json.Unmarshal(raw, config); err != nil {\n\t\treturn nil, err\n\t}\n\treturn config, nil\n}\n\nfunc (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) {\n\tvar obj map[string]json.RawMessage\n\tif err := json.Unmarshal(raw, &obj); err != nil {\n\t\treturn nil, \"\", err\n\t}\n\trawID, found := obj[v.idKey]\n\tif !found {\n\t\treturn nil, \"\", errors.New(v.idKey, \" not found in JSON context\").AtError()\n\t}\n\tvar id string\n\tif err := json.Unmarshal(rawID, &id); err != nil {\n\t\treturn nil, \"\", err\n\t}\n\trawConfig := json.RawMessage(raw)\n\tif len(v.configKey) > 0 {\n\t\tconfigValue, found := obj[v.configKey]\n\t\tif found {\n\t\t\trawConfig = configValue\n\t\t} else {\n\t\t\t// Default to empty json object.\n\t\t\trawConfig = json.RawMessage([]byte(\"{}\"))\n\t\t}\n\t}\n\tconfig, err := v.LoadWithID([]byte(rawConfig), id)\n\tif err != nil {\n\t\treturn nil, id, err\n\t}\n\treturn config, id, nil\n}\n"
  },
  {
    "path": "infra/conf/log.go",
    "content": "package conf\n\nimport (\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n)\n\nfunc DefaultLogConfig() *log.Config {\n\treturn &log.Config{\n\t\tAccessLogType: log.LogType_None,\n\t\tErrorLogType:  log.LogType_Console,\n\t\tErrorLogLevel: clog.Severity_Warning,\n\t}\n}\n\ntype LogConfig struct {\n\tAccessLog   string `json:\"access\"`\n\tErrorLog    string `json:\"error\"`\n\tLogLevel    string `json:\"loglevel\"`\n\tDNSLog      bool   `json:\"dnsLog\"`\n\tMaskAddress string `json:\"maskAddress\"`\n}\n\nfunc (v *LogConfig) Build() *log.Config {\n\tif v == nil {\n\t\treturn nil\n\t}\n\tconfig := &log.Config{\n\t\tErrorLogType:  log.LogType_Console,\n\t\tAccessLogType: log.LogType_Console,\n\t\tEnableDnsLog:  v.DNSLog,\n\t}\n\n\tif v.AccessLog == \"none\" {\n\t\tconfig.AccessLogType = log.LogType_None\n\t} else if len(v.AccessLog) > 0 {\n\t\tconfig.AccessLogPath = v.AccessLog\n\t\tconfig.AccessLogType = log.LogType_File\n\t}\n\tif v.ErrorLog == \"none\" {\n\t\tconfig.ErrorLogType = log.LogType_None\n\t} else if len(v.ErrorLog) > 0 {\n\t\tconfig.ErrorLogPath = v.ErrorLog\n\t\tconfig.ErrorLogType = log.LogType_File\n\t}\n\n\tlevel := strings.ToLower(v.LogLevel)\n\tswitch level {\n\tcase \"debug\":\n\t\tconfig.ErrorLogLevel = clog.Severity_Debug\n\tcase \"info\":\n\t\tconfig.ErrorLogLevel = clog.Severity_Info\n\tcase \"error\":\n\t\tconfig.ErrorLogLevel = clog.Severity_Error\n\tcase \"none\":\n\t\tconfig.ErrorLogType = log.LogType_None\n\t\tconfig.AccessLogType = log.LogType_None\n\tdefault:\n\t\tconfig.ErrorLogLevel = clog.Severity_Warning\n\t}\n\tconfig.MaskAddress = v.MaskAddress\n\treturn config\n}\n"
  },
  {
    "path": "infra/conf/loopback.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/proxy/loopback\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype LoopbackConfig struct {\n\tInboundTag string `json:\"inboundTag\"`\n}\n\nfunc (l LoopbackConfig) Build() (proto.Message, error) {\n\treturn &loopback.Config{InboundTag: l.InboundTag}, nil\n}\n"
  },
  {
    "path": "infra/conf/metrics.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/app/metrics\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype MetricsConfig struct {\n\tTag    string `json:\"tag\"`\n\tListen string `json:\"listen\"`\n}\n\nfunc (c *MetricsConfig) Build() (*metrics.Config, error) {\n\tif c.Listen == \"\" && c.Tag == \"\" {\n\t\treturn nil, errors.New(\"Metrics must have a tag or listen address.\")\n\t}\n\t// If the tag is empty but have \"listen\" set a default \"Metrics\" for compatibility.\n\tif c.Tag == \"\" {\n\t\tc.Tag = \"Metrics\"\n\t}\n\n\treturn &metrics.Config{\n\t\tTag:    c.Tag,\n\t\tListen: c.Listen,\n\t}, nil\n}\n"
  },
  {
    "path": "infra/conf/observatory.go",
    "content": "package conf\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/app/observatory\"\n\t\"github.com/xtls/xray-core/app/observatory/burst\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/infra/conf/cfgcommon/duration\"\n)\n\ntype ObservatoryConfig struct {\n\tSubjectSelector   []string          `json:\"subjectSelector\"`\n\tProbeURL          string            `json:\"probeURL\"`\n\tProbeInterval     duration.Duration `json:\"probeInterval\"`\n\tEnableConcurrency bool              `json:\"enableConcurrency\"`\n}\n\nfunc (o *ObservatoryConfig) Build() (proto.Message, error) {\n\treturn &observatory.Config{SubjectSelector: o.SubjectSelector, ProbeUrl: o.ProbeURL, ProbeInterval: int64(o.ProbeInterval), EnableConcurrency: o.EnableConcurrency}, nil\n}\n\ntype BurstObservatoryConfig struct {\n\tSubjectSelector []string `json:\"subjectSelector\"`\n\t// health check settings\n\tHealthCheck *healthCheckSettings `json:\"pingConfig,omitempty\"`\n}\n\nfunc (b BurstObservatoryConfig) Build() (proto.Message, error) {\n\tif b.HealthCheck == nil {\n\t\treturn nil, errors.New(\"BurstObservatory requires a valid pingConfig\")\n\t}\n\tif result, err := b.HealthCheck.Build(); err == nil {\n\t\treturn &burst.Config{SubjectSelector: b.SubjectSelector, PingConfig: result.(*burst.HealthPingConfig)}, nil\n\t} else {\n\t\treturn nil, err\n\t}\n}\n"
  },
  {
    "path": "infra/conf/policy.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/app/policy\"\n)\n\ntype Policy struct {\n\tHandshake         *uint32 `json:\"handshake\"`\n\tConnectionIdle    *uint32 `json:\"connIdle\"`\n\tUplinkOnly        *uint32 `json:\"uplinkOnly\"`\n\tDownlinkOnly      *uint32 `json:\"downlinkOnly\"`\n\tStatsUserUplink   bool    `json:\"statsUserUplink\"`\n\tStatsUserDownlink bool    `json:\"statsUserDownlink\"`\n\tStatsUserOnline   bool    `json:\"statsUserOnline\"`\n\tBufferSize        *int32  `json:\"bufferSize\"`\n}\n\nfunc (t *Policy) Build() (*policy.Policy, error) {\n\tconfig := new(policy.Policy_Timeout)\n\tif t.Handshake != nil {\n\t\tconfig.Handshake = &policy.Second{Value: *t.Handshake}\n\t}\n\tif t.ConnectionIdle != nil {\n\t\tconfig.ConnectionIdle = &policy.Second{Value: *t.ConnectionIdle}\n\t}\n\tif t.UplinkOnly != nil {\n\t\tconfig.UplinkOnly = &policy.Second{Value: *t.UplinkOnly}\n\t}\n\tif t.DownlinkOnly != nil {\n\t\tconfig.DownlinkOnly = &policy.Second{Value: *t.DownlinkOnly}\n\t}\n\n\tp := &policy.Policy{\n\t\tTimeout: config,\n\t\tStats: &policy.Policy_Stats{\n\t\t\tUserUplink:   t.StatsUserUplink,\n\t\t\tUserDownlink: t.StatsUserDownlink,\n\t\t\tUserOnline:   t.StatsUserOnline,\n\t\t},\n\t}\n\n\tif t.BufferSize != nil {\n\t\tbs := int32(-1)\n\t\tif *t.BufferSize >= 0 {\n\t\t\tbs = (*t.BufferSize) * 1024\n\t\t}\n\t\tp.Buffer = &policy.Policy_Buffer{\n\t\t\tConnection: bs,\n\t\t}\n\t}\n\n\treturn p, nil\n}\n\ntype SystemPolicy struct {\n\tStatsInboundUplink    bool `json:\"statsInboundUplink\"`\n\tStatsInboundDownlink  bool `json:\"statsInboundDownlink\"`\n\tStatsOutboundUplink   bool `json:\"statsOutboundUplink\"`\n\tStatsOutboundDownlink bool `json:\"statsOutboundDownlink\"`\n}\n\nfunc (p *SystemPolicy) Build() (*policy.SystemPolicy, error) {\n\treturn &policy.SystemPolicy{\n\t\tStats: &policy.SystemPolicy_Stats{\n\t\t\tInboundUplink:    p.StatsInboundUplink,\n\t\t\tInboundDownlink:  p.StatsInboundDownlink,\n\t\t\tOutboundUplink:   p.StatsOutboundUplink,\n\t\t\tOutboundDownlink: p.StatsOutboundDownlink,\n\t\t},\n\t}, nil\n}\n\ntype PolicyConfig struct {\n\tLevels map[uint32]*Policy `json:\"levels\"`\n\tSystem *SystemPolicy      `json:\"system\"`\n}\n\nfunc (c *PolicyConfig) Build() (*policy.Config, error) {\n\tlevels := make(map[uint32]*policy.Policy)\n\tfor l, p := range c.Levels {\n\t\tif p != nil {\n\t\t\tpp, err := p.Build()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tlevels[l] = pp\n\t\t}\n\t}\n\tconfig := &policy.Config{\n\t\tLevel: levels,\n\t}\n\n\tif c.System != nil {\n\t\tsc, err := c.System.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.System = sc\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/policy_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n)\n\nfunc TestBufferSize(t *testing.T) {\n\tcases := []struct {\n\t\tInput  int32\n\t\tOutput int32\n\t}{\n\t\t{\n\t\t\tInput:  0,\n\t\t\tOutput: 0,\n\t\t},\n\t\t{\n\t\t\tInput:  -1,\n\t\t\tOutput: -1,\n\t\t},\n\t\t{\n\t\t\tInput:  1,\n\t\t\tOutput: 1024,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tbs := c.Input\n\t\tpConf := Policy{\n\t\t\tBufferSize: &bs,\n\t\t}\n\t\tp, err := pConf.Build()\n\t\tcommon.Must(err)\n\t\tif p.Buffer.Connection != c.Output {\n\t\t\tt.Error(\"expected buffer size \", c.Output, \" but got \", p.Buffer.Connection)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "infra/conf/reverse.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/app/reverse\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype BridgeConfig struct {\n\tTag    string `json:\"tag\"`\n\tDomain string `json:\"domain\"`\n}\n\nfunc (c *BridgeConfig) Build() (*reverse.BridgeConfig, error) {\n\treturn &reverse.BridgeConfig{\n\t\tTag:    c.Tag,\n\t\tDomain: c.Domain,\n\t}, nil\n}\n\ntype PortalConfig struct {\n\tTag    string `json:\"tag\"`\n\tDomain string `json:\"domain\"`\n}\n\nfunc (c *PortalConfig) Build() (*reverse.PortalConfig, error) {\n\treturn &reverse.PortalConfig{\n\t\tTag:    c.Tag,\n\t\tDomain: c.Domain,\n\t}, nil\n}\n\ntype ReverseConfig struct {\n\tBridges []BridgeConfig `json:\"bridges\"`\n\tPortals []PortalConfig `json:\"portals\"`\n}\n\nfunc (c *ReverseConfig) Build() (proto.Message, error) {\n\tconfig := &reverse.Config{}\n\tfor _, bconfig := range c.Bridges {\n\t\tb, err := bconfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.BridgeConfig = append(config.BridgeConfig, b)\n\t}\n\n\tfor _, pconfig := range c.Portals {\n\t\tp, err := pconfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.PortalConfig = append(config.PortalConfig, p)\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/reverse_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/reverse\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n)\n\nfunc TestReverseConfig(t *testing.T) {\n\tcreator := func() conf.Buildable {\n\t\treturn new(conf.ReverseConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"bridges\": [{\n\t\t\t\t\t\"tag\": \"test\",\n\t\t\t\t\t\"domain\": \"test.example.com\"\n\t\t\t\t}]\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &reverse.Config{\n\t\t\t\tBridgeConfig: []*reverse.BridgeConfig{\n\t\t\t\t\t{Tag: \"test\", Domain: \"test.example.com\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"portals\": [{\n\t\t\t\t\t\"tag\": \"test\",\n\t\t\t\t\t\"domain\": \"test.example.com\"\n\t\t\t\t}]\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &reverse.Config{\n\t\t\t\tPortalConfig: []*reverse.PortalConfig{\n\t\t\t\t\t{Tag: \"test\", Domain: \"test.example.com\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/router.go",
    "content": "package conf\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// StrategyConfig represents a strategy config\ntype StrategyConfig struct {\n\tType     string           `json:\"type\"`\n\tSettings *json.RawMessage `json:\"settings\"`\n}\n\ntype BalancingRule struct {\n\tTag         string         `json:\"tag\"`\n\tSelectors   StringList     `json:\"selector\"`\n\tStrategy    StrategyConfig `json:\"strategy\"`\n\tFallbackTag string         `json:\"fallbackTag\"`\n}\n\n// Build builds the balancing rule\nfunc (r *BalancingRule) Build() (*router.BalancingRule, error) {\n\tif r.Tag == \"\" {\n\t\treturn nil, errors.New(\"empty balancer tag\")\n\t}\n\tif len(r.Selectors) == 0 {\n\t\treturn nil, errors.New(\"empty selector list\")\n\t}\n\n\tr.Strategy.Type = strings.ToLower(r.Strategy.Type)\n\tswitch r.Strategy.Type {\n\tcase \"\":\n\t\tr.Strategy.Type = strategyRandom\n\tcase strategyRandom, strategyLeastLoad, strategyLeastPing, strategyRoundRobin:\n\tdefault:\n\t\treturn nil, errors.New(\"unknown balancing strategy: \" + r.Strategy.Type)\n\t}\n\n\tsettings := []byte(\"{}\")\n\tif r.Strategy.Settings != nil {\n\t\tsettings = ([]byte)(*r.Strategy.Settings)\n\t}\n\trawConfig, err := strategyConfigLoader.LoadWithID(settings, r.Strategy.Type)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse to strategy config.\").Base(err)\n\t}\n\tvar ts proto.Message\n\tif builder, ok := rawConfig.(Buildable); ok {\n\t\tts, err = builder.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &router.BalancingRule{\n\t\tStrategy:         r.Strategy.Type,\n\t\tStrategySettings: serial.ToTypedMessage(ts),\n\t\tFallbackTag:      r.FallbackTag,\n\t\tOutboundSelector: r.Selectors,\n\t\tTag:              r.Tag,\n\t}, nil\n}\n\ntype RouterConfig struct {\n\tRuleList       []json.RawMessage `json:\"rules\"`\n\tDomainStrategy *string           `json:\"domainStrategy\"`\n\tBalancers      []*BalancingRule  `json:\"balancers\"`\n}\n\nfunc (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {\n\tds := \"\"\n\tif c.DomainStrategy != nil {\n\t\tds = *c.DomainStrategy\n\t}\n\n\tswitch strings.ToLower(ds) {\n\tcase \"ipifnonmatch\":\n\t\treturn router.Config_IpIfNonMatch\n\tcase \"ipondemand\":\n\t\treturn router.Config_IpOnDemand\n\tdefault:\n\t\treturn router.Config_AsIs\n\t}\n}\n\nfunc (c *RouterConfig) Build() (*router.Config, error) {\n\tconfig := new(router.Config)\n\tconfig.DomainStrategy = c.getDomainStrategy()\n\n\tvar rawRuleList []json.RawMessage\n\tif c != nil {\n\t\trawRuleList = c.RuleList\n\t}\n\n\tfor _, rawRule := range rawRuleList {\n\t\trule, err := parseRule(rawRule)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tconfig.Rule = append(config.Rule, rule)\n\t}\n\tfor _, rawBalancer := range c.Balancers {\n\t\tbalancer, err := rawBalancer.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.BalancingRule = append(config.BalancingRule, balancer)\n\t}\n\treturn config, nil\n}\n\ntype RouterRule struct {\n\tRuleTag     string `json:\"ruleTag\"`\n\tOutboundTag string `json:\"outboundTag\"`\n\tBalancerTag string `json:\"balancerTag\"`\n}\n\nfunc parseIP(s string) (*router.CIDR, error) {\n\tvar addr, mask string\n\ti := strings.Index(s, \"/\")\n\tif i < 0 {\n\t\taddr = s\n\t} else {\n\t\taddr = s[:i]\n\t\tmask = s[i+1:]\n\t}\n\tip := net.ParseAddress(addr)\n\tswitch ip.Family() {\n\tcase net.AddressFamilyIPv4:\n\t\tbits := uint32(32)\n\t\tif len(mask) > 0 {\n\t\t\tbits64, err := strconv.ParseUint(mask, 10, 32)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"invalid network mask for router: \", mask).Base(err)\n\t\t\t}\n\t\t\tbits = uint32(bits64)\n\t\t}\n\t\tif bits > 32 {\n\t\t\treturn nil, errors.New(\"invalid network mask for router: \", bits)\n\t\t}\n\t\treturn &router.CIDR{\n\t\t\tIp:     []byte(ip.IP()),\n\t\t\tPrefix: bits,\n\t\t}, nil\n\tcase net.AddressFamilyIPv6:\n\t\tbits := uint32(128)\n\t\tif len(mask) > 0 {\n\t\t\tbits64, err := strconv.ParseUint(mask, 10, 32)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"invalid network mask for router: \", mask).Base(err)\n\t\t\t}\n\t\t\tbits = uint32(bits64)\n\t\t}\n\t\tif bits > 128 {\n\t\t\treturn nil, errors.New(\"invalid network mask for router: \", bits)\n\t\t}\n\t\treturn &router.CIDR{\n\t\t\tIp:     []byte(ip.IP()),\n\t\t\tPrefix: bits,\n\t\t}, nil\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported address for router: \", s)\n\t}\n}\n\nfunc loadFile(file, code string) ([]byte, error) {\n\truntime.GC()\n\tr, err := filesystem.OpenAsset(file)\n\tdefer r.Close()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to open file: \", file).Base(err)\n\t}\n\tbs := find(r, []byte(code))\n\tif bs == nil {\n\t\treturn nil, errors.New(\"code not found in \", file, \": \", code)\n\t}\n\treturn bs, nil\n}\n\nfunc loadIP(file, code string) ([]*router.CIDR, error) {\n\tbs, err := loadFile(file, code)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar geoip router.GeoIP\n\tif err := proto.Unmarshal(bs, &geoip); err != nil {\n\t\treturn nil, errors.New(\"error unmarshal IP in \", file, \": \", code).Base(err)\n\t}\n\tdefer runtime.GC() // or debug.FreeOSMemory()\n\treturn geoip.Cidr, nil\n}\n\nfunc loadSite(file, code string) ([]*router.Domain, error) {\n\n\t// Check if domain matcher cache is provided via environment\n\tdomainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return \"\" })\n\tif domainMatcherPath != \"\" {\n\t\treturn []*router.Domain{{}}, nil\n\t}\n\n\tbs, err := loadFile(file, code)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar geosite router.GeoSite\n\tif err := proto.Unmarshal(bs, &geosite); err != nil {\n\t\treturn nil, errors.New(\"error unmarshal Site in \", file, \": \", code).Base(err)\n\t}\n\tdefer runtime.GC() // or debug.FreeOSMemory()\n\treturn geosite.Domain, nil\n}\n\nfunc decodeVarint(r *bufio.Reader) (uint64, error) {\n\tvar x uint64\n\tfor shift := uint(0); shift < 64; shift += 7 {\n\t\tb, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tx |= (uint64(b) & 0x7F) << shift\n\t\tif (b & 0x80) == 0 {\n\t\t\treturn x, nil\n\t\t}\n\t}\n\t// The number is too large to represent in a 64-bit value.\n\treturn 0, errors.New(\"varint overflow\")\n}\n\nfunc find(r io.Reader, code []byte) []byte {\n\tcodeL := len(code)\n\tif codeL == 0 {\n\t\treturn nil\n\t}\n\n\tbr := bufio.NewReaderSize(r, 64*1024)\n\tneed := 2 + codeL\n\tprefixBuf := make([]byte, need)\n\n\tfor {\n\t\tif _, err := br.ReadByte(); err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tx, err := decodeVarint(br)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tbodyL := int(x)\n\t\tif bodyL <= 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tprefixL := bodyL\n\t\tif prefixL > need {\n\t\t\tprefixL = need\n\t\t}\n\t\tprefix := prefixBuf[:prefixL]\n\t\tif _, err := io.ReadFull(br, prefix); err != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tmatch := false\n\t\tif bodyL >= need {\n\t\t\tif int(prefix[1]) == codeL && bytes.Equal(prefix[2:need], code) {\n\t\t\t\tmatch = true\n\t\t\t}\n\t\t}\n\n\t\tremain := bodyL - prefixL\n\t\tif match {\n\t\t\tout := make([]byte, bodyL)\n\t\t\tcopy(out, prefix)\n\t\t\tif remain > 0 {\n\t\t\t\tif _, err := io.ReadFull(br, out[prefixL:]); err != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\n\t\tif remain > 0 {\n\t\t\tif _, err := br.Discard(remain); err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype AttributeMatcher interface {\n\tMatch(*router.Domain) bool\n}\n\ntype BooleanMatcher string\n\nfunc (m BooleanMatcher) Match(domain *router.Domain) bool {\n\tfor _, attr := range domain.Attribute {\n\t\tif attr.Key == string(m) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype AttributeList struct {\n\tmatcher []AttributeMatcher\n}\n\nfunc (al *AttributeList) Match(domain *router.Domain) bool {\n\tfor _, matcher := range al.matcher {\n\t\tif !matcher.Match(domain) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (al *AttributeList) IsEmpty() bool {\n\treturn len(al.matcher) == 0\n}\n\nfunc parseAttrs(attrs []string) *AttributeList {\n\tal := new(AttributeList)\n\tfor _, attr := range attrs {\n\t\tlc := strings.ToLower(attr)\n\t\tal.matcher = append(al.matcher, BooleanMatcher(lc))\n\t}\n\treturn al\n}\n\nfunc loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {\n\tparts := strings.Split(siteWithAttr, \"@\")\n\tif len(parts) == 0 {\n\t\treturn nil, errors.New(\"empty site\")\n\t}\n\tcountry := strings.ToUpper(parts[0])\n\tattrs := parseAttrs(parts[1:])\n\tdomains, err := loadSite(file, country)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif attrs.IsEmpty() {\n\t\treturn domains, nil\n\t}\n\n\tfilteredDomains := make([]*router.Domain, 0, len(domains))\n\tfor _, domain := range domains {\n\t\tif attrs.Match(domain) {\n\t\t\tfilteredDomains = append(filteredDomains, domain)\n\t\t}\n\t}\n\n\treturn filteredDomains, nil\n}\n\nfunc parseDomainRule(domain string) ([]*router.Domain, error) {\n\tif strings.HasPrefix(domain, \"geosite:\") {\n\t\tcountry := strings.ToUpper(domain[8:])\n\t\tdomains, err := loadGeositeWithAttr(\"geosite.dat\", country)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to load geosite: \", country).Base(err)\n\t\t}\n\t\treturn domains, nil\n\t}\n\tisExtDatFile := 0\n\t{\n\t\tconst prefix = \"ext:\"\n\t\tif strings.HasPrefix(domain, prefix) {\n\t\t\tisExtDatFile = len(prefix)\n\t\t}\n\t\tconst prefixQualified = \"ext-domain:\"\n\t\tif strings.HasPrefix(domain, prefixQualified) {\n\t\t\tisExtDatFile = len(prefixQualified)\n\t\t}\n\t}\n\tif isExtDatFile != 0 {\n\t\tkv := strings.Split(domain[isExtDatFile:], \":\")\n\t\tif len(kv) != 2 {\n\t\t\treturn nil, errors.New(\"invalid external resource: \", domain)\n\t\t}\n\t\tfilename := kv[0]\n\t\tcountry := kv[1]\n\t\tdomains, err := loadGeositeWithAttr(filename, country)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to load external sites: \", country, \" from \", filename).Base(err)\n\t\t}\n\t\treturn domains, nil\n\t}\n\n\tdomainRule := new(router.Domain)\n\tswitch {\n\tcase strings.HasPrefix(domain, \"regexp:\"):\n\t\tdomainRule.Type = router.Domain_Regex\n\t\tdomainRule.Value = domain[7:]\n\n\tcase strings.HasPrefix(domain, \"domain:\"):\n\t\tdomainRule.Type = router.Domain_Domain\n\t\tdomainRule.Value = domain[7:]\n\n\tcase strings.HasPrefix(domain, \"full:\"):\n\t\tdomainRule.Type = router.Domain_Full\n\t\tdomainRule.Value = domain[5:]\n\n\tcase strings.HasPrefix(domain, \"keyword:\"):\n\t\tdomainRule.Type = router.Domain_Plain\n\t\tdomainRule.Value = domain[8:]\n\n\tcase strings.HasPrefix(domain, \"dotless:\"):\n\t\tdomainRule.Type = router.Domain_Regex\n\t\tswitch substr := domain[8:]; {\n\t\tcase substr == \"\":\n\t\t\tdomainRule.Value = \"^[^.]*$\"\n\t\tcase !strings.Contains(substr, \".\"):\n\t\t\tdomainRule.Value = \"^[^.]*\" + substr + \"[^.]*$\"\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"substr in dotless rule should not contain a dot: \", substr)\n\t\t}\n\n\tdefault:\n\t\tdomainRule.Type = router.Domain_Plain\n\t\tdomainRule.Value = domain\n\t}\n\treturn []*router.Domain{domainRule}, nil\n}\n\nfunc ToCidrList(ips StringList) ([]*router.GeoIP, error) {\n\tvar geoipList []*router.GeoIP\n\tvar customCidrs []*router.CIDR\n\n\tfor _, ip := range ips {\n\t\tif strings.HasPrefix(ip, \"geoip:\") {\n\t\t\tcountry := ip[6:]\n\t\t\tisReverseMatch := false\n\t\t\tif strings.HasPrefix(ip, \"geoip:!\") {\n\t\t\t\tcountry = ip[7:]\n\t\t\t\tisReverseMatch = true\n\t\t\t}\n\t\t\tif len(country) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty country name in rule\")\n\t\t\t}\n\t\t\tgeoip, err := loadIP(\"geoip.dat\", strings.ToUpper(country))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to load GeoIP: \", country).Base(err)\n\t\t\t}\n\n\t\t\tgeoipList = append(geoipList, &router.GeoIP{\n\t\t\t\tCountryCode:  strings.ToUpper(country),\n\t\t\t\tCidr:         geoip,\n\t\t\t\tReverseMatch: isReverseMatch,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\tisExtDatFile := 0\n\t\t{\n\t\t\tconst prefix = \"ext:\"\n\t\t\tif strings.HasPrefix(ip, prefix) {\n\t\t\t\tisExtDatFile = len(prefix)\n\t\t\t}\n\t\t\tconst prefixQualified = \"ext-ip:\"\n\t\t\tif strings.HasPrefix(ip, prefixQualified) {\n\t\t\t\tisExtDatFile = len(prefixQualified)\n\t\t\t}\n\t\t}\n\t\tif isExtDatFile != 0 {\n\t\t\tkv := strings.Split(ip[isExtDatFile:], \":\")\n\t\t\tif len(kv) != 2 {\n\t\t\t\treturn nil, errors.New(\"invalid external resource: \", ip)\n\t\t\t}\n\n\t\t\tfilename := kv[0]\n\t\t\tcountry := kv[1]\n\t\t\tif len(filename) == 0 || len(country) == 0 {\n\t\t\t\treturn nil, errors.New(\"empty filename or empty country in rule\")\n\t\t\t}\n\n\t\t\tisReverseMatch := false\n\t\t\tif strings.HasPrefix(country, \"!\") {\n\t\t\t\tcountry = country[1:]\n\t\t\t\tisReverseMatch = true\n\t\t\t}\n\t\t\tgeoip, err := loadIP(filename, strings.ToUpper(country))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to load IPs: \", country, \" from \", filename).Base(err)\n\t\t\t}\n\n\t\t\tgeoipList = append(geoipList, &router.GeoIP{\n\t\t\t\tCountryCode:  strings.ToUpper(filename + \"_\" + country),\n\t\t\t\tCidr:         geoip,\n\t\t\t\tReverseMatch: isReverseMatch,\n\t\t\t})\n\n\t\t\tcontinue\n\t\t}\n\n\t\tipRule, err := parseIP(ip)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid IP: \", ip).Base(err)\n\t\t}\n\t\tcustomCidrs = append(customCidrs, ipRule)\n\t}\n\n\tif len(customCidrs) > 0 {\n\t\tgeoipList = append(geoipList, &router.GeoIP{\n\t\t\tCidr: customCidrs,\n\t\t})\n\t}\n\n\treturn geoipList, nil\n}\n\ntype WebhookRuleConfig struct {\n\tURL           string            `json:\"url\"`\n\tDeduplication uint32            `json:\"deduplication\"`\n\tHeaders       map[string]string `json:\"headers\"`\n}\n\nfunc parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {\n\ttype RawFieldRule struct {\n\t\tRouterRule\n\t\tDomain     *StringList        `json:\"domain\"`\n\t\tDomains    *StringList        `json:\"domains\"`\n\t\tIP         *StringList        `json:\"ip\"`\n\t\tPort       *PortList          `json:\"port\"`\n\t\tNetwork    *NetworkList       `json:\"network\"`\n\t\tSourceIP   *StringList        `json:\"sourceIP\"`\n\t\tSource     *StringList        `json:\"source\"`\n\t\tSourcePort *PortList          `json:\"sourcePort\"`\n\t\tUser       *StringList        `json:\"user\"`\n\t\tVlessRoute *PortList          `json:\"vlessRoute\"`\n\t\tInboundTag *StringList        `json:\"inboundTag\"`\n\t\tProtocols  *StringList        `json:\"protocol\"`\n\t\tAttributes map[string]string  `json:\"attrs\"`\n\t\tLocalIP    *StringList        `json:\"localIP\"`\n\t\tLocalPort  *PortList          `json:\"localPort\"`\n\t\tProcess    *StringList        `json:\"process\"`\n\t\tWebhook    *WebhookRuleConfig `json:\"webhook\"`\n\t}\n\trawFieldRule := new(RawFieldRule)\n\terr := json.Unmarshal(msg, rawFieldRule)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trule := new(router.RoutingRule)\n\trule.RuleTag = rawFieldRule.RuleTag\n\tswitch {\n\tcase len(rawFieldRule.OutboundTag) > 0:\n\t\trule.TargetTag = &router.RoutingRule_Tag{\n\t\t\tTag: rawFieldRule.OutboundTag,\n\t\t}\n\tcase len(rawFieldRule.BalancerTag) > 0:\n\t\trule.TargetTag = &router.RoutingRule_BalancingTag{\n\t\t\tBalancingTag: rawFieldRule.BalancerTag,\n\t\t}\n\tdefault:\n\t\treturn nil, errors.New(\"neither outboundTag nor balancerTag is specified in routing rule\")\n\t}\n\n\tif rawFieldRule.Domain != nil {\n\t\tfor _, domain := range *rawFieldRule.Domain {\n\t\t\trules, err := parseDomainRule(domain)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to parse domain rule: \", domain).Base(err)\n\t\t\t}\n\t\t\trule.Domain = append(rule.Domain, rules...)\n\t\t}\n\t}\n\n\tif rawFieldRule.Domains != nil {\n\t\tfor _, domain := range *rawFieldRule.Domains {\n\t\t\trules, err := parseDomainRule(domain)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to parse domain rule: \", domain).Base(err)\n\t\t\t}\n\t\t\trule.Domain = append(rule.Domain, rules...)\n\t\t}\n\t}\n\n\tif rawFieldRule.IP != nil {\n\t\tgeoipList, err := ToCidrList(*rawFieldRule.IP)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.Geoip = geoipList\n\t}\n\n\tif rawFieldRule.Port != nil {\n\t\trule.PortList = rawFieldRule.Port.Build()\n\t}\n\n\tif rawFieldRule.Network != nil {\n\t\trule.Networks = rawFieldRule.Network.Build()\n\t}\n\n\tif rawFieldRule.SourceIP == nil {\n\t\trawFieldRule.SourceIP = rawFieldRule.Source\n\t}\n\n\tif rawFieldRule.SourceIP != nil {\n\t\tgeoipList, err := ToCidrList(*rawFieldRule.SourceIP)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.SourceGeoip = geoipList\n\t}\n\n\tif rawFieldRule.SourcePort != nil {\n\t\trule.SourcePortList = rawFieldRule.SourcePort.Build()\n\t}\n\n\tif rawFieldRule.LocalIP != nil {\n\t\tgeoipList, err := ToCidrList(*rawFieldRule.LocalIP)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trule.LocalGeoip = geoipList\n\t}\n\n\tif rawFieldRule.LocalPort != nil {\n\t\trule.LocalPortList = rawFieldRule.LocalPort.Build()\n\t}\n\n\tif rawFieldRule.User != nil {\n\t\tfor _, s := range *rawFieldRule.User {\n\t\t\trule.UserEmail = append(rule.UserEmail, s)\n\t\t}\n\t}\n\n\tif rawFieldRule.VlessRoute != nil {\n\t\trule.VlessRouteList = rawFieldRule.VlessRoute.Build()\n\t}\n\n\tif rawFieldRule.InboundTag != nil {\n\t\tfor _, s := range *rawFieldRule.InboundTag {\n\t\t\trule.InboundTag = append(rule.InboundTag, s)\n\t\t}\n\t}\n\n\tif rawFieldRule.Protocols != nil {\n\t\tfor _, s := range *rawFieldRule.Protocols {\n\t\t\trule.Protocol = append(rule.Protocol, s)\n\t\t}\n\t}\n\n\tif len(rawFieldRule.Attributes) > 0 {\n\t\trule.Attributes = rawFieldRule.Attributes\n\t}\n\n\tif rawFieldRule.Process != nil && len(*rawFieldRule.Process) > 0 {\n\t\trule.Process = *rawFieldRule.Process\n\t}\n\n\tif rawFieldRule.Webhook != nil && rawFieldRule.Webhook.URL != \"\" {\n\t\trule.Webhook = &router.WebhookConfig{\n\t\t\tUrl:           rawFieldRule.Webhook.URL,\n\t\t\tDeduplication: rawFieldRule.Webhook.Deduplication,\n\t\t\tHeaders:       rawFieldRule.Webhook.Headers,\n\t\t}\n\t}\n\n\treturn rule, nil\n}\n\nfunc parseRule(msg json.RawMessage) (*router.RoutingRule, error) {\n\trawRule := new(RouterRule)\n\terr := json.Unmarshal(msg, rawRule)\n\tif err != nil {\n\t\treturn nil, errors.New(\"invalid router rule\").Base(err)\n\t}\n\n\tfieldrule, err := parseFieldRule(msg)\n\tif err != nil {\n\t\treturn nil, errors.New(\"invalid field rule\").Base(err)\n\t}\n\treturn fieldrule, nil\n}\n"
  },
  {
    "path": "infra/conf/router_strategy.go",
    "content": "package conf\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/observatory/burst\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/infra/conf/cfgcommon/duration\"\n)\n\nconst (\n\tstrategyRandom     string = \"random\"\n\tstrategyLeastPing  string = \"leastping\"\n\tstrategyRoundRobin string = \"roundrobin\"\n\tstrategyLeastLoad  string = \"leastload\"\n)\n\nvar (\n\tstrategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{\n\t\tstrategyRandom:     func() interface{} { return new(strategyEmptyConfig) },\n\t\tstrategyLeastPing:  func() interface{} { return new(strategyEmptyConfig) },\n\t\tstrategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },\n\t\tstrategyLeastLoad:  func() interface{} { return new(strategyLeastLoadConfig) },\n\t}, \"type\", \"settings\")\n)\n\ntype strategyEmptyConfig struct {\n}\n\nfunc (v *strategyEmptyConfig) Build() (proto.Message, error) {\n\treturn nil, nil\n}\n\ntype strategyLeastLoadConfig struct {\n\t// weight settings\n\tCosts []*router.StrategyWeight `json:\"costs,omitempty\"`\n\t// ping rtt baselines\n\tBaselines []duration.Duration `json:\"baselines,omitempty\"`\n\t// expected nodes count to select\n\tExpected int32 `json:\"expected,omitempty\"`\n\t// max acceptable rtt, filter away high delay nodes. default 0\n\tMaxRTT duration.Duration `json:\"maxRTT,omitempty\"`\n\t// acceptable failure rate\n\tTolerance float64 `json:\"tolerance,omitempty\"`\n}\n\n// healthCheckSettings holds settings for health Checker\ntype healthCheckSettings struct {\n\tDestination   string            `json:\"destination\"`\n\tConnectivity  string            `json:\"connectivity\"`\n\tInterval      duration.Duration `json:\"interval\"`\n\tSamplingCount int               `json:\"sampling\"`\n\tTimeout       duration.Duration `json:\"timeout\"`\n\tHttpMethod    string            `json:\"httpMethod\"`\n}\n\nfunc (h healthCheckSettings) Build() (proto.Message, error) {\n\tvar httpMethod string\n\tif h.HttpMethod == \"\" {\n\t\thttpMethod = \"HEAD\"\n\t} else {\n\t\thttpMethod = strings.TrimSpace(h.HttpMethod)\n\t}\n\treturn &burst.HealthPingConfig{\n\t\tDestination:   h.Destination,\n\t\tConnectivity:  h.Connectivity,\n\t\tInterval:      int64(h.Interval),\n\t\tTimeout:       int64(h.Timeout),\n\t\tSamplingCount: int32(h.SamplingCount),\n\t\tHttpMethod:    httpMethod,\n\t}, nil\n}\n\n// Build implements Buildable.\nfunc (v *strategyLeastLoadConfig) Build() (proto.Message, error) {\n\tconfig := &router.StrategyLeastLoadConfig{}\n\tconfig.Costs = v.Costs\n\tconfig.Tolerance = float32(v.Tolerance)\n\tif config.Tolerance < 0 {\n\t\tconfig.Tolerance = 0\n\t}\n\tif config.Tolerance > 1 {\n\t\tconfig.Tolerance = 1\n\t}\n\tconfig.Expected = v.Expected\n\tif config.Expected < 0 {\n\t\tconfig.Expected = 0\n\t}\n\tconfig.MaxRTT = int64(v.MaxRTT)\n\tif config.MaxRTT < 0 {\n\t\tconfig.MaxRTT = 0\n\t}\n\tconfig.Baselines = make([]int64, 0)\n\tfor _, b := range v.Baselines {\n\t\tif b <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tconfig.Baselines = append(config.Baselines, int64(b))\n\t}\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/router_test.go",
    "content": "package conf_test\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\t_ \"unsafe\"\n\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc getAssetPath(file string) (string, error) {\n\tpath := platform.GetAssetLocation(file)\n\t_, err := os.Stat(path)\n\tif os.IsNotExist(err) {\n\t\tpath := filepath.Join(\"..\", \"..\", \"resources\", file)\n\t\t_, err := os.Stat(path)\n\t\tif os.IsNotExist(err) {\n\t\t\treturn \"\", fmt.Errorf(\"can't find %s in standard asset locations or {project_root}/resources\", file)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"can't stat %s: %v\", path, err)\n\t\t}\n\t\treturn path, nil\n\t}\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"can't stat %s: %v\", path, err)\n\t}\n\n\treturn path, nil\n}\n\nfunc TestToCidrList(t *testing.T) {\n\ttempDir, err := os.MkdirTemp(\"\", \"test-\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create temp dir: %v\", err)\n\t}\n\tdefer os.RemoveAll(tempDir)\n\n\tgeoipPath, err := getAssetPath(\"geoip.dat\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcommon.Must(filesystem.CopyFile(filepath.Join(tempDir, \"geoip.dat\"), geoipPath))\n\tcommon.Must(filesystem.CopyFile(filepath.Join(tempDir, \"geoiptestrouter.dat\"), geoipPath))\n\n\tos.Setenv(\"xray.location.asset\", tempDir)\n\tdefer os.Unsetenv(\"xray.location.asset\")\n\n\tips := StringList([]string{\n\t\t\"geoip:us\",\n\t\t\"geoip:cn\",\n\t\t\"geoip:!cn\",\n\t\t\"ext:geoiptestrouter.dat:!cn\",\n\t\t\"ext:geoiptestrouter.dat:ca\",\n\t\t\"ext-ip:geoiptestrouter.dat:!cn\",\n\t\t\"ext-ip:geoiptestrouter.dat:!ca\",\n\t})\n\n\t_, err = ToCidrList(ips)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse geoip list, got %s\", err)\n\t}\n}\n\nfunc TestRouterConfig(t *testing.T) {\n\tcreateParser := func() func(string) (proto.Message, error) {\n\t\treturn func(s string) (proto.Message, error) {\n\t\t\tconfig := new(RouterConfig)\n\t\t\tif err := json.Unmarshal([]byte(s), config); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn config.Build()\n\t\t}\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"domainStrategy\": \"AsIs\",\n\t\t\t\t\"rules\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"domain\": [\n\t\t\t\t\t\t\t\"baidu.com\",\n\t\t\t\t\t\t\t\"qq.com\"\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"outboundTag\": \"direct\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"ip\": [\n\t\t\t\t\t\t\t\"10.0.0.0/8\",\n\t\t\t\t\t\t\t\"::1/128\"\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"outboundTag\": \"test\"\n\t\t\t\t\t},{\n\t\t\t\t\t\t\"port\": \"53, 443, 1000-2000\",\n\t\t\t\t\t\t\"outboundTag\": \"test\"\n\t\t\t\t\t},{\n\t\t\t\t\t\t\"port\": 123,\n\t\t\t\t\t\t\"outboundTag\": \"test\"\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"balancers\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"tag\": \"b1\",\n\t\t\t\t\t\t\"selector\": [\"test\"],\n\t\t\t\t\t\t\"fallbackTag\": \"fall\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"tag\": \"b2\",\n\t\t\t\t\t\t\"selector\": [\"test\"],\n\t\t\t\t\t\t\"strategy\": {\n\t\t\t\t\t\t\t\"type\": \"leastload\",\n\t\t\t\t\t\t\t\"settings\": {\n\t\t\t\t\t\t\t\t\"healthCheck\": {\n\t\t\t\t\t\t\t\t\t\"interval\": \"5m0s\",\n\t\t\t\t\t\t\t\t\t\"sampling\": 2,\n\t\t\t\t\t\t\t\t\t\"timeout\": \"5s\",\n\t\t\t\t\t\t\t\t\t\"destination\": \"dest\",\n\t\t\t\t\t\t\t\t\t\"connectivity\": \"conn\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"costs\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"regexp\": true,\n\t\t\t\t\t\t\t\t\t\t\"match\": \"\\\\d+(\\\\.\\\\d+)\",\n\t\t\t\t\t\t\t\t\t\t\"value\": 5\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"baselines\": [\"400ms\", \"600ms\"],\n\t\t\t\t\t\t\t\t\"expected\": 6,\n\t\t\t\t\t\t\t\t\"maxRTT\": \"1000ms\",\n\t\t\t\t\t\t\t\t\"tolerance\": 0.5\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"fallbackTag\": \"fall\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: &router.Config{\n\t\t\t\tDomainStrategy: router.Config_AsIs,\n\t\t\t\tBalancingRule: []*router.BalancingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tTag:              \"b1\",\n\t\t\t\t\t\tOutboundSelector: []string{\"test\"},\n\t\t\t\t\t\tStrategy:         \"random\",\n\t\t\t\t\t\tFallbackTag:      \"fall\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTag:              \"b2\",\n\t\t\t\t\t\tOutboundSelector: []string{\"test\"},\n\t\t\t\t\t\tStrategy:         \"leastload\",\n\t\t\t\t\t\tStrategySettings: serial.ToTypedMessage(&router.StrategyLeastLoadConfig{\n\t\t\t\t\t\t\tCosts: []*router.StrategyWeight{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tRegexp: true,\n\t\t\t\t\t\t\t\t\tMatch:  \"\\\\d+(\\\\.\\\\d+)\",\n\t\t\t\t\t\t\t\t\tValue:  5,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tBaselines: []int64{\n\t\t\t\t\t\t\t\tint64(time.Duration(400) * time.Millisecond),\n\t\t\t\t\t\t\t\tint64(time.Duration(600) * time.Millisecond),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tExpected:  6,\n\t\t\t\t\t\t\tMaxRTT:    int64(time.Duration(1000) * time.Millisecond),\n\t\t\t\t\t\t\tTolerance: 0.5,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tFallbackTag: \"fall\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tDomain: []*router.Domain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:  router.Domain_Plain,\n\t\t\t\t\t\t\t\tValue: \"baidu.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:  router.Domain_Plain,\n\t\t\t\t\t\t\t\tValue: \"qq.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"direct\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{10, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\tPrefix: 8,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\t\t\t\t\t\t\t\tPrefix: 128,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"test\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tPortList: &net.PortList{\n\t\t\t\t\t\t\tRange: []*net.PortRange{\n\t\t\t\t\t\t\t\t{From: 53, To: 53},\n\t\t\t\t\t\t\t\t{From: 443, To: 443},\n\t\t\t\t\t\t\t\t{From: 1000, To: 2000},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"test\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tPortList: &net.PortList{\n\t\t\t\t\t\t\tRange: []*net.PortRange{\n\t\t\t\t\t\t\t\t{From: 123, To: 123},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"test\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"domainStrategy\": \"IPIfNonMatch\",\n\t\t\t\t\"rules\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"domain\": [\n\t\t\t\t\t\t\t\"baidu.com\",\n\t\t\t\t\t\t\t\"qq.com\"\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"outboundTag\": \"direct\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"ip\": [\n\t\t\t\t\t\t\t\"10.0.0.0/8\",\n\t\t\t\t\t\t\t\"::1/128\"\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"outboundTag\": \"test\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: &router.Config{\n\t\t\t\tDomainStrategy: router.Config_IpIfNonMatch,\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tDomain: []*router.Domain{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:  router.Domain_Plain,\n\t\t\t\t\t\t\t\tValue: \"baidu.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:  router.Domain_Plain,\n\t\t\t\t\t\t\t\tValue: \"qq.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"direct\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{10, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\tPrefix: 8,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\t\t\t\t\t\t\t\tPrefix: 128,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"test\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/serial/builder.go",
    "content": "package serial\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\tcreflect \"github.com/xtls/xray-core/common/reflect\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n)\n\nfunc MergeConfigFromFiles(files []*core.ConfigSource) (string, error) {\n\tc, err := mergeConfigs(files)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif j, ok := creflect.MarshalToJson(c, true); ok {\n\t\treturn j, nil\n\t}\n\treturn \"\", errors.New(\"marshal to json failed.\").AtError()\n}\n\nfunc mergeConfigs(files []*core.ConfigSource) (*conf.Config, error) {\n\tcf := &conf.Config{}\n\tfor i, file := range files {\n\t\terrors.LogInfo(context.Background(), \"Reading config: \", file)\n\t\tr, err := confloader.LoadConfig(file.Name)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to read config: \", file).Base(err)\n\t\t}\n\t\tc, err := ReaderDecoderByFormat[file.Format](r)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to decode config: \", file).Base(err)\n\t\t}\n\t\tif i == 0 {\n\t\t\t*cf = *c\n\t\t\tcontinue\n\t\t}\n\t\tcf.Override(c, file.Name)\n\t}\n\treturn cf, nil\n}\n\nfunc BuildConfig(files []*core.ConfigSource) (*core.Config, error) {\n\tconfig, err := mergeConfigs(files)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn config.Build()\n}\n\ntype readerDecoder func(io.Reader) (*conf.Config, error)\n\nvar ReaderDecoderByFormat = make(map[string]readerDecoder)\n\nfunc init() {\n\tReaderDecoderByFormat[\"json\"] = DecodeJSONConfig\n\tReaderDecoderByFormat[\"yaml\"] = DecodeYAMLConfig\n\tReaderDecoderByFormat[\"toml\"] = DecodeTOMLConfig\n\n\tcore.ConfigBuilderForFiles = BuildConfig\n\tcore.ConfigMergedFormFiles = MergeConfigFromFiles\n}\n"
  },
  {
    "path": "infra/conf/serial/loader.go",
    "content": "package serial\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/ghodss/yaml\"\n\t\"github.com/pelletier/go-toml\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\tjson_reader \"github.com/xtls/xray-core/infra/conf/json\"\n)\n\ntype offset struct {\n\tline int\n\tchar int\n}\n\nfunc findOffset(b []byte, o int) *offset {\n\tif o >= len(b) || o < 0 {\n\t\treturn nil\n\t}\n\n\tline := 1\n\tchar := 0\n\tfor i, x := range b {\n\t\tif i == o {\n\t\t\tbreak\n\t\t}\n\t\tif x == '\\n' {\n\t\t\tline++\n\t\t\tchar = 0\n\t\t} else {\n\t\t\tchar++\n\t\t}\n\t}\n\n\treturn &offset{line: line, char: char}\n}\n\n// DecodeJSONConfig reads from reader and decode the config into *conf.Config\n// syntax error could be detected.\nfunc DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {\n\tjsonConfig := &conf.Config{}\n\n\tjsonContent := bytes.NewBuffer(make([]byte, 0, 10240))\n\tjsonReader := io.TeeReader(&json_reader.Reader{\n\t\tReader: reader,\n\t}, jsonContent)\n\tdecoder := json.NewDecoder(jsonReader)\n\n\tif err := decoder.Decode(jsonConfig); err != nil {\n\t\tvar pos *offset\n\t\tcause := errors.Cause(err)\n\t\tswitch tErr := cause.(type) {\n\t\tcase *json.SyntaxError:\n\t\t\tpos = findOffset(jsonContent.Bytes(), int(tErr.Offset))\n\t\tcase *json.UnmarshalTypeError:\n\t\t\tpos = findOffset(jsonContent.Bytes(), int(tErr.Offset))\n\t\t}\n\t\tif pos != nil {\n\t\t\treturn nil, errors.New(\"failed to read config file at line \", pos.line, \" char \", pos.char).Base(err)\n\t\t}\n\t\treturn nil, errors.New(\"failed to read config file\").Base(err)\n\t}\n\n\treturn jsonConfig, nil\n}\n\nfunc LoadJSONConfig(reader io.Reader) (*core.Config, error) {\n\tjsonConfig, err := DecodeJSONConfig(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpbConfig, err := jsonConfig.Build()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse json config\").Base(err)\n\t}\n\n\treturn pbConfig, nil\n}\n\n// DecodeTOMLConfig reads from reader and decode the config into *conf.Config\n// using github.com/pelletier/go-toml and map to convert toml to json.\nfunc DecodeTOMLConfig(reader io.Reader) (*conf.Config, error) {\n\ttomlFile, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read config file\").Base(err)\n\t}\n\n\tconfigMap := make(map[string]interface{})\n\tif err := toml.Unmarshal(tomlFile, &configMap); err != nil {\n\t\treturn nil, errors.New(\"failed to convert toml to map\").Base(err)\n\t}\n\n\tjsonFile, err := json.Marshal(&configMap)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to convert map to json\").Base(err)\n\t}\n\n\treturn DecodeJSONConfig(bytes.NewReader(jsonFile))\n}\n\nfunc LoadTOMLConfig(reader io.Reader) (*core.Config, error) {\n\ttomlConfig, err := DecodeTOMLConfig(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpbConfig, err := tomlConfig.Build()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse toml config\").Base(err)\n\t}\n\n\treturn pbConfig, nil\n}\n\n// DecodeYAMLConfig reads from reader and decode the config into *conf.Config\n// using github.com/ghodss/yaml to convert yaml to json.\nfunc DecodeYAMLConfig(reader io.Reader) (*conf.Config, error) {\n\tyamlFile, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read config file\").Base(err)\n\t}\n\n\tjsonFile, err := yaml.YAMLToJSON(yamlFile)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to convert yaml to json\").Base(err)\n\t}\n\n\treturn DecodeJSONConfig(bytes.NewReader(jsonFile))\n}\n\nfunc LoadYAMLConfig(reader io.Reader) (*core.Config, error) {\n\tyamlConfig, err := DecodeYAMLConfig(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpbConfig, err := yamlConfig.Build()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse yaml config\").Base(err)\n\t}\n\n\treturn pbConfig, nil\n}\n"
  },
  {
    "path": "infra/conf/serial/loader_test.go",
    "content": "package serial_test\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n)\n\nfunc TestLoaderError(t *testing.T) {\n\ttestCases := []struct {\n\t\tInput  string\n\t\tOutput string\n\t}{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"log\": {\n\t\t\t\t\t// abcd\n\t\t\t\t\t0,\n\t\t\t\t\t\"loglevel\": \"info\"\n\t\t\t\t}\n\t\t}`,\n\t\t\tOutput: \"line 4 char 6\",\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"log\": {\n\t\t\t\t\t// abcd\n\t\t\t\t\t\"loglevel\": \"info\",\n\t\t\t\t}\n\t\t}`,\n\t\t\tOutput: \"line 5 char 5\",\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"port\": 1,\n\t\t\t\t\"inbounds\": [{\n\t\t\t\t\t\"protocol\": \"test\"\n\t\t\t\t}]\n\t\t}`,\n\t\t\tOutput: \"parse json config\",\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"inbounds\": [{\n\t\t\t\t\t\"port\": 1,\n\t\t\t\t\t\"listen\": 0,\n\t\t\t\t\t\"protocol\": \"test\"\n\t\t\t\t}]\n\t\t}`,\n\t\t\tOutput: \"line 1 char 1\",\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\treader := bytes.NewReader([]byte(testCase.Input))\n\t\t_, err := serial.LoadJSONConfig(reader)\n\t\terrString := err.Error()\n\t\tif !strings.Contains(errString, testCase.Output) {\n\t\t\tt.Error(\"unexpected output from json: \", testCase.Input, \". expected \", testCase.Output, \", but actually \", errString)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "infra/conf/serial/serial.go",
    "content": "package serial\n"
  },
  {
    "path": "infra/conf/shadowsocks.go",
    "content": "package conf\n\nimport (\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\tC \"github.com/sagernet/sing/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks_2022\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc cipherFromString(c string) shadowsocks.CipherType {\n\tswitch strings.ToLower(c) {\n\tcase \"aes-128-gcm\", \"aead_aes_128_gcm\":\n\t\treturn shadowsocks.CipherType_AES_128_GCM\n\tcase \"aes-256-gcm\", \"aead_aes_256_gcm\":\n\t\treturn shadowsocks.CipherType_AES_256_GCM\n\tcase \"chacha20-poly1305\", \"aead_chacha20_poly1305\", \"chacha20-ietf-poly1305\":\n\t\treturn shadowsocks.CipherType_CHACHA20_POLY1305\n\tcase \"xchacha20-poly1305\", \"aead_xchacha20_poly1305\", \"xchacha20-ietf-poly1305\":\n\t\treturn shadowsocks.CipherType_XCHACHA20_POLY1305\n\tcase \"none\", \"plain\":\n\t\treturn shadowsocks.CipherType_NONE\n\tdefault:\n\t\treturn shadowsocks.CipherType_UNKNOWN\n\t}\n}\n\ntype ShadowsocksUserConfig struct {\n\tCipher   string   `json:\"method\"`\n\tPassword string   `json:\"password\"`\n\tLevel    byte     `json:\"level\"`\n\tEmail    string   `json:\"email\"`\n\tAddress  *Address `json:\"address\"`\n\tPort     uint16   `json:\"port\"`\n}\n\ntype ShadowsocksServerConfig struct {\n\tCipher      string                   `json:\"method\"`\n\tPassword    string                   `json:\"password\"`\n\tLevel       byte                     `json:\"level\"`\n\tEmail       string                   `json:\"email\"`\n\tUsers       []*ShadowsocksUserConfig `json:\"clients\"`\n\tNetworkList *NetworkList             `json:\"network\"`\n}\n\nfunc (v *ShadowsocksServerConfig) Build() (proto.Message, error) {\n\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"Shadowsocks (with no Forward Secrecy, etc.)\", \"VLESS Encryption\")\n\n\tif C.Contains(shadowaead_2022.List, v.Cipher) {\n\t\treturn buildShadowsocks2022(v)\n\t}\n\n\tconfig := new(shadowsocks.ServerConfig)\n\tconfig.Network = v.NetworkList.Build()\n\n\tif v.Users != nil {\n\t\tfor _, user := range v.Users {\n\t\t\taccount := &shadowsocks.Account{\n\t\t\t\tPassword:   user.Password,\n\t\t\t\tCipherType: cipherFromString(user.Cipher),\n\t\t\t}\n\t\t\tif account.Password == \"\" {\n\t\t\t\treturn nil, errors.New(\"Shadowsocks password is not specified.\")\n\t\t\t}\n\t\t\tif account.CipherType < shadowsocks.CipherType_AES_128_GCM ||\n\t\t\t\taccount.CipherType > shadowsocks.CipherType_XCHACHA20_POLY1305 {\n\t\t\t\treturn nil, errors.New(\"unsupported cipher method: \", user.Cipher)\n\t\t\t}\n\t\t\tconfig.Users = append(config.Users, &protocol.User{\n\t\t\t\tEmail:   user.Email,\n\t\t\t\tLevel:   uint32(user.Level),\n\t\t\t\tAccount: serial.ToTypedMessage(account),\n\t\t\t})\n\t\t}\n\t} else {\n\t\taccount := &shadowsocks.Account{\n\t\t\tPassword:   v.Password,\n\t\t\tCipherType: cipherFromString(v.Cipher),\n\t\t}\n\t\tif account.Password == \"\" {\n\t\t\treturn nil, errors.New(\"Shadowsocks password is not specified.\")\n\t\t}\n\t\tif account.CipherType == shadowsocks.CipherType_UNKNOWN {\n\t\t\treturn nil, errors.New(\"unknown cipher method: \", v.Cipher)\n\t\t}\n\t\tconfig.Users = append(config.Users, &protocol.User{\n\t\t\tEmail:   v.Email,\n\t\t\tLevel:   uint32(v.Level),\n\t\t\tAccount: serial.ToTypedMessage(account),\n\t\t})\n\t}\n\n\treturn config, nil\n}\n\nfunc buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {\n\tif len(v.Users) == 0 {\n\t\tconfig := new(shadowsocks_2022.ServerConfig)\n\t\tconfig.Method = v.Cipher\n\t\tconfig.Key = v.Password\n\t\tconfig.Network = v.NetworkList.Build()\n\t\tconfig.Email = v.Email\n\t\treturn config, nil\n\t}\n\n\tif v.Cipher == \"\" {\n\t\treturn nil, errors.New(\"shadowsocks 2022 (multi-user): missing server method\")\n\t}\n\tif !strings.Contains(v.Cipher, \"aes\") {\n\t\treturn nil, errors.New(\"shadowsocks 2022 (multi-user): only blake3-aes-*-gcm methods are supported\")\n\t}\n\n\tif v.Users[0].Address == nil {\n\t\tconfig := new(shadowsocks_2022.MultiUserServerConfig)\n\t\tconfig.Method = v.Cipher\n\t\tconfig.Key = v.Password\n\t\tconfig.Network = v.NetworkList.Build()\n\n\t\tfor _, user := range v.Users {\n\t\t\tif user.Cipher != \"\" {\n\t\t\t\treturn nil, errors.New(\"shadowsocks 2022 (multi-user): users must have empty method\")\n\t\t\t}\n\t\t\taccount := &shadowsocks_2022.Account{\n\t\t\t\tKey: user.Password,\n\t\t\t}\n\t\t\tconfig.Users = append(config.Users, &protocol.User{\n\t\t\t\tEmail:   user.Email,\n\t\t\t\tLevel:   uint32(user.Level),\n\t\t\t\tAccount: serial.ToTypedMessage(account),\n\t\t\t})\n\t\t}\n\t\treturn config, nil\n\t}\n\n\tconfig := new(shadowsocks_2022.RelayServerConfig)\n\tconfig.Method = v.Cipher\n\tconfig.Key = v.Password\n\tconfig.Network = v.NetworkList.Build()\n\tfor _, user := range v.Users {\n\t\tif user.Cipher != \"\" {\n\t\t\treturn nil, errors.New(\"shadowsocks 2022 (relay): users must have empty method\")\n\t\t}\n\t\tif user.Address == nil {\n\t\t\treturn nil, errors.New(\"shadowsocks 2022 (relay): all users must have relay address\")\n\t\t}\n\t\tconfig.Destinations = append(config.Destinations, &shadowsocks_2022.RelayDestination{\n\t\t\tKey:     user.Password,\n\t\t\tEmail:   user.Email,\n\t\t\tAddress: user.Address.Build(),\n\t\t\tPort:    uint32(user.Port),\n\t\t})\n\t}\n\treturn config, nil\n}\n\ntype ShadowsocksServerTarget struct {\n\tAddress    *Address `json:\"address\"`\n\tPort       uint16   `json:\"port\"`\n\tLevel      byte     `json:\"level\"`\n\tEmail      string   `json:\"email\"`\n\tCipher     string   `json:\"method\"`\n\tPassword   string   `json:\"password\"`\n\tUoT        bool     `json:\"uot\"`\n\tUoTVersion int      `json:\"uotVersion\"`\n}\n\ntype ShadowsocksClientConfig struct {\n\tAddress    *Address                   `json:\"address\"`\n\tPort       uint16                     `json:\"port\"`\n\tLevel      byte                       `json:\"level\"`\n\tEmail      string                     `json:\"email\"`\n\tCipher     string                     `json:\"method\"`\n\tPassword   string                     `json:\"password\"`\n\tUoT        bool                       `json:\"uot\"`\n\tUoTVersion int                        `json:\"uotVersion\"`\n\tServers    []*ShadowsocksServerTarget `json:\"servers\"`\n}\n\nfunc (v *ShadowsocksClientConfig) Build() (proto.Message, error) {\n\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"Shadowsocks (with no Forward Secrecy, etc.)\", \"VLESS Encryption\")\n\n\tif v.Address != nil {\n\t\tv.Servers = []*ShadowsocksServerTarget{\n\t\t\t{\n\t\t\t\tAddress:    v.Address,\n\t\t\t\tPort:       v.Port,\n\t\t\t\tLevel:      v.Level,\n\t\t\t\tEmail:      v.Email,\n\t\t\t\tCipher:     v.Cipher,\n\t\t\t\tPassword:   v.Password,\n\t\t\t\tUoT:        v.UoT,\n\t\t\t\tUoTVersion: v.UoTVersion,\n\t\t\t},\n\t\t}\n\t}\n\tif len(v.Servers) != 1 {\n\t\treturn nil, errors.New(`Shadowsocks settings: \"servers\" should have one and only one member. Multiple endpoints in \"servers\" should use multiple Shadowsocks outbounds and routing balancer instead`)\n\t}\n\n\tif len(v.Servers) == 1 {\n\t\tserver := v.Servers[0]\n\t\tif C.Contains(shadowaead_2022.List, server.Cipher) {\n\t\t\tif server.Address == nil {\n\t\t\t\treturn nil, errors.New(\"Shadowsocks server address is not set.\")\n\t\t\t}\n\t\t\tif server.Port == 0 {\n\t\t\t\treturn nil, errors.New(\"Invalid Shadowsocks port.\")\n\t\t\t}\n\t\t\tif server.Password == \"\" {\n\t\t\t\treturn nil, errors.New(\"Shadowsocks password is not specified.\")\n\t\t\t}\n\n\t\t\tconfig := new(shadowsocks_2022.ClientConfig)\n\t\t\tconfig.Address = server.Address.Build()\n\t\t\tconfig.Port = uint32(server.Port)\n\t\t\tconfig.Method = server.Cipher\n\t\t\tconfig.Key = server.Password\n\t\t\tconfig.UdpOverTcp = server.UoT\n\t\t\tconfig.UdpOverTcpVersion = uint32(server.UoTVersion)\n\t\t\treturn config, nil\n\t\t}\n\t}\n\n\tconfig := new(shadowsocks.ClientConfig)\n\tfor _, server := range v.Servers {\n\t\tif C.Contains(shadowaead_2022.List, server.Cipher) {\n\t\t\treturn nil, errors.New(\"Shadowsocks 2022 accept no multi servers\")\n\t\t}\n\t\tif server.Address == nil {\n\t\t\treturn nil, errors.New(\"Shadowsocks server address is not set.\")\n\t\t}\n\t\tif server.Port == 0 {\n\t\t\treturn nil, errors.New(\"Invalid Shadowsocks port.\")\n\t\t}\n\t\tif server.Password == \"\" {\n\t\t\treturn nil, errors.New(\"Shadowsocks password is not specified.\")\n\t\t}\n\t\taccount := &shadowsocks.Account{\n\t\t\tPassword: server.Password,\n\t\t}\n\t\taccount.CipherType = cipherFromString(server.Cipher)\n\t\tif account.CipherType == shadowsocks.CipherType_UNKNOWN {\n\t\t\treturn nil, errors.New(\"unknown cipher method: \", server.Cipher)\n\t\t}\n\n\t\tss := &protocol.ServerEndpoint{\n\t\t\tAddress: server.Address.Build(),\n\t\t\tPort:    uint32(server.Port),\n\t\t\tUser: &protocol.User{\n\t\t\t\tLevel:   uint32(server.Level),\n\t\t\t\tEmail:   server.Email,\n\t\t\t\tAccount: serial.ToTypedMessage(account),\n\t\t\t},\n\t\t}\n\n\t\tconfig.Server = ss\n\t\tbreak\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/shadowsocks_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks\"\n)\n\nfunc TestShadowsocksServerConfigParsing(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(ShadowsocksServerConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"method\": \"aes-256-GCM\",\n\t\t\t\t\"password\": \"xray-password\"\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &shadowsocks.ServerConfig{\n\t\t\t\tUsers: []*protocol.User{{\n\t\t\t\t\tAccount: serial.ToTypedMessage(&shadowsocks.Account{\n\t\t\t\t\t\tCipherType: shadowsocks.CipherType_AES_256_GCM,\n\t\t\t\t\t\tPassword:   \"xray-password\",\n\t\t\t\t\t}),\n\t\t\t\t}},\n\t\t\t\tNetwork: []net.Network{net.Network_TCP},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/socks.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/proxy/socks\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype SocksAccount struct {\n\tUsername string `json:\"user\"`\n\tPassword string `json:\"pass\"`\n}\n\nfunc (v *SocksAccount) Build() *socks.Account {\n\treturn &socks.Account{\n\t\tUsername: v.Username,\n\t\tPassword: v.Password,\n\t}\n}\n\nconst (\n\tAuthMethodNoAuth   = \"noauth\"\n\tAuthMethodUserPass = \"password\"\n)\n\ntype SocksServerConfig struct {\n\tAuthMethod string          `json:\"auth\"`\n\tAccounts   []*SocksAccount `json:\"accounts\"`\n\tUDP        bool            `json:\"udp\"`\n\tHost       *Address        `json:\"ip\"`\n\tUserLevel  uint32          `json:\"userLevel\"`\n}\n\nfunc (v *SocksServerConfig) Build() (proto.Message, error) {\n\tconfig := new(socks.ServerConfig)\n\tswitch v.AuthMethod {\n\tcase AuthMethodNoAuth:\n\t\tconfig.AuthType = socks.AuthType_NO_AUTH\n\tcase AuthMethodUserPass:\n\t\tconfig.AuthType = socks.AuthType_PASSWORD\n\tdefault:\n\t\t// errors.New(\"unknown socks auth method: \", v.AuthMethod, \". Default to noauth.\").AtWarning().WriteToLog()\n\t\tconfig.AuthType = socks.AuthType_NO_AUTH\n\t}\n\n\tif len(v.Accounts) > 0 {\n\t\tconfig.Accounts = make(map[string]string, len(v.Accounts))\n\t\tfor _, account := range v.Accounts {\n\t\t\tconfig.Accounts[account.Username] = account.Password\n\t\t}\n\t}\n\n\tconfig.UdpEnabled = v.UDP\n\tif v.Host != nil {\n\t\tconfig.Address = v.Host.Build()\n\t}\n\n\tconfig.UserLevel = v.UserLevel\n\treturn config, nil\n}\n\ntype SocksRemoteConfig struct {\n\tAddress *Address          `json:\"address\"`\n\tPort    uint16            `json:\"port\"`\n\tUsers   []json.RawMessage `json:\"users\"`\n}\n\ntype SocksClientConfig struct {\n\tAddress  *Address             `json:\"address\"`\n\tPort     uint16               `json:\"port\"`\n\tLevel    uint32               `json:\"level\"`\n\tEmail    string               `json:\"email\"`\n\tUsername string               `json:\"user\"`\n\tPassword string               `json:\"pass\"`\n\tServers  []*SocksRemoteConfig `json:\"servers\"`\n}\n\nfunc (v *SocksClientConfig) Build() (proto.Message, error) {\n\tconfig := new(socks.ClientConfig)\n\tif v.Address != nil {\n\t\tv.Servers = []*SocksRemoteConfig{\n\t\t\t{\n\t\t\t\tAddress: v.Address,\n\t\t\t\tPort:    v.Port,\n\t\t\t},\n\t\t}\n\t\tif len(v.Username) > 0 {\n\t\t\tv.Servers[0].Users = []json.RawMessage{{}}\n\t\t}\n\t}\n\tif len(v.Servers) != 1 {\n\t\treturn nil, errors.New(`SOCKS settings: \"servers\" should have one and only one member. Multiple endpoints in \"servers\" should use multiple SOCKS outbounds and routing balancer instead`)\n\t}\n\tfor _, serverConfig := range v.Servers {\n\t\tif len(serverConfig.Users) > 1 {\n\t\t\treturn nil, errors.New(`SOCKS servers: \"users\" should have one member at most. Multiple members in \"users\" should use multiple SOCKS outbounds and routing balancer instead`)\n\t\t}\n\t\tserver := &protocol.ServerEndpoint{\n\t\t\tAddress: serverConfig.Address.Build(),\n\t\t\tPort:    uint32(serverConfig.Port),\n\t\t}\n\t\tfor _, rawUser := range serverConfig.Users {\n\t\t\tuser := new(protocol.User)\n\t\t\tif v.Address != nil {\n\t\t\t\tuser.Level = v.Level\n\t\t\t\tuser.Email = v.Email\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, user); err != nil {\n\t\t\t\t\treturn nil, errors.New(\"failed to parse Socks user\").Base(err).AtError()\n\t\t\t\t}\n\t\t\t}\n\t\t\taccount := new(SocksAccount)\n\t\t\tif v.Address != nil {\n\t\t\t\taccount.Username = v.Username\n\t\t\t\taccount.Password = v.Password\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, account); err != nil {\n\t\t\t\t\treturn nil, errors.New(\"failed to parse socks account\").Base(err).AtError()\n\t\t\t\t}\n\t\t\t}\n\t\t\tuser.Account = serial.ToTypedMessage(account.Build())\n\t\t\tserver.User = user\n\t\t\tbreak\n\t\t}\n\t\tconfig.Server = server\n\t\tbreak\n\t}\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/socks_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/socks\"\n)\n\nfunc TestSocksInboundConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(SocksServerConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"auth\": \"password\",\n\t\t\t\t\"accounts\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"user\": \"my-username\",\n\t\t\t\t\t\t\"pass\": \"my-password\"\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"udp\": false,\n\t\t\t\t\"ip\": \"127.0.0.1\",\n\t\t\t\t\"userLevel\": 1\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &socks.ServerConfig{\n\t\t\t\tAuthType: socks.AuthType_PASSWORD,\n\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\"my-username\": \"my-password\",\n\t\t\t\t},\n\t\t\t\tUdpEnabled: false,\n\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUserLevel: 1,\n\t\t\t},\n\t\t},\n\t})\n}\n\nfunc TestSocksOutboundConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(SocksClientConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"servers\": [{\n\t\t\t\t\t\"address\": \"127.0.0.1\",\n\t\t\t\t\t\"port\": 1234,\n\t\t\t\t\t\"users\": [\n\t\t\t\t\t\t{\"user\": \"test user\", \"pass\": \"test pass\", \"email\": \"test@email.com\"}\n\t\t\t\t\t]\n\t\t\t\t}]\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &socks.ClientConfig{\n\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tPort: 1234,\n\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\tEmail: \"test@email.com\",\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&socks.Account{\n\t\t\t\t\t\t\tUsername: \"test user\",\n\t\t\t\t\t\t\tPassword: \"test pass\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"address\": \"127.0.0.1\",\n\t\t\t\t\"port\": 1234,\n\t\t\t\t\"user\": \"test user\",\n\t\t\t\t\"pass\": \"test pass\",\n\t\t\t\t\"email\": \"test@email.com\"\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &socks.ClientConfig{\n\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tPort: 1234,\n\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\tEmail: \"test@email.com\",\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&socks.Account{\n\t\t\t\t\t\t\tUsername: \"test user\",\n\t\t\t\t\t\t\tPassword: \"test pass\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/transport_authenticators.go",
    "content": "package conf\n\nimport (\n\t\"sort\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/transport/internet/headers/http\"\n\t\"github.com/xtls/xray-core/transport/internet/headers/noop\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype NoOpConnectionAuthenticator struct{}\n\nfunc (NoOpConnectionAuthenticator) Build() (proto.Message, error) {\n\treturn new(noop.ConnectionConfig), nil\n}\n\ntype AuthenticatorRequest struct {\n\tVersion string                 `json:\"version\"`\n\tMethod  string                 `json:\"method\"`\n\tPath    StringList             `json:\"path\"`\n\tHeaders map[string]*StringList `json:\"headers\"`\n}\n\nfunc sortMapKeys(m map[string]*StringList) []string {\n\tvar keys []string\n\tfor key := range m {\n\t\tkeys = append(keys, key)\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n\nfunc (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {\n\tconfig := &http.RequestConfig{\n\t\tUri: []string{\"/\"},\n\t\tHeader: []*http.Header{\n\t\t\t{\n\t\t\t\tName:  \"Host\",\n\t\t\t\tValue: []string{\"www.baidu.com\", \"www.bing.com\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"User-Agent\",\n\t\t\t\tValue: []string{utils.ChromeUA},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Accept-Encoding\",\n\t\t\t\tValue: []string{\"gzip, deflate\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Connection\",\n\t\t\t\tValue: []string{\"keep-alive\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Pragma\",\n\t\t\t\tValue: []string{\"no-cache\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tif len(v.Version) > 0 {\n\t\tconfig.Version = &http.Version{Value: v.Version}\n\t}\n\n\tif len(v.Method) > 0 {\n\t\tconfig.Method = &http.Method{Value: v.Method}\n\t}\n\n\tif len(v.Path) > 0 {\n\t\tconfig.Uri = append([]string(nil), (v.Path)...)\n\t}\n\n\tif len(v.Headers) > 0 {\n\t\tconfig.Header = make([]*http.Header, 0, len(v.Headers))\n\t\theaderNames := sortMapKeys(v.Headers)\n\t\tfor _, key := range headerNames {\n\t\t\tvalue := v.Headers[key]\n\t\t\tif value == nil {\n\t\t\t\treturn nil, errors.New(\"empty HTTP header value: \" + key).AtError()\n\t\t\t}\n\t\t\tconfig.Header = append(config.Header, &http.Header{\n\t\t\t\tName:  key,\n\t\t\t\tValue: append([]string(nil), (*value)...),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\ntype AuthenticatorResponse struct {\n\tVersion string                 `json:\"version\"`\n\tStatus  string                 `json:\"status\"`\n\tReason  string                 `json:\"reason\"`\n\tHeaders map[string]*StringList `json:\"headers\"`\n}\n\nfunc (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {\n\tconfig := &http.ResponseConfig{\n\t\tHeader: []*http.Header{\n\t\t\t{\n\t\t\t\tName:  \"Content-Type\",\n\t\t\t\tValue: []string{\"application/octet-stream\", \"video/mpeg\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Transfer-Encoding\",\n\t\t\t\tValue: []string{\"chunked\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Connection\",\n\t\t\t\tValue: []string{\"keep-alive\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Pragma\",\n\t\t\t\tValue: []string{\"no-cache\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:  \"Cache-Control\",\n\t\t\t\tValue: []string{\"private\", \"no-cache\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tif len(v.Version) > 0 {\n\t\tconfig.Version = &http.Version{Value: v.Version}\n\t}\n\n\tif len(v.Status) > 0 || len(v.Reason) > 0 {\n\t\tconfig.Status = &http.Status{\n\t\t\tCode:   \"200\",\n\t\t\tReason: \"OK\",\n\t\t}\n\t\tif len(v.Status) > 0 {\n\t\t\tconfig.Status.Code = v.Status\n\t\t}\n\t\tif len(v.Reason) > 0 {\n\t\t\tconfig.Status.Reason = v.Reason\n\t\t}\n\t}\n\n\tif len(v.Headers) > 0 {\n\t\tconfig.Header = make([]*http.Header, 0, len(v.Headers))\n\t\theaderNames := sortMapKeys(v.Headers)\n\t\tfor _, key := range headerNames {\n\t\t\tvalue := v.Headers[key]\n\t\t\tif value == nil {\n\t\t\t\treturn nil, errors.New(\"empty HTTP header value: \" + key).AtError()\n\t\t\t}\n\t\t\tconfig.Header = append(config.Header, &http.Header{\n\t\t\t\tName:  key,\n\t\t\t\tValue: append([]string(nil), (*value)...),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\ntype Authenticator struct {\n\tRequest  AuthenticatorRequest  `json:\"request\"`\n\tResponse AuthenticatorResponse `json:\"response\"`\n}\n\nfunc (v *Authenticator) Build() (proto.Message, error) {\n\tconfig := new(http.Config)\n\trequestConfig, err := v.Request.Build()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.Request = requestConfig\n\n\tresponseConfig, err := v.Response.Build()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconfig.Response = responseConfig\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/transport_internet.go",
    "content": "package conf\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"math\"\n\t\"net/url\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/fragment\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/custom\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/dns\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/utp\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/noise\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/salamander\"\n\tfinalsudoku \"github.com/xtls/xray-core/transport/internet/finalmask/sudoku\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/xdns\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/xicmp\"\n\t\"github.com/xtls/xray-core/transport/internet/httpupgrade\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria\"\n\t\"github.com/xtls/xray-core/transport/internet/kcp\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/splithttp\"\n\t\"github.com/xtls/xray-core/transport/internet/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/internet/websocket\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar (\n\ttcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{\n\t\t\"none\": func() interface{} { return new(NoOpConnectionAuthenticator) },\n\t\t\"http\": func() interface{} { return new(Authenticator) },\n\t}, \"type\", \"\")\n)\n\ntype KCPConfig struct {\n\tMtu             *uint32         `json:\"mtu\"`\n\tTti             *uint32         `json:\"tti\"`\n\tUpCap           *uint32         `json:\"uplinkCapacity\"`\n\tDownCap         *uint32         `json:\"downlinkCapacity\"`\n\tCongestion      *bool           `json:\"congestion\"`\n\tReadBufferSize  *uint32         `json:\"readBufferSize\"`\n\tWriteBufferSize *uint32         `json:\"writeBufferSize\"`\n\tHeaderConfig    json.RawMessage `json:\"header\"`\n\tSeed            *string         `json:\"seed\"`\n}\n\n// Build implements Buildable.\nfunc (c *KCPConfig) Build() (proto.Message, error) {\n\tconfig := new(kcp.Config)\n\n\tif c.Mtu != nil {\n\t\tmtu := *c.Mtu\n\t\t// if mtu < 576 || mtu > 1460 {\n\t\t// \treturn nil, errors.New(\"invalid mKCP MTU size: \", mtu).AtError()\n\t\t// }\n\t\tconfig.Mtu = &kcp.MTU{Value: mtu}\n\t}\n\tif c.Tti != nil {\n\t\ttti := *c.Tti\n\t\tif tti < 10 || tti > 5000 {\n\t\t\treturn nil, errors.New(\"invalid mKCP TTI: \", tti).AtError()\n\t\t}\n\t\tconfig.Tti = &kcp.TTI{Value: tti}\n\t}\n\tif c.UpCap != nil {\n\t\tconfig.UplinkCapacity = &kcp.UplinkCapacity{Value: *c.UpCap}\n\t}\n\tif c.DownCap != nil {\n\t\tconfig.DownlinkCapacity = &kcp.DownlinkCapacity{Value: *c.DownCap}\n\t}\n\tif c.Congestion != nil {\n\t\tconfig.Congestion = *c.Congestion\n\t}\n\tif c.ReadBufferSize != nil {\n\t\tsize := *c.ReadBufferSize\n\t\tif size > 0 {\n\t\t\tconfig.ReadBuffer = &kcp.ReadBuffer{Size: size * 1024 * 1024}\n\t\t} else {\n\t\t\tconfig.ReadBuffer = &kcp.ReadBuffer{Size: 512 * 1024}\n\t\t}\n\t}\n\tif c.WriteBufferSize != nil {\n\t\tsize := *c.WriteBufferSize\n\t\tif size > 0 {\n\t\t\tconfig.WriteBuffer = &kcp.WriteBuffer{Size: size * 1024 * 1024}\n\t\t} else {\n\t\t\tconfig.WriteBuffer = &kcp.WriteBuffer{Size: 512 * 1024}\n\t\t}\n\t}\n\tif c.HeaderConfig != nil || c.Seed != nil {\n\t\treturn nil, errors.PrintRemovedFeatureError(\"mkcp header & seed\", \"finalmask/udp header-* & mkcp-original & mkcp-aes128gcm\")\n\t}\n\n\treturn config, nil\n}\n\ntype TCPConfig struct {\n\tHeaderConfig        json.RawMessage `json:\"header\"`\n\tAcceptProxyProtocol bool            `json:\"acceptProxyProtocol\"`\n}\n\n// Build implements Buildable.\nfunc (c *TCPConfig) Build() (proto.Message, error) {\n\tconfig := new(tcp.Config)\n\tif len(c.HeaderConfig) > 0 {\n\t\theaderConfig, _, err := tcpHeaderLoader.Load(c.HeaderConfig)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid TCP header config\").Base(err).AtError()\n\t\t}\n\t\tts, err := headerConfig.(Buildable).Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid TCP header config\").Base(err).AtError()\n\t\t}\n\t\tconfig.HeaderSettings = serial.ToTypedMessage(ts)\n\t}\n\tif c.AcceptProxyProtocol {\n\t\tconfig.AcceptProxyProtocol = c.AcceptProxyProtocol\n\t}\n\treturn config, nil\n}\n\ntype WebSocketConfig struct {\n\tHost                string            `json:\"host\"`\n\tPath                string            `json:\"path\"`\n\tHeaders             map[string]string `json:\"headers\"`\n\tAcceptProxyProtocol bool              `json:\"acceptProxyProtocol\"`\n\tHeartbeatPeriod     uint32            `json:\"heartbeatPeriod\"`\n}\n\n// Build implements Buildable.\nfunc (c *WebSocketConfig) Build() (proto.Message, error) {\n\tpath := c.Path\n\tvar ed uint32\n\tif u, err := url.Parse(path); err == nil {\n\t\tif q := u.Query(); q.Get(\"ed\") != \"\" {\n\t\t\tEd, _ := strconv.Atoi(q.Get(\"ed\"))\n\t\t\ted = uint32(Ed)\n\t\t\tq.Del(\"ed\")\n\t\t\tu.RawQuery = q.Encode()\n\t\t\tpath = u.String()\n\t\t}\n\t}\n\t// Priority (client): host > serverName > address\n\tfor k, v := range c.Headers {\n\t\tif strings.ToLower(k) == \"host\" {\n\t\t\terrors.PrintDeprecatedFeatureWarning(`\"host\" in \"headers\"`, `independent \"host\"`)\n\t\t\tif c.Host == \"\" {\n\t\t\t\tc.Host = v\n\t\t\t}\n\t\t\tdelete(c.Headers, k)\n\t\t}\n\t}\n\tconfig := &websocket.Config{\n\t\tPath:                path,\n\t\tHost:                c.Host,\n\t\tHeader:              c.Headers,\n\t\tAcceptProxyProtocol: c.AcceptProxyProtocol,\n\t\tEd:                  ed,\n\t\tHeartbeatPeriod:     c.HeartbeatPeriod,\n\t}\n\treturn config, nil\n}\n\ntype HttpUpgradeConfig struct {\n\tHost                string            `json:\"host\"`\n\tPath                string            `json:\"path\"`\n\tHeaders             map[string]string `json:\"headers\"`\n\tAcceptProxyProtocol bool              `json:\"acceptProxyProtocol\"`\n}\n\n// Build implements Buildable.\nfunc (c *HttpUpgradeConfig) Build() (proto.Message, error) {\n\tpath := c.Path\n\tvar ed uint32\n\tif u, err := url.Parse(path); err == nil {\n\t\tif q := u.Query(); q.Get(\"ed\") != \"\" {\n\t\t\tEd, _ := strconv.Atoi(q.Get(\"ed\"))\n\t\t\ted = uint32(Ed)\n\t\t\tq.Del(\"ed\")\n\t\t\tu.RawQuery = q.Encode()\n\t\t\tpath = u.String()\n\t\t}\n\t}\n\t// Priority (client): host > serverName > address\n\tfor k := range c.Headers {\n\t\tif strings.ToLower(k) == \"host\" {\n\t\t\treturn nil, errors.New(`\"headers\" can't contain \"host\"`)\n\t\t}\n\t}\n\tconfig := &httpupgrade.Config{\n\t\tPath:                path,\n\t\tHost:                c.Host,\n\t\tHeader:              c.Headers,\n\t\tAcceptProxyProtocol: c.AcceptProxyProtocol,\n\t\tEd:                  ed,\n\t}\n\treturn config, nil\n}\n\ntype SplitHTTPConfig struct {\n\tHost                 string            `json:\"host\"`\n\tPath                 string            `json:\"path\"`\n\tMode                 string            `json:\"mode\"`\n\tHeaders              map[string]string `json:\"headers\"`\n\tXPaddingBytes        Int32Range        `json:\"xPaddingBytes\"`\n\tXPaddingObfsMode     bool              `json:\"xPaddingObfsMode\"`\n\tXPaddingKey          string            `json:\"xPaddingKey\"`\n\tXPaddingHeader       string            `json:\"xPaddingHeader\"`\n\tXPaddingPlacement    string            `json:\"xPaddingPlacement\"`\n\tXPaddingMethod       string            `json:\"xPaddingMethod\"`\n\tUplinkHTTPMethod     string            `json:\"uplinkHTTPMethod\"`\n\tSessionPlacement     string            `json:\"sessionPlacement\"`\n\tSessionKey           string            `json:\"sessionKey\"`\n\tSeqPlacement         string            `json:\"seqPlacement\"`\n\tSeqKey               string            `json:\"seqKey\"`\n\tUplinkDataPlacement  string            `json:\"uplinkDataPlacement\"`\n\tUplinkDataKey        string            `json:\"uplinkDataKey\"`\n\tUplinkChunkSize      Int32Range        `json:\"uplinkChunkSize\"`\n\tNoGRPCHeader         bool              `json:\"noGRPCHeader\"`\n\tNoSSEHeader          bool              `json:\"noSSEHeader\"`\n\tScMaxEachPostBytes   Int32Range        `json:\"scMaxEachPostBytes\"`\n\tScMinPostsIntervalMs Int32Range        `json:\"scMinPostsIntervalMs\"`\n\tScMaxBufferedPosts   int64             `json:\"scMaxBufferedPosts\"`\n\tScStreamUpServerSecs Int32Range        `json:\"scStreamUpServerSecs\"`\n\tServerMaxHeaderBytes int32             `json:\"serverMaxHeaderBytes\"`\n\tXmux                 XmuxConfig        `json:\"xmux\"`\n\tDownloadSettings     *StreamConfig     `json:\"downloadSettings\"`\n\tExtra                json.RawMessage   `json:\"extra\"`\n}\n\ntype XmuxConfig struct {\n\tMaxConcurrency   Int32Range `json:\"maxConcurrency\"`\n\tMaxConnections   Int32Range `json:\"maxConnections\"`\n\tCMaxReuseTimes   Int32Range `json:\"cMaxReuseTimes\"`\n\tHMaxRequestTimes Int32Range `json:\"hMaxRequestTimes\"`\n\tHMaxReusableSecs Int32Range `json:\"hMaxReusableSecs\"`\n\tHKeepAlivePeriod int64      `json:\"hKeepAlivePeriod\"`\n}\n\nfunc newRangeConfig(input Int32Range) *splithttp.RangeConfig {\n\treturn &splithttp.RangeConfig{\n\t\tFrom: input.From,\n\t\tTo:   input.To,\n\t}\n}\n\n// Build implements Buildable.\nfunc (c *SplitHTTPConfig) Build() (proto.Message, error) {\n\tif c.Extra != nil {\n\t\tvar extra SplitHTTPConfig\n\t\tif err := json.Unmarshal(c.Extra, &extra); err != nil {\n\t\t\treturn nil, errors.New(`Failed to unmarshal \"extra\".`).Base(err)\n\t\t}\n\t\textra.Host = c.Host\n\t\textra.Path = c.Path\n\t\textra.Mode = c.Mode\n\t\tc = &extra\n\t}\n\n\tswitch c.Mode {\n\tcase \"\":\n\t\tc.Mode = \"auto\"\n\tcase \"auto\", \"packet-up\", \"stream-up\", \"stream-one\":\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported mode: \" + c.Mode)\n\t}\n\n\t// Priority (client): host > serverName > address\n\tfor k := range c.Headers {\n\t\tif strings.ToLower(k) == \"host\" {\n\t\t\treturn nil, errors.New(`\"headers\" can't contain \"host\"`)\n\t\t}\n\t}\n\n\tif c.XPaddingBytes != (Int32Range{}) && (c.XPaddingBytes.From <= 0 || c.XPaddingBytes.To <= 0) {\n\t\treturn nil, errors.New(\"xPaddingBytes cannot be disabled\")\n\t}\n\n\tif c.XPaddingKey == \"\" {\n\t\tc.XPaddingKey = \"x_padding\"\n\t}\n\n\tif c.XPaddingHeader == \"\" {\n\t\tc.XPaddingHeader = \"X-Padding\"\n\t}\n\n\tswitch c.XPaddingPlacement {\n\tcase \"\":\n\t\tc.XPaddingPlacement = \"queryInHeader\"\n\tcase \"cookie\", \"header\", \"query\", \"queryInHeader\":\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported padding placement: \" + c.XPaddingPlacement)\n\t}\n\n\tswitch c.XPaddingMethod {\n\tcase \"\":\n\t\tc.XPaddingMethod = \"repeat-x\"\n\tcase \"repeat-x\", \"tokenish\":\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported padding method: \" + c.XPaddingMethod)\n\t}\n\n\tswitch c.UplinkDataPlacement {\n\tcase \"\":\n\t\tc.UplinkDataPlacement = splithttp.PlacementAuto\n\tcase splithttp.PlacementAuto, splithttp.PlacementBody:\n\tcase splithttp.PlacementCookie, splithttp.PlacementHeader:\n\t\tif c.Mode != \"packet-up\" {\n\t\t\treturn nil, errors.New(\"UplinkDataPlacement can be \" + c.UplinkDataPlacement + \" only in packet-up mode\")\n\t\t}\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported uplink data placement: \" + c.UplinkDataPlacement)\n\t}\n\n\tif c.UplinkHTTPMethod == \"\" {\n\t\tc.UplinkHTTPMethod = \"POST\"\n\t}\n\tc.UplinkHTTPMethod = strings.ToUpper(c.UplinkHTTPMethod)\n\n\tif c.UplinkHTTPMethod == \"GET\" && c.Mode != \"packet-up\" {\n\t\treturn nil, errors.New(\"uplinkHTTPMethod can be GET only in packet-up mode\")\n\t}\n\n\tswitch c.SessionPlacement {\n\tcase \"\":\n\t\tc.SessionPlacement = \"path\"\n\tcase \"path\", \"cookie\", \"header\", \"query\":\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported session placement: \" + c.SessionPlacement)\n\t}\n\n\tswitch c.SeqPlacement {\n\tcase \"\":\n\t\tc.SeqPlacement = \"path\"\n\tcase \"path\", \"cookie\", \"header\", \"query\":\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported seq placement: \" + c.SeqPlacement)\n\t}\n\n\tif c.SessionPlacement != \"path\" && c.SessionKey == \"\" {\n\t\tswitch c.SessionPlacement {\n\t\tcase \"cookie\", \"query\":\n\t\t\tc.SessionKey = \"x_session\"\n\t\tcase \"header\":\n\t\t\tc.SessionKey = \"X-Session\"\n\t\t}\n\t}\n\n\tif c.SeqPlacement != \"path\" && c.SeqKey == \"\" {\n\t\tswitch c.SeqPlacement {\n\t\tcase \"cookie\", \"query\":\n\t\t\tc.SeqKey = \"x_seq\"\n\t\tcase \"header\":\n\t\t\tc.SeqKey = \"X-Seq\"\n\t\t}\n\t}\n\n\tif c.UplinkDataPlacement != splithttp.PlacementBody && c.UplinkDataKey == \"\" {\n\t\tswitch c.UplinkDataPlacement {\n\t\tcase splithttp.PlacementCookie:\n\t\t\tc.UplinkDataKey = \"x_data\"\n\t\tcase splithttp.PlacementAuto, splithttp.PlacementHeader:\n\t\t\tc.UplinkDataKey = \"X-Data\"\n\t\t}\n\t}\n\n\tif c.ServerMaxHeaderBytes < 0 {\n\t\treturn nil, errors.New(\"invalid negative value of maxHeaderBytes\")\n\t}\n\n\tif c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {\n\t\treturn nil, errors.New(\"maxConnections cannot be specified together with maxConcurrency\")\n\t}\n\tif c.Xmux == (XmuxConfig{}) {\n\t\tc.Xmux.MaxConcurrency.From = 1\n\t\tc.Xmux.MaxConcurrency.To = 1\n\t\tc.Xmux.HMaxRequestTimes.From = 600\n\t\tc.Xmux.HMaxRequestTimes.To = 900\n\t\tc.Xmux.HMaxReusableSecs.From = 1800\n\t\tc.Xmux.HMaxReusableSecs.To = 3000\n\t}\n\n\tconfig := &splithttp.Config{\n\t\tHost:                 c.Host,\n\t\tPath:                 c.Path,\n\t\tMode:                 c.Mode,\n\t\tHeaders:              c.Headers,\n\t\tXPaddingBytes:        newRangeConfig(c.XPaddingBytes),\n\t\tXPaddingObfsMode:     c.XPaddingObfsMode,\n\t\tXPaddingKey:          c.XPaddingKey,\n\t\tXPaddingHeader:       c.XPaddingHeader,\n\t\tXPaddingPlacement:    c.XPaddingPlacement,\n\t\tXPaddingMethod:       c.XPaddingMethod,\n\t\tUplinkHTTPMethod:     c.UplinkHTTPMethod,\n\t\tSessionPlacement:     c.SessionPlacement,\n\t\tSeqPlacement:         c.SeqPlacement,\n\t\tSessionKey:           c.SessionKey,\n\t\tSeqKey:               c.SeqKey,\n\t\tUplinkDataPlacement:  c.UplinkDataPlacement,\n\t\tUplinkDataKey:        c.UplinkDataKey,\n\t\tUplinkChunkSize:      newRangeConfig(c.UplinkChunkSize),\n\t\tNoGRPCHeader:         c.NoGRPCHeader,\n\t\tNoSSEHeader:          c.NoSSEHeader,\n\t\tScMaxEachPostBytes:   newRangeConfig(c.ScMaxEachPostBytes),\n\t\tScMinPostsIntervalMs: newRangeConfig(c.ScMinPostsIntervalMs),\n\t\tScMaxBufferedPosts:   c.ScMaxBufferedPosts,\n\t\tScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),\n\t\tServerMaxHeaderBytes: c.ServerMaxHeaderBytes,\n\t\tXmux: &splithttp.XmuxConfig{\n\t\t\tMaxConcurrency:   newRangeConfig(c.Xmux.MaxConcurrency),\n\t\t\tMaxConnections:   newRangeConfig(c.Xmux.MaxConnections),\n\t\t\tCMaxReuseTimes:   newRangeConfig(c.Xmux.CMaxReuseTimes),\n\t\t\tHMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),\n\t\t\tHMaxReusableSecs: newRangeConfig(c.Xmux.HMaxReusableSecs),\n\t\t\tHKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,\n\t\t},\n\t}\n\n\tif c.DownloadSettings != nil {\n\t\tif c.Mode == \"stream-one\" {\n\t\t\treturn nil, errors.New(`Can not use \"downloadSettings\" in \"stream-one\" mode.`)\n\t\t}\n\t\tvar err error\n\t\tif config.DownloadSettings, err = c.DownloadSettings.Build(); err != nil {\n\t\t\treturn nil, errors.New(`Failed to build \"downloadSettings\".`).Base(err)\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\nconst (\n\tByte     = 1\n\tKilobyte = 1024 * Byte\n\tMegabyte = 1024 * Kilobyte\n\tGigabyte = 1024 * Megabyte\n\tTerabyte = 1024 * Gigabyte\n)\n\ntype Bandwidth string\n\nfunc (b Bandwidth) Bps() (uint64, error) {\n\ts := strings.TrimSpace(strings.ToLower(string(b)))\n\tif s == \"\" {\n\t\treturn 0, nil\n\t}\n\n\tidx := len(s)\n\tfor i, c := range s {\n\t\tif (c < '0' || c > '9') && c != '.' {\n\t\t\tidx = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tnumStr := s[:idx]\n\tunit := strings.TrimSpace(s[idx:])\n\n\tval, err := strconv.ParseFloat(numStr, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tmul := uint64(1)\n\tswitch unit {\n\tcase \"\", \"b\", \"bps\":\n\t\tmul = Byte\n\tcase \"k\", \"kb\", \"kbps\":\n\t\tmul = Kilobyte\n\tcase \"m\", \"mb\", \"mbps\":\n\t\tmul = Megabyte\n\tcase \"g\", \"gb\", \"gbps\":\n\t\tmul = Gigabyte\n\tcase \"t\", \"tb\", \"tbps\":\n\t\tmul = Terabyte\n\tdefault:\n\t\treturn 0, errors.New(\"unsupported unit: \" + unit)\n\t}\n\n\treturn uint64(val*float64(mul)) / 8, nil\n}\n\ntype UdpHop struct {\n\tPortList json.RawMessage `json:\"ports\"`\n\tInterval *Int32Range     `json:\"interval\"`\n}\n\ntype Masquerade struct {\n\tType string `json:\"type\"`\n\n\tDir string `json:\"dir\"`\n\n\tUrl         string `json:\"url\"`\n\tRewriteHost bool   `json:\"rewriteHost\"`\n\tInsecure    bool   `json:\"insecure\"`\n\n\tContent    string            `json:\"content\"`\n\tHeaders    map[string]string `json:\"headers\"`\n\tStatusCode int32             `json:\"statusCode\"`\n}\n\ntype HysteriaConfig struct {\n\tVersion int32  `json:\"version\"`\n\tAuth    string `json:\"auth\"`\n\n\tCongestion *string    `json:\"congestion\"`\n\tUp         *Bandwidth `json:\"up\"`\n\tDown       *Bandwidth `json:\"down\"`\n\tUdpHop     *UdpHop    `json:\"udphop\"`\n\n\tUdpIdleTimeout int64      `json:\"udpIdleTimeout\"`\n\tMasquerade     Masquerade `json:\"masquerade\"`\n}\n\nfunc (c *HysteriaConfig) Build() (proto.Message, error) {\n\tif c.Version != 2 {\n\t\treturn nil, errors.New(\"version != 2\")\n\t}\n\n\tif c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {\n\t\terrors.LogWarning(context.Background(), \"congestion & up & down & udphop move to finalmask/quicParams\")\n\t}\n\n\tif c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {\n\t\treturn nil, errors.New(\"UdpIdleTimeout must be between 2 and 600\")\n\t}\n\n\tconfig := &hysteria.Config{}\n\tconfig.Version = c.Version\n\tconfig.Auth = c.Auth\n\tconfig.UdpIdleTimeout = c.UdpIdleTimeout\n\tconfig.MasqType = c.Masquerade.Type\n\tconfig.MasqFile = c.Masquerade.Dir\n\tconfig.MasqUrl = c.Masquerade.Url\n\tconfig.MasqUrlRewriteHost = c.Masquerade.RewriteHost\n\tconfig.MasqUrlInsecure = c.Masquerade.Insecure\n\tconfig.MasqString = c.Masquerade.Content\n\tconfig.MasqStringHeaders = c.Masquerade.Headers\n\tconfig.MasqStringStatusCode = c.Masquerade.StatusCode\n\n\tif config.UdpIdleTimeout == 0 {\n\t\tconfig.UdpIdleTimeout = 60\n\t}\n\n\treturn config, nil\n}\n\nfunc readFileOrString(f string, s []string) ([]byte, error) {\n\tif len(f) > 0 {\n\t\treturn filesystem.ReadCert(f)\n\t}\n\tif len(s) > 0 {\n\t\treturn []byte(strings.Join(s, \"\\n\")), nil\n\t}\n\treturn nil, errors.New(\"both file and bytes are empty.\")\n}\n\ntype TLSCertConfig struct {\n\tCertFile       string   `json:\"certificateFile\"`\n\tCertStr        []string `json:\"certificate\"`\n\tKeyFile        string   `json:\"keyFile\"`\n\tKeyStr         []string `json:\"key\"`\n\tUsage          string   `json:\"usage\"`\n\tOcspStapling   uint64   `json:\"ocspStapling\"`\n\tOneTimeLoading bool     `json:\"oneTimeLoading\"`\n\tBuildChain     bool     `json:\"buildChain\"`\n}\n\n// Build implements Buildable.\nfunc (c *TLSCertConfig) Build() (*tls.Certificate, error) {\n\tcertificate := new(tls.Certificate)\n\n\tcert, err := readFileOrString(c.CertFile, c.CertStr)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse certificate\").Base(err)\n\t}\n\tcertificate.Certificate = cert\n\tcertificate.CertificatePath = c.CertFile\n\n\tif len(c.KeyFile) > 0 || len(c.KeyStr) > 0 {\n\t\tkey, err := readFileOrString(c.KeyFile, c.KeyStr)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to parse key\").Base(err)\n\t\t}\n\t\tcertificate.Key = key\n\t\tcertificate.KeyPath = c.KeyFile\n\t}\n\n\tswitch strings.ToLower(c.Usage) {\n\tcase \"encipherment\":\n\t\tcertificate.Usage = tls.Certificate_ENCIPHERMENT\n\tcase \"verify\":\n\t\tcertificate.Usage = tls.Certificate_AUTHORITY_VERIFY\n\tcase \"issue\":\n\t\tcertificate.Usage = tls.Certificate_AUTHORITY_ISSUE\n\tdefault:\n\t\tcertificate.Usage = tls.Certificate_ENCIPHERMENT\n\t}\n\tif certificate.KeyPath == \"\" && certificate.CertificatePath == \"\" {\n\t\tcertificate.OneTimeLoading = true\n\t} else {\n\t\tcertificate.OneTimeLoading = c.OneTimeLoading\n\t}\n\tcertificate.OcspStapling = c.OcspStapling\n\tcertificate.BuildChain = c.BuildChain\n\n\treturn certificate, nil\n}\n\ntype QuicParamsConfig struct {\n\tCongestion                  string    `json:\"congestion\"`\n\tDebug                       bool      `json:\"debug\"`\n\tBrutalUp                    Bandwidth `json:\"brutalUp\"`\n\tBrutalDown                  Bandwidth `json:\"brutalDown\"`\n\tUdpHop                      UdpHop    `json:\"udpHop\"`\n\tInitStreamReceiveWindow     uint64    `json:\"initStreamReceiveWindow\"`\n\tMaxStreamReceiveWindow      uint64    `json:\"maxStreamReceiveWindow\"`\n\tInitConnectionReceiveWindow uint64    `json:\"initConnectionReceiveWindow\"`\n\tMaxConnectionReceiveWindow  uint64    `json:\"maxConnectionReceiveWindow\"`\n\tMaxIdleTimeout              int64     `json:\"maxIdleTimeout\"`\n\tKeepAlivePeriod             int64     `json:\"keepAlivePeriod\"`\n\tDisablePathMTUDiscovery     bool      `json:\"disablePathMTUDiscovery\"`\n\tMaxIncomingStreams          int64     `json:\"maxIncomingStreams\"`\n}\n\ntype TLSConfig struct {\n\tAllowInsecure           bool             `json:\"allowInsecure\"`\n\tCerts                   []*TLSCertConfig `json:\"certificates\"`\n\tServerName              string           `json:\"serverName\"`\n\tALPN                    *StringList      `json:\"alpn\"`\n\tEnableSessionResumption bool             `json:\"enableSessionResumption\"`\n\tDisableSystemRoot       bool             `json:\"disableSystemRoot\"`\n\tMinVersion              string           `json:\"minVersion\"`\n\tMaxVersion              string           `json:\"maxVersion\"`\n\tCipherSuites            string           `json:\"cipherSuites\"`\n\tFingerprint             string           `json:\"fingerprint\"`\n\tRejectUnknownSNI        bool             `json:\"rejectUnknownSni\"`\n\tCurvePreferences        *StringList      `json:\"curvePreferences\"`\n\tMasterKeyLog            string           `json:\"masterKeyLog\"`\n\tPinnedPeerCertSha256    string           `json:\"pinnedPeerCertSha256\"`\n\tVerifyPeerCertByName    string           `json:\"verifyPeerCertByName\"`\n\tVerifyPeerCertInNames   []string         `json:\"verifyPeerCertInNames\"`\n\tECHServerKeys           string           `json:\"echServerKeys\"`\n\tECHConfigList           string           `json:\"echConfigList\"`\n\tECHForceQuery           string           `json:\"echForceQuery\"`\n\tECHSocketSettings       *SocketConfig    `json:\"echSockopt\"`\n}\n\n// Build implements Buildable.\nfunc (c *TLSConfig) Build() (proto.Message, error) {\n\tconfig := new(tls.Config)\n\tconfig.Certificate = make([]*tls.Certificate, len(c.Certs))\n\tfor idx, certConf := range c.Certs {\n\t\tcert, err := certConf.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.Certificate[idx] = cert\n\t}\n\tserverName := c.ServerName\n\tif len(c.ServerName) > 0 {\n\t\tconfig.ServerName = serverName\n\t}\n\tif c.ALPN != nil && len(*c.ALPN) > 0 {\n\t\tconfig.NextProtocol = []string(*c.ALPN)\n\t}\n\tif len(config.NextProtocol) > 1 {\n\t\tfor _, p := range config.NextProtocol {\n\t\t\tif tls.IsFromMitm(p) {\n\t\t\t\treturn nil, errors.New(`only one element is allowed in \"alpn\" when using \"fromMitm\" in it`)\n\t\t\t}\n\t\t}\n\t}\n\tif c.CurvePreferences != nil && len(*c.CurvePreferences) > 0 {\n\t\tconfig.CurvePreferences = []string(*c.CurvePreferences)\n\t}\n\tconfig.EnableSessionResumption = c.EnableSessionResumption\n\tconfig.DisableSystemRoot = c.DisableSystemRoot\n\tconfig.MinVersion = c.MinVersion\n\tconfig.MaxVersion = c.MaxVersion\n\tconfig.CipherSuites = c.CipherSuites\n\tconfig.Fingerprint = strings.ToLower(c.Fingerprint)\n\tif config.Fingerprint != \"unsafe\" && tls.GetFingerprint(config.Fingerprint) == nil {\n\t\treturn nil, errors.New(`unknown \"fingerprint\": `, config.Fingerprint)\n\t}\n\tconfig.RejectUnknownSni = c.RejectUnknownSNI\n\tconfig.MasterKeyLog = c.MasterKeyLog\n\n\tif c.AllowInsecure {\n\t\tif time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {\n\t\t\treturn nil, errors.PrintRemovedFeatureError(`\"allowInsecure\"`, `\"pinnedPeerCertSha256\"`)\n\t\t} else {\n\t\t\terrors.LogWarning(context.Background(), `\"allowInsecure\" will be removed automatically after 2026-06-01, please use \"pinnedPeerCertSha256\"(pcs) and \"verifyPeerCertByName\"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)\n\t\t\tconfig.AllowInsecure = true\n\t\t}\n\t}\n\tif c.PinnedPeerCertSha256 != \"\" {\n\t\tfor v := range strings.SplitSeq(c.PinnedPeerCertSha256, \",\") {\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif v == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// remove colons for OpenSSL format\n\t\t\thashValue, err := hex.DecodeString(strings.ReplaceAll(v, \":\", \"\"))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif len(hashValue) != 32 {\n\t\t\t\treturn nil, errors.New(\"incorrect pinnedPeerCertSha256 length: \", v)\n\t\t\t}\n\t\t\tconfig.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)\n\t\t}\n\t}\n\n\tif c.VerifyPeerCertInNames != nil {\n\t\treturn nil, errors.PrintRemovedFeatureError(`\"verifyPeerCertInNames\"`, `\"verifyPeerCertByName\"`)\n\t}\n\tif c.VerifyPeerCertByName != \"\" {\n\t\tfor v := range strings.SplitSeq(c.VerifyPeerCertByName, \",\") {\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif v == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconfig.VerifyPeerCertByName = append(config.VerifyPeerCertByName, v)\n\t\t}\n\t}\n\n\tif c.ECHServerKeys != \"\" {\n\t\tEchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid ECH Config\", c.ECHServerKeys)\n\t\t}\n\t\tconfig.EchServerKeys = EchPrivateKey\n\t}\n\tswitch c.ECHForceQuery {\n\tcase \"none\", \"half\", \"full\", \"\":\n\t\tconfig.EchForceQuery = c.ECHForceQuery\n\tdefault:\n\t\treturn nil, errors.New(`invalid \"echForceQuery\": `, c.ECHForceQuery)\n\t}\n\tconfig.EchForceQuery = c.ECHForceQuery\n\tconfig.EchConfigList = c.ECHConfigList\n\tif c.ECHSocketSettings != nil {\n\t\tss, err := c.ECHSocketSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build ech sockopt.\").Base(err)\n\t\t}\n\t\tconfig.EchSocketSettings = ss\n\t}\n\n\treturn config, nil\n}\n\ntype LimitFallback struct {\n\tAfterBytes       uint64\n\tBytesPerSec      uint64\n\tBurstBytesPerSec uint64\n}\n\ntype REALITYConfig struct {\n\tMasterKeyLog string          `json:\"masterKeyLog\"`\n\tShow         bool            `json:\"show\"`\n\tTarget       json.RawMessage `json:\"target\"`\n\tDest         json.RawMessage `json:\"dest\"`\n\tType         string          `json:\"type\"`\n\tXver         uint64          `json:\"xver\"`\n\tServerNames  []string        `json:\"serverNames\"`\n\tPrivateKey   string          `json:\"privateKey\"`\n\tMinClientVer string          `json:\"minClientVer\"`\n\tMaxClientVer string          `json:\"maxClientVer\"`\n\tMaxTimeDiff  uint64          `json:\"maxTimeDiff\"`\n\tShortIds     []string        `json:\"shortIds\"`\n\tMldsa65Seed  string          `json:\"mldsa65Seed\"`\n\n\tLimitFallbackUpload   LimitFallback `json:\"limitFallbackUpload\"`\n\tLimitFallbackDownload LimitFallback `json:\"limitFallbackDownload\"`\n\n\tFingerprint   string `json:\"fingerprint\"`\n\tServerName    string `json:\"serverName\"`\n\tPassword      string `json:\"password\"`\n\tPublicKey     string `json:\"publicKey\"`\n\tShortId       string `json:\"shortId\"`\n\tMldsa65Verify string `json:\"mldsa65Verify\"`\n\tSpiderX       string `json:\"spiderX\"`\n}\n\nfunc (c *REALITYConfig) Build() (proto.Message, error) {\n\tconfig := new(reality.Config)\n\tconfig.MasterKeyLog = c.MasterKeyLog\n\tconfig.Show = c.Show\n\tvar err error\n\tif c.Target != nil {\n\t\tc.Dest = c.Target\n\t}\n\tif c.Dest != nil {\n\t\tvar i uint16\n\t\tvar s string\n\t\tif err = json.Unmarshal(c.Dest, &i); err == nil {\n\t\t\ts = strconv.Itoa(int(i))\n\t\t} else {\n\t\t\t_ = json.Unmarshal(c.Dest, &s)\n\t\t}\n\t\tif c.Type == \"\" && s != \"\" {\n\t\t\tswitch s[0] {\n\t\t\tcase '@', '/':\n\t\t\t\tc.Type = \"unix\"\n\t\t\t\tif s[0] == '@' && len(s) > 1 && s[1] == '@' && (runtime.GOOS == \"linux\" || runtime.GOOS == \"android\") {\n\t\t\t\t\tfullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy\n\t\t\t\t\tcopy(fullAddr, s[1:])\n\t\t\t\t\ts = string(fullAddr)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif _, err = strconv.Atoi(s); err == nil {\n\t\t\t\t\ts = \"localhost:\" + s\n\t\t\t\t}\n\t\t\t\tif _, _, err = net.SplitHostPort(s); err == nil {\n\t\t\t\t\tc.Type = \"tcp\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif c.Type == \"\" {\n\t\t\treturn nil, errors.New(`please fill in a valid value for \"target\"`)\n\t\t}\n\t\tif c.Xver > 2 {\n\t\t\treturn nil, errors.New(`invalid PROXY protocol version, \"xver\" only accepts 0, 1, 2`)\n\t\t}\n\t\tif len(c.ServerNames) == 0 {\n\t\t\treturn nil, errors.New(`empty \"serverNames\"`)\n\t\t}\n\t\tif c.PrivateKey == \"\" {\n\t\t\treturn nil, errors.New(`empty \"privateKey\"`)\n\t\t}\n\t\tif config.PrivateKey, err = base64.RawURLEncoding.DecodeString(c.PrivateKey); err != nil || len(config.PrivateKey) != 32 {\n\t\t\treturn nil, errors.New(`invalid \"privateKey\": `, c.PrivateKey)\n\t\t}\n\t\tif c.MinClientVer != \"\" {\n\t\t\tconfig.MinClientVer = make([]byte, 3)\n\t\t\tvar u uint64\n\t\t\tfor i, s := range strings.Split(c.MinClientVer, \".\") {\n\t\t\t\tif i == 3 {\n\t\t\t\t\treturn nil, errors.New(`invalid \"minClientVer\": `, c.MinClientVer)\n\t\t\t\t}\n\t\t\t\tif u, err = strconv.ParseUint(s, 10, 8); err != nil {\n\t\t\t\t\treturn nil, errors.New(`\"minClientVer[`, i, `]\" should be less than 256`)\n\t\t\t\t} else {\n\t\t\t\t\tconfig.MinClientVer[i] = byte(u)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif c.MaxClientVer != \"\" {\n\t\t\tconfig.MaxClientVer = make([]byte, 3)\n\t\t\tvar u uint64\n\t\t\tfor i, s := range strings.Split(c.MaxClientVer, \".\") {\n\t\t\t\tif i == 3 {\n\t\t\t\t\treturn nil, errors.New(`invalid \"maxClientVer\": `, c.MaxClientVer)\n\t\t\t\t}\n\t\t\t\tif u, err = strconv.ParseUint(s, 10, 8); err != nil {\n\t\t\t\t\treturn nil, errors.New(`\"maxClientVer[`, i, `]\" should be less than 256`)\n\t\t\t\t} else {\n\t\t\t\t\tconfig.MaxClientVer[i] = byte(u)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(c.ShortIds) == 0 {\n\t\t\treturn nil, errors.New(`empty \"shortIds\"`)\n\t\t}\n\t\tconfig.ShortIds = make([][]byte, len(c.ShortIds))\n\t\tfor i, s := range c.ShortIds {\n\t\t\tif len(s) > 16 {\n\t\t\t\treturn nil, errors.New(`too long \"shortIds[`, i, `]\": `, s)\n\t\t\t}\n\t\t\tconfig.ShortIds[i] = make([]byte, 8)\n\t\t\tif _, err = hex.Decode(config.ShortIds[i], []byte(s)); err != nil {\n\t\t\t\treturn nil, errors.New(`invalid \"shortIds[`, i, `]\": `, s)\n\t\t\t}\n\t\t}\n\t\tconfig.Dest = s\n\t\tconfig.Type = c.Type\n\t\tconfig.Xver = c.Xver\n\t\tconfig.ServerNames = c.ServerNames\n\t\tconfig.MaxTimeDiff = c.MaxTimeDiff\n\n\t\tif c.Mldsa65Seed != \"\" {\n\t\t\tif c.Mldsa65Seed == c.PrivateKey {\n\t\t\t\treturn nil, errors.New(`\"mldsa65Seed\" and \"privateKey\" can not be the same value: `, c.Mldsa65Seed)\n\t\t\t}\n\t\t\tif config.Mldsa65Seed, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Seed); err != nil || len(config.Mldsa65Seed) != 32 {\n\t\t\t\treturn nil, errors.New(`invalid \"mldsa65Seed\": `, c.Mldsa65Seed)\n\t\t\t}\n\t\t}\n\n\t\tconfig.LimitFallbackUpload = new(reality.LimitFallback)\n\t\tconfig.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes\n\t\tconfig.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec\n\t\tconfig.LimitFallbackUpload.BurstBytesPerSec = c.LimitFallbackUpload.BurstBytesPerSec\n\t\tconfig.LimitFallbackDownload = new(reality.LimitFallback)\n\t\tconfig.LimitFallbackDownload.AfterBytes = c.LimitFallbackDownload.AfterBytes\n\t\tconfig.LimitFallbackDownload.BytesPerSec = c.LimitFallbackDownload.BytesPerSec\n\t\tconfig.LimitFallbackDownload.BurstBytesPerSec = c.LimitFallbackDownload.BurstBytesPerSec\n\t} else {\n\t\tconfig.Fingerprint = strings.ToLower(c.Fingerprint)\n\t\tif config.Fingerprint == \"unsafe\" || config.Fingerprint == \"hellogolang\" {\n\t\t\treturn nil, errors.New(`invalid \"fingerprint\": `, config.Fingerprint)\n\t\t}\n\t\tif tls.GetFingerprint(config.Fingerprint) == nil {\n\t\t\treturn nil, errors.New(`unknown \"fingerprint\": `, config.Fingerprint)\n\t\t}\n\t\tif len(c.ServerNames) != 0 {\n\t\t\treturn nil, errors.New(`non-empty \"serverNames\", please use \"serverName\" instead`)\n\t\t}\n\t\tif c.Password != \"\" {\n\t\t\tc.PublicKey = c.Password\n\t\t}\n\t\tif c.PublicKey == \"\" {\n\t\t\treturn nil, errors.New(`empty \"password\"`)\n\t\t}\n\t\tif config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {\n\t\t\treturn nil, errors.New(`invalid \"password\": `, c.PublicKey)\n\t\t}\n\t\tif len(c.ShortIds) != 0 {\n\t\t\treturn nil, errors.New(`non-empty \"shortIds\", please use \"shortId\" instead`)\n\t\t}\n\t\tif len(c.ShortId) > 16 {\n\t\t\treturn nil, errors.New(`too long \"shortId\": `, c.ShortId)\n\t\t}\n\t\tconfig.ShortId = make([]byte, 8)\n\t\tif _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil {\n\t\t\treturn nil, errors.New(`invalid \"shortId\": `, c.ShortId)\n\t\t}\n\t\tif c.Mldsa65Verify != \"\" {\n\t\t\tif config.Mldsa65Verify, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Verify); err != nil || len(config.Mldsa65Verify) != 1952 {\n\t\t\t\treturn nil, errors.New(`invalid \"mldsa65Verify\": `, c.Mldsa65Verify)\n\t\t\t}\n\t\t}\n\t\tif c.SpiderX == \"\" {\n\t\t\tc.SpiderX = \"/\"\n\t\t}\n\t\tif c.SpiderX[0] != '/' {\n\t\t\treturn nil, errors.New(`invalid \"spiderX\": `, c.SpiderX)\n\t\t}\n\t\tconfig.SpiderY = make([]int64, 10)\n\t\tu, _ := url.Parse(c.SpiderX)\n\t\tq := u.Query()\n\t\tparse := func(param string, index int) {\n\t\t\tif q.Get(param) != \"\" {\n\t\t\t\ts := strings.Split(q.Get(param), \"-\")\n\t\t\t\tif len(s) == 1 {\n\t\t\t\t\tconfig.SpiderY[index], _ = strconv.ParseInt(s[0], 10, 64)\n\t\t\t\t\tconfig.SpiderY[index+1], _ = strconv.ParseInt(s[0], 10, 64)\n\t\t\t\t} else {\n\t\t\t\t\tconfig.SpiderY[index], _ = strconv.ParseInt(s[0], 10, 64)\n\t\t\t\t\tconfig.SpiderY[index+1], _ = strconv.ParseInt(s[1], 10, 64)\n\t\t\t\t}\n\t\t\t}\n\t\t\tq.Del(param)\n\t\t}\n\t\tparse(\"p\", 0) // padding\n\t\tparse(\"c\", 2) // concurrency\n\t\tparse(\"t\", 4) // times\n\t\tparse(\"i\", 6) // interval\n\t\tparse(\"r\", 8) // return\n\t\tu.RawQuery = q.Encode()\n\t\tconfig.SpiderX = u.String()\n\t\tconfig.ServerName = c.ServerName\n\t}\n\treturn config, nil\n}\n\ntype TransportProtocol string\n\n// Build implements Buildable.\nfunc (p TransportProtocol) Build() (string, error) {\n\tswitch strings.ToLower(string(p)) {\n\tcase \"raw\", \"tcp\":\n\t\treturn \"tcp\", nil\n\tcase \"xhttp\", \"splithttp\":\n\t\treturn \"splithttp\", nil\n\tcase \"kcp\", \"mkcp\":\n\t\treturn \"mkcp\", nil\n\tcase \"grpc\":\n\t\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"gRPC transport (with unnecessary costs, etc.)\", \"XHTTP stream-up H2\")\n\t\treturn \"grpc\", nil\n\tcase \"ws\", \"websocket\":\n\t\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"WebSocket transport (with ALPN http/1.1, etc.)\", \"XHTTP H2 & H3\")\n\t\treturn \"websocket\", nil\n\tcase \"httpupgrade\":\n\t\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"HTTPUpgrade transport (with ALPN http/1.1, etc.)\", \"XHTTP H2 & H3\")\n\t\treturn \"httpupgrade\", nil\n\tcase \"h2\", \"h3\", \"http\":\n\t\treturn \"\", errors.PrintRemovedFeatureError(\"HTTP transport (without header padding, etc.)\", \"XHTTP stream-one H2 & H3\")\n\tcase \"quic\":\n\t\treturn \"\", errors.PrintRemovedFeatureError(\"QUIC transport (without web service, etc.)\", \"XHTTP stream-one H3\")\n\tcase \"hysteria\":\n\t\treturn \"hysteria\", nil\n\tdefault:\n\t\treturn \"\", errors.New(\"Config: unknown transport protocol: \", p)\n\t}\n}\n\ntype CustomSockoptConfig struct {\n\tSyetem  string `json:\"system\"`\n\tNetwork string `json:\"network\"`\n\tLevel   string `json:\"level\"`\n\tOpt     string `json:\"opt\"`\n\tValue   string `json:\"value\"`\n\tType    string `json:\"type\"`\n}\n\ntype HappyEyeballsConfig struct {\n\tPrioritizeIPv6   bool   `json:\"prioritizeIPv6\"`\n\tTryDelayMs       uint64 `json:\"tryDelayMs\"`\n\tInterleave       uint32 `json:\"interleave\"`\n\tMaxConcurrentTry uint32 `json:\"maxConcurrentTry\"`\n}\n\nfunc (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {\n\tvar innerHappyEyeballsConfig = struct {\n\t\tPrioritizeIPv6   bool   `json:\"prioritizeIPv6\"`\n\t\tTryDelayMs       uint64 `json:\"tryDelayMs\"`\n\t\tInterleave       uint32 `json:\"interleave\"`\n\t\tMaxConcurrentTry uint32 `json:\"maxConcurrentTry\"`\n\t}{PrioritizeIPv6: false, Interleave: 1, TryDelayMs: 0, MaxConcurrentTry: 4}\n\tif err := json.Unmarshal(data, &innerHappyEyeballsConfig); err != nil {\n\t\treturn err\n\t}\n\th.PrioritizeIPv6 = innerHappyEyeballsConfig.PrioritizeIPv6\n\th.TryDelayMs = innerHappyEyeballsConfig.TryDelayMs\n\th.Interleave = innerHappyEyeballsConfig.Interleave\n\th.MaxConcurrentTry = innerHappyEyeballsConfig.MaxConcurrentTry\n\treturn nil\n}\n\ntype SocketConfig struct {\n\tMark                  int32                  `json:\"mark\"`\n\tTFO                   interface{}            `json:\"tcpFastOpen\"`\n\tTProxy                string                 `json:\"tproxy\"`\n\tAcceptProxyProtocol   bool                   `json:\"acceptProxyProtocol\"`\n\tDomainStrategy        string                 `json:\"domainStrategy\"`\n\tDialerProxy           string                 `json:\"dialerProxy\"`\n\tTCPKeepAliveInterval  int32                  `json:\"tcpKeepAliveInterval\"`\n\tTCPKeepAliveIdle      int32                  `json:\"tcpKeepAliveIdle\"`\n\tTCPCongestion         string                 `json:\"tcpCongestion\"`\n\tTCPWindowClamp        int32                  `json:\"tcpWindowClamp\"`\n\tTCPMaxSeg             int32                  `json:\"tcpMaxSeg\"`\n\tPenetrate             bool                   `json:\"penetrate\"`\n\tTCPUserTimeout        int32                  `json:\"tcpUserTimeout\"`\n\tV6only                bool                   `json:\"v6only\"`\n\tInterface             string                 `json:\"interface\"`\n\tTcpMptcp              bool                   `json:\"tcpMptcp\"`\n\tCustomSockopt         []*CustomSockoptConfig `json:\"customSockopt\"`\n\tAddressPortStrategy   string                 `json:\"addressPortStrategy\"`\n\tHappyEyeballsSettings *HappyEyeballsConfig   `json:\"happyEyeballs\"`\n\tTrustedXForwardedFor  []string               `json:\"trustedXForwardedFor\"`\n}\n\n// Build implements Buildable.\nfunc (c *SocketConfig) Build() (*internet.SocketConfig, error) {\n\ttfo := int32(0) // don't invoke setsockopt() for TFO\n\tif c.TFO != nil {\n\t\tswitch v := c.TFO.(type) {\n\t\tcase bool:\n\t\t\tif v {\n\t\t\t\ttfo = 256\n\t\t\t} else {\n\t\t\t\ttfo = -1 // TFO need to be disabled\n\t\t\t}\n\t\tcase float64:\n\t\t\ttfo = int32(math.Min(v, math.MaxInt32))\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"tcpFastOpen: only boolean and integer value is acceptable\")\n\t\t}\n\t}\n\tvar tproxy internet.SocketConfig_TProxyMode\n\tswitch strings.ToLower(c.TProxy) {\n\tcase \"tproxy\":\n\t\ttproxy = internet.SocketConfig_TProxy\n\tcase \"redirect\":\n\t\ttproxy = internet.SocketConfig_Redirect\n\tdefault:\n\t\ttproxy = internet.SocketConfig_Off\n\t}\n\n\tdStrategy := internet.DomainStrategy_AS_IS\n\tswitch strings.ToLower(c.DomainStrategy) {\n\tcase \"asis\", \"\":\n\t\tdStrategy = internet.DomainStrategy_AS_IS\n\tcase \"useip\":\n\t\tdStrategy = internet.DomainStrategy_USE_IP\n\tcase \"useipv4\":\n\t\tdStrategy = internet.DomainStrategy_USE_IP4\n\tcase \"useipv6\":\n\t\tdStrategy = internet.DomainStrategy_USE_IP6\n\tcase \"useipv4v6\":\n\t\tdStrategy = internet.DomainStrategy_USE_IP46\n\tcase \"useipv6v4\":\n\t\tdStrategy = internet.DomainStrategy_USE_IP64\n\tcase \"forceip\":\n\t\tdStrategy = internet.DomainStrategy_FORCE_IP\n\tcase \"forceipv4\":\n\t\tdStrategy = internet.DomainStrategy_FORCE_IP4\n\tcase \"forceipv6\":\n\t\tdStrategy = internet.DomainStrategy_FORCE_IP6\n\tcase \"forceipv4v6\":\n\t\tdStrategy = internet.DomainStrategy_FORCE_IP46\n\tcase \"forceipv6v4\":\n\t\tdStrategy = internet.DomainStrategy_FORCE_IP64\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported domain strategy: \", c.DomainStrategy)\n\t}\n\n\tvar customSockopts []*internet.CustomSockopt\n\n\tfor _, copt := range c.CustomSockopt {\n\t\tcustomSockopt := &internet.CustomSockopt{\n\t\t\tSystem:  copt.Syetem,\n\t\t\tNetwork: copt.Network,\n\t\t\tLevel:   copt.Level,\n\t\t\tOpt:     copt.Opt,\n\t\t\tValue:   copt.Value,\n\t\t\tType:    copt.Type,\n\t\t}\n\t\tcustomSockopts = append(customSockopts, customSockopt)\n\t}\n\n\taddressPortStrategy := internet.AddressPortStrategy_None\n\tswitch strings.ToLower(c.AddressPortStrategy) {\n\tcase \"none\", \"\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_None\n\tcase \"srvportonly\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_SrvPortOnly\n\tcase \"srvaddressonly\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_SrvAddressOnly\n\tcase \"srvportandaddress\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_SrvPortAndAddress\n\tcase \"txtportonly\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_TxtPortOnly\n\tcase \"txtaddressonly\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_TxtAddressOnly\n\tcase \"txtportandaddress\":\n\t\taddressPortStrategy = internet.AddressPortStrategy_TxtPortAndAddress\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported address and port strategy: \", c.AddressPortStrategy)\n\t}\n\n\tvar happyEyeballs = &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}\n\tif c.HappyEyeballsSettings != nil {\n\t\thappyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6\n\t\thappyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave\n\t\thappyEyeballs.TryDelayMs = c.HappyEyeballsSettings.TryDelayMs\n\t\thappyEyeballs.MaxConcurrentTry = c.HappyEyeballsSettings.MaxConcurrentTry\n\t}\n\n\treturn &internet.SocketConfig{\n\t\tMark:                 c.Mark,\n\t\tTfo:                  tfo,\n\t\tTproxy:               tproxy,\n\t\tDomainStrategy:       dStrategy,\n\t\tAcceptProxyProtocol:  c.AcceptProxyProtocol,\n\t\tDialerProxy:          c.DialerProxy,\n\t\tTcpKeepAliveInterval: c.TCPKeepAliveInterval,\n\t\tTcpKeepAliveIdle:     c.TCPKeepAliveIdle,\n\t\tTcpCongestion:        c.TCPCongestion,\n\t\tTcpWindowClamp:       c.TCPWindowClamp,\n\t\tTcpMaxSeg:            c.TCPMaxSeg,\n\t\tPenetrate:            c.Penetrate,\n\t\tTcpUserTimeout:       c.TCPUserTimeout,\n\t\tV6Only:               c.V6only,\n\t\tInterface:            c.Interface,\n\t\tTcpMptcp:             c.TcpMptcp,\n\t\tCustomSockopt:        customSockopts,\n\t\tAddressPortStrategy:  addressPortStrategy,\n\t\tHappyEyeballs:        happyEyeballs,\n\t\tTrustedXForwardedFor: c.TrustedXForwardedFor,\n\t}, nil\n}\n\nfunc PraseByteSlice(data json.RawMessage, typ string) ([]byte, error) {\n\tswitch strings.ToLower(typ) {\n\tcase \"\", \"array\":\n\t\tif len(data) == 0 {\n\t\t\treturn data, nil\n\t\t}\n\t\tvar packet []byte\n\t\tif err := json.Unmarshal(data, &packet); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn packet, nil\n\tcase \"str\":\n\t\tvar str string\n\t\tif err := json.Unmarshal(data, &str); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []byte(str), nil\n\tcase \"hex\":\n\t\tvar str string\n\t\tif err := json.Unmarshal(data, &str); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn hex.DecodeString(str)\n\tcase \"base64\":\n\t\tvar str string\n\t\tif err := json.Unmarshal(data, &str); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn base64.StdEncoding.DecodeString(str)\n\tdefault:\n\t\treturn nil, errors.New(\"unknown type\")\n\t}\n}\n\nvar (\n\ttcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{\n\t\t\"header-custom\": func() interface{} { return new(HeaderCustomTCP) },\n\t\t\"fragment\":      func() interface{} { return new(FragmentMask) },\n\t\t\"sudoku\":        func() interface{} { return new(Sudoku) },\n\t}, \"type\", \"settings\")\n\n\tudpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{\n\t\t\"header-custom\":    func() interface{} { return new(HeaderCustomUDP) },\n\t\t\"header-dns\":       func() interface{} { return new(Dns) },\n\t\t\"header-dtls\":      func() interface{} { return new(Dtls) },\n\t\t\"header-srtp\":      func() interface{} { return new(Srtp) },\n\t\t\"header-utp\":       func() interface{} { return new(Utp) },\n\t\t\"header-wechat\":    func() interface{} { return new(Wechat) },\n\t\t\"header-wireguard\": func() interface{} { return new(Wireguard) },\n\t\t\"mkcp-original\":    func() interface{} { return new(Original) },\n\t\t\"mkcp-aes128gcm\":   func() interface{} { return new(Aes128Gcm) },\n\t\t\"noise\":            func() interface{} { return new(NoiseMask) },\n\t\t\"salamander\":       func() interface{} { return new(Salamander) },\n\t\t\"sudoku\":           func() interface{} { return new(Sudoku) },\n\t\t\"xdns\":             func() interface{} { return new(Xdns) },\n\t\t\"xicmp\":            func() interface{} { return new(Xicmp) },\n\t}, \"type\", \"settings\")\n)\n\ntype TCPItem struct {\n\tDelay  Int32Range      `json:\"delay\"`\n\tRand   int32           `json:\"rand\"`\n\tType   string          `json:\"type\"`\n\tPacket json.RawMessage `json:\"packet\"`\n}\n\ntype HeaderCustomTCP struct {\n\tClients [][]TCPItem `json:\"clients\"`\n\tServers [][]TCPItem `json:\"servers\"`\n\tErrors  [][]TCPItem `json:\"errors\"`\n}\n\nfunc (c *HeaderCustomTCP) Build() (proto.Message, error) {\n\tfor _, value := range c.Clients {\n\t\tfor _, item := range value {\n\t\t\tif len(item.Packet) > 0 && item.Rand > 0 {\n\t\t\t\treturn nil, errors.New(\"len(item.Packet) > 0 && item.Rand > 0\")\n\t\t\t}\n\t\t}\n\t}\n\tfor _, value := range c.Servers {\n\t\tfor _, item := range value {\n\t\t\tif len(item.Packet) > 0 && item.Rand > 0 {\n\t\t\t\treturn nil, errors.New(\"len(item.Packet) > 0 && item.Rand > 0\")\n\t\t\t}\n\t\t}\n\t}\n\tfor _, value := range c.Errors {\n\t\tfor _, item := range value {\n\t\t\tif len(item.Packet) > 0 && item.Rand > 0 {\n\t\t\t\treturn nil, errors.New(\"len(item.Packet) > 0 && item.Rand > 0\")\n\t\t\t}\n\t\t}\n\t}\n\n\tclients := make([]*custom.TCPSequence, len(c.Clients))\n\tfor i, value := range c.Clients {\n\t\tclients[i] = &custom.TCPSequence{}\n\t\tfor _, item := range value {\n\t\t\tvar err error\n\t\t\tif item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tclients[i].Sequence = append(clients[i].Sequence, &custom.TCPItem{\n\t\t\t\tDelayMin: int64(item.Delay.From),\n\t\t\t\tDelayMax: int64(item.Delay.To),\n\t\t\t\tRand:     item.Rand,\n\t\t\t\tPacket:   item.Packet,\n\t\t\t})\n\t\t}\n\t}\n\n\tservers := make([]*custom.TCPSequence, len(c.Servers))\n\tfor i, value := range c.Servers {\n\t\tservers[i] = &custom.TCPSequence{}\n\t\tfor _, item := range value {\n\t\t\tvar err error\n\t\t\tif item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tservers[i].Sequence = append(servers[i].Sequence, &custom.TCPItem{\n\t\t\t\tDelayMin: int64(item.Delay.From),\n\t\t\t\tDelayMax: int64(item.Delay.To),\n\t\t\t\tRand:     item.Rand,\n\t\t\t\tPacket:   item.Packet,\n\t\t\t})\n\t\t}\n\t}\n\n\terrors := make([]*custom.TCPSequence, len(c.Errors))\n\tfor i, value := range c.Errors {\n\t\terrors[i] = &custom.TCPSequence{}\n\t\tfor _, item := range value {\n\t\t\tvar err error\n\t\t\tif item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\terrors[i].Sequence = append(errors[i].Sequence, &custom.TCPItem{\n\t\t\t\tDelayMin: int64(item.Delay.From),\n\t\t\t\tDelayMax: int64(item.Delay.To),\n\t\t\t\tRand:     item.Rand,\n\t\t\t\tPacket:   item.Packet,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn &custom.TCPConfig{\n\t\tClients: clients,\n\t\tServers: servers,\n\t\tErrors:  errors,\n\t}, nil\n}\n\ntype FragmentMask struct {\n\tPackets  string     `json:\"packets\"`\n\tLength   Int32Range `json:\"length\"`\n\tDelay    Int32Range `json:\"delay\"`\n\tMaxSplit Int32Range `json:\"maxSplit\"`\n}\n\nfunc (c *FragmentMask) Build() (proto.Message, error) {\n\tconfig := &fragment.Config{}\n\n\tswitch strings.ToLower(c.Packets) {\n\tcase \"tlshello\":\n\t\tconfig.PacketsFrom = 0\n\t\tconfig.PacketsTo = 1\n\tcase \"\":\n\t\tconfig.PacketsFrom = 0\n\t\tconfig.PacketsTo = 0\n\tdefault:\n\t\tfrom, to, err := ParseRangeString(c.Packets)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Invalid PacketsFrom\").Base(err)\n\t\t}\n\t\tconfig.PacketsFrom = int64(from)\n\t\tconfig.PacketsTo = int64(to)\n\t\tif config.PacketsFrom == 0 {\n\t\t\treturn nil, errors.New(\"PacketsFrom can't be 0\")\n\t\t}\n\t}\n\n\tconfig.LengthMin = int64(c.Length.From)\n\tconfig.LengthMax = int64(c.Length.To)\n\tif config.LengthMin == 0 {\n\t\treturn nil, errors.New(\"LengthMin can't be 0\")\n\t}\n\n\tconfig.DelayMin = int64(c.Delay.From)\n\tconfig.DelayMax = int64(c.Delay.To)\n\n\tconfig.MaxSplitMin = int64(c.MaxSplit.From)\n\tconfig.MaxSplitMax = int64(c.MaxSplit.To)\n\n\treturn config, nil\n}\n\ntype NoiseItem struct {\n\tRand   Int32Range      `json:\"rand\"`\n\tType   string          `json:\"type\"`\n\tPacket json.RawMessage `json:\"packet\"`\n\tDelay  Int32Range      `json:\"delay\"`\n}\n\ntype NoiseMask struct {\n\tReset Int32Range  `json:\"reset\"`\n\tNoise []NoiseItem `json:\"noise\"`\n}\n\nfunc (c *NoiseMask) Build() (proto.Message, error) {\n\tfor _, item := range c.Noise {\n\t\tif len(item.Packet) > 0 && item.Rand.To > 0 {\n\t\t\treturn nil, errors.New(\"len(item.Packet) > 0 && item.Rand.To > 0\")\n\t\t}\n\t}\n\n\tnoiseSlice := make([]*noise.Item, 0, len(c.Noise))\n\tfor _, item := range c.Noise {\n\t\tvar err error\n\t\tif item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnoiseSlice = append(noiseSlice, &noise.Item{\n\t\t\tRandMin:  int64(item.Rand.From),\n\t\t\tRandMax:  int64(item.Rand.To),\n\t\t\tPacket:   item.Packet,\n\t\t\tDelayMin: int64(item.Delay.From),\n\t\t\tDelayMax: int64(item.Delay.To),\n\t\t})\n\t}\n\n\treturn &noise.Config{\n\t\tResetMin: int64(c.Reset.From),\n\t\tResetMax: int64(c.Reset.To),\n\t\tItems:    noiseSlice,\n\t}, nil\n}\n\ntype UDPItem struct {\n\tRand   int32           `json:\"rand\"`\n\tType   string          `json:\"type\"`\n\tPacket json.RawMessage `json:\"packet\"`\n}\n\ntype HeaderCustomUDP struct {\n\tClient []UDPItem `json:\"client\"`\n\tServer []UDPItem `json:\"server\"`\n}\n\nfunc (c *HeaderCustomUDP) Build() (proto.Message, error) {\n\tfor _, item := range c.Client {\n\t\tif len(item.Packet) > 0 && item.Rand > 0 {\n\t\t\treturn nil, errors.New(\"len(item.Packet) > 0 && item.Rand > 0\")\n\t\t}\n\t}\n\tfor _, item := range c.Server {\n\t\tif len(item.Packet) > 0 && item.Rand > 0 {\n\t\t\treturn nil, errors.New(\"len(item.Packet) > 0 && item.Rand > 0\")\n\t\t}\n\t}\n\n\tclient := make([]*custom.UDPItem, 0, len(c.Client))\n\tfor _, item := range c.Client {\n\t\tvar err error\n\t\tif item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclient = append(client, &custom.UDPItem{\n\t\t\tRand:   item.Rand,\n\t\t\tPacket: item.Packet,\n\t\t})\n\t}\n\n\tserver := make([]*custom.UDPItem, 0, len(c.Server))\n\tfor _, item := range c.Server {\n\t\tvar err error\n\t\tif item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tserver = append(server, &custom.UDPItem{\n\t\t\tRand:   item.Rand,\n\t\t\tPacket: item.Packet,\n\t\t})\n\t}\n\n\treturn &custom.UDPConfig{\n\t\tClient: client,\n\t\tServer: server,\n\t}, nil\n}\n\ntype Dns struct {\n\tDomain string `json:\"domain\"`\n}\n\nfunc (c *Dns) Build() (proto.Message, error) {\n\tconfig := &dns.Config{}\n\tconfig.Domain = \"www.baidu.com\"\n\n\tif len(c.Domain) > 0 {\n\t\tconfig.Domain = c.Domain\n\t}\n\n\treturn config, nil\n}\n\ntype Dtls struct{}\n\nfunc (c *Dtls) Build() (proto.Message, error) {\n\treturn &dtls.Config{}, nil\n}\n\ntype Srtp struct{}\n\nfunc (c *Srtp) Build() (proto.Message, error) {\n\treturn &srtp.Config{}, nil\n}\n\ntype Utp struct{}\n\nfunc (c *Utp) Build() (proto.Message, error) {\n\treturn &utp.Config{}, nil\n}\n\ntype Wechat struct{}\n\nfunc (c *Wechat) Build() (proto.Message, error) {\n\treturn &wechat.Config{}, nil\n}\n\ntype Wireguard struct{}\n\nfunc (c *Wireguard) Build() (proto.Message, error) {\n\treturn &wireguard.Config{}, nil\n}\n\ntype Original struct{}\n\nfunc (c *Original) Build() (proto.Message, error) {\n\treturn &original.Config{}, nil\n}\n\ntype Aes128Gcm struct {\n\tPassword string `json:\"password\"`\n}\n\nfunc (c *Aes128Gcm) Build() (proto.Message, error) {\n\treturn &aes128gcm.Config{\n\t\tPassword: c.Password,\n\t}, nil\n}\n\ntype Salamander struct {\n\tPassword string `json:\"password\"`\n}\n\nfunc (c *Salamander) Build() (proto.Message, error) {\n\tconfig := &salamander.Config{}\n\tconfig.Password = c.Password\n\treturn config, nil\n}\n\ntype Sudoku struct {\n\tPassword string `json:\"password\"`\n\tASCII    string `json:\"ascii\"`\n\n\tCustomTable       string   `json:\"customTable\"`\n\tLegacyCustomTable string   `json:\"custom_table\"`\n\tCustomTables      []string `json:\"customTables\"`\n\tLegacyCustomSets  []string `json:\"custom_tables\"`\n\n\tPaddingMin       uint32 `json:\"paddingMin\"`\n\tLegacyPaddingMin uint32 `json:\"padding_min\"`\n\tPaddingMax       uint32 `json:\"paddingMax\"`\n\tLegacyPaddingMax uint32 `json:\"padding_max\"`\n}\n\nfunc (c *Sudoku) Build() (proto.Message, error) {\n\tcustomTable := c.CustomTable\n\tif customTable == \"\" {\n\t\tcustomTable = c.LegacyCustomTable\n\t}\n\tcustomTables := c.CustomTables\n\tif len(customTables) == 0 {\n\t\tcustomTables = c.LegacyCustomSets\n\t}\n\n\tpaddingMin := c.PaddingMin\n\tif paddingMin == 0 {\n\t\tpaddingMin = c.LegacyPaddingMin\n\t}\n\tpaddingMax := c.PaddingMax\n\tif paddingMax == 0 {\n\t\tpaddingMax = c.LegacyPaddingMax\n\t}\n\n\treturn &finalsudoku.Config{\n\t\tPassword:     c.Password,\n\t\tAscii:        c.ASCII,\n\t\tCustomTable:  customTable,\n\t\tCustomTables: customTables,\n\t\tPaddingMin:   paddingMin,\n\t\tPaddingMax:   paddingMax,\n\t}, nil\n}\n\ntype Xdns struct {\n\tDomain string `json:\"domain\"`\n}\n\nfunc (c *Xdns) Build() (proto.Message, error) {\n\tif c.Domain == \"\" {\n\t\treturn nil, errors.New(\"empty domain\")\n\t}\n\n\treturn &xdns.Config{\n\t\tDomain: c.Domain,\n\t}, nil\n}\n\ntype Xicmp struct {\n\tListenIp string `json:\"listenIp\"`\n\tId       uint16 `json:\"id\"`\n}\n\nfunc (c *Xicmp) Build() (proto.Message, error) {\n\tconfig := &xicmp.Config{\n\t\tIp: c.ListenIp,\n\t\tId: int32(c.Id),\n\t}\n\n\tif config.Ip == \"\" {\n\t\tconfig.Ip = \"0.0.0.0\"\n\t}\n\n\treturn config, nil\n}\n\ntype Mask struct {\n\tType     string           `json:\"type\"`\n\tSettings *json.RawMessage `json:\"settings\"`\n}\n\nfunc (c *Mask) Build(tcp bool) (proto.Message, error) {\n\tloader := udpmaskLoader\n\tif tcp {\n\t\tloader = tcpmaskLoader\n\t}\n\n\tsettings := []byte(\"{}\")\n\tif c.Settings != nil {\n\t\tsettings = ([]byte)(*c.Settings)\n\t}\n\trawConfig, err := loader.LoadWithID(settings, c.Type)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tts, err := rawConfig.(Buildable).Build()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ts, nil\n}\n\ntype FinalMask struct {\n\tTcp        []Mask            `json:\"tcp\"`\n\tUdp        []Mask            `json:\"udp\"`\n\tQuicParams *QuicParamsConfig `json:\"quicParams\"`\n}\n\ntype StreamConfig struct {\n\tAddress             *Address           `json:\"address\"`\n\tPort                uint16             `json:\"port\"`\n\tNetwork             *TransportProtocol `json:\"network\"`\n\tSecurity            string             `json:\"security\"`\n\tFinalMask           *FinalMask         `json:\"finalmask\"`\n\tTLSSettings         *TLSConfig         `json:\"tlsSettings\"`\n\tREALITYSettings     *REALITYConfig     `json:\"realitySettings\"`\n\tRAWSettings         *TCPConfig         `json:\"rawSettings\"`\n\tTCPSettings         *TCPConfig         `json:\"tcpSettings\"`\n\tXHTTPSettings       *SplitHTTPConfig   `json:\"xhttpSettings\"`\n\tSplitHTTPSettings   *SplitHTTPConfig   `json:\"splithttpSettings\"`\n\tKCPSettings         *KCPConfig         `json:\"kcpSettings\"`\n\tGRPCSettings        *GRPCConfig        `json:\"grpcSettings\"`\n\tWSSettings          *WebSocketConfig   `json:\"wsSettings\"`\n\tHTTPUPGRADESettings *HttpUpgradeConfig `json:\"httpupgradeSettings\"`\n\tHysteriaSettings    *HysteriaConfig    `json:\"hysteriaSettings\"`\n\tSocketSettings      *SocketConfig      `json:\"sockopt\"`\n}\n\n// Build implements Buildable.\nfunc (c *StreamConfig) Build() (*internet.StreamConfig, error) {\n\tconfig := &internet.StreamConfig{\n\t\tPort:         uint32(c.Port),\n\t\tProtocolName: \"tcp\",\n\t}\n\tif c.Address != nil {\n\t\tconfig.Address = c.Address.Build()\n\t}\n\tif c.Network != nil {\n\t\tprotocol, err := c.Network.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfig.ProtocolName = protocol\n\t}\n\n\tswitch strings.ToLower(c.Security) {\n\tcase \"\", \"none\":\n\tcase \"tls\":\n\t\ttlsSettings := c.TLSSettings\n\t\tif tlsSettings == nil {\n\t\t\ttlsSettings = &TLSConfig{}\n\t\t}\n\t\tts, err := tlsSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build TLS config.\").Base(err)\n\t\t}\n\t\ttm := serial.ToTypedMessage(ts)\n\t\tconfig.SecuritySettings = append(config.SecuritySettings, tm)\n\t\tconfig.SecurityType = tm.Type\n\tcase \"reality\":\n\t\tif config.ProtocolName != \"tcp\" && config.ProtocolName != \"splithttp\" && config.ProtocolName != \"grpc\" {\n\t\t\treturn nil, errors.New(\"REALITY only supports RAW, XHTTP and gRPC for now.\")\n\t\t}\n\t\tif c.REALITYSettings == nil {\n\t\t\treturn nil, errors.New(`REALITY: Empty \"realitySettings\".`)\n\t\t}\n\t\tts, err := c.REALITYSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build REALITY config.\").Base(err)\n\t\t}\n\t\ttm := serial.ToTypedMessage(ts)\n\t\tconfig.SecuritySettings = append(config.SecuritySettings, tm)\n\t\tconfig.SecurityType = tm.Type\n\tcase \"xtls\":\n\t\treturn nil, errors.PrintRemovedFeatureError(`Legacy XTLS`, `xtls-rprx-vision with TLS or REALITY`)\n\tdefault:\n\t\treturn nil, errors.New(`Unknown security \"` + c.Security + `\".`)\n\t}\n\n\tif c.RAWSettings != nil {\n\t\tc.TCPSettings = c.RAWSettings\n\t}\n\tif c.TCPSettings != nil {\n\t\tts, err := c.TCPSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build RAW config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"tcp\",\n\t\t\tSettings:     serial.ToTypedMessage(ts),\n\t\t})\n\t}\n\tif c.XHTTPSettings != nil {\n\t\tc.SplitHTTPSettings = c.XHTTPSettings\n\t}\n\tif c.SplitHTTPSettings != nil {\n\t\ths, err := c.SplitHTTPSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build XHTTP config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"splithttp\",\n\t\t\tSettings:     serial.ToTypedMessage(hs),\n\t\t})\n\t}\n\tif c.KCPSettings != nil {\n\t\tts, err := c.KCPSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build mKCP config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"mkcp\",\n\t\t\tSettings:     serial.ToTypedMessage(ts),\n\t\t})\n\t}\n\tif c.GRPCSettings != nil {\n\t\tgs, err := c.GRPCSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build gRPC config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"grpc\",\n\t\t\tSettings:     serial.ToTypedMessage(gs),\n\t\t})\n\t}\n\tif c.WSSettings != nil {\n\t\tts, err := c.WSSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build WebSocket config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"websocket\",\n\t\t\tSettings:     serial.ToTypedMessage(ts),\n\t\t})\n\t}\n\tif c.HTTPUPGRADESettings != nil {\n\t\ths, err := c.HTTPUPGRADESettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build HTTPUpgrade config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"httpupgrade\",\n\t\t\tSettings:     serial.ToTypedMessage(hs),\n\t\t})\n\t}\n\tif c.HysteriaSettings != nil {\n\t\ths, err := c.HysteriaSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build Hysteria config.\").Base(err)\n\t\t}\n\t\tconfig.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{\n\t\t\tProtocolName: \"hysteria\",\n\t\t\tSettings:     serial.ToTypedMessage(hs),\n\t\t})\n\t}\n\tif c.SocketSettings != nil {\n\t\tss, err := c.SocketSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Failed to build sockopt.\").Base(err)\n\t\t}\n\t\tconfig.SocketSettings = ss\n\t}\n\n\tif c.FinalMask != nil {\n\t\tfor _, mask := range c.FinalMask.Tcp {\n\t\t\tu, err := mask.Build(true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to build mask with type \", mask.Type).Base(err)\n\t\t\t}\n\t\t\tconfig.Tcpmasks = append(config.Tcpmasks, serial.ToTypedMessage(u))\n\t\t}\n\t\tfor _, mask := range c.FinalMask.Udp {\n\t\t\tu, err := mask.Build(false)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to build mask with type \", mask.Type).Base(err)\n\t\t\t}\n\t\t\tconfig.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))\n\t\t}\n\t\tif c.FinalMask.QuicParams != nil {\n\t\t\tup, err := c.FinalMask.QuicParams.BrutalUp.Bps()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdown, err := c.FinalMask.QuicParams.BrutalDown.Bps()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif up > 0 && up < 65536 {\n\t\t\t\treturn nil, errors.New(\"BrutalUp must be at least 65536 bytes per second\")\n\t\t\t}\n\t\t\tif down > 0 && down < 65536 {\n\t\t\t\treturn nil, errors.New(\"BrutalDown must be at least 65536 bytes per second\")\n\t\t\t}\n\n\t\t\tc.FinalMask.QuicParams.Congestion = strings.ToLower(c.FinalMask.QuicParams.Congestion)\n\t\t\tswitch c.FinalMask.QuicParams.Congestion {\n\t\t\tcase \"\", \"brutal\", \"reno\", \"bbr\":\n\t\t\tcase \"force-brutal\":\n\t\t\t\tif up == 0 {\n\t\t\t\t\treturn nil, errors.New(\"force-brutal requires up\")\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"unknown congestion control: \", c.FinalMask.QuicParams.Congestion, \", valid values: reno, bbr, brutal, force-brutal\")\n\t\t\t}\n\n\t\t\tvar hop *PortList\n\t\t\tif err := json.Unmarshal(c.FinalMask.QuicParams.UdpHop.PortList, &hop); err != nil {\n\t\t\t\thop = &PortList{}\n\t\t\t}\n\n\t\t\tvar inertvalMin, inertvalMax int64\n\t\t\tif c.FinalMask.QuicParams.UdpHop.Interval != nil {\n\t\t\t\tinertvalMin = int64(c.FinalMask.QuicParams.UdpHop.Interval.From)\n\t\t\t\tinertvalMax = int64(c.FinalMask.QuicParams.UdpHop.Interval.To)\n\t\t\t}\n\n\t\t\tif (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {\n\t\t\t\treturn nil, errors.New(\"Interval must be at least 5\")\n\t\t\t}\n\n\t\t\tif c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {\n\t\t\t\treturn nil, errors.New(\"InitStreamReceiveWindow must be at least 16384\")\n\t\t\t}\n\t\t\tif c.FinalMask.QuicParams.MaxStreamReceiveWindow > 0 && c.FinalMask.QuicParams.MaxStreamReceiveWindow < 16384 {\n\t\t\t\treturn nil, errors.New(\"MaxStreamReceiveWindow must be at least 16384\")\n\t\t\t}\n\t\t\tif c.FinalMask.QuicParams.InitConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.InitConnectionReceiveWindow < 16384 {\n\t\t\t\treturn nil, errors.New(\"InitConnectionReceiveWindow must be at least 16384\")\n\t\t\t}\n\t\t\tif c.FinalMask.QuicParams.MaxConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.MaxConnectionReceiveWindow < 16384 {\n\t\t\t\treturn nil, errors.New(\"MaxConnectionReceiveWindow must be at least 16384\")\n\t\t\t}\n\t\t\tif c.FinalMask.QuicParams.MaxIdleTimeout != 0 && (c.FinalMask.QuicParams.MaxIdleTimeout < 4 || c.FinalMask.QuicParams.MaxIdleTimeout > 120) {\n\t\t\t\treturn nil, errors.New(\"MaxIdleTimeout must be between 4 and 120\")\n\t\t\t}\n\t\t\tif c.FinalMask.QuicParams.KeepAlivePeriod != 0 && (c.FinalMask.QuicParams.KeepAlivePeriod < 2 || c.FinalMask.QuicParams.KeepAlivePeriod > 60) {\n\t\t\t\treturn nil, errors.New(\"KeepAlivePeriod must be between 2 and 60\")\n\t\t\t}\n\t\t\tif c.FinalMask.QuicParams.MaxIncomingStreams != 0 && c.FinalMask.QuicParams.MaxIncomingStreams < 8 {\n\t\t\t\treturn nil, errors.New(\"MaxIncomingStreams must be at least 8\")\n\t\t\t}\n\n\t\t\tif c.FinalMask.QuicParams.Debug {\n\t\t\t\tos.Setenv(\"HYSTERIA_BBR_DEBUG\", \"true\")\n\t\t\t\tos.Setenv(\"HYSTERIA_BRUTAL_DEBUG\", \"true\")\n\t\t\t}\n\n\t\t\tconfig.QuicParams = &internet.QuicParams{\n\t\t\t\tCongestion: c.FinalMask.QuicParams.Congestion,\n\t\t\t\tBrutalUp:   up,\n\t\t\t\tBrutalDown: down,\n\t\t\t\tUdpHop: &internet.UdpHop{\n\t\t\t\t\tPorts:       hop.Build().Ports(),\n\t\t\t\t\tIntervalMin: inertvalMin,\n\t\t\t\t\tIntervalMax: inertvalMax,\n\t\t\t\t},\n\t\t\t\tInitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,\n\t\t\t\tMaxStreamReceiveWindow:  c.FinalMask.QuicParams.MaxStreamReceiveWindow,\n\t\t\t\tInitConnReceiveWindow:   c.FinalMask.QuicParams.InitConnectionReceiveWindow,\n\t\t\t\tMaxConnReceiveWindow:    c.FinalMask.QuicParams.MaxConnectionReceiveWindow,\n\t\t\t\tMaxIdleTimeout:          c.FinalMask.QuicParams.MaxIdleTimeout,\n\t\t\t\tKeepAlivePeriod:         c.FinalMask.QuicParams.KeepAlivePeriod,\n\t\t\t\tDisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,\n\t\t\t\tMaxIncomingStreams:      c.FinalMask.QuicParams.MaxIncomingStreams,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\ntype ProxyConfig struct {\n\tTag string `json:\"tag\"`\n\n\t// TransportLayerProxy: For compatibility.\n\tTransportLayerProxy bool `json:\"transportLayer\"`\n}\n\n// Build implements Buildable.\nfunc (v *ProxyConfig) Build() (*internet.ProxyConfig, error) {\n\tif v.Tag == \"\" {\n\t\treturn nil, errors.New(\"Proxy tag is not set.\")\n\t}\n\treturn &internet.ProxyConfig{\n\t\tTag:                 v.Tag,\n\t\tTransportLayerProxy: v.TransportLayerProxy,\n\t}, nil\n}\n"
  },
  {
    "path": "infra/conf/transport_test.go",
    "content": "package conf_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestSocketConfig(t *testing.T) {\n\tcreateParser := func() func(string) (proto.Message, error) {\n\t\treturn func(s string) (proto.Message, error) {\n\t\t\tconfig := new(SocketConfig)\n\t\t\tif err := json.Unmarshal([]byte(s), config); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn config.Build()\n\t\t}\n\t}\n\n\t// test \"tcpFastOpen\": true, queue length 256 is expected. other parameters are tested here too\n\texpectedOutput := &internet.SocketConfig{\n\t\tMark:           1,\n\t\tTfo:            256,\n\t\tDomainStrategy: internet.DomainStrategy_USE_IP,\n\t\tDialerProxy:    \"tag\",\n\t\tHappyEyeballs:  &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"mark\": 1,\n\t\t\t\t\"tcpFastOpen\": true,\n\t\t\t\t\"domainStrategy\": \"UseIP\",\n\t\t\t\t\"dialerProxy\": \"tag\"\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != 256 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be 256\")\n\t}\n\n\t// test \"tcpFastOpen\": false, disabled TFO is expected\n\texpectedOutput = &internet.SocketConfig{\n\t\tMark:          0,\n\t\tTfo:           -1,\n\t\tHappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"tcpFastOpen\": false\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != 0 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be 0\")\n\t}\n\n\t// test \"tcpFastOpen\": 65535, queue length 65535 is expected\n\texpectedOutput = &internet.SocketConfig{\n\t\tMark:          0,\n\t\tTfo:           65535,\n\t\tHappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"tcpFastOpen\": 65535\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != 65535 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be 65535\")\n\t}\n\n\t// test \"tcpFastOpen\": -65535, disable TFO is expected\n\texpectedOutput = &internet.SocketConfig{\n\t\tMark:          0,\n\t\tTfo:           -65535,\n\t\tHappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"tcpFastOpen\": -65535\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != 0 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be 0\")\n\t}\n\n\t// test \"tcpFastOpen\": 0, no operation is expected\n\texpectedOutput = &internet.SocketConfig{\n\t\tMark:          0,\n\t\tTfo:           0,\n\t\tHappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"tcpFastOpen\": 0\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != -1 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be -1\")\n\t}\n\n\t// test omit \"tcpFastOpen\", no operation is expected\n\texpectedOutput = &internet.SocketConfig{\n\t\tMark:          0,\n\t\tTfo:           0,\n\t\tHappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput:  `{}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != -1 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be -1\")\n\t}\n\n\t// test \"tcpFastOpen\": null, no operation is expected\n\texpectedOutput = &internet.SocketConfig{\n\t\tMark:          0,\n\t\tTfo:           0,\n\t\tHappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},\n\t}\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"tcpFastOpen\": null\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: expectedOutput,\n\t\t},\n\t})\n\tif expectedOutput.ParseTFOValue() != -1 {\n\t\tt.Fatalf(\"unexpected parsed TFO value, which should be -1\")\n\t}\n}\n"
  },
  {
    "path": "infra/conf/trojan.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/proxy/trojan\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// TrojanServerTarget is configuration of a single trojan server\ntype TrojanServerTarget struct {\n\tAddress  *Address `json:\"address\"`\n\tPort     uint16   `json:\"port\"`\n\tLevel    byte     `json:\"level\"`\n\tEmail    string   `json:\"email\"`\n\tPassword string   `json:\"password\"`\n\tFlow     string   `json:\"flow\"`\n}\n\n// TrojanClientConfig is configuration of trojan servers\ntype TrojanClientConfig struct {\n\tAddress  *Address              `json:\"address\"`\n\tPort     uint16                `json:\"port\"`\n\tLevel    byte                  `json:\"level\"`\n\tEmail    string                `json:\"email\"`\n\tPassword string                `json:\"password\"`\n\tFlow     string                `json:\"flow\"`\n\tServers  []*TrojanServerTarget `json:\"servers\"`\n}\n\n// Build implements Buildable\nfunc (c *TrojanClientConfig) Build() (proto.Message, error) {\n\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"Trojan (with no Flow, etc.)\", \"VLESS with Flow & Seed\")\n\n\tif c.Address != nil {\n\t\tc.Servers = []*TrojanServerTarget{\n\t\t\t{\n\t\t\t\tAddress:  c.Address,\n\t\t\t\tPort:     c.Port,\n\t\t\t\tLevel:    c.Level,\n\t\t\t\tEmail:    c.Email,\n\t\t\t\tPassword: c.Password,\n\t\t\t\tFlow:     c.Flow,\n\t\t\t},\n\t\t}\n\t}\n\tif len(c.Servers) != 1 {\n\t\treturn nil, errors.New(`Trojan settings: \"servers\" should have one and only one member. Multiple endpoints in \"servers\" should use multiple Trojan outbounds and routing balancer instead`)\n\t}\n\n\tconfig := &trojan.ClientConfig{}\n\n\tfor _, rec := range c.Servers {\n\t\tif rec.Address == nil {\n\t\t\treturn nil, errors.New(\"Trojan server address is not set.\")\n\t\t}\n\t\tif rec.Port == 0 {\n\t\t\treturn nil, errors.New(\"Invalid Trojan port.\")\n\t\t}\n\t\tif rec.Password == \"\" {\n\t\t\treturn nil, errors.New(\"Trojan password is not specified.\")\n\t\t}\n\t\tif rec.Flow != \"\" {\n\t\t\treturn nil, errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)\n\t\t}\n\n\t\tconfig.Server = &protocol.ServerEndpoint{\n\t\t\tAddress: rec.Address.Build(),\n\t\t\tPort:    uint32(rec.Port),\n\t\t\tUser: &protocol.User{\n\t\t\t\tLevel: uint32(rec.Level),\n\t\t\t\tEmail: rec.Email,\n\t\t\t\tAccount: serial.ToTypedMessage(&trojan.Account{\n\t\t\t\t\tPassword: rec.Password,\n\t\t\t\t}),\n\t\t\t},\n\t\t}\n\n\t\tbreak\n\t}\n\n\treturn config, nil\n}\n\n// TrojanInboundFallback is fallback configuration\ntype TrojanInboundFallback struct {\n\tName string          `json:\"name\"`\n\tAlpn string          `json:\"alpn\"`\n\tPath string          `json:\"path\"`\n\tType string          `json:\"type\"`\n\tDest json.RawMessage `json:\"dest\"`\n\tXver uint64          `json:\"xver\"`\n}\n\n// TrojanUserConfig is user configuration\ntype TrojanUserConfig struct {\n\tPassword string `json:\"password\"`\n\tLevel    byte   `json:\"level\"`\n\tEmail    string `json:\"email\"`\n\tFlow     string `json:\"flow\"`\n}\n\n// TrojanServerConfig is Inbound configuration\ntype TrojanServerConfig struct {\n\tClients   []*TrojanUserConfig      `json:\"clients\"`\n\tFallbacks []*TrojanInboundFallback `json:\"fallbacks\"`\n}\n\n// Build implements Buildable\nfunc (c *TrojanServerConfig) Build() (proto.Message, error) {\n\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"Trojan (with no Flow, etc.)\", \"VLESS with Flow & Seed\")\n\n\tconfig := &trojan.ServerConfig{\n\t\tUsers: make([]*protocol.User, len(c.Clients)),\n\t}\n\n\tfor idx, rawUser := range c.Clients {\n\t\tif rawUser.Flow != \"\" {\n\t\t\treturn nil, errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)\n\t\t}\n\n\t\tconfig.Users[idx] = &protocol.User{\n\t\t\tLevel: uint32(rawUser.Level),\n\t\t\tEmail: rawUser.Email,\n\t\t\tAccount: serial.ToTypedMessage(&trojan.Account{\n\t\t\t\tPassword: rawUser.Password,\n\t\t\t}),\n\t\t}\n\t}\n\n\tfor _, fb := range c.Fallbacks {\n\t\tvar i uint16\n\t\tvar s string\n\t\tif err := json.Unmarshal(fb.Dest, &i); err == nil {\n\t\t\ts = strconv.Itoa(int(i))\n\t\t} else {\n\t\t\t_ = json.Unmarshal(fb.Dest, &s)\n\t\t}\n\t\tconfig.Fallbacks = append(config.Fallbacks, &trojan.Fallback{\n\t\t\tName: fb.Name,\n\t\t\tAlpn: fb.Alpn,\n\t\t\tPath: fb.Path,\n\t\t\tType: fb.Type,\n\t\t\tDest: s,\n\t\t\tXver: fb.Xver,\n\t\t})\n\t}\n\tfor _, fb := range config.Fallbacks {\n\t\t/*\n\t\t\tif fb.Alpn == \"h2\" && fb.Path != \"\" {\n\t\t\t\treturn nil, errors.New(`Trojan fallbacks: \"alpn\":\"h2\" doesn't support \"path\"`)\n\t\t\t}\n\t\t*/\n\t\tif fb.Path != \"\" && fb.Path[0] != '/' {\n\t\t\treturn nil, errors.New(`Trojan fallbacks: \"path\" must be empty or start with \"/\"`)\n\t\t}\n\t\tif fb.Type == \"\" && fb.Dest != \"\" {\n\t\t\tif fb.Dest == \"serve-ws-none\" {\n\t\t\t\tfb.Type = \"serve\"\n\t\t\t} else if filepath.IsAbs(fb.Dest) || fb.Dest[0] == '@' {\n\t\t\t\tfb.Type = \"unix\"\n\t\t\t\tif strings.HasPrefix(fb.Dest, \"@@\") && (runtime.GOOS == \"linux\" || runtime.GOOS == \"android\") {\n\t\t\t\t\tfullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy\n\t\t\t\t\tcopy(fullAddr, fb.Dest[1:])\n\t\t\t\t\tfb.Dest = string(fullAddr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, err := strconv.Atoi(fb.Dest); err == nil {\n\t\t\t\t\tfb.Dest = \"localhost:\" + fb.Dest\n\t\t\t\t}\n\t\t\t\tif _, _, err := net.SplitHostPort(fb.Dest); err == nil {\n\t\t\t\t\tfb.Type = \"tcp\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif fb.Type == \"\" {\n\t\t\treturn nil, errors.New(`Trojan fallbacks: please fill in a valid value for every \"dest\"`)\n\t\t}\n\t\tif fb.Xver > 2 {\n\t\t\treturn nil, errors.New(`Trojan fallbacks: invalid PROXY protocol version, \"xver\" only accepts 0, 1, 2`)\n\t\t}\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/tun.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/proxy/tun\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype TunConfig struct {\n\tName      string `json:\"name\"`\n\tMTU       uint32 `json:\"MTU\"`\n\tUserLevel uint32 `json:\"userLevel\"`\n}\n\nfunc (v *TunConfig) Build() (proto.Message, error) {\n\tconfig := &tun.Config{\n\t\tName:      v.Name,\n\t\tMTU:       v.MTU,\n\t\tUserLevel: v.UserLevel,\n\t}\n\n\tif v.Name == \"\" {\n\t\tconfig.Name = \"xray0\"\n\t}\n\n\tif v.MTU == 0 {\n\t\tconfig.MTU = 1500\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/version.go",
    "content": "package conf\n\nimport (\n\t\"github.com/xtls/xray-core/app/version\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"strconv\"\n)\n\ntype VersionConfig struct {\n\tMinVersion string `json:\"min\"`\n\tMaxVersion string `json:\"max\"`\n}\n\nfunc (c *VersionConfig) Build() (*version.Config, error) {\n\tcoreVersion := strconv.Itoa(int(core.Version_x)) + \".\" + strconv.Itoa(int(core.Version_y)) + \".\" + strconv.Itoa(int(core.Version_z))\n\n\treturn &version.Config{\n\t\tCoreVersion: coreVersion,\n\t\tMinVersion:  c.MinVersion,\n\t\tMaxVersion:  c.MaxVersion,\n\t}, nil\n}\n"
  },
  {
    "path": "infra/conf/vless.go",
    "content": "package conf\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t\"github.com/xtls/xray-core/proxy/vless/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vless/outbound\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype VLessInboundFallback struct {\n\tName string          `json:\"name\"`\n\tAlpn string          `json:\"alpn\"`\n\tPath string          `json:\"path\"`\n\tType string          `json:\"type\"`\n\tDest json.RawMessage `json:\"dest\"`\n\tXver uint64          `json:\"xver\"`\n}\n\ntype VLessInboundConfig struct {\n\tClients    []json.RawMessage       `json:\"clients\"`\n\tDecryption string                  `json:\"decryption\"`\n\tFallbacks  []*VLessInboundFallback `json:\"fallbacks\"`\n\tFlow       string                  `json:\"flow\"`\n\tTestseed   []uint32                `json:\"testseed\"`\n}\n\n// Build implements Buildable\nfunc (c *VLessInboundConfig) Build() (proto.Message, error) {\n\tconfig := new(inbound.Config)\n\tconfig.Clients = make([]*protocol.User, len(c.Clients))\n\tswitch c.Flow {\n\tcase vless.XRV, \"\":\n\tdefault:\n\t\treturn nil, errors.New(`VLESS \"settings.flow\" doesn't support \"` + c.Flow + `\" in this version`)\n\t}\n\tfor idx, rawUser := range c.Clients {\n\t\tuser := new(protocol.User)\n\t\tif err := json.Unmarshal(rawUser, user); err != nil {\n\t\t\treturn nil, errors.New(`VLESS clients: invalid user`).Base(err)\n\t\t}\n\t\taccount := new(vless.Account)\n\t\tif err := json.Unmarshal(rawUser, account); err != nil {\n\t\t\treturn nil, errors.New(`VLESS clients: invalid user`).Base(err)\n\t\t}\n\n\t\tu, err := uuid.ParseString(account.Id)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\taccount.Id = u.String()\n\n\t\tswitch account.Flow {\n\t\tcase \"\":\n\t\t\taccount.Flow = c.Flow\n\t\tcase vless.XRV:\n\t\tdefault:\n\t\t\treturn nil, errors.New(`VLESS clients: \"flow\" doesn't support \"` + account.Flow + `\" in this version`)\n\t\t}\n\n\t\tif len(account.Testseed) < 4 {\n\t\t\taccount.Testseed = c.Testseed\n\t\t}\n\n\t\tif account.Encryption != \"\" {\n\t\t\treturn nil, errors.New(`VLESS clients: \"encryption\" should not be in inbound settings`)\n\t\t}\n\n\t\tif account.Reverse != nil && account.Reverse.Tag == \"\" {\n\t\t\treturn nil, errors.New(`VLESS clients: \"tag\" can't be empty for \"reverse\"`)\n\t\t}\n\n\t\tuser.Account = serial.ToTypedMessage(account)\n\t\tconfig.Clients[idx] = user\n\t}\n\n\tconfig.Decryption = c.Decryption\n\tif !func() bool {\n\t\ts := strings.Split(config.Decryption, \".\")\n\t\tif len(s) < 4 || s[0] != \"mlkem768x25519plus\" {\n\t\t\treturn false\n\t\t}\n\t\tswitch s[1] {\n\t\tcase \"native\":\n\t\tcase \"xorpub\":\n\t\t\tconfig.XorMode = 1\n\t\tcase \"random\":\n\t\t\tconfig.XorMode = 2\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t\tt := strings.SplitN(strings.TrimSuffix(s[2], \"s\"), \"-\", 2)\n\t\ti, err := strconv.Atoi(t[0])\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tconfig.SecondsFrom = int64(i)\n\t\tif len(t) == 2 {\n\t\t\ti, err := strconv.Atoi(t[1])\n\t\t\tif err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconfig.SecondsTo = int64(i)\n\t\t}\n\t\tpadding := 0\n\t\tfor _, r := range s[3:] {\n\t\t\tif len(r) < 20 {\n\t\t\t\tpadding += len(r) + 1\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 64 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tconfig.Decryption = config.Decryption[27+len(s[2]):]\n\t\tif padding > 0 {\n\t\t\tconfig.Padding = config.Decryption[:padding-1]\n\t\t\tconfig.Decryption = config.Decryption[padding:]\n\t\t}\n\t\treturn true\n\t}() && config.Decryption != \"none\" {\n\t\tif config.Decryption == \"\" {\n\t\t\treturn nil, errors.New(`VLESS settings: please add/set \"decryption\":\"none\" to every settings`)\n\t\t}\n\t\treturn nil, errors.New(`VLESS settings: unsupported \"decryption\": ` + config.Decryption)\n\t}\n\n\tif config.Decryption != \"none\" && c.Fallbacks != nil {\n\t\treturn nil, errors.New(`VLESS settings: \"fallbacks\" can not be used together with \"decryption\"`)\n\t}\n\n\tfor _, fb := range c.Fallbacks {\n\t\tvar i uint16\n\t\tvar s string\n\t\tif err := json.Unmarshal(fb.Dest, &i); err == nil {\n\t\t\ts = strconv.Itoa(int(i))\n\t\t} else {\n\t\t\t_ = json.Unmarshal(fb.Dest, &s)\n\t\t}\n\t\tconfig.Fallbacks = append(config.Fallbacks, &inbound.Fallback{\n\t\t\tName: fb.Name,\n\t\t\tAlpn: fb.Alpn,\n\t\t\tPath: fb.Path,\n\t\t\tType: fb.Type,\n\t\t\tDest: s,\n\t\t\tXver: fb.Xver,\n\t\t})\n\t}\n\tfor _, fb := range config.Fallbacks {\n\t\t/*\n\t\t\tif fb.Alpn == \"h2\" && fb.Path != \"\" {\n\t\t\t\treturn nil, errors.New(`VLESS fallbacks: \"alpn\":\"h2\" doesn't support \"path\"`)\n\t\t\t}\n\t\t*/\n\t\tif fb.Path != \"\" && fb.Path[0] != '/' {\n\t\t\treturn nil, errors.New(`VLESS fallbacks: \"path\" must be empty or start with \"/\"`)\n\t\t}\n\t\tif fb.Type == \"\" && fb.Dest != \"\" {\n\t\t\tif fb.Dest == \"serve-ws-none\" {\n\t\t\t\tfb.Type = \"serve\"\n\t\t\t} else if filepath.IsAbs(fb.Dest) || fb.Dest[0] == '@' {\n\t\t\t\tfb.Type = \"unix\"\n\t\t\t\tif strings.HasPrefix(fb.Dest, \"@@\") && (runtime.GOOS == \"linux\" || runtime.GOOS == \"android\") {\n\t\t\t\t\tfullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy\n\t\t\t\t\tcopy(fullAddr, fb.Dest[1:])\n\t\t\t\t\tfb.Dest = string(fullAddr)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, err := strconv.Atoi(fb.Dest); err == nil {\n\t\t\t\t\tfb.Dest = \"localhost:\" + fb.Dest\n\t\t\t\t}\n\t\t\t\tif _, _, err := net.SplitHostPort(fb.Dest); err == nil {\n\t\t\t\t\tfb.Type = \"tcp\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif fb.Type == \"\" {\n\t\t\treturn nil, errors.New(`VLESS fallbacks: please fill in a valid value for every \"dest\"`)\n\t\t}\n\t\tif fb.Xver > 2 {\n\t\t\treturn nil, errors.New(`VLESS fallbacks: invalid PROXY protocol version, \"xver\" only accepts 0, 1, 2`)\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\ntype VLessOutboundVnext struct {\n\tAddress *Address          `json:\"address\"`\n\tPort    uint16            `json:\"port\"`\n\tUsers   []json.RawMessage `json:\"users\"`\n}\n\ntype VLessOutboundConfig struct {\n\tAddress    *Address              `json:\"address\"`\n\tPort       uint16                `json:\"port\"`\n\tLevel      uint32                `json:\"level\"`\n\tEmail      string                `json:\"email\"`\n\tId         string                `json:\"id\"`\n\tFlow       string                `json:\"flow\"`\n\tSeed       string                `json:\"seed\"`\n\tEncryption string                `json:\"encryption\"`\n\tReverse    *vless.Reverse        `json:\"reverse\"`\n\tTestpre    uint32                `json:\"testpre\"`\n\tTestseed   []uint32              `json:\"testseed\"`\n\tVnext      []*VLessOutboundVnext `json:\"vnext\"`\n}\n\n// Build implements Buildable\nfunc (c *VLessOutboundConfig) Build() (proto.Message, error) {\n\tconfig := new(outbound.Config)\n\tif c.Address != nil {\n\t\tc.Vnext = []*VLessOutboundVnext{\n\t\t\t{\n\t\t\t\tAddress: c.Address,\n\t\t\t\tPort:    c.Port,\n\t\t\t\tUsers:   []json.RawMessage{{}},\n\t\t\t},\n\t\t}\n\t}\n\tif len(c.Vnext) != 1 {\n\t\treturn nil, errors.New(`VLESS settings: \"vnext\" should have one and only one member. Multiple endpoints in \"vnext\" should use multiple VLESS outbounds and routing balancer instead`)\n\t}\n\tfor _, rec := range c.Vnext {\n\t\tif rec.Address == nil {\n\t\t\treturn nil, errors.New(`VLESS vnext: \"address\" is not set`)\n\t\t}\n\t\tif len(rec.Users) != 1 {\n\t\t\treturn nil, errors.New(`VLESS vnext: \"users\" should have one and only one member. Multiple members in \"users\" should use multiple VLESS outbounds and routing balancer instead`)\n\t\t}\n\t\tspec := &protocol.ServerEndpoint{\n\t\t\tAddress: rec.Address.Build(),\n\t\t\tPort:    uint32(rec.Port),\n\t\t}\n\t\tfor _, rawUser := range rec.Users {\n\t\t\tuser := new(protocol.User)\n\t\t\tif c.Address != nil {\n\t\t\t\tuser.Level = c.Level\n\t\t\t\tuser.Email = c.Email\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, user); err != nil {\n\t\t\t\t\treturn nil, errors.New(`VLESS users: invalid user`).Base(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\taccount := new(vless.Account)\n\t\t\tif c.Address != nil {\n\t\t\t\taccount.Id = c.Id\n\t\t\t\taccount.Flow = c.Flow\n\t\t\t\t//account.Seed = c.Seed\n\t\t\t\taccount.Encryption = c.Encryption\n\t\t\t\taccount.Reverse = c.Reverse\n\t\t\t\taccount.Testpre = c.Testpre\n\t\t\t\taccount.Testseed = c.Testseed\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, account); err != nil {\n\t\t\t\t\treturn nil, errors.New(`VLESS users: invalid user`).Base(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tu, err := uuid.ParseString(account.Id)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\taccount.Id = u.String()\n\n\t\t\tswitch account.Flow {\n\t\t\tcase \"\":\n\t\t\tcase vless.XRV, vless.XRV + \"-udp443\":\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(`VLESS users: \"flow\" doesn't support \"` + account.Flow + `\" in this version`)\n\t\t\t}\n\n\t\t\tif !func() bool {\n\t\t\t\ts := strings.Split(account.Encryption, \".\")\n\t\t\t\tif len(s) < 4 || s[0] != \"mlkem768x25519plus\" {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tswitch s[1] {\n\t\t\t\tcase \"native\":\n\t\t\t\tcase \"xorpub\":\n\t\t\t\t\taccount.XorMode = 1\n\t\t\t\tcase \"random\":\n\t\t\t\t\taccount.XorMode = 2\n\t\t\t\tdefault:\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tswitch s[2] {\n\t\t\t\tcase \"1rtt\":\n\t\t\t\tcase \"0rtt\":\n\t\t\t\t\taccount.Seconds = 1\n\t\t\t\tdefault:\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tpadding := 0\n\t\t\t\tfor _, r := range s[3:] {\n\t\t\t\t\tif len(r) < 20 {\n\t\t\t\t\t\tpadding += len(r) + 1\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif b, _ := base64.RawURLEncoding.DecodeString(r); len(b) != 32 && len(b) != 1184 {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\taccount.Encryption = account.Encryption[27+len(s[2]):]\n\t\t\t\tif padding > 0 {\n\t\t\t\t\taccount.Padding = account.Encryption[:padding-1]\n\t\t\t\t\taccount.Encryption = account.Encryption[padding:]\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}() && account.Encryption != \"none\" {\n\t\t\t\tif account.Encryption == \"\" {\n\t\t\t\t\treturn nil, errors.New(`VLESS users: please add/set \"encryption\":\"none\" for every user`)\n\t\t\t\t}\n\t\t\t\treturn nil, errors.New(`VLESS users: unsupported \"encryption\": ` + account.Encryption)\n\t\t\t}\n\n\t\t\tif account.Reverse != nil && account.Reverse.Tag == \"\" {\n\t\t\t\treturn nil, errors.New(`VLESS clients: \"tag\" can't be empty for \"reverse\"`)\n\t\t\t}\n\n\t\t\tuser.Account = serial.ToTypedMessage(account)\n\t\t\tspec.User = user\n\t\t\tbreak\n\t\t}\n\t\tconfig.Vnext = spec\n\t\tbreak\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/vless_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t\"github.com/xtls/xray-core/proxy/vless/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vless/outbound\"\n)\n\nfunc TestVLessOutbound(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(VLessOutboundConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"vnext\": [{\n\t\t\t\t\t\"address\": \"example.com\",\n\t\t\t\t\t\"port\": 443,\n\t\t\t\t\t\"users\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"id\": \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\t\"flow\": \"xtls-rprx-vision-udp443\",\n\t\t\t\t\t\t\t\"encryption\": \"none\",\n\t\t\t\t\t\t\t\"level\": 0\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}]\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &outbound.Config{\n\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\tAddress: &net.IPOrDomain_Domain{\n\t\t\t\t\t\t\tDomain: \"example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tPort: 443,\n\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\tId:         \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\tFlow:       \"xtls-rprx-vision-udp443\",\n\t\t\t\t\t\t\tEncryption: \"none\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tLevel: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"address\": \"example.com\",\n\t\t\t\t\"port\": 443,\n\t\t\t\t\"id\": \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\"flow\": \"xtls-rprx-vision-udp443\",\n\t\t\t\t\"encryption\": \"none\",\n\t\t\t\t\"level\": 0\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &outbound.Config{\n\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\tAddress: &net.IPOrDomain_Domain{\n\t\t\t\t\t\t\tDomain: \"example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tPort: 443,\n\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\tId:         \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\tFlow:       \"xtls-rprx-vision-udp443\",\n\t\t\t\t\t\t\tEncryption: \"none\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tLevel: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n\nfunc TestVLessInbound(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(VLessInboundConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"clients\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"id\": \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\"flow\": \"xtls-rprx-vision\",\n\t\t\t\t\t\t\"level\": 0,\n\t\t\t\t\t\t\"email\": \"love@example.com\"\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"decryption\": \"none\",\n\t\t\t\t\"fallbacks\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"dest\": 80\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"alpn\": \"h2\",\n\t\t\t\t\t\t\"dest\": \"@/dev/shm/domain.socket\",\n\t\t\t\t\t\t\"xver\": 2\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"path\": \"/innerws\",\n\t\t\t\t\t\t\"dest\": \"serve-ws-none\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &inbound.Config{\n\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t{\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\tId:   \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\tFlow: \"xtls-rprx-vision\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tLevel: 0,\n\t\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tDecryption: \"none\",\n\t\t\t\tFallbacks: []*inbound.Fallback{\n\t\t\t\t\t{\n\t\t\t\t\t\tAlpn: \"\",\n\t\t\t\t\t\tPath: \"\",\n\t\t\t\t\t\tType: \"tcp\",\n\t\t\t\t\t\tDest: \"localhost:80\",\n\t\t\t\t\t\tXver: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAlpn: \"h2\",\n\t\t\t\t\t\tPath: \"\",\n\t\t\t\t\t\tType: \"unix\",\n\t\t\t\t\t\tDest: \"@/dev/shm/domain.socket\",\n\t\t\t\t\t\tXver: 2,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tAlpn: \"\",\n\t\t\t\t\t\tPath: \"/innerws\",\n\t\t\t\t\t\tType: \"serve\",\n\t\t\t\t\t\tDest: \"serve-ws-none\",\n\t\t\t\t\t\tXver: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/vmess.go",
    "content": "package conf\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype VMessAccount struct {\n\tID          string `json:\"id\"`\n\tSecurity    string `json:\"security\"`\n\tExperiments string `json:\"experiments\"`\n}\n\n// Build implements Buildable\nfunc (a *VMessAccount) Build() *vmess.Account {\n\tvar st protocol.SecurityType\n\tswitch strings.ToLower(a.Security) {\n\tcase \"aes-128-gcm\":\n\t\tst = protocol.SecurityType_AES128_GCM\n\tcase \"chacha20-poly1305\":\n\t\tst = protocol.SecurityType_CHACHA20_POLY1305\n\tcase \"auto\":\n\t\tst = protocol.SecurityType_AUTO\n\tcase \"none\":\n\t\tst = protocol.SecurityType_NONE\n\tcase \"zero\":\n\t\tst = protocol.SecurityType_ZERO\n\tdefault:\n\t\tst = protocol.SecurityType_AUTO\n\t}\n\treturn &vmess.Account{\n\t\tId: a.ID,\n\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\tType: st,\n\t\t},\n\t\tTestsEnabled: a.Experiments,\n\t}\n}\n\ntype VMessDefaultConfig struct {\n\tLevel byte `json:\"level\"`\n}\n\n// Build implements Buildable\nfunc (c *VMessDefaultConfig) Build() *inbound.DefaultConfig {\n\tconfig := new(inbound.DefaultConfig)\n\tconfig.Level = uint32(c.Level)\n\treturn config\n}\n\ntype VMessInboundConfig struct {\n\tUsers    []json.RawMessage   `json:\"clients\"`\n\tDefaults *VMessDefaultConfig `json:\"default\"`\n}\n\n// Build implements Buildable\nfunc (c *VMessInboundConfig) Build() (proto.Message, error) {\n\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"VMess (with no Forward Secrecy, etc.)\", \"VLESS Encryption\")\n\n\tconfig := &inbound.Config{}\n\n\tif c.Defaults != nil {\n\t\tconfig.Default = c.Defaults.Build()\n\t}\n\n\tconfig.User = make([]*protocol.User, len(c.Users))\n\tfor idx, rawData := range c.Users {\n\t\tuser := new(protocol.User)\n\t\tif err := json.Unmarshal(rawData, user); err != nil {\n\t\t\treturn nil, errors.New(\"invalid VMess user\").Base(err)\n\t\t}\n\t\taccount := new(VMessAccount)\n\t\tif err := json.Unmarshal(rawData, account); err != nil {\n\t\t\treturn nil, errors.New(\"invalid VMess user\").Base(err)\n\t\t}\n\n\t\tu, err := uuid.ParseString(account.ID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\taccount.ID = u.String()\n\n\t\tuser.Account = serial.ToTypedMessage(account.Build())\n\t\tconfig.User[idx] = user\n\t}\n\n\treturn config, nil\n}\n\ntype VMessOutboundTarget struct {\n\tAddress *Address          `json:\"address\"`\n\tPort    uint16            `json:\"port\"`\n\tUsers   []json.RawMessage `json:\"users\"`\n}\n\ntype VMessOutboundConfig struct {\n\tAddress     *Address               `json:\"address\"`\n\tPort        uint16                 `json:\"port\"`\n\tLevel       uint32                 `json:\"level\"`\n\tEmail       string                 `json:\"email\"`\n\tID          string                 `json:\"id\"`\n\tSecurity    string                 `json:\"security\"`\n\tExperiments string                 `json:\"experiments\"`\n\tReceivers   []*VMessOutboundTarget `json:\"vnext\"`\n}\n\n// Build implements Buildable\nfunc (c *VMessOutboundConfig) Build() (proto.Message, error) {\n\terrors.PrintNonRemovalDeprecatedFeatureWarning(\"VMess (with no Forward Secrecy, etc.)\", \"VLESS Encryption\")\n\n\tconfig := new(outbound.Config)\n\tif c.Address != nil {\n\t\tc.Receivers = []*VMessOutboundTarget{\n\t\t\t{\n\t\t\t\tAddress: c.Address,\n\t\t\t\tPort:    c.Port,\n\t\t\t\tUsers:   []json.RawMessage{{}},\n\t\t\t},\n\t\t}\n\t}\n\tif len(c.Receivers) != 1 {\n\t\treturn nil, errors.New(`VMess settings: \"vnext\" should have one and only one member. Multiple endpoints in \"vnext\" should use multiple VMess outbounds and routing balancer instead`)\n\t}\n\tfor _, rec := range c.Receivers {\n\t\tif len(rec.Users) != 1 {\n\t\t\treturn nil, errors.New(`VMess vnext: \"users\" should have one and only one member. Multiple members in \"users\" should use multiple VMess outbounds and routing balancer instead`)\n\t\t}\n\t\tif rec.Address == nil {\n\t\t\treturn nil, errors.New(`VMess vnext: \"address\" is not set`)\n\t\t}\n\t\tspec := &protocol.ServerEndpoint{\n\t\t\tAddress: rec.Address.Build(),\n\t\t\tPort:    uint32(rec.Port),\n\t\t}\n\t\tfor _, rawUser := range rec.Users {\n\t\t\tuser := new(protocol.User)\n\t\t\tif c.Address != nil {\n\t\t\t\tuser.Level = c.Level\n\t\t\t\tuser.Email = c.Email\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, user); err != nil {\n\t\t\t\t\treturn nil, errors.New(\"invalid VMess user\").Base(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\taccount := new(VMessAccount)\n\t\t\tif c.Address != nil {\n\t\t\t\taccount.ID = c.ID\n\t\t\t\taccount.Security = c.Security\n\t\t\t\taccount.Experiments = c.Experiments\n\t\t\t} else {\n\t\t\t\tif err := json.Unmarshal(rawUser, account); err != nil {\n\t\t\t\t\treturn nil, errors.New(\"invalid VMess user\").Base(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tu, err := uuid.ParseString(account.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\taccount.ID = u.String()\n\n\t\t\tuser.Account = serial.ToTypedMessage(account.Build())\n\t\t\tspec.User = user\n\t\t\tbreak\n\t\t}\n\t\tconfig.Receiver = spec\n\t\tbreak\n\t}\n\treturn config, nil\n}\n"
  },
  {
    "path": "infra/conf/vmess_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n)\n\nfunc TestVMessOutbound(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(VMessOutboundConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"vnext\": [{\n\t\t\t\t\t\"address\": \"127.0.0.1\",\n\t\t\t\t\t\"port\": 80,\n\t\t\t\t\t\"users\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"id\": \"e641f5ad-9397-41e3-bf1a-e8740dfed019\",\n\t\t\t\t\t\t\t\"email\": \"love@example.com\",\n\t\t\t\t\t\t\t\"level\": 255\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}]\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &outbound.Config{\n\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tPort: 80,\n\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\t\tLevel: 255,\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\tId: \"e641f5ad-9397-41e3-bf1a-e8740dfed019\",\n\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\tType: protocol.SecurityType_AUTO,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"address\": \"127.0.0.1\",\n\t\t\t\t\"port\": 80,\n\t\t\t\t\"id\": \"e641f5ad-9397-41e3-bf1a-e8740dfed019\",\n\t\t\t\t\"email\": \"love@example.com\",\n\t\t\t\t\"level\": 255\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &outbound.Config{\n\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tPort: 80,\n\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\t\tLevel: 255,\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\tId: \"e641f5ad-9397-41e3-bf1a-e8740dfed019\",\n\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\tType: protocol.SecurityType_AUTO,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n\nfunc TestVMessInbound(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(VMessInboundConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"clients\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"id\": \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\"level\": 0,\n\t\t\t\t\t\t\"email\": \"love@example.com\",\n\t\t\t\t\t\t\"security\": \"aes-128-gcm\"\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"default\": {\n\t\t\t\t\t\"level\": 0\n\t\t\t\t}\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &inbound.Config{\n\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t{\n\t\t\t\t\t\tLevel: 0,\n\t\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\tId: \"27848739-7e62-4138-9fd3-098a63964b6b\",\n\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tDefault: &inbound.DefaultConfig{\n\t\t\t\t\tLevel: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/wireguard.go",
    "content": "package conf\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/proxy/wireguard\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype WireGuardPeerConfig struct {\n\tPublicKey    string   `json:\"publicKey\"`\n\tPreSharedKey string   `json:\"preSharedKey\"`\n\tEndpoint     string   `json:\"endpoint\"`\n\tKeepAlive    uint32   `json:\"keepAlive\"`\n\tAllowedIPs   []string `json:\"allowedIPs,omitempty\"`\n}\n\nfunc (c *WireGuardPeerConfig) Build() (proto.Message, error) {\n\tvar err error\n\tconfig := new(wireguard.PeerConfig)\n\n\tif c.PublicKey != \"\" {\n\t\tconfig.PublicKey, err = ParseWireGuardKey(c.PublicKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif c.PreSharedKey != \"\" {\n\t\tconfig.PreSharedKey, err = ParseWireGuardKey(c.PreSharedKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tconfig.Endpoint = c.Endpoint\n\t// default 0\n\tconfig.KeepAlive = c.KeepAlive\n\tif c.AllowedIPs == nil {\n\t\tconfig.AllowedIps = []string{\"0.0.0.0/0\", \"::0/0\"}\n\t} else {\n\t\tconfig.AllowedIps = c.AllowedIPs\n\t}\n\n\treturn config, nil\n}\n\ntype WireGuardConfig struct {\n\tIsClient bool `json:\"\"`\n\n\tNoKernelTun    bool                   `json:\"noKernelTun\"`\n\tSecretKey      string                 `json:\"secretKey\"`\n\tAddress        []string               `json:\"address\"`\n\tPeers          []*WireGuardPeerConfig `json:\"peers\"`\n\tMTU            int32                  `json:\"mtu\"`\n\tNumWorkers     int32                  `json:\"workers\"`\n\tReserved       []byte                 `json:\"reserved\"`\n\tDomainStrategy string                 `json:\"domainStrategy\"`\n}\n\nfunc (c *WireGuardConfig) Build() (proto.Message, error) {\n\tconfig := new(wireguard.DeviceConfig)\n\n\tvar err error\n\tconfig.SecretKey, err = ParseWireGuardKey(c.SecretKey)\n\tif err != nil {\n\t\treturn nil, errors.New(\"invalid WireGuard secret key: %w\", err)\n\t}\n\n\tif c.Address == nil {\n\t\t// bogon ips\n\t\tconfig.Endpoint = []string{\"10.0.0.1\", \"fd59:7153:2388:b5fd:0000:0000:0000:0001\"}\n\t} else {\n\t\tconfig.Endpoint = c.Address\n\t}\n\n\tif c.Peers != nil {\n\t\tconfig.Peers = make([]*wireguard.PeerConfig, len(c.Peers))\n\t\tfor i, p := range c.Peers {\n\t\t\tmsg, err := p.Build()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tconfig.Peers[i] = msg.(*wireguard.PeerConfig)\n\t\t}\n\t}\n\n\tif c.MTU == 0 {\n\t\tconfig.Mtu = 1420\n\t} else {\n\t\tconfig.Mtu = c.MTU\n\t}\n\t// these a fallback code exists in wireguard-go code,\n\t// we don't need to process fallback manually\n\tconfig.NumWorkers = c.NumWorkers\n\n\tif len(c.Reserved) != 0 && len(c.Reserved) != 3 {\n\t\treturn nil, errors.New(`\"reserved\" should be empty or 3 bytes`)\n\t}\n\tconfig.Reserved = c.Reserved\n\n\tswitch strings.ToLower(c.DomainStrategy) {\n\tcase \"forceip\", \"\":\n\t\tconfig.DomainStrategy = wireguard.DeviceConfig_FORCE_IP\n\tcase \"forceipv4\":\n\t\tconfig.DomainStrategy = wireguard.DeviceConfig_FORCE_IP4\n\tcase \"forceipv6\":\n\t\tconfig.DomainStrategy = wireguard.DeviceConfig_FORCE_IP6\n\tcase \"forceipv4v6\":\n\t\tconfig.DomainStrategy = wireguard.DeviceConfig_FORCE_IP46\n\tcase \"forceipv6v4\":\n\t\tconfig.DomainStrategy = wireguard.DeviceConfig_FORCE_IP64\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported domain strategy: \", c.DomainStrategy)\n\t}\n\n\tconfig.IsClient = c.IsClient\n\tconfig.NoKernelTun = c.NoKernelTun\n\n\treturn config, nil\n}\n\nfunc ParseWireGuardKey(str string) (string, error) {\n\tvar err error\n\n\tif str == \"\" {\n\t\treturn \"\", errors.New(\"key must not be empty\")\n\t}\n\n\tif len(str)%2 == 0 {\n\t\t_, err = hex.DecodeString(str)\n\t\tif err == nil {\n\t\t\treturn str, nil\n\t\t}\n\t}\n\n\tvar dat []byte\n\tstr = strings.TrimSuffix(str, \"=\")\n\tif strings.ContainsRune(str, '+') || strings.ContainsRune(str, '/') {\n\t\tdat, err = base64.RawStdEncoding.DecodeString(str)\n\t} else {\n\t\tdat, err = base64.RawURLEncoding.DecodeString(str)\n\t}\n\tif err == nil {\n\t\treturn hex.EncodeToString(dat), nil\n\t}\n\n\treturn \"\", errors.New(\"failed to deserialize key\").Base(err)\n}\n"
  },
  {
    "path": "infra/conf/wireguard_test.go",
    "content": "package conf_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/wireguard\"\n)\n\nfunc TestWireGuardConfig(t *testing.T) {\n\tcreator := func() Buildable {\n\t\treturn new(WireGuardConfig)\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"secretKey\": \"uJv5tZMDltsiYEn+kUwb0Ll/CXWhMkaSCWWhfPEZM3A=\",\n\t\t\t\t\"address\": [\"10.1.1.1\", \"fd59:7153:2388:b5fd:0000:0000:1234:0001\"],\n\t\t\t\t\"peers\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"publicKey\": \"6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a\",\n\t\t\t\t\t\t\"endpoint\": \"127.0.0.1:1234\"\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"mtu\": 1300,\n\t\t\t\t\"workers\": 2,\n\t\t\t\t\"domainStrategy\": \"ForceIPv6v4\",\n\t\t\t\t\"noKernelTun\": false\n\t\t\t}`,\n\t\t\tParser: loadJSON(creator),\n\t\t\tOutput: &wireguard.DeviceConfig{\n\t\t\t\t// key converted into hex form\n\t\t\t\tSecretKey: \"b89bf9b5930396db226049fe914c1bd0b97f0975a13246920965a17cf1193370\",\n\t\t\t\tEndpoint:  []string{\"10.1.1.1\", \"fd59:7153:2388:b5fd:0000:0000:1234:0001\"},\n\t\t\t\tPeers: []*wireguard.PeerConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\t// also can read from hex form directly\n\t\t\t\t\t\tPublicKey:  \"6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a\",\n\t\t\t\t\t\tEndpoint:   \"127.0.0.1:1234\",\n\t\t\t\t\t\tKeepAlive:  0,\n\t\t\t\t\t\tAllowedIps: []string{\"0.0.0.0/0\", \"::0/0\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMtu:            1300,\n\t\t\t\tNumWorkers:     2,\n\t\t\t\tDomainStrategy: wireguard.DeviceConfig_FORCE_IP64,\n\t\t\t\tNoKernelTun:    false,\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "infra/conf/xray.go",
    "content": "package conf\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/app/stats\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nvar (\n\tinboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{\n\t\t\"tunnel\":        func() interface{} { return new(DokodemoConfig) },\n\t\t\"dokodemo-door\": func() interface{} { return new(DokodemoConfig) },\n\t\t\"http\":          func() interface{} { return new(HTTPServerConfig) },\n\t\t\"shadowsocks\":   func() interface{} { return new(ShadowsocksServerConfig) },\n\t\t\"mixed\":         func() interface{} { return new(SocksServerConfig) },\n\t\t\"socks\":         func() interface{} { return new(SocksServerConfig) },\n\t\t\"vless\":         func() interface{} { return new(VLessInboundConfig) },\n\t\t\"vmess\":         func() interface{} { return new(VMessInboundConfig) },\n\t\t\"trojan\":        func() interface{} { return new(TrojanServerConfig) },\n\t\t\"wireguard\":     func() interface{} { return &WireGuardConfig{IsClient: false} },\n\t\t\"hysteria\":      func() interface{} { return new(HysteriaServerConfig) },\n\t\t\"tun\":           func() interface{} { return new(TunConfig) },\n\t}, \"protocol\", \"settings\")\n\n\toutboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{\n\t\t\"block\":       func() interface{} { return new(BlackholeConfig) },\n\t\t\"blackhole\":   func() interface{} { return new(BlackholeConfig) },\n\t\t\"loopback\":    func() interface{} { return new(LoopbackConfig) },\n\t\t\"direct\":      func() interface{} { return new(FreedomConfig) },\n\t\t\"freedom\":     func() interface{} { return new(FreedomConfig) },\n\t\t\"http\":        func() interface{} { return new(HTTPClientConfig) },\n\t\t\"shadowsocks\": func() interface{} { return new(ShadowsocksClientConfig) },\n\t\t\"socks\":       func() interface{} { return new(SocksClientConfig) },\n\t\t\"vless\":       func() interface{} { return new(VLessOutboundConfig) },\n\t\t\"vmess\":       func() interface{} { return new(VMessOutboundConfig) },\n\t\t\"trojan\":      func() interface{} { return new(TrojanClientConfig) },\n\t\t\"hysteria\":    func() interface{} { return new(HysteriaClientConfig) },\n\t\t\"dns\":         func() interface{} { return new(DNSOutboundConfig) },\n\t\t\"wireguard\":   func() interface{} { return &WireGuardConfig{IsClient: true} },\n\t}, \"protocol\", \"settings\")\n)\n\ntype SniffingConfig struct {\n\tEnabled         bool        `json:\"enabled\"`\n\tDestOverride    *StringList `json:\"destOverride\"`\n\tDomainsExcluded *StringList `json:\"domainsExcluded\"`\n\tMetadataOnly    bool        `json:\"metadataOnly\"`\n\tRouteOnly       bool        `json:\"routeOnly\"`\n}\n\n// Build implements Buildable.\nfunc (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {\n\tvar p []string\n\tif c.DestOverride != nil {\n\t\tfor _, protocol := range *c.DestOverride {\n\t\t\tswitch strings.ToLower(protocol) {\n\t\t\tcase \"http\":\n\t\t\t\tp = append(p, \"http\")\n\t\t\tcase \"tls\", \"https\", \"ssl\":\n\t\t\t\tp = append(p, \"tls\")\n\t\t\tcase \"quic\":\n\t\t\t\tp = append(p, \"quic\")\n\t\t\tcase \"fakedns\", \"fakedns+others\":\n\t\t\t\tp = append(p, \"fakedns\")\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"unknown protocol: \", protocol)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar d []string\n\tif c.DomainsExcluded != nil {\n\t\tfor _, domain := range *c.DomainsExcluded {\n\t\t\td = append(d, strings.ToLower(domain))\n\t\t}\n\t}\n\n\treturn &proxyman.SniffingConfig{\n\t\tEnabled:             c.Enabled,\n\t\tDestinationOverride: p,\n\t\tDomainsExcluded:     d,\n\t\tMetadataOnly:        c.MetadataOnly,\n\t\tRouteOnly:           c.RouteOnly,\n\t}, nil\n}\n\ntype MuxConfig struct {\n\tEnabled         bool   `json:\"enabled\"`\n\tConcurrency     int16  `json:\"concurrency\"`\n\tXudpConcurrency int16  `json:\"xudpConcurrency\"`\n\tXudpProxyUDP443 string `json:\"xudpProxyUDP443\"`\n}\n\n// Build creates MultiplexingConfig, Concurrency < 0 completely disables mux.\nfunc (m *MuxConfig) Build() (*proxyman.MultiplexingConfig, error) {\n\tswitch m.XudpProxyUDP443 {\n\tcase \"\":\n\t\tm.XudpProxyUDP443 = \"reject\"\n\tcase \"reject\", \"allow\", \"skip\":\n\tdefault:\n\t\treturn nil, errors.New(`unknown \"xudpProxyUDP443\": `, m.XudpProxyUDP443)\n\t}\n\treturn &proxyman.MultiplexingConfig{\n\t\tEnabled:         m.Enabled,\n\t\tConcurrency:     int32(m.Concurrency),\n\t\tXudpConcurrency: int32(m.XudpConcurrency),\n\t\tXudpProxyUDP443: m.XudpProxyUDP443,\n\t}, nil\n}\n\ntype InboundDetourConfig struct {\n\tProtocol       string           `json:\"protocol\"`\n\tPortList       *PortList        `json:\"port\"`\n\tListenOn       *Address         `json:\"listen\"`\n\tSettings       *json.RawMessage `json:\"settings\"`\n\tTag            string           `json:\"tag\"`\n\tStreamSetting  *StreamConfig    `json:\"streamSettings\"`\n\tSniffingConfig *SniffingConfig  `json:\"sniffing\"`\n}\n\n// Build implements Buildable.\nfunc (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {\n\treceiverSettings := &proxyman.ReceiverConfig{}\n\n\t// TUN inbound doesn't need port configuration as it uses network interface instead\n\tif strings.ToLower(c.Protocol) == \"tun\" {\n\t\t// Skip port validation for TUN\n\t} else if c.ListenOn == nil {\n\t\t// Listen on anyip, must set PortList\n\t\tif c.PortList == nil {\n\t\t\treturn nil, errors.New(\"Listen on AnyIP but no Port(s) set in InboundDetour.\")\n\t\t}\n\t\treceiverSettings.PortList = c.PortList.Build()\n\t} else {\n\t\t// Listen on specific IP or Unix Domain Socket\n\t\treceiverSettings.Listen = c.ListenOn.Build()\n\t\tlistenDS := c.ListenOn.Family().IsDomain() && (filepath.IsAbs(c.ListenOn.Domain()) || c.ListenOn.Domain()[0] == '@')\n\t\tlistenIP := c.ListenOn.Family().IsIP() || (c.ListenOn.Family().IsDomain() && c.ListenOn.Domain() == \"localhost\")\n\t\tif listenIP {\n\t\t\t// Listen on specific IP, must set PortList\n\t\t\tif c.PortList == nil {\n\t\t\t\treturn nil, errors.New(\"Listen on specific ip without port in InboundDetour.\")\n\t\t\t}\n\t\t\t// Listen on IP:Port\n\t\t\treceiverSettings.PortList = c.PortList.Build()\n\t\t} else if listenDS {\n\t\t\tif c.PortList != nil {\n\t\t\t\t// Listen on Unix Domain Socket, PortList should be nil\n\t\t\t\treceiverSettings.PortList = nil\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, errors.New(\"unable to listen on domain address: \", c.ListenOn.Domain())\n\t\t}\n\t}\n\n\tif c.StreamSetting != nil {\n\t\tss, err := c.StreamSetting.Build()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treceiverSettings.StreamSettings = ss\n\t}\n\tif c.SniffingConfig != nil {\n\t\ts, err := c.SniffingConfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build sniffing config\").Base(err)\n\t\t}\n\t\treceiverSettings.SniffingSettings = s\n\t}\n\n\tsettings := []byte(\"{}\")\n\tif c.Settings != nil {\n\t\tsettings = ([]byte)(*c.Settings)\n\t}\n\trawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to load inbound detour config for protocol \", c.Protocol).Base(err)\n\t}\n\tif dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {\n\t\treceiverSettings.ReceiveOriginalDestination = dokodemoConfig.FollowRedirect\n\t}\n\tts, err := rawConfig.(Buildable).Build()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to build inbound handler for protocol \", c.Protocol).Base(err)\n\t}\n\n\treturn &core.InboundHandlerConfig{\n\t\tTag:              c.Tag,\n\t\tReceiverSettings: serial.ToTypedMessage(receiverSettings),\n\t\tProxySettings:    serial.ToTypedMessage(ts),\n\t}, nil\n}\n\ntype OutboundDetourConfig struct {\n\tProtocol       string           `json:\"protocol\"`\n\tSendThrough    *string          `json:\"sendThrough\"`\n\tTag            string           `json:\"tag\"`\n\tSettings       *json.RawMessage `json:\"settings\"`\n\tStreamSetting  *StreamConfig    `json:\"streamSettings\"`\n\tProxySettings  *ProxyConfig     `json:\"proxySettings\"`\n\tMuxSettings    *MuxConfig       `json:\"mux\"`\n\tTargetStrategy string           `json:\"targetStrategy\"`\n}\n\nfunc (c *OutboundDetourConfig) checkChainProxyConfig() error {\n\tif c.StreamSetting == nil || c.ProxySettings == nil || c.StreamSetting.SocketSettings == nil {\n\t\treturn nil\n\t}\n\tif len(c.ProxySettings.Tag) > 0 && len(c.StreamSetting.SocketSettings.DialerProxy) > 0 {\n\t\treturn errors.New(\"proxySettings.tag is conflicted with sockopt.dialerProxy\").AtWarning()\n\t}\n\treturn nil\n}\n\n// Build implements Buildable.\nfunc (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {\n\tsenderSettings := &proxyman.SenderConfig{}\n\tswitch strings.ToLower(c.TargetStrategy) {\n\tcase \"asis\", \"\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_AS_IS\n\tcase \"useip\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_USE_IP\n\tcase \"useipv4\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_USE_IP4\n\tcase \"useipv6\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_USE_IP6\n\tcase \"useipv4v6\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_USE_IP46\n\tcase \"useipv6v4\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_USE_IP64\n\tcase \"forceip\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP\n\tcase \"forceipv4\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP4\n\tcase \"forceipv6\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP6\n\tcase \"forceipv4v6\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP46\n\tcase \"forceipv6v4\":\n\t\tsenderSettings.TargetStrategy = internet.DomainStrategy_FORCE_IP64\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported target domain strategy: \", c.TargetStrategy)\n\t}\n\tif err := c.checkChainProxyConfig(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.SendThrough != nil {\n\t\taddress := ParseSendThough(c.SendThrough)\n\t\t//Check if CIDR exists\n\t\tif strings.Contains(*c.SendThrough, \"/\") {\n\t\t\tsenderSettings.ViaCidr = strings.Split(*c.SendThrough, \"/\")[1]\n\t\t} else {\n\t\t\tif address.Family().IsDomain() {\n\t\t\t\tdomain := address.Address.Domain()\n\t\t\t\tif domain != \"origin\" && domain != \"srcip\" {\n\t\t\t\t\treturn nil, errors.New(\"unable to send through: \" + address.String())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsenderSettings.Via = address.Build()\n\t}\n\n\tif c.StreamSetting != nil {\n\t\tss, err := c.StreamSetting.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build stream settings for outbound detour\").Base(err)\n\t\t}\n\t\tsenderSettings.StreamSettings = ss\n\t}\n\n\tif c.ProxySettings != nil {\n\t\tps, err := c.ProxySettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid outbound detour proxy settings\").Base(err)\n\t\t}\n\t\tif ps.TransportLayerProxy {\n\t\t\tif senderSettings.StreamSettings != nil {\n\t\t\t\tif senderSettings.StreamSettings.SocketSettings != nil {\n\t\t\t\t\tsenderSettings.StreamSettings.SocketSettings.DialerProxy = ps.Tag\n\t\t\t\t} else {\n\t\t\t\t\tsenderSettings.StreamSettings.SocketSettings = &internet.SocketConfig{DialerProxy: ps.Tag}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsenderSettings.StreamSettings = &internet.StreamConfig{SocketSettings: &internet.SocketConfig{DialerProxy: ps.Tag}}\n\t\t\t}\n\t\t\tps = nil\n\t\t}\n\t\tsenderSettings.ProxySettings = ps\n\t}\n\n\tif c.MuxSettings != nil {\n\t\tms, err := c.MuxSettings.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build Mux config\").Base(err)\n\t\t}\n\t\tsenderSettings.MultiplexSettings = ms\n\t}\n\n\tsettings := []byte(\"{}\")\n\tif c.Settings != nil {\n\t\tsettings = ([]byte)(*c.Settings)\n\t}\n\trawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to load outbound detour config for protocol \", c.Protocol).Base(err)\n\t}\n\tts, err := rawConfig.(Buildable).Build()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to build outbound handler for protocol \", c.Protocol).Base(err)\n\t}\n\n\treturn &core.OutboundHandlerConfig{\n\t\tSenderSettings: serial.ToTypedMessage(senderSettings),\n\t\tTag:            c.Tag,\n\t\tProxySettings:  serial.ToTypedMessage(ts),\n\t}, nil\n}\n\ntype StatsConfig struct{}\n\n// Build implements Buildable.\nfunc (c *StatsConfig) Build() (*stats.Config, error) {\n\treturn &stats.Config{}, nil\n}\n\ntype Config struct {\n\t// Deprecated: Global transport config is no longer used\n\t// left for returning error\n\tTransport map[string]json.RawMessage `json:\"transport\"`\n\n\tLogConfig        *LogConfig              `json:\"log\"`\n\tRouterConfig     *RouterConfig           `json:\"routing\"`\n\tDNSConfig        *DNSConfig              `json:\"dns\"`\n\tInboundConfigs   []InboundDetourConfig   `json:\"inbounds\"`\n\tOutboundConfigs  []OutboundDetourConfig  `json:\"outbounds\"`\n\tPolicy           *PolicyConfig           `json:\"policy\"`\n\tAPI              *APIConfig              `json:\"api\"`\n\tMetrics          *MetricsConfig          `json:\"metrics\"`\n\tStats            *StatsConfig            `json:\"stats\"`\n\tReverse          *ReverseConfig          `json:\"reverse\"`\n\tFakeDNS          *FakeDNSConfig          `json:\"fakeDns\"`\n\tObservatory      *ObservatoryConfig      `json:\"observatory\"`\n\tBurstObservatory *BurstObservatoryConfig `json:\"burstObservatory\"`\n\tVersion          *VersionConfig          `json:\"version\"`\n}\n\nfunc (c *Config) findInboundTag(tag string) int {\n\tfound := -1\n\tfor idx, ib := range c.InboundConfigs {\n\t\tif ib.Tag == tag {\n\t\t\tfound = idx\n\t\t\tbreak\n\t\t}\n\t}\n\treturn found\n}\n\nfunc (c *Config) findOutboundTag(tag string) int {\n\tfound := -1\n\tfor idx, ob := range c.OutboundConfigs {\n\t\tif ob.Tag == tag {\n\t\t\tfound = idx\n\t\t\tbreak\n\t\t}\n\t}\n\treturn found\n}\n\n// Override method accepts another Config overrides the current attribute\nfunc (c *Config) Override(o *Config, fn string) {\n\t// only process the non-deprecated members\n\n\tif o.LogConfig != nil {\n\t\tc.LogConfig = o.LogConfig\n\t}\n\tif o.RouterConfig != nil {\n\t\tc.RouterConfig = o.RouterConfig\n\t}\n\tif o.DNSConfig != nil {\n\t\tc.DNSConfig = o.DNSConfig\n\t}\n\tif o.Transport != nil {\n\t\tc.Transport = o.Transport\n\t}\n\tif o.Policy != nil {\n\t\tc.Policy = o.Policy\n\t}\n\tif o.API != nil {\n\t\tc.API = o.API\n\t}\n\tif o.Metrics != nil {\n\t\tc.Metrics = o.Metrics\n\t}\n\tif o.Stats != nil {\n\t\tc.Stats = o.Stats\n\t}\n\tif o.Reverse != nil {\n\t\tc.Reverse = o.Reverse\n\t}\n\n\tif o.FakeDNS != nil {\n\t\tc.FakeDNS = o.FakeDNS\n\t}\n\n\tif o.Observatory != nil {\n\t\tc.Observatory = o.Observatory\n\t}\n\n\tif o.BurstObservatory != nil {\n\t\tc.BurstObservatory = o.BurstObservatory\n\t}\n\n\tif o.Version != nil {\n\t\tc.Version = o.Version\n\t}\n\n\t// update the Inbound in slice if the only one in override config has same tag\n\tif len(o.InboundConfigs) > 0 {\n\t\tfor i := range o.InboundConfigs {\n\t\t\tif idx := c.findInboundTag(o.InboundConfigs[i].Tag); idx > -1 {\n\t\t\t\tc.InboundConfigs[idx] = o.InboundConfigs[i]\n\t\t\t\terrors.LogInfo(context.Background(), \"[\", fn, \"] updated inbound with tag: \", o.InboundConfigs[i].Tag)\n\n\t\t\t} else {\n\t\t\t\tc.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[i])\n\t\t\t\terrors.LogInfo(context.Background(), \"[\", fn, \"] appended inbound with tag: \", o.InboundConfigs[i].Tag)\n\t\t\t}\n\n\t\t}\n\t}\n\n\t// update the Outbound in slice if the only one in override config has same tag\n\tif len(o.OutboundConfigs) > 0 {\n\t\toutboundPrepends := []OutboundDetourConfig{}\n\t\tfor i := range o.OutboundConfigs {\n\t\t\tif idx := c.findOutboundTag(o.OutboundConfigs[i].Tag); idx > -1 {\n\t\t\t\tc.OutboundConfigs[idx] = o.OutboundConfigs[i]\n\t\t\t\terrors.LogInfo(context.Background(), \"[\", fn, \"] updated outbound with tag: \", o.OutboundConfigs[i].Tag)\n\t\t\t} else {\n\t\t\t\tif strings.Contains(strings.ToLower(fn), \"tail\") {\n\t\t\t\t\tc.OutboundConfigs = append(c.OutboundConfigs, o.OutboundConfigs[i])\n\t\t\t\t\terrors.LogInfo(context.Background(), \"[\", fn, \"] appended outbound with tag: \", o.OutboundConfigs[i].Tag)\n\t\t\t\t} else {\n\t\t\t\t\toutboundPrepends = append(outboundPrepends, o.OutboundConfigs[i])\n\t\t\t\t\terrors.LogInfo(context.Background(), \"[\", fn, \"] prepend outbound with tag: \", o.OutboundConfigs[i].Tag)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !strings.Contains(strings.ToLower(fn), \"tail\") && len(outboundPrepends) > 0 {\n\t\t\tc.OutboundConfigs = append(outboundPrepends, c.OutboundConfigs...)\n\t\t}\n\t}\n}\n\n// Build implements Buildable.\nfunc (c *Config) Build() (*core.Config, error) {\n\tif err := PostProcessConfigureFile(c); err != nil {\n\t\treturn nil, errors.New(\"failed to post-process configuration file\").Base(err)\n\t}\n\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t}\n\n\tif c.API != nil {\n\t\tapiConf, err := c.API.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build API configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(apiConf))\n\t}\n\tif c.Metrics != nil {\n\t\tmetricsConf, err := c.Metrics.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build metrics configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(metricsConf))\n\t}\n\tif c.Stats != nil {\n\t\tstatsConf, err := c.Stats.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build stats configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(statsConf))\n\t}\n\n\tvar logConfMsg *serial.TypedMessage\n\tif c.LogConfig != nil {\n\t\tlogConfMsg = serial.ToTypedMessage(c.LogConfig.Build())\n\t} else {\n\t\tlogConfMsg = serial.ToTypedMessage(DefaultLogConfig())\n\t}\n\t// let logger module be the first App to start,\n\t// so that other modules could print log during initiating\n\tconfig.App = append([]*serial.TypedMessage{logConfMsg}, config.App...)\n\n\tif c.RouterConfig != nil {\n\t\trouterConfig, err := c.RouterConfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build routing configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(routerConfig))\n\t}\n\n\tif c.DNSConfig != nil {\n\t\tdnsApp, err := c.DNSConfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build DNS configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(dnsApp))\n\t}\n\n\tif c.Policy != nil {\n\t\tpc, err := c.Policy.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build policy configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(pc))\n\t}\n\n\tif c.Reverse != nil {\n\t\tr, err := c.Reverse.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build reverse configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(r))\n\t}\n\n\tif c.FakeDNS != nil {\n\t\tr, err := c.FakeDNS.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build fake DNS configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append([]*serial.TypedMessage{serial.ToTypedMessage(r)}, config.App...)\n\t}\n\n\tif c.Observatory != nil {\n\t\tr, err := c.Observatory.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build observatory configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(r))\n\t}\n\n\tif c.BurstObservatory != nil {\n\t\tr, err := c.BurstObservatory.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build burst observatory configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(r))\n\t}\n\n\tif c.Version != nil {\n\t\tr, err := c.Version.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build version configuration\").Base(err)\n\t\t}\n\t\tconfig.App = append(config.App, serial.ToTypedMessage(r))\n\t}\n\n\tvar inbounds []InboundDetourConfig\n\n\tif len(c.InboundConfigs) > 0 {\n\t\tinbounds = append(inbounds, c.InboundConfigs...)\n\t}\n\n\tif len(c.Transport) > 0 {\n\t\treturn nil, errors.PrintRemovedFeatureError(\"Global transport config\", \"streamSettings in inbounds and outbounds\")\n\t}\n\n\tfor _, rawInboundConfig := range inbounds {\n\t\tic, err := rawInboundConfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build inbound config with tag \", rawInboundConfig.Tag).Base(err)\n\t\t}\n\t\tconfig.Inbound = append(config.Inbound, ic)\n\t}\n\n\tvar outbounds []OutboundDetourConfig\n\n\tif len(c.OutboundConfigs) > 0 {\n\t\toutbounds = append(outbounds, c.OutboundConfigs...)\n\t}\n\n\tfor _, rawOutboundConfig := range outbounds {\n\t\toc, err := rawOutboundConfig.Build()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to build outbound config with tag \", rawOutboundConfig.Tag).Base(err)\n\t\t}\n\t\tconfig.Outbound = append(config.Outbound, oc)\n\t}\n\n\treturn config, nil\n}\n\nfunc (c *Config) BuildMPHCache(customMatcherFilePath *string) error {\n\tvar geosite []*router.GeoSite\n\tdeps := make(map[string][]string)\n\tuniqueGeosites := make(map[string]bool)\n\tuniqueTags := make(map[string]bool)\n\tmatcherFilePath := platform.GetAssetLocation(\"matcher.cache\")\n\n\tif customMatcherFilePath != nil {\n\t\tmatcherFilePath = *customMatcherFilePath\n\t}\n\n\tprocessGeosite := func(dStr string) bool {\n\t\tprefix := \"\"\n\t\tif strings.HasPrefix(dStr, \"geosite:\") {\n\t\t\tprefix = \"geosite:\"\n\t\t} else if strings.HasPrefix(dStr, \"ext-domain:\") {\n\t\t\tprefix = \"ext-domain:\"\n\t\t}\n\t\tif prefix == \"\" {\n\t\t\treturn false\n\t\t}\n\t\tkey := strings.ToLower(dStr)\n\t\tcountry := strings.ToUpper(dStr[len(prefix):])\n\t\tif !uniqueGeosites[country] {\n\t\t\tds, err := loadGeositeWithAttr(\"geosite.dat\", country)\n\t\t\tif err == nil {\n\t\t\t\tuniqueGeosites[country] = true\n\t\t\t\tgeosite = append(geosite, &router.GeoSite{CountryCode: key, Domain: ds})\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tprocessDomains := func(tag string, rawDomains []string) {\n\t\tvar manualDomains []*router.Domain\n\t\tvar dDeps []string\n\t\tfor _, dStr := range rawDomains {\n\t\t\tif processGeosite(dStr) {\n\t\t\t\tdDeps = append(dDeps, strings.ToLower(dStr))\n\t\t\t} else {\n\t\t\t\tds, err := parseDomainRule(dStr)\n\t\t\t\tif err == nil {\n\t\t\t\t\tmanualDomains = append(manualDomains, ds...)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(manualDomains) > 0 {\n\t\t\tif !uniqueTags[tag] {\n\t\t\t\tuniqueTags[tag] = true\n\t\t\t\tgeosite = append(geosite, &router.GeoSite{CountryCode: tag, Domain: manualDomains})\n\t\t\t}\n\t\t}\n\t\tif len(dDeps) > 0 {\n\t\t\tdeps[tag] = append(deps[tag], dDeps...)\n\t\t}\n\t}\n\n\t// proccess rules\n\tif c.RouterConfig != nil {\n\t\tfor _, rawRule := range c.RouterConfig.RuleList {\n\t\t\ttype SimpleRule struct {\n\t\t\t\tRuleTag string      `json:\"ruleTag\"`\n\t\t\t\tDomain  *StringList `json:\"domain\"`\n\t\t\t\tDomains *StringList `json:\"domains\"`\n\t\t\t}\n\t\t\tvar sr SimpleRule\n\t\t\tjson.Unmarshal(rawRule, &sr)\n\t\t\tif sr.RuleTag == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar allDomains []string\n\t\t\tif sr.Domain != nil {\n\t\t\t\tallDomains = append(allDomains, *sr.Domain...)\n\t\t\t}\n\t\t\tif sr.Domains != nil {\n\t\t\t\tallDomains = append(allDomains, *sr.Domains...)\n\t\t\t}\n\t\t\tprocessDomains(sr.RuleTag, allDomains)\n\t\t}\n\t}\n\n\t// proccess dns servers\n\tif c.DNSConfig != nil {\n\t\tfor _, ns := range c.DNSConfig.Servers {\n\t\t\tif ns.Tag == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tprocessDomains(ns.Tag, ns.Domains)\n\t\t}\n\t}\n\n\tvar hostIPs map[string][]string\n\tif c.DNSConfig != nil && c.DNSConfig.Hosts != nil {\n\t\thostIPs = make(map[string][]string)\n\t\tvar hostDeps []string\n\t\tvar hostPatterns []string\n\n\t\t// use raw map to avoid expanding geosites\n\t\tvar domains []string\n\t\tfor domain := range c.DNSConfig.Hosts.Hosts {\n\t\t\tdomains = append(domains, domain)\n\t\t}\n\t\tsort.Strings(domains)\n\n\t\tmanualHostGroups := make(map[string][]*router.Domain)\n\t\tmanualHostIPs := make(map[string][]string)\n\t\tmanualHostNames := make(map[string]string)\n\n\t\tfor _, domain := range domains {\n\t\t\tha := c.DNSConfig.Hosts.Hosts[domain]\n\t\t\tm := getHostMapping(ha)\n\n\t\t\tvar ips []string\n\t\t\tif m.ProxiedDomain != \"\" {\n\t\t\t\tips = append(ips, m.ProxiedDomain)\n\t\t\t} else {\n\t\t\t\tfor _, ip := range m.Ip {\n\t\t\t\t\tips = append(ips, net.IPAddress(ip).String())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif processGeosite(domain) {\n\t\t\t\ttag := strings.ToLower(domain)\n\t\t\t\thostDeps = append(hostDeps, tag)\n\t\t\t\thostIPs[tag] = ips\n\t\t\t\thostPatterns = append(hostPatterns, domain)\n\t\t\t} else {\n\t\t\t\t// build manual domains by their destination IPs\n\t\t\t\tsort.Strings(ips)\n\t\t\t\tipKey := strings.Join(ips, \",\")\n\t\t\t\tds, err := parseDomainRule(domain)\n\t\t\t\tif err == nil {\n\t\t\t\t\tmanualHostGroups[ipKey] = append(manualHostGroups[ipKey], ds...)\n\t\t\t\t\tmanualHostIPs[ipKey] = ips\n\t\t\t\t\tif _, ok := manualHostNames[ipKey]; !ok {\n\t\t\t\t\t\tmanualHostNames[ipKey] = domain\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// create manual host groups\n\t\tvar ipKeys []string\n\t\tfor k := range manualHostGroups {\n\t\t\tipKeys = append(ipKeys, k)\n\t\t}\n\t\tsort.Strings(ipKeys)\n\n\t\tfor _, k := range ipKeys {\n\t\t\ttag := manualHostNames[k]\n\t\t\tgeosite = append(geosite, &router.GeoSite{CountryCode: tag, Domain: manualHostGroups[k]})\n\t\t\thostDeps = append(hostDeps, tag)\n\t\t\thostIPs[tag] = manualHostIPs[k]\n\n\t\t\t// record tag _ORDER links the matcher to IP addresses\n\t\t\thostPatterns = append(hostPatterns, tag)\n\t\t}\n\n\t\tdeps[\"HOSTS\"] = hostDeps\n\t\thostIPs[\"_ORDER\"] = hostPatterns\n\t}\n\n\tf, err := os.Create(matcherFilePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tvar buf bytes.Buffer\n\n\tif err := router.SerializeGeoSiteList(geosite, deps, hostIPs, &buf); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := f.Write(buf.Bytes()); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Convert string to Address.\nfunc ParseSendThough(Addr *string) *Address {\n\tvar addr Address\n\taddr.Address = net.ParseAddress(strings.Split(*Addr, \"/\")[0])\n\treturn &addr\n}\n"
  },
  {
    "path": "infra/conf/xray_test.go",
    "content": "package conf_test\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t. \"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/internet/websocket\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestXrayConfig(t *testing.T) {\n\tcreateParser := func() func(string) (proto.Message, error) {\n\t\treturn func(s string) (proto.Message, error) {\n\t\t\tconfig := new(Config)\n\t\t\tif err := json.Unmarshal([]byte(s), config); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn config.Build()\n\t\t}\n\t}\n\n\trunMultiTestCase(t, []TestCase{\n\t\t{\n\t\t\tInput: `{\n\t\t\t\t\"log\": {\n\t\t\t\t\t\"access\": \"/var/log/xray/access.log\",\n\t\t\t\t\t\"loglevel\": \"error\",\n\t\t\t\t\t\"error\": \"/var/log/xray/error.log\"\n\t\t\t\t},\n\t\t\t\t\"inbounds\": [{\n\t\t\t\t\t\"streamSettings\": {\n\t\t\t\t\t\t\"network\": \"ws\",\n\t\t\t\t\t\t\"wsSettings\": {\n\t\t\t\t\t\t\t\"host\": \"example.domain\",\n\t\t\t\t\t\t\t\"path\": \"\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"tlsSettings\": {\n\t\t\t\t\t\t\t\"alpn\": \"h2\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"security\": \"tls\"\n\t\t\t\t\t},\n\t\t\t\t\t\"protocol\": \"vmess\",\n\t\t\t\t\t\"port\": \"443-500\",\n\t\t\t\t\t\"settings\": {\n\t\t\t\t\t\t\"clients\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"security\": \"aes-128-gcm\",\n\t\t\t\t\t\t\t\t\"id\": \"0cdf8a45-303d-4fed-9780-29aa7f54175e\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t}\n\t\t\t\t}],\n\t\t\t\t\"routing\": {\n\t\t\t\t\t\"rules\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"ip\": [\n\t\t\t\t\t\t\t\t\"10.0.0.0/8\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"outboundTag\": \"blocked\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}`,\n\t\t\tParser: createParser(),\n\t\t\tOutput: &core.Config{\n\t\t\t\tApp: []*serial.TypedMessage{\n\t\t\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\t\t\tErrorLogType:  log.LogType_File,\n\t\t\t\t\t\tErrorLogPath:  \"/var/log/xray/error.log\",\n\t\t\t\t\t\tErrorLogLevel: clog.Severity_Error,\n\t\t\t\t\t\tAccessLogType: log.LogType_File,\n\t\t\t\t\t\tAccessLogPath: \"/var/log/xray/access.log\",\n\t\t\t\t\t}),\n\t\t\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\t\t\tDomainStrategy: router.Config_AsIs,\n\t\t\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tIp:     []byte{10, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t\t\tPrefix: 8,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\t\t\tTag: \"blocked\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{{\n\t\t\t\t\t\t\t\tFrom: 443,\n\t\t\t\t\t\t\t\tTo:   500,\n\t\t\t\t\t\t\t}}},\n\t\t\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\t\t\tProtocolName: \"websocket\",\n\t\t\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tProtocolName: \"websocket\",\n\t\t\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&websocket.Config{\n\t\t\t\t\t\t\t\t\t\t\tHost: \"example.domain\",\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tSecurityType: \"xray.transport.internet.tls.Config\",\n\t\t\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\t\t\tNextProtocol: []string{\"h2\"},\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tLevel: 0,\n\t\t\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\t\t\tId: \"0cdf8a45-303d-4fed-9780-29aa7f54175e\",\n\t\t\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n}\n\nfunc TestMuxConfig_Build(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tfields string\n\t\twant   *proxyman.MultiplexingConfig\n\t}{\n\t\t{\"default\", `{\"enabled\": true, \"concurrency\": 16}`, &proxyman.MultiplexingConfig{\n\t\t\tEnabled:         true,\n\t\t\tConcurrency:     16,\n\t\t\tXudpConcurrency: 0,\n\t\t\tXudpProxyUDP443: \"reject\",\n\t\t}},\n\t\t{\"empty def\", `{}`, &proxyman.MultiplexingConfig{\n\t\t\tEnabled:         false,\n\t\t\tConcurrency:     0,\n\t\t\tXudpConcurrency: 0,\n\t\t\tXudpProxyUDP443: \"reject\",\n\t\t}},\n\t\t{\"not enable\", `{\"enabled\": false, \"concurrency\": 4}`, &proxyman.MultiplexingConfig{\n\t\t\tEnabled:         false,\n\t\t\tConcurrency:     4,\n\t\t\tXudpConcurrency: 0,\n\t\t\tXudpProxyUDP443: \"reject\",\n\t\t}},\n\t\t{\"forbidden\", `{\"enabled\": false, \"concurrency\": -1}`, &proxyman.MultiplexingConfig{\n\t\t\tEnabled:         false,\n\t\t\tConcurrency:     -1,\n\t\t\tXudpConcurrency: 0,\n\t\t\tXudpProxyUDP443: \"reject\",\n\t\t}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tm := &MuxConfig{}\n\t\t\tcommon.Must(json.Unmarshal([]byte(tt.fields), m))\n\t\t\tif got, _ := m.Build(); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"MuxConfig.Build() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConfig_Override(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\torig *Config\n\t\tover *Config\n\t\tfn   string\n\t\twant *Config\n\t}{\n\t\t{\n\t\t\t\"combine/empty\",\n\t\t\t&Config{},\n\t\t\t&Config{\n\t\t\t\tLogConfig:    &LogConfig{},\n\t\t\t\tRouterConfig: &RouterConfig{},\n\t\t\t\tDNSConfig:    &DNSConfig{},\n\t\t\t\tPolicy:       &PolicyConfig{},\n\t\t\t\tAPI:          &APIConfig{},\n\t\t\t\tStats:        &StatsConfig{},\n\t\t\t\tReverse:      &ReverseConfig{},\n\t\t\t},\n\t\t\t\"\",\n\t\t\t&Config{\n\t\t\t\tLogConfig:    &LogConfig{},\n\t\t\t\tRouterConfig: &RouterConfig{},\n\t\t\t\tDNSConfig:    &DNSConfig{},\n\t\t\t\tPolicy:       &PolicyConfig{},\n\t\t\t\tAPI:          &APIConfig{},\n\t\t\t\tStats:        &StatsConfig{},\n\t\t\t\tReverse:      &ReverseConfig{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"combine/newattr\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"old\"}}},\n\t\t\t&Config{LogConfig: &LogConfig{}}, \"\",\n\t\t\t&Config{LogConfig: &LogConfig{}, InboundConfigs: []InboundDetourConfig{{Tag: \"old\"}}},\n\t\t},\n\t\t{\n\t\t\t\"replace/inbounds\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos0\"}, {Protocol: \"vmess\", Tag: \"pos1\"}}},\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos1\", Protocol: \"kcp\"}}},\n\t\t\t\"\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos0\"}, {Tag: \"pos1\", Protocol: \"kcp\"}}},\n\t\t},\n\t\t{\n\t\t\t\"replace/inbounds-replaceall\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos0\"}, {Protocol: \"vmess\", Tag: \"pos1\"}}},\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos1\", Protocol: \"kcp\"}, {Tag: \"pos2\", Protocol: \"kcp\"}}},\n\t\t\t\"\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos0\"}, {Tag: \"pos1\", Protocol: \"kcp\"}, {Tag: \"pos2\", Protocol: \"kcp\"}}},\n\t\t},\n\t\t{\n\t\t\t\"replace/notag-append\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{}, {Protocol: \"vmess\"}}},\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{Tag: \"pos1\", Protocol: \"kcp\"}}},\n\t\t\t\"\",\n\t\t\t&Config{InboundConfigs: []InboundDetourConfig{{}, {Protocol: \"vmess\"}, {Tag: \"pos1\", Protocol: \"kcp\"}}},\n\t\t},\n\t\t{\n\t\t\t\"replace/outbounds\",\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos0\"}, {Protocol: \"vmess\", Tag: \"pos1\"}}},\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos1\", Protocol: \"kcp\"}}},\n\t\t\t\"\",\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos0\"}, {Tag: \"pos1\", Protocol: \"kcp\"}}},\n\t\t},\n\t\t{\n\t\t\t\"replace/outbounds-prepend\",\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos0\"}, {Protocol: \"vmess\", Tag: \"pos1\"}, {Tag: \"pos3\"}}},\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos1\", Protocol: \"kcp\"}, {Tag: \"pos2\", Protocol: \"kcp\"}, {Tag: \"pos4\", Protocol: \"kcp\"}}},\n\t\t\t\"config.json\",\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos2\", Protocol: \"kcp\"}, {Tag: \"pos4\", Protocol: \"kcp\"}, {Tag: \"pos0\"}, {Tag: \"pos1\", Protocol: \"kcp\"}, {Tag: \"pos3\"}}},\n\t\t},\n\t\t{\n\t\t\t\"replace/outbounds-append\",\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos0\"}, {Protocol: \"vmess\", Tag: \"pos1\"}}},\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos2\", Protocol: \"kcp\"}}},\n\t\t\t\"config_tail.json\",\n\t\t\t&Config{OutboundConfigs: []OutboundDetourConfig{{Tag: \"pos0\"}, {Protocol: \"vmess\", Tag: \"pos1\"}, {Tag: \"pos2\", Protocol: \"kcp\"}}},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.orig.Override(tt.over, tt.fn)\n\t\t\tif r := cmp.Diff(tt.orig, tt.want); r != \"\" {\n\t\t\t\tt.Error(r)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "infra/vformat/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"go/build\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nvar directory = flag.String(\"pwd\", \"\", \"Working directory of Xray vformat.\")\n\n// envFile returns the name of the Go environment configuration file.\n// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166\nfunc envFile() (string, error) {\n\tif file := os.Getenv(\"GOENV\"); file != \"\" {\n\t\tif file == \"off\" {\n\t\t\treturn \"\", errors.New(\"GOENV=off\")\n\t\t}\n\t\treturn file, nil\n\t}\n\tdir, err := os.UserConfigDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif dir == \"\" {\n\t\treturn \"\", errors.New(\"missing user-config dir\")\n\t}\n\treturn filepath.Join(dir, \"go\", \"env\"), nil\n}\n\n// GetRuntimeEnv returns the value of runtime environment variable,\n// that is set by running following command: `go env -w key=value`.\nfunc GetRuntimeEnv(key string) (string, error) {\n\tfile, err := envFile()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif file == \"\" {\n\t\treturn \"\", errors.New(\"missing runtime env file\")\n\t}\n\tvar data []byte\n\tvar runtimeEnv string\n\tdata, readErr := os.ReadFile(file)\n\tif readErr != nil {\n\t\treturn \"\", readErr\n\t}\n\tenvStrings := strings.Split(string(data), \"\\n\")\n\tfor _, envItem := range envStrings {\n\t\tenvItem = strings.TrimSuffix(envItem, \"\\r\")\n\t\tenvKeyValue := strings.Split(envItem, \"=\")\n\t\tif len(envKeyValue) == 2 && strings.TrimSpace(envKeyValue[0]) == key {\n\t\t\truntimeEnv = strings.TrimSpace(envKeyValue[1])\n\t\t}\n\t}\n\treturn runtimeEnv, nil\n}\n\n// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.\nfunc GetGOBIN() string {\n\t// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`\n\tGOBIN := os.Getenv(\"GOBIN\")\n\tif GOBIN == \"\" {\n\t\tvar err error\n\t\t// The one set by user by running `go env -w GOBIN=/path`\n\t\tGOBIN, err = GetRuntimeEnv(\"GOBIN\")\n\t\tif err != nil {\n\t\t\t// The default one that Golang uses\n\t\t\treturn filepath.Join(build.Default.GOPATH, \"bin\")\n\t\t}\n\t\tif GOBIN == \"\" {\n\t\t\treturn filepath.Join(build.Default.GOPATH, \"bin\")\n\t\t}\n\t\treturn GOBIN\n\t}\n\treturn GOBIN\n}\n\nfunc Run(binary string, args []string) ([]byte, error) {\n\tcmd := exec.Command(binary, args...)\n\tcmd.Env = append(cmd.Env, os.Environ()...)\n\toutput, cmdErr := cmd.CombinedOutput()\n\tif cmdErr != nil {\n\t\treturn nil, cmdErr\n\t}\n\treturn output, nil\n}\n\nfunc RunMany(binary string, args, files []string) {\n\tfmt.Println(\"Processing...\")\n\n\tmaxTasks := make(chan struct{}, runtime.NumCPU())\n\tfor _, file := range files {\n\t\tmaxTasks <- struct{}{}\n\t\tgo func(file string) {\n\t\t\toutput, err := Run(binary, append(args, file))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t} else if len(output) > 0 {\n\t\t\t\tfmt.Println(string(output))\n\t\t\t}\n\t\t\t<-maxTasks\n\t\t}(file)\n\t}\n}\n\nfunc main() {\n\tflag.Usage = func() {\n\t\tfmt.Fprintf(flag.CommandLine.Output(), \"Usage of vformat:\\n\")\n\t\tflag.PrintDefaults()\n\t}\n\tflag.Parse()\n\n\tif !filepath.IsAbs(*directory) {\n\t\tpwd, wdErr := os.Getwd()\n\t\tif wdErr != nil {\n\t\t\tfmt.Println(\"Can not get current working directory.\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\t*directory = filepath.Join(pwd, *directory)\n\t}\n\n\tpwd := *directory\n\tGOBIN := GetGOBIN()\n\tbinPath := os.Getenv(\"PATH\")\n\tpathSlice := []string{pwd, GOBIN, binPath}\n\tbinPath = strings.Join(pathSlice, string(os.PathListSeparator))\n\tos.Setenv(\"PATH\", binPath)\n\n\tsuffix := \"\"\n\tif runtime.GOOS == \"windows\" {\n\t\tsuffix = \".exe\"\n\t}\n\tgofmt := \"gofumpt\" + suffix\n\tgoimports := \"gci\" + suffix\n\n\tif gofmtPath, err := exec.LookPath(gofmt); err != nil {\n\t\tfmt.Println(\"Can not find\", gofmt, \"in system path or current working directory.\")\n\t\tos.Exit(1)\n\t} else {\n\t\tgofmt = gofmtPath\n\t}\n\n\tif goimportsPath, err := exec.LookPath(goimports); err != nil {\n\t\tfmt.Println(\"Can not find\", goimports, \"in system path or current working directory.\")\n\t\tos.Exit(1)\n\t} else {\n\t\tgoimports = goimportsPath\n\t}\n\n\trawFilesSlice := make([]string, 0, 1000)\n\twalkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tdir := filepath.Dir(path)\n\t\tfilename := filepath.Base(path)\n\t\tif strings.HasSuffix(filename, \".go\") &&\n\t\t\t!strings.HasSuffix(filename, \".pb.go\") &&\n\t\t\t!strings.Contains(dir, filepath.Join(\"testing\", \"mocks\")) &&\n\t\t\t!strings.Contains(path, filepath.Join(\"main\", \"distro\", \"all\", \"all.go\")) {\n\t\t\trawFilesSlice = append(rawFilesSlice, path)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif walkErr != nil {\n\t\tfmt.Println(walkErr)\n\t\tos.Exit(1)\n\t}\n\n\tgofmtArgs := []string{\n\t\t\"-s\", \"-l\", \"-e\", \"-w\",\n\t}\n\n\tgoimportsArgs := []string{\n\t\t\"write\",\n\t}\n\n\tRunMany(gofmt, gofmtArgs, rawFilesSlice)\n\tRunMany(goimports, goimportsArgs, rawFilesSlice)\n\tfmt.Println(\"Do NOT forget to commit file changes.\")\n}\n"
  },
  {
    "path": "main/commands/all/api/api.go",
    "content": "package api\n\nimport (\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\n// CmdAPI calls an API in an Xray process\nvar CmdAPI = &base.Command{\n\tUsageLine: \"{{.Exec}} api\",\n\tShort:     \"Call an API in an Xray process\",\n\tLong: `{{.Exec}} {{.LongName}} provides tools to manipulate Xray via its API.\n`,\n\tCommands: []*base.Command{\n\t\tcmdRestartLogger,\n\t\tcmdGetStats,\n\t\tcmdQueryStats,\n\t\tcmdSysStats,\n\t\tcmdBalancerInfo,\n\t\tcmdBalancerOverride,\n\t\tcmdAddInbounds,\n\t\tcmdAddOutbounds,\n\t\tcmdRemoveInbounds,\n\t\tcmdRemoveOutbounds,\n\t\tcmdListInbounds,\n\t\tcmdListOutbounds,\n\t\tcmdAddInboundUsers,\n\t\tcmdRemoveInboundUsers,\n\t\tcmdInboundUser,\n\t\tcmdInboundUserCount,\n\t\tcmdAddRules,\n\t\tcmdRemoveRules,\n\t\tcmdListRules,\n\t\tcmdSourceIpBlock,\n\t\tcmdOnlineStats,\n\t\tcmdOnlineStatsIpList,\n\t\tcmdGetAllOnlineUsers,\n\t},\n}\n"
  },
  {
    "path": "main/commands/all/api/balancer_info.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\trouterService \"github.com/xtls/xray-core/app/router/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\n// TODO: support \"-json\" flag for json output\nvar cmdBalancerInfo = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...\",\n\tShort:       \"Retrieve balancer information\",\n\tLong: `\nRetrieve information of specified balancers, including health, strategy and selecting.\nIf no balancer tag specified, information for all balancers is returned.\n\n> Ensure that \"RoutingService\" is enabled under \"config.api.services\" in the server configuration.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n    {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 balancer1 balancer2\n`,\n\tRun: executeBalancerInfo,\n}\n\nfunc executeBalancerInfo(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"set balancer tag\")\n\t\tunnamedArgs = []string{\"\"}\n\t}\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\tclient := routerService.NewRoutingServiceClient(conn)\n\tr := &routerService.GetBalancerInfoRequest{Tag: unnamedArgs[0]}\n\tresp, err := client.GetBalancerInfo(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get health information: %s\", err)\n\t}\n\n\tif apiJSON {\n\t\tshowJSONResponse(resp)\n\t\treturn\n\t}\n\n\tshowBalancerInfo(resp.Balancer)\n\n}\n\nfunc showBalancerInfo(b *routerService.BalancerMsg) {\n\tconst tableIndent = 4\n\tsb := new(strings.Builder)\n\t// Override\n\tif b.Override != nil {\n\t\tsb.WriteString(\"  - Selecting Override:\\n\")\n\t\tfor i, s := range []string{b.Override.Target} {\n\t\t\twriteRow(sb, tableIndent, i+1, []string{s}, nil)\n\t\t}\n\t}\n\t// Selects\n\tsb.WriteString(\"  - Selects:\\n\")\n\tif b.PrincipleTarget != nil {\n\t\tfor i, o := range b.PrincipleTarget.Tag {\n\t\t\twriteRow(sb, tableIndent, i+1, []string{o}, nil)\n\t\t}\n\t}\n\tos.Stdout.WriteString(sb.String())\n}\n\nfunc getColumnFormats(titles []string) []string {\n\tw := make([]string, len(titles))\n\tfor i, t := range titles {\n\t\tw[i] = fmt.Sprintf(\"%%-%ds \", len(t))\n\t}\n\treturn w\n}\n\nfunc writeRow(sb *strings.Builder, indent, index int, values, formats []string) {\n\tif index == 0 {\n\t\t// title line\n\t\tsb.WriteString(strings.Repeat(\" \", indent+4))\n\t} else {\n\t\tsb.WriteString(fmt.Sprintf(\"%s%-4d\", strings.Repeat(\" \", indent), index))\n\t}\n\tfor i, v := range values {\n\t\tformat := \"%-14s\"\n\t\tif i < len(formats) {\n\t\t\tformat = formats[i]\n\t\t}\n\t\tsb.WriteString(fmt.Sprintf(format, v))\n\t}\n\tsb.WriteByte('\\n')\n}\n"
  },
  {
    "path": "main/commands/all/api/balancer_override.go",
    "content": "package api\n\nimport (\n\trouterService \"github.com/xtls/xray-core/app/router/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdBalancerOverride = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag <-r>\",\n\tShort:       \"Override balancer\",\n\tLong: `\nOverride the selection target of a balancer.\n\n> Ensure that the \"RoutingService\" is properly configured under \"config.api.services\" in the server configuration.\n\nOnce the balancer's selection is overridden:\n\n- The balancer's selection result will always be outboundTag\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-r, -remove\n\t\tRemove the existing override.\n\nExample:\n\n    {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer tag\n    {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer -r\n`,\n\tRun: executeBalancerOverride,\n}\n\nfunc executeBalancerOverride(cmd *base.Command, args []string) {\n\tvar (\n\t\tbalancer string\n\t\tremove   bool\n\t)\n\tcmd.Flag.StringVar(&balancer, \"b\", \"\", \"\")\n\tcmd.Flag.StringVar(&balancer, \"balancer\", \"\", \"\")\n\tcmd.Flag.BoolVar(&remove, \"r\", false, \"\")\n\tcmd.Flag.BoolVar(&remove, \"remove\", false, \"\")\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\n\tif balancer == \"\" {\n\t\tbase.Fatalf(\"balancer tag not specified\")\n\t}\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := routerService.NewRoutingServiceClient(conn)\n\ttarget := \"\"\n\tif !remove {\n\t\ttarget = cmd.Flag.Args()[0]\n\t}\n\tr := &routerService.OverrideBalancerTargetRequest{\n\t\tBalancerTag: balancer,\n\t\tTarget:      target,\n\t}\n\n\t_, err := client.OverrideBalancerTarget(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to perform balancer health checks: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/api/inbound_user.go",
    "content": "package api\n\nimport (\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdInboundUser = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api inbounduser [--server=127.0.0.1:8080] -tag=tag [-email=email]\",\n\tShort:       \"Retrieve inbound user(s)\",\n\tLong: `\nGet User info from an inbound.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-tag\n\t    Inbound tag\n\n\t-email\n\t\tThe user's email address. If not provided, all users will be retrieved.\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag=\"tag name\"\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag=\"tag name\" -email=\"xray@love.com\"\n`,\n\tRun: executeInboundUser,\n}\n\nfunc executeInboundUser(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tvar tag string\n\tvar email string\n\tcmd.Flag.StringVar(&tag, \"tag\", \"\", \"\")\n\tcmd.Flag.StringVar(&email, \"email\", \"\", \"\")\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tr := &handlerService.GetInboundUserRequest{\n\t\tTag:   tag,\n\t\tEmail: email,\n\t}\n\tresp, err := client.GetInboundUsers(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get inbound user: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/inbound_user_add.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/xtls/xray-core/common/protocol\"\n\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks_2022\"\n\t\"github.com/xtls/xray-core/proxy/trojan\"\n\tvlessin \"github.com/xtls/xray-core/proxy/vless/inbound\"\n\tvmessin \"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdAddInboundUsers = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api adu [--server=127.0.0.1:8080] <c1.json> [c2.json]...\",\n\tShort:       \"Add users to inbounds\",\n\tLong: `\nAdd users to inbounds.\nArguments:\n\t-s, -server\n\t\tThe API server address. Default 127.0.0.1:8080\n\t-t, -timeout\n\t\tTimeout seconds to call API. Default 3\nExample:\n    {{.Exec}} {{.LongName}} --server=127.0.0.1:8080  c1.json c2.json\n`,\n\tRun: executeAddInboundUsers,\n}\n\nfunc executeAddInboundUsers(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tunnamedArgs := cmd.Flag.Args()\n\tinbs := extractInboundsConfig(unnamedArgs)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\n\tsuccess := 0\n\tfor _, inb := range inbs {\n\t\tsuccess += executeInboundUserAction(ctx, client, inb, addInboundUserAction)\n\t}\n\tfmt.Println(\"Added\", success, \"user(s) in total.\")\n}\n\nfunc addInboundUserAction(ctx context.Context, client handlerService.HandlerServiceClient, tag string, user *protocol.User) error {\n\tfmt.Println(\"add user:\", user.Email)\n\t_, err := client.AlterInbound(ctx, &handlerService.AlterInboundRequest{\n\t\tTag: tag,\n\t\tOperation: cserial.ToTypedMessage(\n\t\t\t&handlerService.AddUserOperation{\n\t\t\t\tUser: user,\n\t\t\t}),\n\t})\n\treturn err\n}\n\nfunc extractInboundUsers(inb *core.InboundHandlerConfig) []*protocol.User {\n\tif inb == nil {\n\t\treturn nil\n\t}\n\tinst, err := inb.ProxySettings.GetInstance()\n\tif err != nil || inst == nil {\n\t\tfmt.Println(\"failed to get inbound instance:\", err)\n\t\treturn nil\n\t}\n\tswitch ty := inst.(type) {\n\tcase *vmessin.Config:\n\t\treturn ty.User\n\tcase *vlessin.Config:\n\t\treturn ty.Clients\n\tcase *trojan.ServerConfig:\n\t\treturn ty.Users\n\tcase *shadowsocks.ServerConfig:\n\t\treturn ty.Users\n\tcase *shadowsocks_2022.MultiUserServerConfig:\n\t\treturn ty.Users\n\tdefault:\n\t\tfmt.Println(\"unsupported inbound type\")\n\t}\n\treturn nil\n}\n\nfunc extractInboundsConfig(unnamedArgs []string) []conf.InboundDetourConfig {\n\tins := make([]conf.InboundDetourConfig, 0)\n\tfor _, arg := range unnamedArgs {\n\t\tr, err := loadArg(arg)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to load %s: %s\", arg, err)\n\t\t}\n\t\tconf, err := serial.DecodeJSONConfig(r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to decode %s: %s\", arg, err)\n\t\t}\n\t\tins = append(ins, conf.InboundConfigs...)\n\t}\n\treturn ins\n}\n\nfunc executeInboundUserAction(ctx context.Context, client handlerService.HandlerServiceClient, inb conf.InboundDetourConfig, action func(ctx context.Context, client handlerService.HandlerServiceClient, tag string, user *protocol.User) error) int {\n\tsuccess := 0\n\n\ttag := inb.Tag\n\tif len(tag) < 1 {\n\t\treturn success\n\t}\n\n\tfmt.Println(\"processing inbound:\", tag)\n\tbuilt, err := inb.Build()\n\tif err != nil {\n\t\tfmt.Println(\"failed to build config:\", err)\n\t\treturn success\n\t}\n\n\tusers := extractInboundUsers(built)\n\tif users == nil {\n\t\treturn success\n\t}\n\n\tfor _, user := range users {\n\t\tif len(user.Email) < 1 {\n\t\t\tcontinue\n\t\t}\n\t\tif err := action(ctx, client, inb.Tag, user); err == nil {\n\t\t\tfmt.Println(\"result: ok\")\n\t\t\tsuccess += 1\n\t\t} else {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}\n\treturn success\n}\n"
  },
  {
    "path": "main/commands/all/api/inbound_user_count.go",
    "content": "package api\n\nimport (\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdInboundUserCount = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api inboundusercount [--server=127.0.0.1:8080] -tag=tag\",\n\tShort:       \"Retrieve inbound user count\",\n\tLong: `\nRetrieve the user count for a specified inbound tag.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-tag\n\t\tInbound tag\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag=\"tag name\"\n`,\n\tRun: executeInboundUserCount,\n}\n\nfunc executeInboundUserCount(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tvar tag string\n\tcmd.Flag.StringVar(&tag, \"tag\", \"\", \"\")\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tr := &handlerService.GetInboundUserRequest{\n\t\tTag: tag,\n\t}\n\tresp, err := client.GetInboundUsersCount(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get inbound user count: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/inbound_user_remove.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdRemoveInboundUsers = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api rmu [--server=127.0.0.1:8080] -tag=tag <email1> [email2]...\",\n\tShort:       \"Remove users from inbounds\",\n\tLong: `\nRemove users from inbounds.\nArguments:\n\t-s, -server\n\t\tThe API server address. Default 127.0.0.1:8080\n\t-t, -timeout\n\t\tTimeout seconds to call API. Default 3\n\t-tag\n\t\tInbound tag\nExample:\n    {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag=\"vless-in\" \"xray@love.com\" ...\n`,\n\tRun: executeRemoveUsers,\n}\n\nfunc executeRemoveUsers(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tvar tag string\n\tcmd.Flag.StringVar(&tag, \"tag\", \"\", \"\")\n\tcmd.Flag.Parse(args)\n\temails := cmd.Flag.Args()\n\tif len(tag) < 1 {\n\t\tbase.Fatalf(\"inbound tag not specified\")\n\t}\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\n\tsuccess := 0\n\tfor _, email := range emails {\n\t\tfmt.Println(\"remove user:\", email)\n\t\t_, err := client.AlterInbound(ctx, &handlerService.AlterInboundRequest{\n\t\t\tTag: tag,\n\t\t\tOperation: cserial.ToTypedMessage(\n\t\t\t\t&handlerService.RemoveUserOperation{\n\t\t\t\t\tEmail: email,\n\t\t\t\t}),\n\t\t})\n\t\tif err == nil {\n\t\t\tsuccess += 1\n\t\t} else {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}\n\tfmt.Println(\"Removed\", success, \"user(s) in total.\")\n}\n"
  },
  {
    "path": "main/commands/all/api/inbounds_add.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdAddInbounds = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api adi [--server=127.0.0.1:8080] <c1.json> [c2.json]...\",\n\tShort:       \"Add inbounds\",\n\tLong: `\nAdd inbounds to Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json\n`,\n\tRun: executeAddInbounds,\n}\n\nfunc executeAddInbounds(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"reading from stdin:\")\n\t\tunnamedArgs = []string{\"stdin:\"}\n\t}\n\n\tins := make([]conf.InboundDetourConfig, 0)\n\tfor _, arg := range unnamedArgs {\n\t\tr, err := loadArg(arg)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to load %s: %s\", arg, err)\n\t\t}\n\t\tconf, err := serial.DecodeJSONConfig(r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to decode %s: %s\", arg, err)\n\t\t}\n\t\tins = append(ins, conf.InboundConfigs...)\n\t}\n\tif len(ins) == 0 {\n\t\tbase.Fatalf(\"no valid inbound found\")\n\t}\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tfor _, in := range ins {\n\t\tfmt.Println(\"adding:\", in.Tag)\n\t\ti, err := in.Build()\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to build conf: %s\", err)\n\t\t}\n\t\tr := &handlerService.AddInboundRequest{\n\t\t\tInbound: i,\n\t\t}\n\t\tresp, err := client.AddInbound(ctx, r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to add inbound: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/api/inbounds_list.go",
    "content": "package api\n\nimport (\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdListInbounds = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api lsi [--server=127.0.0.1:8080] [--isOnlyTags=true]\",\n\tShort:       \"List inbounds\",\n\tLong: `\nList inbounds in Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080\n`,\n\tRun: executeListInbounds,\n}\n\nfunc executeListInbounds(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tvar isOnlyTagsStr string\n\tcmd.Flag.StringVar(&isOnlyTagsStr, \"isOnlyTags\", \"\", \"\")\n\tcmd.Flag.Parse(args)\n\tisOnlyTags := isOnlyTagsStr == \"true\"\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\n\tresp, err := client.ListInbounds(ctx, &handlerService.ListInboundsRequest{IsOnlyTags: isOnlyTags})\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to list inbounds: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/inbounds_remove.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdRemoveInbounds = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api rmi [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...\",\n\tShort:       \"Remove inbounds\",\n\tLong: `\nRemove inbounds from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json \"tag name\"\n`,\n\tRun: executeRemoveInbounds,\n}\n\nfunc executeRemoveInbounds(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"reading from stdin:\")\n\t\tunnamedArgs = []string{\"stdin:\"}\n\t}\n\n\ttags := make([]string, 0)\n\tfor _, arg := range unnamedArgs {\n\t\tif r, err := loadArg(arg); err == nil {\n\t\t\tconf, err := serial.DecodeJSONConfig(r)\n\t\t\tif err != nil {\n\t\t\t\tbase.Fatalf(\"failed to decode %s: %s\", arg, err)\n\t\t\t}\n\t\t\tins := conf.InboundConfigs\n\t\t\tfor _, i := range ins {\n\t\t\t\ttags = append(tags, i.Tag)\n\t\t\t}\n\t\t} else {\n\t\t\t// take request as tag\n\t\t\ttags = append(tags, arg)\n\t\t}\n\t}\n\n\tif len(tags) == 0 {\n\t\tbase.Fatalf(\"no inbound to remove\")\n\t}\n\tfmt.Println(\"removing inbounds:\", tags)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tfor _, tag := range tags {\n\t\tfmt.Println(\"removing:\", tag)\n\t\tr := &handlerService.RemoveInboundRequest{\n\t\t\tTag: tag,\n\t\t}\n\t\tresp, err := client.RemoveInbound(ctx, r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to remove inbound: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/api/logger_restart.go",
    "content": "package api\n\nimport (\n\tlogService \"github.com/xtls/xray-core/app/log/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdRestartLogger = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api restartlogger [--server=127.0.0.1:8080]\",\n\tShort:       \"Restart the logger\",\n\tLong: `\nRestart the logger of Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080\n`,\n\tRun: executeRestartLogger,\n}\n\nfunc executeRestartLogger(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := logService.NewLoggerServiceClient(conn)\n\tr := &logService.RestartLoggerRequest{}\n\tresp, err := client.RestartLogger(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to restart logger: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/outbounds_add.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdAddOutbounds = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api ado [--server=127.0.0.1:8080] <c1.json> [c2.json]...\",\n\tShort:       \"Add outbounds\",\n\tLong: `\nAdd outbounds to Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json\n`,\n\tRun: executeAddOutbounds,\n}\n\nfunc executeAddOutbounds(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"Reading from STDIN\")\n\t\tunnamedArgs = []string{\"stdin:\"}\n\t}\n\n\touts := make([]conf.OutboundDetourConfig, 0)\n\tfor _, arg := range unnamedArgs {\n\t\tr, err := loadArg(arg)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to load %s: %s\", arg, err)\n\t\t}\n\t\tconf, err := serial.DecodeJSONConfig(r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to decode %s: %s\", arg, err)\n\t\t}\n\t\touts = append(outs, conf.OutboundConfigs...)\n\t}\n\tif len(outs) == 0 {\n\t\tbase.Fatalf(\"no valid outbound found\")\n\t}\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tfor _, out := range outs {\n\t\tfmt.Println(\"adding:\", out.Tag)\n\t\to, err := out.Build()\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to build conf: %s\", err)\n\t\t}\n\t\tr := &handlerService.AddOutboundRequest{\n\t\t\tOutbound: o,\n\t\t}\n\t\tresp, err := client.AddOutbound(ctx, r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to add outbound: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/api/outbounds_list.go",
    "content": "package api\n\nimport (\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdListOutbounds = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api lso [--server=127.0.0.1:8080]\",\n\tShort:       \"List outbounds\",\n\tLong: `\nList outbounds in Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080\n`,\n\tRun: executeListOutbounds,\n}\n\nfunc executeListOutbounds(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tresp, err := client.ListOutbounds(ctx, &handlerService.ListOutboundsRequest{})\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to list outbounds: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/outbounds_remove.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\thandlerService \"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdRemoveOutbounds = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api rmo [--server=127.0.0.1:8080] <json_file|tag> [json_file] [tag]...\",\n\tShort:       \"Remove outbounds\",\n\tLong: `\nRemove outbounds from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json \"tag name\"\n`,\n\tRun: executeRemoveOutbounds,\n}\n\nfunc executeRemoveOutbounds(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"reading from stdin:\")\n\t\tunnamedArgs = []string{\"stdin:\"}\n\t}\n\n\ttags := make([]string, 0)\n\tfor _, arg := range unnamedArgs {\n\t\tif r, err := loadArg(arg); err == nil {\n\t\t\tconf, err := serial.DecodeJSONConfig(r)\n\t\t\tif err != nil {\n\t\t\t\tbase.Fatalf(\"failed to decode %s: %s\", arg, err)\n\t\t\t}\n\t\t\touts := conf.OutboundConfigs\n\t\t\tfor _, o := range outs {\n\t\t\t\ttags = append(tags, o.Tag)\n\t\t\t}\n\t\t} else {\n\t\t\t// take request as tag\n\t\t\ttags = append(tags, arg)\n\t\t}\n\t}\n\n\tif len(tags) == 0 {\n\t\tbase.Fatalf(\"no outbound to remove\")\n\t}\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := handlerService.NewHandlerServiceClient(conn)\n\tfor _, tag := range tags {\n\t\tfmt.Println(\"removing:\", tag)\n\t\tr := &handlerService.RemoveOutboundRequest{\n\t\t\tTag: tag,\n\t\t}\n\t\tresp, err := client.RemoveOutbound(ctx, r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to remove outbound: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/api/rules_add.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\trouterService \"github.com/xtls/xray-core/app/router/command\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdAddRules = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api adrules [--server=127.0.0.1:8080] <c1.json> [c2.json]...\",\n\tShort:       \"Add routing rules\",\n\tLong: `\nAdd routing rules to Xray.\n\nArguments:\n\t<c1.json> [c2.json]...\n\t\tThe configs with the rules to be added. Must be in the xray config format and must have the \"routing\" field\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout seconds to call API. Default 3\n\n\t-append\n\t\tAppend to the existing configuration instead of replacing it. Default false\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json\n`,\n\tRun: executeAddRules,\n}\n\nfunc executeAddRules(cmd *base.Command, args []string) {\n\tvar (\n\t\tshouldAppend bool\n\t)\n\tsetSharedFlags(cmd)\n\tcmd.Flag.BoolVar(&shouldAppend, \"append\", false, \"\")\n\tcmd.Flag.Parse(args)\n\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"reading from stdin:\")\n\t\tunnamedArgs = []string{\"stdin:\"}\n\t}\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := routerService.NewRoutingServiceClient(conn)\n\n\trcs := make([]conf.RouterConfig, 0)\n\tfor _, arg := range unnamedArgs {\n\t\tr, err := loadArg(arg)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to load %s: %s\", arg, err)\n\t\t}\n\t\tconf, err := serial.DecodeJSONConfig(r)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to decode %s: %s\", arg, err)\n\t\t}\n\n\t\tif conf.RouterConfig == nil {\n\t\t\tbase.Fatalf(\"failed to add routing rule: config did not have \\\"routing\\\" field\")\n\t\t}\n\n\t\trcs = append(rcs, *conf.RouterConfig)\n\t}\n\tif len(rcs) == 0 {\n\t\tbase.Fatalf(\"no valid rule found in config\")\n\t}\n\tfor _, in := range rcs {\n\n\t\tconfig, err := in.Build()\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to build conf: %s\", err)\n\t\t}\n\t\ttmsg := cserial.ToTypedMessage(config)\n\t\tif tmsg == nil {\n\t\t\tbase.Fatalf(\"failed to format config to TypedMessage.\")\n\t\t}\n\n\t\tra := &routerService.AddRuleRequest{\n\t\t\tConfig:       tmsg,\n\t\t\tShouldAppend: shouldAppend,\n\t\t}\n\t\tresp, err := client.AddRule(ctx, ra)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to perform AddRule: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\t}\n\n}\n"
  },
  {
    "path": "main/commands/all/api/rules_list.go",
    "content": "package api\n\nimport (\n\trouterService \"github.com/xtls/xray-core/app/router/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdListRules = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api lsrules [--server=127.0.0.1:8080]\",\n\tShort:       \"List routing rules\",\n\tLong: `\nList routing rules in Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080\n`,\n\tRun: executeListRules,\n}\n\nfunc executeListRules(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := routerService.NewRoutingServiceClient(conn)\n\tresp, err := client.ListRule(ctx, &routerService.ListRuleRequest{})\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to list rules: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/rules_remove.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\trouterService \"github.com/xtls/xray-core/app/router/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdRemoveRules = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api rmrules [--server=127.0.0.1:8080] [ruleTag]...\",\n\tShort:       \"Remove routing rules by ruleTag\",\n\tLong: `\nRemove routing rules by ruleTag from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 ruleTag1 ruleTag2\n`,\n\tRun: executeRemoveRules,\n}\n\nfunc executeRemoveRules(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\truleTags := cmd.Flag.Args()\n\tif len(ruleTags) == 0 {\n\t\tfmt.Println(\"reading from stdin:\")\n\t\truleTags = []string{\"stdin:\"}\n\t}\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := routerService.NewRoutingServiceClient(conn)\n\n\tif len(ruleTags) == 0 {\n\t\tbase.Fatalf(\"no valid ruleTag input\")\n\t}\n\tfor _, tag := range ruleTags {\n\n\t\trr := &routerService.RemoveRuleRequest{\n\t\t\tRuleTag: tag,\n\t\t}\n\t\tresp, err := client.RemoveRule(ctx, rr)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to perform RemoveRule: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\t}\n\n}\n"
  },
  {
    "path": "main/commands/all/api/shared.go",
    "content": "package api\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\tcreflect \"github.com/xtls/xray-core/common/reflect\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.Command, args []string) string\n\nvar (\n\tapiServerAddrPtr string\n\tapiTimeout       int\n\tapiJSON          bool\n)\n\nfunc setSharedFlags(cmd *base.Command) {\n\tcmd.Flag.StringVar(&apiServerAddrPtr, \"s\", \"127.0.0.1:8080\", \"\")\n\tcmd.Flag.StringVar(&apiServerAddrPtr, \"server\", \"127.0.0.1:8080\", \"\")\n\tcmd.Flag.IntVar(&apiTimeout, \"t\", 3, \"\")\n\tcmd.Flag.IntVar(&apiTimeout, \"timeout\", 3, \"\")\n\tcmd.Flag.BoolVar(&apiJSON, \"json\", false, \"\")\n}\n\nfunc dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Duration(apiTimeout)*time.Second)\n\tconn, err := grpc.DialContext(ctx, apiServerAddrPtr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to dial %s\", apiServerAddrPtr)\n\t}\n\tclose = func() {\n\t\tcancel()\n\t\tconn.Close()\n\t}\n\treturn\n}\n\n// loadArg loads one arg, maybe an remote url, or local file path\nfunc loadArg(arg string) (out io.Reader, err error) {\n\tvar data []byte\n\tswitch {\n\tcase strings.HasPrefix(arg, \"http://\"), strings.HasPrefix(arg, \"https://\"):\n\t\tdata, err = fetchHTTPContent(arg)\n\n\tcase arg == \"stdin:\":\n\t\tdata, err = io.ReadAll(os.Stdin)\n\n\tdefault:\n\t\tdata, err = os.ReadFile(arg)\n\t}\n\n\tif err != nil {\n\t\treturn\n\t}\n\tout = bytes.NewBuffer(data)\n\treturn\n}\n\n// fetchHTTPContent dials https for remote content\nfunc fetchHTTPContent(target string) ([]byte, error) {\n\tparsedTarget, err := url.Parse(target)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s := strings.ToLower(parsedTarget.Scheme); s != \"http\" && s != \"https\" {\n\t\treturn nil, fmt.Errorf(\"invalid scheme: %s\", parsedTarget.Scheme)\n\t}\n\n\tclient := &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t}\n\tresp, err := client.Do(&http.Request{\n\t\tMethod: \"GET\",\n\t\tURL:    parsedTarget,\n\t\tClose:  true,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to dial to %s\", target)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"unexpected HTTP status code: %d\", resp.StatusCode)\n\t}\n\n\tcontent, err := buf.ReadAllToBytes(resp.Body)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read HTTP response\")\n\t}\n\n\treturn content, nil\n}\n\nfunc showJSONResponse(m proto.Message) {\n\tif isNil(m) {\n\t\treturn\n\t}\n\tif j, ok := creflect.MarshalToJson(m, true); ok {\n\t\tfmt.Println(j)\n\t} else {\n\t\tfmt.Fprintf(os.Stdout, \"%v\\n\", m)\n\t\tbase.Fatalf(\"error encode json\")\n\t}\n}\n\nfunc isNil(i interface{}) bool {\n\tvi := reflect.ValueOf(i)\n\tif vi.Kind() == reflect.Ptr {\n\t\treturn vi.IsNil()\n\t}\n\treturn i == nil\n}\n"
  },
  {
    "path": "main/commands/all/api/source_ip_block.go",
    "content": "package api\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\trouterService \"github.com/xtls/xray-core/app/router/command\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdSourceIpBlock = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api sib [--server=127.0.0.1:8080] -outbound=blocked -inbound=socks 1.2.3.4\",\n\tShort:       \"Block connections by source IP\",\n\tLong: `\nBlock connections by source IP address.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-outbound\n\t\tSpecifies the outbound tag.\n\n\t-inbound\n\t\tSpecifies the inbound tag.\n\n\t-ruletag\n\t\tThe ruleTag. Default sourceIpBlock\n\n\t-reset\n\t\tremove ruletag and apply new source IPs. Default false\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -outbound=blocked -inbound=socks 1.2.3.4\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -outbound=blocked -inbound=socks 1.2.3.4 -reset\n`,\n\tRun: executeSourceIpBlock,\n}\n\nfunc executeSourceIpBlock(cmd *base.Command, args []string) {\n\tvar (\n\t\tinbound  string\n\t\toutbound string\n\t\truletag  string\n\t\treset    bool\n\t)\n\tsetSharedFlags(cmd)\n\tcmd.Flag.StringVar(&inbound, \"inbound\", \"\", \"\")\n\tcmd.Flag.StringVar(&outbound, \"outbound\", \"\", \"\")\n\tcmd.Flag.StringVar(&ruletag, \"ruletag\", \"sourceIpBlock\", \"\")\n\tcmd.Flag.BoolVar(&reset, \"reset\", false, \"\")\n\n\tcmd.Flag.Parse(args)\n\n\tunnamedArgs := cmd.Flag.Args()\n\tif len(unnamedArgs) == 0 {\n\t\tfmt.Println(\"reading from stdin:\")\n\t\tunnamedArgs = []string{\"stdin:\"}\n\t}\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := routerService.NewRoutingServiceClient(conn)\n\n\tjsonIps, err := json.Marshal(unnamedArgs)\n\tif err != nil {\n\t\tfmt.Println(\"Error marshaling JSON:\", err)\n\t\treturn\n\t}\n\n\tjsonInbound, err := json.Marshal([]string{inbound})\n\tif inbound == \"\" {\n\t\tjsonInbound, err = json.Marshal([]string{})\n\t}\n\tif err != nil {\n\t\tfmt.Println(\"Error marshaling JSON:\", err)\n\t\treturn\n\t}\n\tstringConfig := fmt.Sprintf(`\n\t{\n\t\t\"routing\": {\n\t\t\t\"rules\": [\n\t\t\t  {\n\t\t\t\t\"ruleTag\" : \"%s\",\n\t\t\t\t\"inboundTag\": %s,\t\t\n\t\t\t\t\"outboundTag\": \"%s\",\n\t\t\t\t\"source\": %s\n\t\t\t  }\n\t\t\t]\n\t\t  }\n\t  }\n\t  \n\t`, ruletag, string(jsonInbound), outbound, string(jsonIps))\n\n\tconf, err := serial.DecodeJSONConfig(strings.NewReader(stringConfig))\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to decode : %s\", err)\n\t}\n\trc := *conf.RouterConfig\n\n\tconfig, err := rc.Build()\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to build conf: %s\", err)\n\t}\n\ttmsg := cserial.ToTypedMessage(config)\n\tif tmsg == nil {\n\t\tbase.Fatalf(\"failed to format config to TypedMessage.\")\n\t}\n\n\tif reset {\n\t\trr := &routerService.RemoveRuleRequest{\n\t\t\tRuleTag: ruletag,\n\t\t}\n\t\tresp, err := client.RemoveRule(ctx, rr)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to perform RemoveRule: %s\", err)\n\t\t}\n\t\tshowJSONResponse(resp)\n\n\t}\n\tra := &routerService.AddRuleRequest{\n\t\tConfig:       tmsg,\n\t\tShouldAppend: true,\n\t}\n\tresp, err := client.AddRule(ctx, ra)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to perform AddRule: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n\n}\n"
  },
  {
    "path": "main/commands/all/api/stats_get.go",
    "content": "package api\n\nimport (\n\tstatsService \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdGetStats = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api stats [--server=127.0.0.1:8080] [-name '']\",\n\tShort:       \"Retrieve statistics\",\n\tLong: `\nRetrieve the statistics from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-name\n\t\tName of the counter.\n\n\t-reset\n\t\tReset the counter after fetching their values. Default false\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -name \"inbound>>>statin>>>traffic>>>downlink\"\n`,\n\tRun: executeGetStats,\n}\n\nfunc executeGetStats(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tstatName := cmd.Flag.String(\"name\", \"\", \"\")\n\treset := cmd.Flag.Bool(\"reset\", false, \"\")\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := statsService.NewStatsServiceClient(conn)\n\tr := &statsService.GetStatsRequest{\n\t\tName:   *statName,\n\t\tReset_: *reset,\n\t}\n\tresp, err := client.GetStats(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get stats: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/stats_get_all_online_users.go",
    "content": "package api\n\nimport (\n\tstatsService \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdGetAllOnlineUsers = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api statsgetallonlineusers [--server=127.0.0.1:8080]\",\n\tShort:       \"Retrieve array of all online users\",\n\tLong: `\nRetrieve array of all online users.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080\"\n`,\n\tRun: executeGetAllOnlineUsers,\n}\n\nfunc executeGetAllOnlineUsers(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := statsService.NewStatsServiceClient(conn)\n\tr := &statsService.GetAllOnlineUsersRequest{}\n\tresp, err := client.GetAllOnlineUsers(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get stats: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/stats_online.go",
    "content": "package api\n\nimport (\n\tstatsService \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdOnlineStats = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api statsonline [--server=127.0.0.1:8080] [-email '']\",\n\tShort:       \"Retrieve the online session count for a user\",\n\tLong: `\nRetrieve the current number of active sessions for a user from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-email\n\t\tThe user's email address.\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email \"xray@love.com\"\n`,\n\tRun: executeOnlineStats,\n}\n\nfunc executeOnlineStats(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\temail := cmd.Flag.String(\"email\", \"\", \"\")\n\tcmd.Flag.Parse(args)\n\tstatName := \"user>>>\" + *email + \">>>online\"\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := statsService.NewStatsServiceClient(conn)\n\tr := &statsService.GetStatsRequest{\n\t\tName:   statName,\n\t\tReset_: false,\n\t}\n\tresp, err := client.GetStatsOnline(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get stats: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/stats_online_ip_list.go",
    "content": "package api\n\nimport (\n\tstatsService \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdOnlineStatsIpList = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api statsonlineiplist [--server=127.0.0.1:8080] [-email '']\",\n\tShort:       \"Retrieve a user's online IP addresses and access times\",\n\tLong: `\nRetrieve the online IP addresses and corresponding access timestamps for a user from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-email\n\t\tThe user's email address.\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email \"xray@love.com\"\n`,\n\tRun: executeOnlineStatsIpList,\n}\n\nfunc executeOnlineStatsIpList(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\temail := cmd.Flag.String(\"email\", \"\", \"\")\n\tcmd.Flag.Parse(args)\n\tstatName := \"user>>>\" + *email + \">>>online\"\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := statsService.NewStatsServiceClient(conn)\n\tr := &statsService.GetStatsRequest{\n\t\tName:   statName,\n\t\tReset_: false,\n\t}\n\tresp, err := client.GetStatsOnlineIpList(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get stats: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/stats_query.go",
    "content": "package api\n\nimport (\n\tstatsService \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdQueryStats = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api statsquery [--server=127.0.0.1:8080] [-pattern '']\",\n\tShort:       \"Query statistics\",\n\tLong: `\nQuery statistics from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\n\t-pattern\n\t\tFilter pattern for the statistics query.\n\n\t-reset\n\t\tReset the counter after fetching their values. Default false\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -pattern \"counter_\"\n`,\n\tRun: executeQueryStats,\n}\n\nfunc executeQueryStats(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tpattern := cmd.Flag.String(\"pattern\", \"\", \"\")\n\treset := cmd.Flag.Bool(\"reset\", false, \"\")\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := statsService.NewStatsServiceClient(conn)\n\tr := &statsService.QueryStatsRequest{\n\t\tPattern: *pattern,\n\t\tReset_:  *reset,\n\t}\n\tresp, err := client.QueryStats(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to query stats: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/api/stats_sys.go",
    "content": "package api\n\nimport (\n\tstatsService \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdSysStats = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} api statssys [--server=127.0.0.1:8080]\",\n\tShort:       \"Retrieve system statistics\",\n\tLong: `\nRetrieve system statistics from Xray.\n\nArguments:\n\n\t-s, -server <server:port>\n\t\tThe API server address. Default 127.0.0.1:8080\n\n\t-t, -timeout <seconds>\n\t\tTimeout in seconds for calling API. Default 3\n\nExample:\n\n\t{{.Exec}} {{.LongName}} --server=127.0.0.1:8080\n`,\n\tRun: executeSysStats,\n}\n\nfunc executeSysStats(cmd *base.Command, args []string) {\n\tsetSharedFlags(cmd)\n\tcmd.Flag.Parse(args)\n\n\tconn, ctx, close := dialAPIServer()\n\tdefer close()\n\n\tclient := statsService.NewStatsServiceClient(conn)\n\tr := &statsService.SysStatsRequest{}\n\tresp, err := client.GetSysStats(ctx, r)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to get sys stats: %s\", err)\n\t}\n\tshowJSONResponse(resp)\n}\n"
  },
  {
    "path": "main/commands/all/buildmphcache.go",
    "content": "package all\n\nimport (\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdBuildMphCache = &base.Command{\n\tUsageLine: `{{.Exec}} buildMphCache [-c config.json] [-o domain.cache]`,\n\tShort:     `Build domain matcher cache`,\n\tLong: `\nBuild domain matcher cache from a configuration file.\n\nExample: {{.Exec}} buildMphCache -c config.json -o domain.cache\n`,\n}\n\nfunc init() {\n\tcmdBuildMphCache.Run = executeBuildMphCache\n}\n\nvar (\n\tconfigPath = cmdBuildMphCache.Flag.String(\"c\", \"config.json\", \"Config file path\")\n\toutputPath = cmdBuildMphCache.Flag.String(\"o\", \"domain.cache\", \"Output cache file path\")\n)\n\nfunc executeBuildMphCache(cmd *base.Command, args []string) {\n\tcf, err := os.Open(*configPath)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to open config file: %v\", err)\n\t}\n\tdefer cf.Close()\n\n\t// prevent using existing cache\n\tdomainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return \"\" })\n\tif domainMatcherPath != \"\" {\n\t\tos.Setenv(\"XRAY_MPH_CACHE\", \"\")\n\t\tdefer os.Setenv(\"XRAY_MPH_CACHE\", domainMatcherPath)\n\t}\n\n\tconfig, err := serial.DecodeJSONConfig(cf)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to decode config file: %v\", err)\n\t}\n\n\tif err := config.BuildMPHCache(outputPath); err != nil {\n\t\tbase.Fatalf(\"failed to build MPH cache: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/commands.go",
    "content": "package all\n\nimport (\n\t\"github.com/xtls/xray-core/main/commands/all/api\"\n\t\"github.com/xtls/xray-core/main/commands/all/convert\"\n\t\"github.com/xtls/xray-core/main/commands/all/tls\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nfunc init() {\n\tbase.RootCommand.Commands = append(\n\t\tbase.RootCommand.Commands,\n\t\tapi.CmdAPI,\n\t\tconvert.CmdConvert,\n\t\ttls.CmdTLS,\n\t\tcmdUUID,\n\t\tcmdX25519,\n\t\tcmdWG,\n\t\tcmdMLDSA65,\n\t\tcmdMLKEM768,\n\t\tcmdVLESSEnc,\n\t\tcmdBuildMphCache,\n\t)\n}\n"
  },
  {
    "path": "main/commands/all/convert/convert.go",
    "content": "package convert\n\nimport (\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\n// CmdConvert do config convertion\nvar CmdConvert = &base.Command{\n\tUsageLine: \"{{.Exec}} convert\",\n\tShort:     \"Convert configs\",\n\tLong: `{{.Exec}} {{.LongName}} provides tools to convert config.\n`,\n\tCommands: []*base.Command{\n\t\tcmdProtobuf,\n\t\tcmdJson,\n\t},\n}\n"
  },
  {
    "path": "main/commands/all/convert/json.go",
    "content": "package convert\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\tcreflect \"github.com/xtls/xray-core/common/reflect\"\n\tcserial \"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n)\n\nvar cmdJson = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} convert json [-type] [stdin:] [typedMessage file] \",\n\tShort:       \"Convert typedMessage to json\",\n\tLong: `\nConvert ONE typedMessage to json.\n\nWhere typedMessage file need to be in the following format:\n\n{\n  \"type\": \"xray.proxy.shadowsocks.Account\",\n  \"value\": \"CgMxMTEQBg==\"\n}\n\nArguments:\n\n\t-t, -type\n\t\tInject type infomation.\n\nExamples:\n\n    {{.Exec}} convert json user.tmsg\n\t`,\n\tRun: executeTypedMessageToJson,\n}\n\nfunc executeTypedMessageToJson(cmd *base.Command, args []string) {\n\n\tvar injectTypeInfo bool\n\tcmd.Flag.BoolVar(&injectTypeInfo, \"t\", false, \"\")\n\tcmd.Flag.BoolVar(&injectTypeInfo, \"type\", false, \"\")\n\tcmd.Flag.Parse(args)\n\n\tif cmd.Flag.NArg() < 1 {\n\t\tbase.Fatalf(\"empty input list\")\n\t}\n\n\treader, err := confloader.LoadConfig(cmd.Flag.Arg(0))\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to load config: %s\", err)\n\t}\n\n\tb, err := io.ReadAll(reader)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to read config: %s\", err)\n\t}\n\n\ttm := cserial.TypedMessage{}\n\tif err = json.Unmarshal(b, &tm); err != nil {\n\t\tbase.Fatalf(\"failed to unmarshal config: %s\", err)\n\t}\n\n\tif j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok {\n\t\tfmt.Println(j)\n\t} else {\n\t\tbase.Fatalf(\"marshal TypedMessage to json failed\")\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/convert/protobuf.go",
    "content": "package convert\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\tcreflect \"github.com/xtls/xray-core/common/reflect\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar cmdProtobuf = &base.Command{\n\tCustomFlags: true,\n\tUsageLine:   \"{{.Exec}} convert pb [-outpbfile file] [-debug] [-type] [json file] [json file] ...\",\n\tShort:       \"Convert multiple json configs to protobuf\",\n\tLong: `\nConvert multiple configs to ProtoBuf. JSON, YAML and TOML can be used.\n\nArguments:\n\n\t-o file, -outpbfile file\n\t\tWrite the ProtoBuf output (eg. mix.pb) to specified file location.\n\n\t-d, -debug\n\t\tShow mix.pb as JSON format.\n\t\tFOR DEBUGGING ONLY!\n\t\tDO NOT PASS THIS OUTPUT TO XRAY-CORE!\n\n\t-t, -type\n\t\tInject type information into debug output.\n\nExamples:\n\n    {{.Exec}} convert pb -outpbfile output.pb config.json c1.json c2.json c3.json\n    {{.Exec}} convert pb -debug mix.pb\n\t`,\n\tRun: executeConvertConfigsToProtobuf,\n}\n\nfunc executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {\n\n\tvar optFile string\n\tvar optDump bool\n\tvar optType bool\n\n\tcmd.Flag.StringVar(&optFile, \"o\", \"\", \"\")\n\tcmd.Flag.StringVar(&optFile, \"outpbfile\", \"\", \"\")\n\tcmd.Flag.BoolVar(&optDump, \"d\", false, \"\")\n\tcmd.Flag.BoolVar(&optDump, \"debug\", false, \"\")\n\tcmd.Flag.BoolVar(&optType, \"t\", false, \"\")\n\tcmd.Flag.BoolVar(&optType, \"type\", false, \"\")\n\tcmd.Flag.Parse(args)\n\n\tunnamedArgs := cmdarg.Arg{}\n\tfor _, v := range cmd.Flag.Args() {\n\t\tunnamedArgs.Set(v)\n\t}\n\n\tif len(optFile) > 0 {\n\t\tswitch core.GetFormat(optFile){\n\t\tcase \"protobuf\", \"\":\n\t\t\tfmt.Println(\"Output ProtoBuf file is \", optFile)\n\t\tdefault:\n\t\t\tbase.Fatalf(\"-outpbfile followed by a possible original config.\")\n\t\t}\n\t} else if !optDump {\n\t\tbase.Fatalf(\"-outpbfile not specified\")\n\t}\n\n\tif len(unnamedArgs) < 1 {\n\t\tbase.Fatalf(\"invalid config list length: %d\", len(unnamedArgs))\n\t}\n\n\tpbConfig, err := core.LoadConfig(\"auto\", unnamedArgs)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to load config: %s\", err)\n\t}\n\n\tif optDump {\n\t\tif j, ok := creflect.MarshalToJson(pbConfig, optType); ok {\n\t\t\tfmt.Println(j)\n\t\t\treturn\n\t\t} else {\n\t\t\tbase.Fatalf(\"failed to marshal proto config to json.\")\n\t\t}\n\t}\n\n\tif len(optFile) > 0 {\n\t\tbytesConfig, err := proto.Marshal(pbConfig)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to marshal proto config: %s\", err)\n\t\t}\n\n\t\tf, err := os.Create(optFile)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"failed to create proto file: %s\", err)\n\t\t}\n\t\tdefer f.Close()\n\n\t\tif _, err := f.Write(bytesConfig); err != nil {\n\t\t\tbase.Fatalf(\"failed to write proto file: %s\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/curve25519.go",
    "content": "package all\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\t\"lukechampine.com/blake3\"\n)\n\nfunc Curve25519Genkey(StdEncoding bool, input_base64 string) {\n\tvar encoding *base64.Encoding\n\tif *input_stdEncoding || StdEncoding {\n\t\tencoding = base64.StdEncoding\n\t} else {\n\t\tencoding = base64.RawURLEncoding\n\t}\n\n\tvar privateKey []byte\n\tif len(input_base64) > 0 {\n\t\tprivateKey, _ = encoding.DecodeString(input_base64)\n\t\tif len(privateKey) != 32 {\n\t\t\tfmt.Println(\"Invalid length of X25519 private key.\")\n\t\t\treturn\n\t\t}\n\t}\n\tprivateKey, password, hash32, err := genCurve25519(privateKey)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tfmt.Printf(\"PrivateKey: %v\\nPassword (PublicKey): %v\\nHash32: %v\\n\",\n\t\tencoding.EncodeToString(privateKey),\n\t\tencoding.EncodeToString(password),\n\t\tencoding.EncodeToString(hash32[:]))\n}\n\nfunc genCurve25519(inputPrivateKey []byte) (privateKey []byte, password []byte, hash32 [32]byte, returnErr error) {\n\tif len(inputPrivateKey) > 0 {\n\t\tprivateKey = inputPrivateKey\n\t}\n\tif privateKey == nil {\n\t\tprivateKey = make([]byte, 32)\n\t\trand.Read(privateKey)\n\t}\n\n\t// Modify random bytes using algorithm described at:\n\t// https://cr.yp.to/ecdh.html\n\t// (Just to make sure printing the real private key)\n\tprivateKey[0] &= 248\n\tprivateKey[31] &= 127\n\tprivateKey[31] |= 64\n\n\tkey, err := ecdh.X25519().NewPrivateKey(privateKey)\n\tif err != nil {\n\t\treturnErr = err\n\t\treturn\n\t}\n\tpassword = key.PublicKey().Bytes()\n\thash32 = blake3.Sum256(password)\n\treturn\n}\n"
  },
  {
    "path": "main/commands/all/mldsa65.go",
    "content": "package all\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\t\"github.com/cloudflare/circl/sign/mldsa/mldsa65\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdMLDSA65 = &base.Command{\n\tUsageLine: `{{.Exec}} mldsa65 [-i \"seed (base64.RawURLEncoding)\"]`,\n\tShort:     `Generate key pair for ML-DSA-65 post-quantum signature (REALITY)`,\n\tLong: `\nGenerate key pair for ML-DSA-65 post-quantum signature (REALITY).\n\nRandom: {{.Exec}} mldsa65\n\nFrom seed: {{.Exec}} mldsa65 -i \"seed (base64.RawURLEncoding)\"\n`,\n}\n\nfunc init() {\n\tcmdMLDSA65.Run = executeMLDSA65 // break init loop\n}\n\nvar input_mldsa65 = cmdMLDSA65.Flag.String(\"i\", \"\", \"\")\n\nfunc executeMLDSA65(cmd *base.Command, args []string) {\n\tvar seed [32]byte\n\tif len(*input_mldsa65) > 0 {\n\t\ts, _ := base64.RawURLEncoding.DecodeString(*input_mldsa65)\n\t\tif len(s) != 32 {\n\t\t\tfmt.Println(\"Invalid length of ML-DSA-65 seed.\")\n\t\t\treturn\n\t\t}\n\t\tseed = [32]byte(s)\n\t} else {\n\t\trand.Read(seed[:])\n\t}\n\tpub, _ := mldsa65.NewKeyFromSeed(&seed)\n\tfmt.Printf(\"Seed: %v\\nVerify: %v\\n\",\n\t\tbase64.RawURLEncoding.EncodeToString(seed[:]),\n\t\tbase64.RawURLEncoding.EncodeToString(pub.Bytes()))\n}\n"
  },
  {
    "path": "main/commands/all/mlkem768.go",
    "content": "package all\n\nimport (\n\t\"crypto/mlkem\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t\"lukechampine.com/blake3\"\n)\n\nvar cmdMLKEM768 = &base.Command{\n\tUsageLine: `{{.Exec}} mlkem768 [-i \"seed (base64.RawURLEncoding)\"]`,\n\tShort:     `Generate key pair for ML-KEM-768 post-quantum key exchange (VLESS Encryption)`,\n\tLong: `\nGenerate key pair for ML-KEM-768 post-quantum key exchange (VLESS Encryption).\n\nRandom: {{.Exec}} mlkem768\n\nFrom seed: {{.Exec}} mlkem768 -i \"seed (base64.RawURLEncoding)\"\n`,\n}\n\nfunc init() {\n\tcmdMLKEM768.Run = executeMLKEM768 // break init loop\n}\n\nvar input_mlkem768 = cmdMLKEM768.Flag.String(\"i\", \"\", \"\")\n\nfunc executeMLKEM768(cmd *base.Command, args []string) {\n\tvar seed [64]byte\n\tif len(*input_mlkem768) > 0 {\n\t\ts, _ := base64.RawURLEncoding.DecodeString(*input_mlkem768)\n\t\tif len(s) != 64 {\n\t\t\tfmt.Println(\"Invalid length of ML-KEM-768 seed.\")\n\t\t\treturn\n\t\t}\n\t\tseed = [64]byte(s)\n\t} else {\n\t\trand.Read(seed[:])\n\t}\n\tseed, client, hash32 := genMLKEM768(&seed)\n\tfmt.Printf(\"Seed: %v\\nClient: %v\\nHash32: %v\\n\",\n\t\tbase64.RawURLEncoding.EncodeToString(seed[:]),\n\t\tbase64.RawURLEncoding.EncodeToString(client),\n\t\tbase64.RawURLEncoding.EncodeToString(hash32[:]))\n}\n\nfunc genMLKEM768(inputSeed *[64]byte) (seed [64]byte, client []byte, hash32 [32]byte) {\n\tif inputSeed == nil {\n\t\trand.Read(seed[:])\n\t} else {\n\t\tseed = *inputSeed\n\t}\n\tkey, _ := mlkem.NewDecapsulationKey768(seed[:])\n\tclient = key.EncapsulationKey().Bytes()\n\thash32 = blake3.Sum256(client)\n\treturn\n}\n"
  },
  {
    "path": "main/commands/all/tls/cert.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\n// cmdCert is the tls cert command\nvar cmdCert = &base.Command{\n\tUsageLine: \"{{.Exec}} tls cert [--ca] [--domain=example.com] [--expire=240h]\",\n\tShort:     \"Generate TLS certificates\",\n\tLong: `\nGenerate TLS certificates.\n\nArguments:\n\n\t-domain=domain_name \n\t\tThe domain name for the certificate.\n\n\t-name=common_name \n\t\tThe common name for the certificate.\n\n\t-org=organization \n\t\tThe organization name for the certificate.\n\n\t-ca \n\t\tWhether this certificate is a CA\n\n\t-json \n\t\tThe output of certificate to JSON\n\n\t-file \n\t\tThe certificate path to save.\n\n\t-expire \n\t\tExpire time of the certificate. Default value 3 months.\n`,\n}\n\nfunc init() {\n\tcmdCert.Run = executeCert // break init loop\n}\n\nvar (\n\tcertDomainNames stringList\n\t_               = func() bool {\n\t\tcmdCert.Flag.Var(&certDomainNames, \"domain\", \"Domain name for the certificate\")\n\t\treturn true\n\t}()\n\n\tcertCommonName   = cmdCert.Flag.String(\"name\", \"Xray Inc\", \"The common name of this certificate\")\n\tcertOrganization = cmdCert.Flag.String(\"org\", \"Xray Inc\", \"Organization of the certificate\")\n\tcertIsCA         = cmdCert.Flag.Bool(\"ca\", false, \"Whether this certificate is a CA\")\n\tcertJSONOutput   = cmdCert.Flag.Bool(\"json\", true, \"Print certificate in JSON format\")\n\tcertFileOutput   = cmdCert.Flag.String(\"file\", \"\", \"Save certificate in file.\")\n\tcertExpire       = cmdCert.Flag.Duration(\"expire\", time.Hour*24*90 /* 90 days */, \"Time until the certificate expires. Default value 3 months.\")\n)\n\nfunc executeCert(cmd *base.Command, args []string) {\n\tvar opts []cert.Option\n\tif *certIsCA {\n\t\topts = append(opts, cert.Authority(*certIsCA))\n\t\topts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))\n\t}\n\n\topts = append(opts, cert.NotAfter(time.Now().Add(*certExpire)))\n\topts = append(opts, cert.CommonName(*certCommonName))\n\tif len(certDomainNames) > 0 {\n\t\topts = append(opts, cert.DNSNames(certDomainNames...))\n\t}\n\topts = append(opts, cert.Organization(*certOrganization))\n\n\tcert, err := cert.Generate(nil, opts...)\n\tif err != nil {\n\t\tbase.Fatalf(\"failed to generate TLS certificate: %s\", err)\n\t}\n\n\tif *certJSONOutput {\n\t\tprintJSON(cert)\n\t}\n\n\tif len(*certFileOutput) > 0 {\n\t\tif err := printFile(cert, *certFileOutput); err != nil {\n\t\t\tbase.Fatalf(\"failed to save file: %s\", err)\n\t\t}\n\t}\n}\n\nfunc printJSON(certificate *cert.Certificate) {\n\tcertPEM, keyPEM := certificate.ToPEM()\n\tjCert := &jsonCert{\n\t\tCertificate: strings.Split(strings.TrimSpace(string(certPEM)), \"\\n\"),\n\t\tKey:         strings.Split(strings.TrimSpace(string(keyPEM)), \"\\n\"),\n\t}\n\tcontent, err := json.MarshalIndent(jCert, \"\", \"  \")\n\tcommon.Must(err)\n\tos.Stdout.Write(content)\n\tos.Stdout.WriteString(\"\\n\")\n}\n\nfunc writeFile(content []byte, name string) error {\n\tf, err := os.Create(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\treturn common.Error2(f.Write(content))\n}\n\nfunc printFile(certificate *cert.Certificate, name string) error {\n\tcertPEM, keyPEM := certificate.ToPEM()\n\treturn task.Run(context.Background(), func() error {\n\t\treturn writeFile(certPEM, name+\".crt\")\n\t}, func() error {\n\t\treturn writeFile(keyPEM, name+\".key\")\n\t})\n}\n\ntype stringList []string\n\nfunc (l *stringList) String() string {\n\treturn \"String list\"\n}\n\nfunc (l *stringList) Set(v string) error {\n\tif v == \"\" {\n\t\tbase.Fatalf(\"empty value\")\n\t}\n\t*l = append(*l, v)\n\treturn nil\n}\n\ntype jsonCert struct {\n\tCertificate []string `json:\"certificate\"`\n\tKey         []string `json:\"key\"`\n}\n"
  },
  {
    "path": "main/commands/all/tls/ech.go",
    "content": "package tls\n\nimport (\n\t\"crypto/ecdh\"\n\t\"crypto/hpke\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"golang.org/x/crypto/cryptobyte\"\n)\n\nvar cmdECH = &base.Command{\n\tUsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem] [-i \"ECHServerKeys (base64.StdEncoding)\"]`,\n\tShort:     `Generate TLS-ECH certificates`,\n\tLong: `\nGenerate TLS-ECH certificates.\n\nSet serverName to your custom string: {{.Exec}} tls ech --serverName (string)\nGenerate into pem format: {{.Exec}} tls ech --pem\nRestore ECHConfigs from ECHServerKeys: {{.Exec}} tls ech -i \"ECHServerKeys (base64.StdEncoding)\"\n`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled\n}\n\nfunc init() {\n\tcmdECH.Run = executeECH\n}\n\nvar input_echServerKeys = cmdECH.Flag.String(\"i\", \"\", \"ECHServerKeys (base64.StdEncoding)\")\n\n// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool(\"pqSignatureSchemesEnabled\", false, \"\")\nvar input_serverName = cmdECH.Flag.String(\"serverName\", \"cloudflare-ech.com\", \"\")\nvar input_pem = cmdECH.Flag.Bool(\"pem\", false, \"True == turn on pem output\")\n\nfunc executeECH(cmd *base.Command, args []string) {\n\tvar kem uint16\n\n\t// if *input_pqSignatureSchemesEnabled {\n\t// \tkem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00\n\t// } else {\n\tkem = hpke.DHKEM(ecdh.X25519()).ID()\n\t// }\n\n\techConfig, priv, err := generateECHKeySet(0, *input_serverName, kem)\n\tcommon.Must(err)\n\n\tvar configBuffer, keyBuffer []byte\n\tif *input_echServerKeys == \"\" {\n\t\tconfigBytes, _ := marshalBinary(echConfig)\n\t\tvar b cryptobyte.Builder\n\t\tb.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {\n\t\t\tchild.AddBytes(configBytes)\n\t\t})\n\t\tconfigBuffer, _ = b.Bytes()\n\t\tvar b2 cryptobyte.Builder\n\t\tb2.AddUint16(uint16(len(priv)))\n\t\tb2.AddBytes(priv)\n\t\tb2.AddUint16(uint16(len(configBytes)))\n\t\tb2.AddBytes(configBytes)\n\t\tkeyBuffer, _ = b2.Bytes()\n\t} else {\n\t\tkeySetsByte, err := base64.StdEncoding.DecodeString(*input_echServerKeys)\n\t\tif err != nil {\n\t\t\tos.Stdout.WriteString(\"Failed to decode ECHServerKeys: \" + err.Error() + \"\\n\")\n\t\t\treturn\n\t\t}\n\t\tkeyBuffer = keySetsByte\n\t\tKeySets, err := tls.ConvertToGoECHKeys(keySetsByte)\n\t\tif err != nil {\n\t\t\tos.Stdout.WriteString(\"Failed to decode ECHServerKeys: \" + err.Error() + \"\\n\")\n\t\t\treturn\n\t\t}\n\t\tvar b cryptobyte.Builder\n\t\tfor _, keySet := range KeySets {\n\t\t\tb.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {\n\t\t\t\tchild.AddBytes(keySet.Config)\n\t\t\t})\n\t\t}\n\t\tconfigBuffer, _ = b.Bytes()\n\t}\n\n\tif *input_pem {\n\t\tconfigPEM := string(pem.EncodeToMemory(&pem.Block{Type: \"ECH CONFIGS\", Bytes: configBuffer}))\n\t\tkeyPEM := string(pem.EncodeToMemory(&pem.Block{Type: \"ECH KEYS\", Bytes: keyBuffer}))\n\t\tos.Stdout.WriteString(configPEM)\n\t\tos.Stdout.WriteString(keyPEM)\n\t} else {\n\t\tos.Stdout.WriteString(\"ECH config list: \\n\" + base64.StdEncoding.EncodeToString(configBuffer) + \"\\n\")\n\t\tos.Stdout.WriteString(\"ECH server keys: \\n\" + base64.StdEncoding.EncodeToString(keyBuffer) + \"\\n\")\n\t}\n}\n\ntype EchConfig struct {\n\tVersion              uint16\n\tConfigID             uint8\n\tKemID                uint16\n\tPublicKey            []byte\n\tSymmetricCipherSuite []EchCipher\n\tMaxNameLength        uint8\n\tPublicName           []byte\n\tExtensions           []Extension\n}\n\ntype EchCipher struct {\n\tKDFID  uint16\n\tAEADID uint16\n}\n\ntype Extension struct {\n\tType uint16\n\tData []byte\n}\n\n// reference github.com/OmarTariq612/goech\nfunc marshalBinary(ech EchConfig) ([]byte, error) {\n\tvar b cryptobyte.Builder\n\tb.AddUint16(ech.Version)\n\tb.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {\n\t\tchild.AddUint8(ech.ConfigID)\n\t\tchild.AddUint16(ech.KemID)\n\t\tchild.AddUint16(uint16(len(ech.PublicKey)))\n\t\tchild.AddBytes(ech.PublicKey)\n\t\tchild.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {\n\t\t\tfor _, cipherSuite := range ech.SymmetricCipherSuite {\n\t\t\t\tchild.AddUint16(cipherSuite.KDFID)\n\t\t\t\tchild.AddUint16(cipherSuite.AEADID)\n\t\t\t}\n\t\t})\n\t\tchild.AddUint8(ech.MaxNameLength)\n\t\tchild.AddUint8(uint8(len(ech.PublicName)))\n\t\tchild.AddBytes(ech.PublicName)\n\t\tchild.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {\n\t\t\tfor _, extention := range ech.Extensions {\n\t\t\t\tchild.AddUint16(extention.Type)\n\t\t\t\tchild.AddBytes(extention.Data)\n\t\t\t}\n\t\t})\n\t})\n\treturn b.Bytes()\n}\n\nconst ExtensionEncryptedClientHello = 0xfe0d\n\nfunc generateECHKeySet(configID uint8, domain string, kem uint16) (EchConfig, []byte, error) {\n\tconfig := EchConfig{\n\t\tVersion:    ExtensionEncryptedClientHello,\n\t\tConfigID:   configID,\n\t\tPublicName: []byte(domain),\n\t\tKemID:      kem,\n\t\tSymmetricCipherSuite: []EchCipher{\n\t\t\t{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES128GCM().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES256GCM().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES128GCM().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES256GCM().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES128GCM().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES256GCM().ID()},\n\t\t\t{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},\n\t\t},\n\t\tMaxNameLength: 0,\n\t\tExtensions:    nil,\n\t}\n\t// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {\n\tcurve := ecdh.X25519()\n\tpriv := make([]byte, 32)\n\t_, err := io.ReadFull(rand.Reader, priv)\n\tif err != nil {\n\t\treturn config, nil, err\n\t}\n\tprivKey, _ := curve.NewPrivateKey(priv)\n\tconfig.PublicKey = privKey.PublicKey().Bytes()\n\treturn config, priv, nil\n}\n"
  },
  {
    "path": "main/commands/all/tls/hash.go",
    "content": "package tls\n\nimport (\n\t\"bytes\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"text/tabwriter\"\n\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t. \"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nvar cmdHash = &base.Command{\n\tUsageLine: \"{{.Exec}} tls hash\",\n\tShort:     \"Calculate TLS certificate hash.\",\n\tLong: `\n\txray tls hash --cert <cert.pem>\n\tCalculate TLS certificate hash.\n\t`,\n}\n\nfunc init() {\n\tcmdHash.Run = executeHash // break init loop\n}\n\nvar input = cmdHash.Flag.String(\"cert\", \"fullchain.pem\", \"The file path of the certificate\")\n\nfunc executeHash(cmd *base.Command, args []string) {\n\tfs := flag.NewFlagSet(\"hash\", flag.ContinueOnError)\n\tif err := fs.Parse(args); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tcertContent, err := os.ReadFile(*input)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tvar certs []*x509.Certificate\n\tif bytes.Contains(certContent, []byte(\"BEGIN\")) {\n\t\tfor {\n\t\t\tblock, remain := pem.Decode(certContent)\n\t\t\tif block == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcert, err := x509.ParseCertificate(block.Bytes)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"Unable to decode certificate:\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcerts = append(certs, cert)\n\t\t\tcertContent = remain\n\t\t}\n\t} else {\n\t\tcerts, err = x509.ParseCertificates(certContent)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Unable to parse certificates:\", err)\n\t\t\treturn\n\t\t}\n\t}\n\tif len(certs) == 0 {\n\t\tfmt.Println(\"No certificates found\")\n\t\treturn\n\t}\n\ttabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\tfor i, cert := range certs {\n\t\thash := GenerateCertHashHex(cert)\n\t\tif i == 0 {\n\t\t\tfmt.Fprintf(tabWriter, \"Leaf SHA256:\\t%s\\n\", hash)\n\t\t} else {\n\t\t\tfmt.Fprintf(tabWriter, \"CA <%s> SHA256:\\t%s\\n\", cert.Subject.CommonName, hash)\n\t\t}\n\t}\n\ttabWriter.Flush()\n}\n"
  },
  {
    "path": "main/commands/all/tls/ping.go",
    "content": "package tls\n\nimport (\n\tgotls \"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"text/tabwriter\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t. \"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\n// cmdPing is the tls ping command\nvar cmdPing = &base.Command{\n\tUsageLine: \"{{.Exec}} tls ping [-ip <ip>] <domain>\",\n\tShort:     \"Ping the domain with TLS handshake\",\n\tLong: `\nPing the domain with TLS handshake.\n\nArguments:\n\n\t-ip\n\t\tThe IP address of the domain.\n`,\n}\n\nfunc init() {\n\tcmdPing.Run = executePing // break init loop\n}\n\nvar pingIPStr = cmdPing.Flag.String(\"ip\", \"\", \"\")\n\nfunc executePing(cmd *base.Command, args []string) {\n\tif cmdPing.Flag.NArg() < 1 {\n\t\tbase.Fatalf(\"domain not specified\")\n\t}\n\n\tdomainWithPort := cmdPing.Flag.Arg(0)\n\tfmt.Println(\"TLS ping: \", domainWithPort)\n\tTargetPort := 443\n\tdomain, port, err := net.SplitHostPort(domainWithPort)\n\tif err != nil {\n\t\tdomain = domainWithPort\n\t} else {\n\t\tTargetPort, _ = strconv.Atoi(port)\n\t}\n\ttabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\n\tvar ip net.IP\n\tif len(*pingIPStr) > 0 {\n\t\tv := net.ParseIP(*pingIPStr)\n\t\tif v == nil {\n\t\t\tbase.Fatalf(\"invalid IP: %s\", *pingIPStr)\n\t\t}\n\t\tip = v\n\t} else {\n\t\tv, err := net.ResolveIPAddr(\"ip\", domain)\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"Failed to resolve IP: %s\", err)\n\t\t}\n\t\tip = v.IP\n\t}\n\tfmt.Println(\"Using IP: \", ip.String()+\":\"+strconv.Itoa(TargetPort))\n\n\tfmt.Println(\"-------------------\")\n\tfmt.Println(\"Pinging without SNI\")\n\t{\n\t\ttcpConn, err := net.DialTCP(\"tcp\", nil, &net.TCPAddr{IP: ip, Port: TargetPort})\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"Failed to dial tcp: %s\", err)\n\t\t}\n\t\ttlsConn := GeneraticUClient(tcpConn, &gotls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t\tNextProtos:         []string{\"h2\", \"http/1.1\"},\n\t\t\tMaxVersion:         gotls.VersionTLS13,\n\t\t\tMinVersion:         gotls.VersionTLS12,\n\t\t})\n\t\terr = tlsConn.Handshake()\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Handshake failure: \", err)\n\t\t} else {\n\t\t\tfmt.Println(\"Handshake succeeded\")\n\t\t\tprintTLSConnDetail(tabWriter, tlsConn)\n\t\t\tprintCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)\n\t\t\ttabWriter.Flush()\n\t\t}\n\t\ttlsConn.Close()\n\t}\n\n\tfmt.Println(\"-------------------\")\n\tfmt.Println(\"Pinging with SNI\")\n\t{\n\t\ttcpConn, err := net.DialTCP(\"tcp\", nil, &net.TCPAddr{IP: ip, Port: TargetPort})\n\t\tif err != nil {\n\t\t\tbase.Fatalf(\"Failed to dial tcp: %s\", err)\n\t\t}\n\t\ttlsConn := GeneraticUClient(tcpConn, &gotls.Config{\n\t\t\tServerName: domain,\n\t\t\tNextProtos: []string{\"h2\", \"http/1.1\"},\n\t\t\tMaxVersion: gotls.VersionTLS13,\n\t\t\tMinVersion: gotls.VersionTLS12,\n\t\t})\n\t\terr = tlsConn.Handshake()\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Handshake failure: \", err)\n\t\t} else {\n\t\t\tfmt.Println(\"Handshake succeeded\")\n\t\t\tprintTLSConnDetail(tabWriter, tlsConn)\n\t\t\tprintCertificates(tabWriter, tlsConn.ConnectionState().PeerCertificates)\n\t\t\ttabWriter.Flush()\n\t\t}\n\t\ttlsConn.Close()\n\t}\n\n\tfmt.Println(\"-------------------\")\n\tfmt.Println(\"TLS ping finished\")\n}\n\nfunc printCertificates(tabWriter *tabwriter.Writer, certs []*x509.Certificate) {\n\tvar leaf *x509.Certificate\n\tvar CAs []*x509.Certificate\n\tvar length int\n\tfor _, cert := range certs {\n\t\tlength += len(cert.Raw)\n\t\tif len(cert.DNSNames) != 0 {\n\t\t\tleaf = cert\n\t\t} else {\n\t\t\tCAs = append(CAs, cert)\n\t\t}\n\t}\n\tfmt.Fprintf(tabWriter, \"Certificate chain's total length:\\t%d (certs count: %s)\\n\", length, strconv.Itoa(len(certs)))\n\tif leaf != nil {\n\t\tfmt.Fprintf(tabWriter, \"Cert's signature algorithm:\\t%s\\n\", leaf.SignatureAlgorithm.String())\n\t\tfmt.Fprintf(tabWriter, \"Cert's publicKey algorithm:\\t%s\\n\", leaf.PublicKeyAlgorithm.String())\n\t\tfmt.Fprintf(tabWriter, \"Cert's leaf SHA256:\\t%s\\n\", hex.EncodeToString(GenerateCertHash(leaf)))\n\t\tfor _, ca := range CAs {\n\t\t\tfmt.Fprintf(tabWriter, \"Cert's CA <%s> SHA256:\\t%s\\n\", ca.Subject.CommonName, hex.EncodeToString(GenerateCertHash(ca)))\n\t\t}\n\t\tfmt.Fprintf(tabWriter, \"Cert's allowed domains:\\t%v\\n\", leaf.DNSNames)\n\t}\n}\n\nfunc printTLSConnDetail(tabWriter *tabwriter.Writer, tlsConn *utls.UConn) {\n\tconnectionState := tlsConn.ConnectionState()\n\tvar tlsVersion string\n\tswitch connectionState.Version {\n\tcase gotls.VersionTLS13:\n\t\ttlsVersion = \"TLS 1.3\"\n\tcase gotls.VersionTLS12:\n\t\ttlsVersion = \"TLS 1.2\"\n\t}\n\tfmt.Fprintf(tabWriter, \"TLS Version:\\t%s\\n\", tlsVersion)\n\tcurveID := utils.AccessField[utls.CurveID](tlsConn.Conn, \"curveID\")\n\tif curveID != nil {\n\t\tPostQuantum := (*curveID == utls.X25519MLKEM768)\n\t\tfmt.Fprintf(tabWriter, \"TLS Post-Quantum key exchange:\\t%t (%s)\\n\", PostQuantum, curveID.String())\n\t} else {\n\t\tfmt.Fprintf(tabWriter, \"TLS Post-Quantum key exchange:  false (RSA Exchange)\\n\")\n\t}\n}\n"
  },
  {
    "path": "main/commands/all/tls/tls.go",
    "content": "package tls\n\nimport (\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\n// CmdTLS holds all tls sub commands\nvar CmdTLS = &base.Command{\n\tUsageLine: \"{{.Exec}} tls\",\n\tShort:     \"TLS tools\",\n\tLong: `{{.Exec}} {{.LongName}} provides tools for TLS.\n`,\n\tCommands: []*base.Command{\n\t\tcmdCert,\n\t\tcmdPing,\n\t\tcmdHash,\n\t\tcmdECH,\n\t},\n}\n"
  },
  {
    "path": "main/commands/all/uuid.go",
    "content": "package all\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdUUID = &base.Command{\n\tUsageLine: `{{.Exec}} uuid [-i \"example\"]`,\n\tShort:     `Generate UUIDv4 or UUIDv5 (VLESS)`,\n\tLong: `\nGenerate UUIDv4 or UUIDv5 (VLESS).\n\nUUIDv4 (random): {{.Exec}} uuid\n\nUUIDv5 (from input): {{.Exec}} uuid -i \"example\"\n`,\n}\n\nfunc init() {\n\tcmdUUID.Run = executeUUID // break init loop\n}\n\nvar input = cmdUUID.Flag.String(\"i\", \"\", \"\")\n\nfunc executeUUID(cmd *base.Command, args []string) {\n\tvar output string\n\tif l := len(*input); l == 0 {\n\t\tu := uuid.New()\n\t\toutput = u.String()\n\t} else if l <= 30 {\n\t\tu, _ := uuid.ParseString(*input)\n\t\toutput = u.String()\n\t} else {\n\t\toutput = \"Input must be within 30 bytes.\"\n\t}\n\tfmt.Println(output)\n}\n"
  },
  {
    "path": "main/commands/all/vlessenc.go",
    "content": "package all\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdVLESSEnc = &base.Command{\n\tUsageLine: `{{.Exec}} vlessenc`,\n\tShort:     `Generate decryption/encryption json pair (VLESS Encryption)`,\n\tLong: `\nGenerate decryption/encryption json pair (VLESS Encryption).\n`,\n}\n\nfunc init() {\n\tcmdVLESSEnc.Run = executeVLESSEnc // break init loop\n}\n\nfunc executeVLESSEnc(cmd *base.Command, args []string) {\n\tprivateKey, password, _, _ := genCurve25519(nil)\n\tserverKey := base64.RawURLEncoding.EncodeToString(privateKey)\n\tclientKey := base64.RawURLEncoding.EncodeToString(password)\n\tdecryption := generateDotConfig(\"mlkem768x25519plus\", \"native\", \"600s\", serverKey)\n\tencryption := generateDotConfig(\"mlkem768x25519plus\", \"native\", \"0rtt\", clientKey)\n\tseed, client, _ := genMLKEM768(nil)\n\tserverKeyPQ := base64.RawURLEncoding.EncodeToString(seed[:])\n\tclientKeyPQ := base64.RawURLEncoding.EncodeToString(client)\n\tdecryptionPQ := generateDotConfig(\"mlkem768x25519plus\", \"native\", \"600s\", serverKeyPQ)\n\tencryptionPQ := generateDotConfig(\"mlkem768x25519plus\", \"native\", \"0rtt\", clientKeyPQ)\n\tfmt.Printf(\"Choose one Authentication to use, do not mix them. Ephemeral key exchange is Post-Quantum safe anyway.\\n\\n\")\n\tfmt.Printf(\"Authentication: X25519, not Post-Quantum\\n\\\"decryption\\\": \\\"%v\\\"\\n\\\"encryption\\\": \\\"%v\\\"\\n\\n\", decryption, encryption)\n\tfmt.Printf(\"Authentication: ML-KEM-768, Post-Quantum\\n\\\"decryption\\\": \\\"%v\\\"\\n\\\"encryption\\\": \\\"%v\\\"\\n\", decryptionPQ, encryptionPQ)\n}\n\nfunc generateDotConfig(fields ...string) string {\n\treturn strings.Join(fields, \".\")\n}\n"
  },
  {
    "path": "main/commands/all/wg.go",
    "content": "package all\n\nimport (\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdWG = &base.Command{\n\tUsageLine: `{{.Exec}} wg [-i \"private key (base64.StdEncoding)\"]`,\n\tShort:     `Generate key pair for X25519 key exchange (WireGuard)`,\n\tLong: `\nGenerate key pair for X25519 key exchange (WireGuard).\n\nRandom: {{.Exec}} wg\n\nFrom private key: {{.Exec}} wg -i \"private key (base64.StdEncoding)\"\n`,\n}\n\nfunc init() {\n\tcmdWG.Run = executeWG // break init loop\n}\n\nvar input_wireguard = cmdWG.Flag.String(\"i\", \"\", \"\")\n\nfunc executeWG(cmd *base.Command, args []string) {\n\tCurve25519Genkey(true, *input_wireguard)\n}\n"
  },
  {
    "path": "main/commands/all/x25519.go",
    "content": "package all\n\nimport (\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdX25519 = &base.Command{\n\tUsageLine: `{{.Exec}} x25519 [-i \"private key (base64.RawURLEncoding)\"] [--std-encoding]`,\n\tShort:     `Generate key pair for X25519 key exchange (REALITY, VLESS Encryption)`,\n\tLong: `\nGenerate key pair for X25519 key exchange (REALITY, VLESS Encryption).\n\nRandom: {{.Exec}} x25519\n\nFrom private key: {{.Exec}} x25519 -i \"private key (base64.RawURLEncoding)\"\nFor Std Encoding: {{.Exec}} x25519 --std-encoding\n`,\n}\n\nfunc init() {\n\tcmdX25519.Run = executeX25519 // break init loop\n}\n\nvar input_stdEncoding = cmdX25519.Flag.Bool(\"std-encoding\", false, \"\")\nvar input_x25519 = cmdX25519.Flag.String(\"i\", \"\", \"\")\n\nfunc executeX25519(cmd *base.Command, args []string) {\n\tCurve25519Genkey(false, *input_x25519)\n}\n"
  },
  {
    "path": "main/commands/base/command.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package base defines shared basic pieces of the commands,\n// in particular logging and the Command structure.\npackage base\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// A Command is an implementation of a xray command\n// like xray run or xray version.\ntype Command struct {\n\t// Run runs the command.\n\t// The args are the arguments after the command name.\n\tRun func(cmd *Command, args []string)\n\n\t// UsageLine is the one-line usage message.\n\t// The words between the first word (the \"executable name\") and the first flag or argument in the line are taken to be the command name.\n\t//\n\t// UsageLine supports go template syntax. It's recommended to use \"{{.Exec}}\" instead of hardcoding name\n\tUsageLine string\n\n\t// Short is the short description shown in the 'go help' output.\n\t//\n\t// Note: Short does not support go template syntax.\n\tShort string\n\n\t// Long is the long message shown in the 'go help <this-command>' output.\n\t//\n\t// Long supports go template syntax. It's recommended to use \"{{.Exec}}\", \"{{.LongName}}\" instead of hardcoding strings\n\tLong string\n\n\t// Flag is a set of flags specific to this command.\n\tFlag flag.FlagSet\n\n\t// CustomFlags indicates that the command will do its own\n\t// flag parsing.\n\tCustomFlags bool\n\n\t// Commands lists the available commands and help topics.\n\t// The order here is the order in which they are printed by 'go help'.\n\t// Note that subcommands are in general best avoided.\n\tCommands []*Command\n}\n\n// LongName returns the command's long name: all the words in the usage line between first word (e.g. \"xray\") and a flag or argument,\nfunc (c *Command) LongName() string {\n\tname := c.UsageLine\n\tif i := strings.Index(name, \" [\"); i >= 0 {\n\t\tname = strings.TrimSpace(name[:i])\n\t}\n\tif i := strings.Index(name, \" \"); i >= 0 {\n\t\tname = name[i+1:]\n\t} else {\n\t\tname = \"\"\n\t}\n\treturn strings.TrimSpace(name)\n}\n\n// Name returns the command's short name: the last word in the usage line before a flag or argument.\nfunc (c *Command) Name() string {\n\tname := c.LongName()\n\tif i := strings.LastIndex(name, \" \"); i >= 0 {\n\t\tname = name[i+1:]\n\t}\n\treturn strings.TrimSpace(name)\n}\n\n// Usage prints usage of the Command\nfunc (c *Command) Usage() {\n\tbuildCommandText(c)\n\tfmt.Fprintf(os.Stderr, \"usage: %s\\n\", c.UsageLine)\n\tfmt.Fprintf(os.Stderr, \"Run 'xray help %s' for details.\\n\", c.LongName())\n\tSetExitStatus(2)\n\tExit()\n}\n\n// Runnable reports whether the command can be run; otherwise\n// it is a documentation pseudo-command such as importpath.\nfunc (c *Command) Runnable() bool {\n\treturn c.Run != nil\n}\n\n// Exit exits with code set with SetExitStatus()\nfunc Exit() {\n\tos.Exit(exitStatus)\n}\n\n// Fatalf logs error and exit with code 1\nfunc Fatalf(format string, args ...interface{}) {\n\tErrorf(format, args...)\n\tExit()\n}\n\n// Errorf logs error and set exit status to 1, but not exit\nfunc Errorf(format string, args ...interface{}) {\n\tfmt.Fprintf(os.Stderr, format, args...)\n\tfmt.Fprintln(os.Stderr)\n\tSetExitStatus(1)\n}\n\n// ExitIfErrors exits if current status is not zero\nfunc ExitIfErrors() {\n\tif exitStatus != 0 {\n\t\tExit()\n\t}\n}\n\nvar (\n\texitStatus = 0\n\texitMu     sync.Mutex\n)\n\n// SetExitStatus set exit status code\nfunc SetExitStatus(n int) {\n\texitMu.Lock()\n\tif exitStatus < n {\n\t\texitStatus = n\n\t}\n\texitMu.Unlock()\n}\n\n// GetExitStatus get exit status code\nfunc GetExitStatus() int {\n\treturn exitStatus\n}\n"
  },
  {
    "path": "main/commands/base/env.go",
    "content": "package base\n\n// CommandEnvHolder is a struct holds the environment info of commands\ntype CommandEnvHolder struct {\n\t// Executable name of current binary\n\tExec string\n\t// commands column width of current command\n\tCommandsWidth int\n}\n\n// CommandEnv holds the environment info of commands\nvar CommandEnv CommandEnvHolder\n\nfunc init() {\n\t/*\n\t\texec, err := os.Executable()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tCommandEnv.Exec = path.Base(exec)\n\t*/\n\tCommandEnv.Exec = \"xray\"\n}\n"
  },
  {
    "path": "main/commands/base/execute.go",
    "content": "package base\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// Copyright 2011 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// copied from \"github.com/golang/go/main.go\"\n\n// Execute execute the commands\nfunc Execute() {\n\tflag.Parse()\n\targs := flag.Args()\n\tif len(args) < 1 {\n\t\tPrintUsage(os.Stderr, RootCommand)\n\t\treturn\n\t}\n\tcmdName := args[0] // for error messages\n\tif args[0] == \"help\" {\n\t\tHelp(os.Stdout, args[1:])\n\t\treturn\n\t}\n\nBigCmdLoop:\n\tfor bigCmd := RootCommand; ; {\n\t\tfor _, cmd := range bigCmd.Commands {\n\t\t\tif cmd.Name() != args[0] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(cmd.Commands) > 0 {\n\t\t\t\t// test sub commands\n\t\t\t\tbigCmd = cmd\n\t\t\t\targs = args[1:]\n\t\t\t\tif len(args) == 0 {\n\t\t\t\t\tPrintUsage(os.Stderr, bigCmd)\n\t\t\t\t\tSetExitStatus(2)\n\t\t\t\t\tExit()\n\t\t\t\t}\n\t\t\t\tif args[0] == \"help\" {\n\t\t\t\t\t// Accept 'go mod help' and 'go mod help foo' for 'go help mod' and 'go help mod foo'.\n\t\t\t\t\tHelp(os.Stdout, append(strings.Split(cmdName, \" \"), args[1:]...))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcmdName += \" \" + args[0]\n\t\t\t\tcontinue BigCmdLoop\n\t\t\t}\n\t\t\tif !cmd.Runnable() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcmd.Flag.Usage = func() { cmd.Usage() }\n\t\t\tif cmd.CustomFlags {\n\t\t\t\targs = args[1:]\n\t\t\t} else {\n\t\t\t\tcmd.Flag.Parse(args[1:])\n\t\t\t\targs = cmd.Flag.Args()\n\t\t\t}\n\n\t\t\tbuildCommandText(cmd)\n\t\t\tcmd.Run(cmd, args)\n\t\t\tExit()\n\t\t\treturn\n\t\t}\n\t\thelpArg := \"\"\n\t\tif i := strings.LastIndex(cmdName, \" \"); i >= 0 {\n\t\t\thelpArg = \" \" + cmdName[:i]\n\t\t}\n\t\tfmt.Fprintf(os.Stderr, \"%s %s: unknown command\\nRun '%s help%s' for usage.\\n\", CommandEnv.Exec, cmdName, CommandEnv.Exec, helpArg)\n\t\tSetExitStatus(2)\n\t\tExit()\n\t}\n}\n\n// Sort sorts the commands\nfunc Sort() {\n\tsort.Slice(RootCommand.Commands, func(i, j int) bool {\n\t\treturn SortLessFunc(RootCommand.Commands[i], RootCommand.Commands[j])\n\t})\n}\n\n// SortLessFunc used for sort commands list, can be override from outside\nvar SortLessFunc = func(i, j *Command) bool {\n\treturn i.Name() < j.Name()\n}\n"
  },
  {
    "path": "main/commands/base/help.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage base\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"text/template\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\n// Help implements the 'help' command.\nfunc Help(w io.Writer, args []string) {\n\tcmd := RootCommand\nArgs:\n\tfor i, arg := range args {\n\t\tfor _, sub := range cmd.Commands {\n\t\t\tif sub.Name() == arg {\n\t\t\t\tcmd = sub\n\t\t\t\tcontinue Args\n\t\t\t}\n\t\t}\n\n\t\t// helpSuccess is the help command using as many args as possible that would succeed.\n\t\thelpSuccess := CommandEnv.Exec + \" help\"\n\t\tif i > 0 {\n\t\t\thelpSuccess += \" \" + strings.Join(args[:i], \" \")\n\t\t}\n\t\tfmt.Fprintf(os.Stderr, \"%s help %s: unknown help topic. Run '%s'.\\n\", CommandEnv.Exec, strings.Join(args, \" \"), helpSuccess)\n\t\tSetExitStatus(2) // failed at 'xray help cmd'\n\t\tExit()\n\t}\n\n\tif len(cmd.Commands) > 0 {\n\t\tPrintUsage(os.Stdout, cmd)\n\t} else {\n\t\tbuildCommandText(cmd)\n\t\ttmpl(os.Stdout, helpTemplate, makeTmplData(cmd))\n\t}\n}\n\nvar usageTemplate = `{{.Long | trim}}\n\nUsage:\n\n\t{{.UsageLine}} <command> [arguments]\n\nThe commands are:\n{{range .Commands}}{{if and (ne .Short \"\") (or (.Runnable) .Commands)}}\n\t{{.Name | width $.CommandsWidth}} {{.Short}}{{end}}{{end}}\n\nUse \"{{.Exec}} help{{with .LongName}} {{.}}{{end}} <command>\" for more information about a command.\n`\n\n// APPEND FOLLOWING TO 'usageTemplate' IF YOU WANT DOC,\n// A DOC TOPIC IS JUST A COMMAND NOT RUNNABLE:\n//\n// {{if eq (.UsageLine) (.Exec)}}\n// Additional help topics:\n// {{range .Commands}}{{if and (not .Runnable) (not .Commands)}}\n// \t{{.Name | printf \"%-15s\"}} {{.Short}}{{end}}{{end}}\n//\n// Use \"{{.Exec}} help{{with .LongName}} {{.}}{{end}} <topic>\" for more information about that topic.\n// {{end}}\n\nvar helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}\n\n{{end}}{{.Long | trim}}\n`\n\n// An errWriter wraps a writer, recording whether a write error occurred.\ntype errWriter struct {\n\tw   io.Writer\n\terr error\n}\n\nfunc (w *errWriter) Write(b []byte) (int, error) {\n\tn, err := w.w.Write(b)\n\tif err != nil {\n\t\tw.err = err\n\t}\n\treturn n, err\n}\n\n// tmpl executes the given template text on data, writing the result to w.\nfunc tmpl(w io.Writer, text string, data interface{}) {\n\tt := template.New(\"top\")\n\tt.Funcs(template.FuncMap{\"trim\": strings.TrimSpace, \"capitalize\": capitalize, \"width\": width})\n\ttemplate.Must(t.Parse(text))\n\tew := &errWriter{w: w}\n\terr := t.Execute(ew, data)\n\tif ew.err != nil {\n\t\t// I/O error writing. Ignore write on closed pipe.\n\t\tif strings.Contains(ew.err.Error(), \"pipe\") {\n\t\t\tSetExitStatus(1)\n\t\t\tExit()\n\t\t}\n\t\tFatalf(\"writing output: %v\", ew.err)\n\t}\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc capitalize(s string) string {\n\tif s == \"\" {\n\t\treturn s\n\t}\n\tr, n := utf8.DecodeRuneInString(s)\n\treturn string(unicode.ToTitle(r)) + s[n:]\n}\n\nfunc width(width int, value string) string {\n\tformat := fmt.Sprintf(\"%%-%ds\", width)\n\treturn fmt.Sprintf(format, value)\n}\n\n// PrintUsage prints usage of cmd to w\nfunc PrintUsage(w io.Writer, cmd *Command) {\n\tbuildCommandText(cmd)\n\tbw := bufio.NewWriter(w)\n\ttmpl(bw, usageTemplate, makeTmplData(cmd))\n\tbw.Flush()\n}\n\n// buildCommandText build command text as template\nfunc buildCommandText(cmd *Command) {\n\tdata := makeTmplData(cmd)\n\tcmd.UsageLine = buildText(cmd.UsageLine, data)\n\t// DO NOT SUPPORT \".Short\":\n\t// - It's not necessary\n\t// - Or, we have to build text for all sub commands of current command, like \"xray help api\"\n\t// cmd.Short = buildText(cmd.Short, data)\n\tcmd.Long = buildText(cmd.Long, data)\n}\n\nfunc buildText(text string, data interface{}) string {\n\tbuf := bytes.NewBuffer([]byte{})\n\ttmpl(buf, text, data)\n\treturn buf.String()\n}\n\ntype tmplData struct {\n\t*Command\n\t*CommandEnvHolder\n}\n\nfunc makeTmplData(cmd *Command) tmplData {\n\t// Minimum width of the command column\n\twidth := 12\n\tfor _, c := range cmd.Commands {\n\t\tl := len(c.Name())\n\t\tif width < l {\n\t\t\twidth = l\n\t\t}\n\t}\n\tCommandEnv.CommandsWidth = width\n\treturn tmplData{\n\t\tCommand:          cmd,\n\t\tCommandEnvHolder: &CommandEnv,\n\t}\n}\n"
  },
  {
    "path": "main/commands/base/root.go",
    "content": "package base\n\n// RootCommand is the root command of all commands\nvar RootCommand *Command\n\nfunc init() {\n\tRootCommand = &Command{\n\t\tUsageLine: CommandEnv.Exec,\n\t\tLong:      \"The root command\",\n\t}\n}\n\n// RegisterCommand register a command to RootCommand\nfunc RegisterCommand(cmd *Command) {\n\tRootCommand.Commands = append(RootCommand.Commands, cmd)\n}\n"
  },
  {
    "path": "main/confloader/confloader.go",
    "content": "package confloader\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype (\n\tconfigFileLoader func(string) (io.Reader, error)\n)\n\nvar (\n\tEffectiveConfigFileLoader configFileLoader\n)\n\n// LoadConfig reads from a path/url/stdin\n// actual work is in external module\nfunc LoadConfig(file string) (io.Reader, error) {\n\tif EffectiveConfigFileLoader == nil {\n\t\terrors.LogInfo(context.Background(), \"external config module not loaded, reading from stdin\")\n\t\treturn os.Stdin, nil\n\t}\n\treturn EffectiveConfigFileLoader(file)\n}\n"
  },
  {
    "path": "main/confloader/external/external.go",
    "content": "package external\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n)\n\nfunc ConfigLoader(arg string) (out io.Reader, err error) {\n\tvar data []byte\n\tswitch {\n\tcase strings.HasPrefix(arg, \"http+unix://\"):\n\t\tdata, err = FetchUnixSocketHTTPContent(arg)\n\n\tcase strings.HasPrefix(arg, \"http://\"), strings.HasPrefix(arg, \"https://\"):\n\t\tdata, err = FetchHTTPContent(arg)\n\n\tcase arg == \"stdin:\":\n\t\tdata, err = io.ReadAll(os.Stdin)\n\n\tdefault:\n\t\tdata, err = os.ReadFile(arg)\n\t}\n\n\tif err != nil {\n\t\treturn\n\t}\n\tout = bytes.NewBuffer(data)\n\treturn\n}\n\nfunc FetchHTTPContent(target string) ([]byte, error) {\n\tparsedTarget, err := url.Parse(target)\n\tif err != nil {\n\t\treturn nil, errors.New(\"invalid URL: \", target).Base(err)\n\t}\n\n\tif s := strings.ToLower(parsedTarget.Scheme); s != \"http\" && s != \"https\" {\n\t\treturn nil, errors.New(\"invalid scheme: \", parsedTarget.Scheme)\n\t}\n\n\tclient := &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t}\n\tresp, err := client.Do(&http.Request{\n\t\tMethod: \"GET\",\n\t\tURL:    parsedTarget,\n\t\tClose:  true,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to dial to \", target).Base(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, errors.New(\"unexpected HTTP status code: \", resp.StatusCode)\n\t}\n\n\tcontent, err := buf.ReadAllToBytes(resp.Body)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read HTTP response\").Base(err)\n\t}\n\n\treturn content, nil\n}\n\n// Format: http+unix:///path/to/socket.sock/api/endpoint\nfunc FetchUnixSocketHTTPContent(target string) ([]byte, error) {\n\tpath := strings.TrimPrefix(target, \"http+unix://\")\n\t\n\tif !strings.HasPrefix(path, \"/\") {\n\t\treturn nil, errors.New(\"unix socket path must be absolute\")\n\t}\n\t\n\tvar socketPath, httpPath string\n\t\n\tsockIdx := strings.Index(path, \".sock\")\n\tif sockIdx != -1 {\n\t\tsocketPath = path[:sockIdx+5]\n\t\thttpPath = path[sockIdx+5:]\n\t\tif httpPath == \"\" {\n\t\t\thttpPath = \"/\"\n\t\t}\n\t} else {\n\t\treturn nil, errors.New(\"cannot determine socket path, socket file should have .sock extension\")\n\t}\n\t\n\tif _, err := os.Stat(socketPath); err != nil {\n\t\treturn nil, errors.New(\"socket file not found: \", socketPath).Base(err)\n\t}\n\t\n\tclient := &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {\n\t\t\t\tvar d net.Dialer\n\t\t\t\treturn d.DialContext(ctx, \"unix\", socketPath)\n\t\t\t},\n\t\t},\n\t}\n\tdefer client.CloseIdleConnections()\n\t\n\tresp, err := client.Get(\"http://localhost\" + httpPath)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to fetch from unix socket: \", socketPath).Base(err)\n\t}\n\tdefer resp.Body.Close()\n\t\n\tif resp.StatusCode != 200 {\n\t\treturn nil, errors.New(\"unexpected HTTP status code: \", resp.StatusCode)\n\t}\n\t\n\tcontent, err := buf.ReadAllToBytes(resp.Body)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read response\").Base(err)\n\t}\n\t\n\treturn content, nil\n}\n\nfunc init() {\n\tconfloader.EffectiveConfigFileLoader = ConfigLoader\n}\n"
  },
  {
    "path": "main/distro/all/all.go",
    "content": "package all\n\nimport (\n\t// The following are necessary as they register handlers in their init functions.\n\n\t// Mandatory features. Can't remove unless there are replacements.\n\t_ \"github.com/xtls/xray-core/app/dispatcher\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/inbound\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\n\t// Default commander and all its services. This is an optional feature.\n\t_ \"github.com/xtls/xray-core/app/commander\"\n\t_ \"github.com/xtls/xray-core/app/log/command\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/command\"\n\t_ \"github.com/xtls/xray-core/app/stats/command\"\n\n\t// Developer preview services\n\t_ \"github.com/xtls/xray-core/app/observatory/command\"\n\n\t// Other optional features.\n\t_ \"github.com/xtls/xray-core/app/dns\"\n\t_ \"github.com/xtls/xray-core/app/dns/fakedns\"\n\t_ \"github.com/xtls/xray-core/app/log\"\n\t_ \"github.com/xtls/xray-core/app/metrics\"\n\t_ \"github.com/xtls/xray-core/app/policy\"\n\t_ \"github.com/xtls/xray-core/app/reverse\"\n\t_ \"github.com/xtls/xray-core/app/router\"\n\t_ \"github.com/xtls/xray-core/app/stats\"\n\n\t// Fix dependency cycle caused by core import in internet package\n\t_ \"github.com/xtls/xray-core/transport/internet/tagged/taggedimpl\"\n\n\t// Developer preview features\n\t_ \"github.com/xtls/xray-core/app/observatory\"\n\n\t// Inbound and outbound proxies.\n\t_ \"github.com/xtls/xray-core/proxy/blackhole\"\n\t_ \"github.com/xtls/xray-core/proxy/dns\"\n\t_ \"github.com/xtls/xray-core/proxy/dokodemo\"\n\t_ \"github.com/xtls/xray-core/proxy/freedom\"\n\t_ \"github.com/xtls/xray-core/proxy/http\"\n\t_ \"github.com/xtls/xray-core/proxy/loopback\"\n\t_ \"github.com/xtls/xray-core/proxy/shadowsocks\"\n\t_ \"github.com/xtls/xray-core/proxy/socks\"\n\t_ \"github.com/xtls/xray-core/proxy/trojan\"\n\t_ \"github.com/xtls/xray-core/proxy/vless/inbound\"\n\t_ \"github.com/xtls/xray-core/proxy/vless/outbound\"\n\t_ \"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t_ \"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t_ \"github.com/xtls/xray-core/proxy/wireguard\"\n\n\t// Transports\n\t_ \"github.com/xtls/xray-core/transport/internet/grpc\"\n\t_ \"github.com/xtls/xray-core/transport/internet/httpupgrade\"\n\t_ \"github.com/xtls/xray-core/transport/internet/kcp\"\n\t_ \"github.com/xtls/xray-core/transport/internet/reality\"\n\t_ \"github.com/xtls/xray-core/transport/internet/splithttp\"\n\t_ \"github.com/xtls/xray-core/transport/internet/tcp\"\n\t_ \"github.com/xtls/xray-core/transport/internet/tls\"\n\t_ \"github.com/xtls/xray-core/transport/internet/udp\"\n\t_ \"github.com/xtls/xray-core/transport/internet/websocket\"\n\n\t// Transport headers\n\t_ \"github.com/xtls/xray-core/transport/internet/headers/http\"\n\t_ \"github.com/xtls/xray-core/transport/internet/headers/noop\"\n\n\t// JSON & TOML & YAML\n\t_ \"github.com/xtls/xray-core/main/json\"\n\t_ \"github.com/xtls/xray-core/main/toml\"\n\t_ \"github.com/xtls/xray-core/main/yaml\"\n\n\t// Load config from file or http(s)\n\t_ \"github.com/xtls/xray-core/main/confloader/external\"\n\n\t// Commands\n\t_ \"github.com/xtls/xray-core/main/commands/all\"\n)\n"
  },
  {
    "path": "main/json/json.go",
    "content": "package json\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n)\n\nfunc init() {\n\tcommon.Must(core.RegisterConfigLoader(&core.ConfigFormat{\n\t\tName:      \"JSON\",\n\t\tExtension: []string{\"json\"},\n\t\tLoader: func(input interface{}) (*core.Config, error) {\n\t\t\tswitch v := input.(type) {\n\t\t\tcase cmdarg.Arg:\n\t\t\t\tcf := &conf.Config{}\n\t\t\t\tfor i, arg := range v {\n\t\t\t\t\terrors.LogInfo(context.Background(), \"Reading config: \", arg)\n\t\t\t\t\tr, err := confloader.LoadConfig(arg)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"failed to read config: \", arg).Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tc, err := serial.DecodeJSONConfig(r)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"failed to decode config: \", arg).Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t// This ensure even if the muti-json parser do not support a setting,\n\t\t\t\t\t\t// It is still respected automatically for the first configure file\n\t\t\t\t\t\t*cf = *c\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tcf.Override(c, arg)\n\t\t\t\t}\n\t\t\t\treturn cf.Build()\n\t\t\tcase io.Reader:\n\t\t\t\treturn serial.LoadJSONConfig(v)\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"unknown type\")\n\t\t\t}\n\t\t},\n\t}))\n}\n"
  },
  {
    "path": "main/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/main/commands/base\"\n\t_ \"github.com/xtls/xray-core/main/distro/all\"\n)\n\nfunc main() {\n\tos.Args = getArgsV4Compatible()\n\n\tbase.RootCommand.Long = \"Xray is a platform for building proxies.\"\n\tbase.RootCommand.Commands = append(\n\t\t[]*base.Command{\n\t\t\tcmdRun,\n\t\t\tcmdVersion,\n\t\t},\n\t\tbase.RootCommand.Commands...,\n\t)\n\tbase.Execute()\n}\n\nfunc getArgsV4Compatible() []string {\n\tif len(os.Args) == 1 {\n\t\treturn []string{os.Args[0], \"run\"}\n\t}\n\tif os.Args[1][0] != '-' {\n\t\treturn os.Args\n\t}\n\tversion := false\n\tfs := flag.NewFlagSet(\"\", flag.ContinueOnError)\n\tfs.BoolVar(&version, \"version\", false, \"\")\n\t// parse silently, no usage, no error output\n\tfs.Usage = func() {}\n\tfs.SetOutput(&null{})\n\terr := fs.Parse(os.Args[1:])\n\tif err == flag.ErrHelp {\n\t\t// fmt.Println(\"DEPRECATED: -h, WILL BE REMOVED IN V5.\")\n\t\t// fmt.Println(\"PLEASE USE: xray help\")\n\t\t// fmt.Println()\n\t\treturn []string{os.Args[0], \"help\"}\n\t}\n\tif version {\n\t\t// fmt.Println(\"DEPRECATED: -version, WILL BE REMOVED IN V5.\")\n\t\t// fmt.Println(\"PLEASE USE: xray version\")\n\t\t// fmt.Println()\n\t\treturn []string{os.Args[0], \"version\"}\n\t}\n\t// fmt.Println(\"COMPATIBLE MODE, DEPRECATED.\")\n\t// fmt.Println(\"PLEASE USE: xray run [arguments] INSTEAD.\")\n\t// fmt.Println()\n\treturn append([]string{os.Args[0], \"run\"}, os.Args[1:]...)\n}\n\ntype null struct{}\n\nfunc (n *null) Write(p []byte) (int, error) {\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "main/main_test.go",
    "content": "//go:build coveragemain\n// +build coveragemain\n\npackage main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestRunMainForCoverage(t *testing.T) {\n\tmain()\n}\n"
  },
  {
    "path": "main/run.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdRun = &base.Command{\n\tUsageLine: \"{{.Exec}} run [-c config.json] [-confdir dir]\",\n\tShort:     \"Run Xray with config, the default command\",\n\tLong: `\nRun Xray with config, the default command.\n\nThe -config=file, -c=file flags set the config files for \nXray. Multiple assign is accepted.\n\nThe -confdir=dir flag sets a dir with multiple json config\n\nThe -format=json flag sets the format of config files. \nDefault \"auto\".\n\nThe -test flag tells Xray to test config files only, \nwithout launching the server.\n\nThe -dump flag tells Xray to print the merged config.\n\t`,\n}\n\nfunc init() {\n\tcmdRun.Run = executeRun // break init loop\n\tlog.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)\n}\n\nvar (\n\tconfigFiles cmdarg.Arg // \"Config file for Xray.\", the option is customed type, parse in main\n\tconfigDir   string\n\tdump        = cmdRun.Flag.Bool(\"dump\", false, \"Dump merged config only, without launching Xray server.\")\n\ttest        = cmdRun.Flag.Bool(\"test\", false, \"Test config file only, without launching Xray server.\")\n\tformat      = cmdRun.Flag.String(\"format\", \"auto\", \"Format of input file.\")\n\n\t/* We have to do this here because Golang's Test will also need to parse flag, before\n\t * main func in this file is run.\n\t */\n\t_ = func() bool {\n\t\tcmdRun.Flag.Var(&configFiles, \"config\", \"Config path for Xray.\")\n\t\tcmdRun.Flag.Var(&configFiles, \"c\", \"Short alias of -config\")\n\t\tcmdRun.Flag.StringVar(&configDir, \"confdir\", \"\", \"A dir with multiple json config\")\n\n\t\treturn true\n\t}()\n)\n\nfunc executeRun(cmd *base.Command, args []string) {\n\tif *dump {\n\t\tclog.ReplaceWithSeverityLogger(clog.Severity_Warning)\n\t\terrCode := dumpConfig()\n\t\tos.Exit(errCode)\n\t}\n\n\tprintVersion()\n\tserver, err := startXray()\n\tif err != nil {\n\t\tfmt.Println(\"Failed to start:\", err)\n\t\t// Configuration error. Exit with a special value to prevent systemd from restarting.\n\t\tos.Exit(23)\n\t}\n\n\tif *test {\n\t\tfmt.Println(\"Configuration OK.\")\n\t\tos.Exit(0)\n\t}\n\n\tif err := server.Start(); err != nil {\n\t\tfmt.Println(\"Failed to start:\", err)\n\t\tos.Exit(-1)\n\t}\n\tdefer server.Close()\n\n\t/*\n\t\tconf.FileCache = nil\n\t\tconf.IPCache = nil\n\t\tconf.SiteCache = nil\n\t*/\n\n\t// Explicitly triggering GC to remove garbage from config loading.\n\truntime.GC()\n\tdebug.FreeOSMemory()\n\n\t{\n\t\tosSignals := make(chan os.Signal, 1)\n\t\tsignal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)\n\t\t<-osSignals\n\t}\n}\n\nfunc dumpConfig() int {\n\tfiles := getConfigFilePath(false)\n\tif config, err := core.GetMergedConfig(files); err != nil {\n\t\tfmt.Println(err)\n\t\ttime.Sleep(1 * time.Second)\n\t\treturn 23\n\t} else {\n\t\tfmt.Print(config)\n\t}\n\treturn 0\n}\n\nfunc fileExists(file string) bool {\n\tinfo, err := os.Stat(file)\n\treturn err == nil && !info.IsDir()\n}\n\nfunc dirExists(file string) bool {\n\tif file == \"\" {\n\t\treturn false\n\t}\n\tinfo, err := os.Stat(file)\n\treturn err == nil && info.IsDir()\n}\n\nfunc getRegepxByFormat() string {\n\tswitch strings.ToLower(*format) {\n\tcase \"json\":\n\t\treturn `^.+\\.(json|jsonc)$`\n\tcase \"toml\":\n\t\treturn `^.+\\.toml$`\n\tcase \"yaml\", \"yml\":\n\t\treturn `^.+\\.(yaml|yml)$`\n\tdefault:\n\t\treturn `^.+\\.(json|jsonc|toml|yaml|yml)$`\n\t}\n}\n\nfunc readConfDir(dirPath string) {\n\tconfs, err := os.ReadDir(dirPath)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tfor _, f := range confs {\n\t\tmatched, err := regexp.MatchString(getRegepxByFormat(), f.Name())\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t\tif matched {\n\t\t\tconfigFiles.Set(path.Join(dirPath, f.Name()))\n\t\t}\n\t}\n}\n\nfunc getConfigFilePath(verbose bool) cmdarg.Arg {\n\tif dirExists(configDir) {\n\t\tif verbose {\n\t\t\tlog.Println(\"Using confdir from arg:\", configDir)\n\t\t}\n\t\treadConfDir(configDir)\n\t} else if envConfDir := platform.GetConfDirPath(); dirExists(envConfDir) {\n\t\tif verbose {\n\t\t\tlog.Println(\"Using confdir from env:\", envConfDir)\n\t\t}\n\t\treadConfDir(envConfDir)\n\t}\n\n\tif len(configFiles) > 0 {\n\t\treturn configFiles\n\t}\n\n\tif workingDir, err := os.Getwd(); err == nil {\n\t\tsuffixes := []string{\".json\", \".jsonc\", \".toml\", \".yaml\", \".yml\"}\n\t\tfor _, suffix := range suffixes {\n\t\t\tconfigFile := filepath.Join(workingDir, \"config\"+suffix)\n\t\t\tif fileExists(configFile) {\n\t\t\t\tif verbose {\n\t\t\t\t\tlog.Println(\"Using default config: \", configFile)\n\t\t\t\t}\n\t\t\t\treturn cmdarg.Arg{configFile}\n\t\t\t}\n\t\t}\n\t}\n\n\tif configFile := platform.GetConfigurationPath(); fileExists(configFile) {\n\t\tif verbose {\n\t\t\tlog.Println(\"Using config from env: \", configFile)\n\t\t}\n\t\treturn cmdarg.Arg{configFile}\n\t}\n\n\tif verbose {\n\t\tlog.Println(\"Using config from STDIN\")\n\t}\n\treturn cmdarg.Arg{\"stdin:\"}\n}\n\nfunc getConfigFormat() string {\n\tf := core.GetFormatByExtension(*format)\n\tif f == \"\" {\n\t\tf = \"auto\"\n\t}\n\treturn f\n}\n\nfunc startXray() (core.Server, error) {\n\tconfigFiles := getConfigFilePath(true)\n\n\t// config, err := core.LoadConfig(getConfigFormat(), configFiles[0], configFiles)\n\n\tc, err := core.LoadConfig(getConfigFormat(), configFiles)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to load config files: [\", configFiles.String(), \"]\").Base(err)\n\t}\n\n\tserver, err := core.New(c)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to create server\").Base(err)\n\t}\n\n\treturn server, nil\n}\n"
  },
  {
    "path": "main/toml/toml.go",
    "content": "package toml\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n)\n\nfunc init() {\n\tcommon.Must(core.RegisterConfigLoader(&core.ConfigFormat{\n\t\tName:      \"TOML\",\n\t\tExtension: []string{\"toml\"},\n\t\tLoader: func(input interface{}) (*core.Config, error) {\n\t\t\tswitch v := input.(type) {\n\t\t\tcase cmdarg.Arg:\n\t\t\t\tcf := &conf.Config{}\n\t\t\t\tfor i, arg := range v {\n\t\t\t\t\terrors.LogInfo(context.Background(), \"Reading config: \", arg)\n\t\t\t\t\tr, err := confloader.LoadConfig(arg)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"failed to read config: \", arg).Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tc, err := serial.DecodeTOMLConfig(r)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"failed to decode config: \", arg).Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t// This ensure even if the muti-json parser do not support a setting,\n\t\t\t\t\t\t// It is still respected automatically for the first configure file\n\t\t\t\t\t\t*cf = *c\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tcf.Override(c, arg)\n\t\t\t\t}\n\t\t\t\treturn cf.Build()\n\t\t\tcase io.Reader:\n\t\t\t\treturn serial.LoadTOMLConfig(v)\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"unknown type\")\n\t\t\t}\n\t\t},\n\t}))\n}\n"
  },
  {
    "path": "main/version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/main/commands/base\"\n)\n\nvar cmdVersion = &base.Command{\n\tUsageLine: \"{{.Exec}} version\",\n\tShort:     \"Show current version of Xray\",\n\tLong: `Version prints the build information for Xray executables.\n\t`,\n\tRun: executeVersion,\n}\n\nfunc executeVersion(cmd *base.Command, args []string) {\n\tprintVersion()\n}\n\nfunc printVersion() {\n\tversion := core.VersionStatement()\n\tfor _, s := range version {\n\t\tfmt.Println(s)\n\t}\n}\n"
  },
  {
    "path": "main/yaml/yaml.go",
    "content": "package yaml\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/cmdarg\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/infra/conf/serial\"\n\t\"github.com/xtls/xray-core/main/confloader\"\n)\n\nfunc init() {\n\tcommon.Must(core.RegisterConfigLoader(&core.ConfigFormat{\n\t\tName:      \"YAML\",\n\t\tExtension: []string{\"yaml\", \"yml\"},\n\t\tLoader: func(input interface{}) (*core.Config, error) {\n\t\t\tswitch v := input.(type) {\n\t\t\tcase cmdarg.Arg:\n\t\t\t\tcf := &conf.Config{}\n\t\t\t\tfor i, arg := range v {\n\t\t\t\t\terrors.LogInfo(context.Background(), \"Reading config: \", arg)\n\t\t\t\t\tr, err := confloader.LoadConfig(arg)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"failed to read config: \", arg).Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tc, err := serial.DecodeYAMLConfig(r)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"failed to decode config: \", arg).Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t// This ensure even if the muti-json parser do not support a setting,\n\t\t\t\t\t\t// It is still respected automatically for the first configure file\n\t\t\t\t\t\t*cf = *c\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tcf.Override(c, arg)\n\t\t\t\t}\n\t\t\t\treturn cf.Build()\n\t\t\tcase io.Reader:\n\t\t\t\treturn serial.LoadYAMLConfig(v)\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.New(\"unknown type\")\n\t\t\t}\n\t\t},\n\t}))\n}\n"
  },
  {
    "path": "proxy/blackhole/blackhole.go",
    "content": "// Package blackhole is an outbound handler that blocks all connections.\npackage blackhole\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// Handler is an outbound connection that silently swallow the entire payload.\ntype Handler struct {\n\tresponse ResponseConfig\n}\n\n// New creates a new blackhole handler.\nfunc New(ctx context.Context, config *Config) (*Handler, error) {\n\tresponse, err := config.GetInternalResponse()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Handler{\n\t\tresponse: response,\n\t}, nil\n}\n\n// Process implements OutboundHandler.Dispatch().\nfunc (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tob.Name = \"blackhole\"\n\n\tnBytes := h.response.WriteTo(link.Writer)\n\tif nBytes > 0 {\n\t\t// Sleep a little here to make sure the response is sent to client.\n\t\ttime.Sleep(time.Second)\n\t}\n\tcommon.Interrupt(link.Writer)\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "proxy/blackhole/blackhole_test.go",
    "content": "package blackhole_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc TestBlackholeHTTPResponse(t *testing.T) {\n\tctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})\n\thandler, err := blackhole.New(ctx, &blackhole.Config{\n\t\tResponse: serial.ToTypedMessage(&blackhole.HTTPResponse{}),\n\t})\n\tcommon.Must(err)\n\n\treader, writer := pipe.New(pipe.WithoutSizeLimit())\n\n\tvar mb buf.MultiBuffer\n\tvar rerr error\n\tgo func() {\n\t\tb, e := reader.ReadMultiBuffer()\n\t\tmb = b\n\t\trerr = e\n\t}()\n\n\tlink := transport.Link{\n\t\tReader: reader,\n\t\tWriter: writer,\n\t}\n\tcommon.Must(handler.Process(ctx, &link, nil))\n\tcommon.Must(rerr)\n\tif mb.IsEmpty() {\n\t\tt.Error(\"expect http response, but nothing\")\n\t}\n}\n"
  },
  {
    "path": "proxy/blackhole/config.go",
    "content": "package blackhole\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\nconst (\n\thttp403response = `HTTP/1.1 403 Forbidden\nConnection: close\nCache-Control: max-age=3600, public\nContent-Length: 0\n\n\n`\n)\n\n// ResponseConfig is the configuration for blackhole responses.\ntype ResponseConfig interface {\n\t// WriteTo writes a predefined response to the specified buffer.\n\tWriteTo(buf.Writer) int32\n}\n\n// WriteTo implements ResponseConfig.WriteTo().\nfunc (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }\n\n// WriteTo implements ResponseConfig.WriteTo().\nfunc (*HTTPResponse) WriteTo(writer buf.Writer) int32 {\n\tb := buf.New()\n\tcommon.Must2(b.WriteString(http403response))\n\tn := b.Len()\n\twriter.WriteMultiBuffer(buf.MultiBuffer{b})\n\treturn n\n}\n\n// GetInternalResponse converts response settings from proto to internal data structure.\nfunc (c *Config) GetInternalResponse() (ResponseConfig, error) {\n\tif c.GetResponse() == nil {\n\t\treturn new(NoneResponse), nil\n\t}\n\n\tconfig, err := c.GetResponse().GetInstance()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn config.(ResponseConfig), nil\n}\n"
  },
  {
    "path": "proxy/blackhole/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/blackhole/config.proto\n\npackage blackhole\n\nimport (\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype NoneResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NoneResponse) Reset() {\n\t*x = NoneResponse{}\n\tmi := &file_proxy_blackhole_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NoneResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NoneResponse) ProtoMessage() {}\n\nfunc (x *NoneResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_blackhole_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NoneResponse.ProtoReflect.Descriptor instead.\nfunc (*NoneResponse) Descriptor() ([]byte, []int) {\n\treturn file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype HTTPResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *HTTPResponse) Reset() {\n\t*x = HTTPResponse{}\n\tmi := &file_proxy_blackhole_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HTTPResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HTTPResponse) ProtoMessage() {}\n\nfunc (x *HTTPResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_blackhole_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead.\nfunc (*HTTPResponse) Descriptor() ([]byte, []int) {\n\treturn file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tResponse      *serial.TypedMessage   `protobuf:\"bytes,1,opt,name=response,proto3\" json:\"response,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_blackhole_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_blackhole_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Config) GetResponse() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Response\n\t}\n\treturn nil\n}\n\nvar File_proxy_blackhole_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_blackhole_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1cproxy/blackhole/config.proto\\x12\\x14xray.proxy.blackhole\\x1a!common/serial/typed_message.proto\\\"\\x0e\\n\" +\n\t\"\\fNoneResponse\\\"\\x0e\\n\" +\n\t\"\\fHTTPResponse\\\"F\\n\" +\n\t\"\\x06Config\\x12<\\n\" +\n\t\"\\bresponse\\x18\\x01 \\x01(\\v2 .xray.common.serial.TypedMessageR\\bresponseB^\\n\" +\n\t\"\\x18com.xray.proxy.blackholeP\\x01Z)github.com/xtls/xray-core/proxy/blackhole\\xaa\\x02\\x14Xray.Proxy.Blackholeb\\x06proto3\"\n\nvar (\n\tfile_proxy_blackhole_config_proto_rawDescOnce sync.Once\n\tfile_proxy_blackhole_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_blackhole_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_blackhole_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_blackhole_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_blackhole_config_proto_rawDescData\n}\n\nvar file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_proxy_blackhole_config_proto_goTypes = []any{\n\t(*NoneResponse)(nil),        // 0: xray.proxy.blackhole.NoneResponse\n\t(*HTTPResponse)(nil),        // 1: xray.proxy.blackhole.HTTPResponse\n\t(*Config)(nil),              // 2: xray.proxy.blackhole.Config\n\t(*serial.TypedMessage)(nil), // 3: xray.common.serial.TypedMessage\n}\nvar file_proxy_blackhole_config_proto_depIdxs = []int32{\n\t3, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.common.serial.TypedMessage\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_blackhole_config_proto_init() }\nfunc file_proxy_blackhole_config_proto_init() {\n\tif File_proxy_blackhole_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_blackhole_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_blackhole_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_blackhole_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_blackhole_config_proto = out.File\n\tfile_proxy_blackhole_config_proto_goTypes = nil\n\tfile_proxy_blackhole_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/blackhole/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.blackhole;\noption csharp_namespace = \"Xray.Proxy.Blackhole\";\noption go_package = \"github.com/xtls/xray-core/proxy/blackhole\";\noption java_package = \"com.xray.proxy.blackhole\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\n\nmessage NoneResponse {}\n\nmessage HTTPResponse {}\n\nmessage Config {\n  xray.common.serial.TypedMessage response = 1;\n}\n"
  },
  {
    "path": "proxy/blackhole/config_test.go",
    "content": "package blackhole_test\n\nimport (\n\t\"bufio\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/proxy/blackhole\"\n)\n\nfunc TestHTTPResponse(t *testing.T) {\n\tbuffer := buf.New()\n\n\thttpResponse := new(HTTPResponse)\n\thttpResponse.WriteTo(buf.NewWriter(buffer))\n\n\treader := bufio.NewReader(buffer)\n\tresponse, err := http.ReadResponse(reader, nil)\n\tcommon.Must(err)\n\n\tif response.StatusCode != 403 {\n\t\tt.Error(\"expected status code 403, but got \", response.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "proxy/dns/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/dns/config.proto\n\npackage dns\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Server is the DNS server address. If specified, this address overrides the\n\t// original one.\n\tServer        *net.Endpoint `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tUserLevel     uint32        `protobuf:\"varint,2,opt,name=user_level,json=userLevel,proto3\" json:\"user_level,omitempty\"`\n\tNon_IPQuery   string        `protobuf:\"bytes,3,opt,name=non_IP_query,json=nonIPQuery,proto3\" json:\"non_IP_query,omitempty\"`\n\tBlockTypes    []int32       `protobuf:\"varint,4,rep,packed,name=block_types,json=blockTypes,proto3\" json:\"block_types,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_dns_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_dns_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_dns_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetServer() *net.Endpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetUserLevel() uint32 {\n\tif x != nil {\n\t\treturn x.UserLevel\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetNon_IPQuery() string {\n\tif x != nil {\n\t\treturn x.Non_IPQuery\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetBlockTypes() []int32 {\n\tif x != nil {\n\t\treturn x.BlockTypes\n\t}\n\treturn nil\n}\n\nvar File_proxy_dns_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_dns_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x16proxy/dns/config.proto\\x12\\x0exray.proxy.dns\\x1a\\x1ccommon/net/destination.proto\\\"\\x9d\\x01\\n\" +\n\t\"\\x06Config\\x121\\n\" +\n\t\"\\x06server\\x18\\x01 \\x01(\\v2\\x19.xray.common.net.EndpointR\\x06server\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_level\\x18\\x02 \\x01(\\rR\\tuserLevel\\x12 \\n\" +\n\t\"\\fnon_IP_query\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"nonIPQuery\\x12\\x1f\\n\" +\n\t\"\\vblock_types\\x18\\x04 \\x03(\\x05R\\n\" +\n\t\"blockTypesBL\\n\" +\n\t\"\\x12com.xray.proxy.dnsP\\x01Z#github.com/xtls/xray-core/proxy/dns\\xaa\\x02\\x0eXray.Proxy.Dnsb\\x06proto3\"\n\nvar (\n\tfile_proxy_dns_config_proto_rawDescOnce sync.Once\n\tfile_proxy_dns_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_dns_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_dns_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_dns_config_proto_rawDesc), len(file_proxy_dns_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_dns_config_proto_rawDescData\n}\n\nvar file_proxy_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_dns_config_proto_goTypes = []any{\n\t(*Config)(nil),       // 0: xray.proxy.dns.Config\n\t(*net.Endpoint)(nil), // 1: xray.common.net.Endpoint\n}\nvar file_proxy_dns_config_proto_depIdxs = []int32{\n\t1, // 0: xray.proxy.dns.Config.server:type_name -> xray.common.net.Endpoint\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_dns_config_proto_init() }\nfunc file_proxy_dns_config_proto_init() {\n\tif File_proxy_dns_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_dns_config_proto_rawDesc), len(file_proxy_dns_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_dns_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_dns_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_dns_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_dns_config_proto = out.File\n\tfile_proxy_dns_config_proto_goTypes = nil\n\tfile_proxy_dns_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/dns/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.dns;\noption csharp_namespace = \"Xray.Proxy.Dns\";\noption go_package = \"github.com/xtls/xray-core/proxy/dns\";\noption java_package = \"com.xray.proxy.dns\";\noption java_multiple_files = true;\n\nimport \"common/net/destination.proto\";\n\nmessage Config {\n  // Server is the DNS server address. If specified, this address overrides the\n  // original one.\n  xray.common.net.Endpoint server = 1;\n  uint32 user_level = 2;\n  string non_IP_query = 3;\n  repeated int32 block_types = 4;\n}\n"
  },
  {
    "path": "proxy/dns/dns.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\tgo_errors \"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\tdns_proto \"github.com/xtls/xray-core/common/protocol/dns\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"golang.org/x/net/dns/dnsmessage\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\th := new(Handler)\n\t\tif err := core.RequireFeatures(ctx, func(dnsClient dns.Client, policyManager policy.Manager) error {\n\t\t\tcore.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {\n\t\t\t\th.fdns = fdns\n\t\t\t})\n\t\t\treturn h.Init(config.(*Config), dnsClient, policyManager)\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn h, nil\n\t}))\n}\n\ntype ownLinkVerifier interface {\n\tIsOwnLink(ctx context.Context) bool\n}\n\ntype Handler struct {\n\tclient          dns.Client\n\tfdns            dns.FakeDNSEngine\n\townLinkVerifier ownLinkVerifier\n\tserver          net.Destination\n\ttimeout         time.Duration\n\tnonIPQuery      string\n\tblockTypes      []int32\n}\n\nfunc (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager policy.Manager) error {\n\th.client = dnsClient\n\th.timeout = policyManager.ForLevel(config.UserLevel).Timeouts.ConnectionIdle\n\n\tif v, ok := dnsClient.(ownLinkVerifier); ok {\n\t\th.ownLinkVerifier = v\n\t}\n\n\tif config.Server != nil {\n\t\th.server = config.Server.AsDestination()\n\t}\n\th.nonIPQuery = config.Non_IPQuery\n\tif h.nonIPQuery == \"\" {\n\t\th.nonIPQuery = \"reject\"\n\t}\n\th.blockTypes = config.BlockTypes\n\treturn nil\n}\n\nfunc (h *Handler) isOwnLink(ctx context.Context) bool {\n\treturn h.ownLinkVerifier != nil && h.ownLinkVerifier.IsOwnLink(ctx)\n}\n\nfunc parseIPQuery(b []byte) (r bool, domain string, id uint16, qType dnsmessage.Type) {\n\tvar parser dnsmessage.Parser\n\theader, err := parser.Start(b)\n\tif err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"parser start\")\n\t\treturn\n\t}\n\n\tid = header.ID\n\tq, err := parser.Question()\n\tif err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"question\")\n\t\treturn\n\t}\n\tdomain = q.Name.String()\n\tqType = q.Type\n\tif qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA {\n\t\treturn\n\t}\n\n\tr = true\n\treturn\n}\n\n// Process implements proxy.Outbound.\nfunc (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"invalid outbound\")\n\t}\n\tob.Name = \"dns\"\n\n\tsrcNetwork := ob.Target.Network\n\n\tdest := ob.Target\n\tif h.server.Network != net.Network_Unknown {\n\t\tdest.Network = h.server.Network\n\t}\n\tif h.server.Address != nil {\n\t\tdest.Address = h.server.Address\n\t}\n\tif h.server.Port != 0 {\n\t\tdest.Port = h.server.Port\n\t}\n\n\terrors.LogInfo(ctx, \"handling DNS traffic to \", dest)\n\n\tconn := &outboundConn{\n\t\tdialer: func() (stat.Connection, error) {\n\t\t\treturn d.Dial(ctx, dest)\n\t\t},\n\t\tconnReady: make(chan struct{}, 1),\n\t}\n\n\tvar reader dns_proto.MessageReader\n\tvar writer dns_proto.MessageWriter\n\tif srcNetwork == net.Network_TCP {\n\t\treader = dns_proto.NewTCPReader(link.Reader)\n\t\twriter = &dns_proto.TCPWriter{\n\t\t\tWriter: link.Writer,\n\t\t}\n\t} else {\n\t\treader = &dns_proto.UDPReader{\n\t\t\tReader: link.Reader,\n\t\t}\n\t\twriter = &dns_proto.UDPWriter{\n\t\t\tWriter: link.Writer,\n\t\t}\n\t}\n\n\tvar connReader dns_proto.MessageReader\n\tvar connWriter dns_proto.MessageWriter\n\tif dest.Network == net.Network_TCP {\n\t\tconnReader = dns_proto.NewTCPReader(buf.NewReader(conn))\n\t\tconnWriter = &dns_proto.TCPWriter{\n\t\t\tWriter: buf.NewWriter(conn),\n\t\t}\n\t} else {\n\t\tconnReader = &dns_proto.UDPReader{\n\t\t\tReader: buf.NewPacketReader(conn),\n\t\t}\n\t\tconnWriter = &dns_proto.UDPWriter{\n\t\t\tWriter: buf.NewWriter(conn),\n\t\t}\n\t}\n\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tctx, _ = context.WithCancel(context.Background())\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\tterminate := func() {\n\t\tcancel()\n\t\tconn.Close()\n\t}\n\ttimer := signal.CancelAfterInactivity(ctx, terminate, h.timeout)\n\tdefer timer.SetTimeout(0)\n\n\trequest := func() error {\n\t\tdefer timer.SetTimeout(0)\n\t\tfor {\n\t\t\tb, err := reader.ReadMessage()\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttimer.Update()\n\n\t\t\tif !h.isOwnLink(ctx) {\n\t\t\t\tisIPQuery, domain, id, qType := parseIPQuery(b.Bytes())\n\t\t\t\tif len(h.blockTypes) > 0 {\n\t\t\t\t\tfor _, blocktype := range h.blockTypes {\n\t\t\t\t\t\tif blocktype == int32(qType) {\n\t\t\t\t\t\t\tb.Release()\n\t\t\t\t\t\t\terrors.LogInfo(ctx, \"blocked type \", qType, \" query for domain \", domain)\n\t\t\t\t\t\t\tif h.nonIPQuery == \"reject\" {\n\t\t\t\t\t\t\t\terr := h.rejectNonIPQuery(id, qType, domain, writer)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif isIPQuery {\n\t\t\t\t\tb.Release()\n\t\t\t\t\tgo h.handleIPQuery(id, qType, domain, writer, timer)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif h.nonIPQuery == \"drop\" {\n\t\t\t\t\tb.Release()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif h.nonIPQuery == \"reject\" {\n\t\t\t\t\tb.Release()\n\t\t\t\t\terr := h.rejectNonIPQuery(id, qType, domain, writer)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err := connWriter.WriteMessage(b); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tresponse := func() error {\n\t\tdefer timer.SetTimeout(0)\n\t\tfor {\n\t\t\tb, err := connReader.ReadMessage()\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttimer.Update()\n\n\t\t\tif err := writer.WriteMessage(b); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := task.Run(ctx, request, response); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, timer *signal.ActivityTimer) {\n\tvar ips []net.IP\n\tvar err error\n\n\tvar ttl4 uint32\n\tvar ttl6 uint32\n\n\tswitch qType {\n\tcase dnsmessage.TypeA:\n\t\tips, ttl4, err = h.client.LookupIP(domain, dns.IPOption{\n\t\t\tIPv4Enable: true,\n\t\t\tIPv6Enable: false,\n\t\t\tFakeEnable: true,\n\t\t})\n\tcase dnsmessage.TypeAAAA:\n\t\tips, ttl6, err = h.client.LookupIP(domain, dns.IPOption{\n\t\t\tIPv4Enable: false,\n\t\t\tIPv6Enable: true,\n\t\t\tFakeEnable: true,\n\t\t})\n\t}\n\n\trcode := dns.RCodeFromError(err)\n\tif rcode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) {\n\t\terrors.LogInfoInner(context.Background(), err, \"ip query\")\n\t\treturn\n\t}\n\n\tswitch qType {\n\tcase dnsmessage.TypeA:\n\t\tfor i, ip := range ips {\n\t\t\tips[i] = ip.To4()\n\t\t}\n\tcase dnsmessage.TypeAAAA:\n\t\tfor i, ip := range ips {\n\t\t\tips[i] = ip.To16()\n\t\t}\n\t}\n\n\tb := buf.New()\n\trawBytes := b.Extend(buf.Size)\n\tbuilder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{\n\t\tID:                 id,\n\t\tRCode:              dnsmessage.RCode(rcode),\n\t\tRecursionAvailable: true,\n\t\tRecursionDesired:   true,\n\t\tResponse:           true,\n\t\tAuthoritative:      true,\n\t})\n\tbuilder.EnableCompression()\n\tcommon.Must(builder.StartQuestions())\n\tcommon.Must(builder.Question(dnsmessage.Question{\n\t\tName:  dnsmessage.MustNewName(domain),\n\t\tClass: dnsmessage.ClassINET,\n\t\tType:  qType,\n\t}))\n\tcommon.Must(builder.StartAnswers())\n\n\trHeader4 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl4}\n\trHeader6 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl6}\n\tfor _, ip := range ips {\n\t\tif len(ip) == net.IPv4len {\n\t\t\tvar r dnsmessage.AResource\n\t\t\tcopy(r.A[:], ip)\n\t\t\tcommon.Must(builder.AResource(rHeader4, r))\n\t\t} else {\n\t\t\tvar r dnsmessage.AAAAResource\n\t\t\tcopy(r.AAAA[:], ip)\n\t\t\tcommon.Must(builder.AAAAResource(rHeader6, r))\n\t\t}\n\t}\n\tmsgBytes, err := builder.Finish()\n\tif err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"pack message\")\n\t\tb.Release()\n\t\ttimer.SetTimeout(0)\n\t}\n\tb.Resize(0, int32(len(msgBytes)))\n\n\tif err := writer.WriteMessage(b); err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"write IP answer\")\n\t\ttimer.SetTimeout(0)\n\t}\n}\n\nfunc (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) error {\n\tdomainT := strings.TrimSuffix(domain, \".\")\n\tif domainT == \"\" {\n\t\treturn errors.New(\"empty domain name\")\n\t}\n\tb := buf.New()\n\trawBytes := b.Extend(buf.Size)\n\tbuilder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{\n\t\tID:                 id,\n\t\tRCode:              dnsmessage.RCodeRefused,\n\t\tRecursionAvailable: true,\n\t\tRecursionDesired:   true,\n\t\tResponse:           true,\n\t\tAuthoritative:      true,\n\t})\n\tbuilder.EnableCompression()\n\tcommon.Must(builder.StartQuestions())\n\terr := builder.Question(dnsmessage.Question{\n\t\tName:  dnsmessage.MustNewName(domain),\n\t\tClass: dnsmessage.ClassINET,\n\t\tType:  qType,\n\t})\n\tif err != nil {\n\t\terrors.LogInfo(context.Background(), \"unexpected domain \", domain, \" when building reject message: \", err)\n\t\tb.Release()\n\t\treturn err\n\t}\n\n\tmsgBytes, err := builder.Finish()\n\tif err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"pack reject message\")\n\t\tb.Release()\n\t\treturn err\n\t}\n\tb.Resize(0, int32(len(msgBytes)))\n\n\tif err := writer.WriteMessage(b); err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"write reject answer\")\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype outboundConn struct {\n\taccess sync.Mutex\n\tdialer func() (stat.Connection, error)\n\n\tconn      net.Conn\n\tconnReady chan struct{}\n\tclosed    bool\n}\n\nfunc (c *outboundConn) dial() error {\n\tconn, err := c.dialer()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.conn = conn\n\tc.connReady <- struct{}{}\n\treturn nil\n}\n\nfunc (c *outboundConn) Write(b []byte) (int, error) {\n\tc.access.Lock()\n\tif c.closed {\n\t\tc.access.Unlock()\n\t\treturn 0, errors.New(\"outbound connection closed\")\n\t}\n\n\tif c.conn == nil {\n\t\tif err := c.dial(); err != nil {\n\t\t\tc.access.Unlock()\n\t\t\terrors.LogWarningInner(context.Background(), err, \"failed to dial outbound connection\")\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tc.access.Unlock()\n\n\treturn c.conn.Write(b)\n}\n\nfunc (c *outboundConn) Read(b []byte) (int, error) {\n\tc.access.Lock()\n\tif c.closed {\n\t\tc.access.Unlock()\n\t\treturn 0, io.EOF\n\t}\n\n\tif c.conn == nil {\n\t\tc.access.Unlock()\n\t\t_, open := <-c.connReady\n\t\tif !open {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\treturn c.conn.Read(b)\n\t}\n\tc.access.Unlock()\n\treturn c.conn.Read(b)\n}\n\nfunc (c *outboundConn) Close() error {\n\tc.access.Lock()\n\tc.closed = true\n\tclose(c.connReady)\n\tif c.conn != nil {\n\t\tc.conn.Close()\n\t}\n\tc.access.Unlock()\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/dns/dns_test.go",
    "content": "package dns_test\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\tdnsapp \"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/inbound\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\tdns_proxy \"github.com/xtls/xray-core/proxy/dns\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n)\n\ntype staticHandler struct{}\n\nfunc (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {\n\tans := new(dns.Msg)\n\tans.Id = r.Id\n\n\tvar clientIP net.IP\n\n\topt := r.IsEdns0()\n\tif opt != nil {\n\t\tfor _, o := range opt.Option {\n\t\t\tif o.Option() == dns.EDNS0SUBNET {\n\t\t\t\tsubnet := o.(*dns.EDNS0_SUBNET)\n\t\t\t\tclientIP = subnet.Address\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, q := range r.Question {\n\t\tswitch {\n\t\tcase q.Name == \"google.com.\" && q.Qtype == dns.TypeA:\n\t\t\tif clientIP == nil {\n\t\t\t\trr, _ := dns.NewRR(\"google.com. IN A 8.8.8.8\")\n\t\t\t\tans.Answer = append(ans.Answer, rr)\n\t\t\t} else {\n\t\t\t\trr, _ := dns.NewRR(\"google.com. IN A 8.8.4.4\")\n\t\t\t\tans.Answer = append(ans.Answer, rr)\n\t\t\t}\n\n\t\tcase q.Name == \"facebook.com.\" && q.Qtype == dns.TypeA:\n\t\t\trr, _ := dns.NewRR(\"facebook.com. IN A 9.9.9.9\")\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"ipv6.google.com.\" && q.Qtype == dns.TypeA:\n\t\t\trr, err := dns.NewRR(\"ipv6.google.com. IN A 8.8.8.7\")\n\t\t\tcommon.Must(err)\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"ipv6.google.com.\" && q.Qtype == dns.TypeAAAA:\n\t\t\trr, err := dns.NewRR(\"ipv6.google.com. IN AAAA 2001:4860:4860::8888\")\n\t\t\tcommon.Must(err)\n\t\t\tans.Answer = append(ans.Answer, rr)\n\n\t\tcase q.Name == \"notexist.google.com.\" && q.Qtype == dns.TypeAAAA:\n\t\t\tans.MsgHdr.Rcode = dns.RcodeNameError\n\t\t}\n\t}\n\tw.WriteMsg(ans)\n}\n\nfunc TestUDPDNSTunnel(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t\tUDPSize: 1200,\n\t}\n\tdefer dnsServer.Shutdown()\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tserverPort := udp.PickPort()\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dnsapp.Config{\n\t\t\t\tNameServer: []*dnsapp.NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:     uint32(port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\tcommon.Must(v.Start())\n\tdefer v.Close()\n\n\t{\n\t\tm1 := new(dns.Msg)\n\t\tm1.Id = dns.Id()\n\t\tm1.RecursionDesired = true\n\t\tm1.Question = make([]dns.Question, 1)\n\t\tm1.Question[0] = dns.Question{Name: \"google.com.\", Qtype: dns.TypeA, Qclass: dns.ClassINET}\n\n\t\tc := new(dns.Client)\n\t\tin, _, err := c.Exchange(m1, \"127.0.0.1:\"+strconv.Itoa(int(serverPort)))\n\t\tcommon.Must(err)\n\n\t\tif len(in.Answer) != 1 {\n\t\t\tt.Fatal(\"len(answer): \", len(in.Answer))\n\t\t}\n\n\t\trr, ok := in.Answer[0].(*dns.A)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not A record\")\n\t\t}\n\t\tif r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != \"\" {\n\t\t\tt.Error(r)\n\t\t}\n\t}\n\n\t{\n\t\tm1 := new(dns.Msg)\n\t\tm1.Id = dns.Id()\n\t\tm1.RecursionDesired = true\n\t\tm1.Question = make([]dns.Question, 1)\n\t\tm1.Question[0] = dns.Question{Name: \"ipv4only.google.com.\", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}\n\n\t\tc := new(dns.Client)\n\t\tc.Timeout = 10 * time.Second\n\t\tin, _, err := c.Exchange(m1, \"127.0.0.1:\"+strconv.Itoa(int(serverPort)))\n\t\tcommon.Must(err)\n\n\t\tif len(in.Answer) != 0 {\n\t\t\tt.Fatal(\"len(answer): \", len(in.Answer))\n\t\t}\n\t}\n\n\t{\n\t\tm1 := new(dns.Msg)\n\t\tm1.Id = dns.Id()\n\t\tm1.RecursionDesired = true\n\t\tm1.Question = make([]dns.Question, 1)\n\t\tm1.Question[0] = dns.Question{Name: \"notexist.google.com.\", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}\n\n\t\tc := new(dns.Client)\n\t\tin, _, err := c.Exchange(m1, \"127.0.0.1:\"+strconv.Itoa(int(serverPort)))\n\t\tcommon.Must(err)\n\n\t\tif in.Rcode != dns.RcodeNameError {\n\t\t\tt.Error(\"expected NameError, but got \", in.Rcode)\n\t\t}\n\t}\n}\n\nfunc TestTCPDNSTunnel(t *testing.T) {\n\tport := udp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"udp\",\n\t\tHandler: &staticHandler{},\n\t}\n\tdefer dnsServer.Shutdown()\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tserverPort := tcp.PickPort()\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dnsapp.Config{\n\t\t\t\tNameServer: []*dnsapp.NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:     uint32(port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dns_proxy.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\tcommon.Must(v.Start())\n\tdefer v.Close()\n\n\tm1 := new(dns.Msg)\n\tm1.Id = dns.Id()\n\tm1.RecursionDesired = true\n\tm1.Question = make([]dns.Question, 1)\n\tm1.Question[0] = dns.Question{Name: \"google.com.\", Qtype: dns.TypeA, Qclass: dns.ClassINET}\n\n\tc := &dns.Client{\n\t\tNet: \"tcp\",\n\t}\n\tin, _, err := c.Exchange(m1, \"127.0.0.1:\"+serverPort.String())\n\tcommon.Must(err)\n\n\tif len(in.Answer) != 1 {\n\t\tt.Fatal(\"len(answer): \", len(in.Answer))\n\t}\n\n\trr, ok := in.Answer[0].(*dns.A)\n\tif !ok {\n\t\tt.Fatal(\"not A record\")\n\t}\n\tif r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestUDP2TCPDNSTunnel(t *testing.T) {\n\tport := tcp.PickPort()\n\n\tdnsServer := dns.Server{\n\t\tAddr:    \"127.0.0.1:\" + port.String(),\n\t\tNet:     \"tcp\",\n\t\tHandler: &staticHandler{},\n\t}\n\tdefer dnsServer.Shutdown()\n\n\tgo dnsServer.ListenAndServe()\n\ttime.Sleep(time.Second)\n\n\tserverPort := tcp.PickPort()\n\tconfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dnsapp.Config{\n\t\t\t\tNameServer: []*dnsapp.NameServer{\n\t\t\t\t\t{\n\t\t\t\t\t\tAddress: &net.Endpoint{\n\t\t\t\t\t\t\tNetwork: net.Network_UDP,\n\t\t\t\t\t\t\tAddress: &net.IPOrDomain{\n\t\t\t\t\t\t\t\tAddress: &net.IPOrDomain_Ip{\n\t\t\t\t\t\t\t\t\tIp: []byte{127, 0, 0, 1},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPort: uint32(port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&policy.Config{}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:     uint32(port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dns_proxy.Config{\n\t\t\t\t\tServer: &net.Endpoint{\n\t\t\t\t\t\tNetwork: net.Network_TCP,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tv, err := core.New(config)\n\tcommon.Must(err)\n\tcommon.Must(v.Start())\n\tdefer v.Close()\n\n\tm1 := new(dns.Msg)\n\tm1.Id = dns.Id()\n\tm1.RecursionDesired = true\n\tm1.Question = make([]dns.Question, 1)\n\tm1.Question[0] = dns.Question{Name: \"google.com.\", Qtype: dns.TypeA, Qclass: dns.ClassINET}\n\n\tc := &dns.Client{\n\t\tNet: \"tcp\",\n\t}\n\tin, _, err := c.Exchange(m1, \"127.0.0.1:\"+serverPort.String())\n\tcommon.Must(err)\n\n\tif len(in.Answer) != 1 {\n\t\tt.Fatal(\"len(answer): \", len(in.Answer))\n\t}\n\n\trr, ok := in.Answer[0].(*dns.A)\n\tif !ok {\n\t\tt.Fatal(\"not A record\")\n\t}\n\tif r := cmp.Diff(rr.A[:], net.IP{8, 8, 8, 8}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n"
  },
  {
    "path": "proxy/dokodemo/config.go",
    "content": "package dokodemo\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// GetPredefinedAddress returns the defined address from proto config. Null if address is not valid.\nfunc (v *Config) GetPredefinedAddress() net.Address {\n\taddr := v.Address.AsAddress()\n\tif addr == nil {\n\t\treturn nil\n\t}\n\treturn addr\n}\n"
  },
  {
    "path": "proxy/dokodemo/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/dokodemo/config.proto\n\npackage dokodemo\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate   protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddress *net.IPOrDomain        `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tPort    uint32                 `protobuf:\"varint,2,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tPortMap map[string]string      `protobuf:\"bytes,3,rep,name=port_map,json=portMap,proto3\" json:\"port_map,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// List of networks that the Dokodemo accepts.\n\tNetworks       []net.Network `protobuf:\"varint,7,rep,packed,name=networks,proto3,enum=xray.common.net.Network\" json:\"networks,omitempty\"`\n\tFollowRedirect bool          `protobuf:\"varint,5,opt,name=follow_redirect,json=followRedirect,proto3\" json:\"follow_redirect,omitempty\"`\n\tUserLevel      uint32        `protobuf:\"varint,6,opt,name=user_level,json=userLevel,proto3\" json:\"user_level,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_dokodemo_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_dokodemo_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetAddress() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetPortMap() map[string]string {\n\tif x != nil {\n\t\treturn x.PortMap\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetNetworks() []net.Network {\n\tif x != nil {\n\t\treturn x.Networks\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetFollowRedirect() bool {\n\tif x != nil {\n\t\treturn x.FollowRedirect\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetUserLevel() uint32 {\n\tif x != nil {\n\t\treturn x.UserLevel\n\t}\n\treturn 0\n}\n\nvar File_proxy_dokodemo_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_dokodemo_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1bproxy/dokodemo/config.proto\\x12\\x13xray.proxy.dokodemo\\x1a\\x18common/net/address.proto\\x1a\\x18common/net/network.proto\\\"\\xd2\\x02\\n\" +\n\t\"\\x06Config\\x125\\n\" +\n\t\"\\aaddress\\x18\\x01 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x02 \\x01(\\rR\\x04port\\x12C\\n\" +\n\t\"\\bport_map\\x18\\x03 \\x03(\\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\\aportMap\\x124\\n\" +\n\t\"\\bnetworks\\x18\\a \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\bnetworks\\x12'\\n\" +\n\t\"\\x0ffollow_redirect\\x18\\x05 \\x01(\\bR\\x0efollowRedirect\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_level\\x18\\x06 \\x01(\\rR\\tuserLevel\\x1a:\\n\" +\n\t\"\\fPortMapEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B[\\n\" +\n\t\"\\x17com.xray.proxy.dokodemoP\\x01Z(github.com/xtls/xray-core/proxy/dokodemo\\xaa\\x02\\x13Xray.Proxy.Dokodemob\\x06proto3\"\n\nvar (\n\tfile_proxy_dokodemo_config_proto_rawDescOnce sync.Once\n\tfile_proxy_dokodemo_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_dokodemo_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_dokodemo_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_dokodemo_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_dokodemo_config_proto_rawDesc), len(file_proxy_dokodemo_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_dokodemo_config_proto_rawDescData\n}\n\nvar file_proxy_dokodemo_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_proxy_dokodemo_config_proto_goTypes = []any{\n\t(*Config)(nil),         // 0: xray.proxy.dokodemo.Config\n\tnil,                    // 1: xray.proxy.dokodemo.Config.PortMapEntry\n\t(*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain\n\t(net.Network)(0),       // 3: xray.common.net.Network\n}\nvar file_proxy_dokodemo_config_proto_depIdxs = []int32{\n\t2, // 0: xray.proxy.dokodemo.Config.address:type_name -> xray.common.net.IPOrDomain\n\t1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry\n\t3, // 2: xray.proxy.dokodemo.Config.networks:type_name -> xray.common.net.Network\n\t3, // [3:3] is the sub-list for method output_type\n\t3, // [3:3] is the sub-list for method input_type\n\t3, // [3:3] is the sub-list for extension type_name\n\t3, // [3:3] is the sub-list for extension extendee\n\t0, // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_dokodemo_config_proto_init() }\nfunc file_proxy_dokodemo_config_proto_init() {\n\tif File_proxy_dokodemo_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_dokodemo_config_proto_rawDesc), len(file_proxy_dokodemo_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_dokodemo_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_dokodemo_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_dokodemo_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_dokodemo_config_proto = out.File\n\tfile_proxy_dokodemo_config_proto_goTypes = nil\n\tfile_proxy_dokodemo_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/dokodemo/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.dokodemo;\noption csharp_namespace = \"Xray.Proxy.Dokodemo\";\noption go_package = \"github.com/xtls/xray-core/proxy/dokodemo\";\noption java_package = \"com.xray.proxy.dokodemo\";\noption java_multiple_files = true;\n\nimport \"common/net/address.proto\";\nimport \"common/net/network.proto\";\n\nmessage Config {\n  xray.common.net.IPOrDomain address = 1;\n  uint32 port = 2;\n\n  map<string, string> port_map = 3;\n\n  // List of networks that the Dokodemo accepts.\n  repeated xray.common.net.Network networks = 7;\n\n  bool follow_redirect = 5;\n  uint32 user_level = 6;\n}\n"
  },
  {
    "path": "proxy/dokodemo/dokodemo.go",
    "content": "package dokodemo\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\td := new(DokodemoDoor)\n\t\terr := core.RequireFeatures(ctx, func(pm policy.Manager) error {\n\t\t\treturn d.Init(config.(*Config), pm, session.SockoptFromContext(ctx))\n\t\t})\n\t\treturn d, err\n\t}))\n}\n\ntype DokodemoDoor struct {\n\tpolicyManager policy.Manager\n\tconfig        *Config\n\taddress       net.Address\n\tport          net.Port\n\tportMap       map[string]string\n\tsockopt       *session.Sockopt\n}\n\n// Init initializes the DokodemoDoor instance with necessary parameters.\nfunc (d *DokodemoDoor) Init(config *Config, pm policy.Manager, sockopt *session.Sockopt) error {\n\tif len(config.Networks) == 0 {\n\t\treturn errors.New(\"no network specified\")\n\t}\n\td.config = config\n\td.address = config.GetPredefinedAddress()\n\td.port = net.Port(config.Port)\n\td.portMap = config.PortMap\n\td.policyManager = pm\n\td.sockopt = sockopt\n\n\treturn nil\n}\n\n// Network implements proxy.Inbound.\nfunc (d *DokodemoDoor) Network() []net.Network {\n\treturn d.config.Networks\n}\n\nfunc (d *DokodemoDoor) policy() policy.Session {\n\tconfig := d.config\n\tp := d.policyManager.ForLevel(config.UserLevel)\n\treturn p\n}\n\n// Process implements proxy.Inbound.\nfunc (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\terrors.LogDebug(ctx, \"processing connection from: \", conn.RemoteAddr())\n\tdest := net.Destination{\n\t\tNetwork: network,\n\t\tAddress: d.address,\n\t\tPort:    d.port,\n\t}\n\n\tif !d.config.FollowRedirect {\n\t\thost, port, err := net.SplitHostPort(conn.LocalAddr().String())\n\t\tif dest.Address == nil {\n\t\t\tif err != nil {\n\t\t\t\tdest.Address = net.DomainAddress(\"localhost\")\n\t\t\t} else {\n\t\t\t\tif strings.Contains(host, \".\") {\n\t\t\t\t\tdest.Address = net.LocalHostIP\n\t\t\t\t} else {\n\t\t\t\t\tdest.Address = net.LocalHostIPv6\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif dest.Port == 0 {\n\t\t\tdest.Port = net.Port(common.Must2(strconv.Atoi(port)))\n\t\t}\n\t\tif d.portMap != nil && d.portMap[port] != \"\" {\n\t\t\th, p, _ := net.SplitHostPort(d.portMap[port])\n\t\t\tif len(h) > 0 {\n\t\t\t\tdest.Address = net.ParseAddress(h)\n\t\t\t}\n\t\t\tif len(p) > 0 {\n\t\t\t\tdest.Port = net.Port(common.Must2(strconv.Atoi(p)))\n\t\t\t}\n\t\t}\n\t}\n\n\tdestinationOverridden := false\n\tif d.config.FollowRedirect {\n\t\toutbounds := session.OutboundsFromContext(ctx)\n\t\tif len(outbounds) > 0 {\n\t\t\tob := outbounds[len(outbounds)-1]\n\t\t\tif ob.Target.IsValid() {\n\t\t\t\tdest = ob.Target\n\t\t\t\tdestinationOverridden = true\n\t\t\t}\n\t\t}\n\t\tiConn := stat.TryUnwrapStatsConn(conn)\n\t\tif tlsConn, ok := iConn.(tls.Interface); ok && !destinationOverridden {\n\t\t\tif serverName := tlsConn.HandshakeContextServerName(ctx); serverName != \"\" {\n\t\t\t\tdest.Address = net.DomainAddress(serverName)\n\t\t\t\tdestinationOverridden = true\n\t\t\t\tctx = session.ContextWithMitmServerName(ctx, serverName)\n\t\t\t}\n\t\t\tif tlsConn.NegotiatedProtocol() != \"h2\" {\n\t\t\t\tctx = session.ContextWithMitmAlpn11(ctx, true)\n\t\t\t}\n\t\t}\n\t}\n\tif !dest.IsValid() || dest.Address == nil {\n\t\treturn errors.New(\"unable to get destination\")\n\t}\n\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"dokodemo-door\"\n\tinbound.CanSpliceCopy = 1\n\tinbound.User = &protocol.MemoryUser{\n\t\tLevel: d.config.UserLevel,\n\t}\n\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   conn.RemoteAddr(),\n\t\tTo:     dest,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t})\n\terrors.LogInfo(ctx, \"received request for \", conn.RemoteAddr())\n\n\tvar reader buf.Reader\n\tif dest.Network == net.Network_TCP {\n\t\treader = buf.NewReader(conn)\n\t} else {\n\t\treader = buf.NewPacketReader(conn)\n\t}\n\n\tvar writer buf.Writer\n\tif network == net.Network_TCP {\n\t\twriter = buf.NewWriter(conn)\n\t} else {\n\t\t// if we are in TPROXY mode, use linux's udp forging functionality\n\t\tif !destinationOverridden {\n\t\t\twriter = &buf.SequentialWriter{Writer: conn}\n\t\t} else {\n\t\t\tback := conn.RemoteAddr().(*net.UDPAddr)\n\t\t\tif !dest.Address.Family().IsIP() {\n\t\t\t\tif len(back.IP) == 4 {\n\t\t\t\t\tdest.Address = net.AnyIP\n\t\t\t\t} else {\n\t\t\t\t\tdest.Address = net.AnyIPv6\n\t\t\t\t}\n\t\t\t}\n\t\t\taddr := &net.UDPAddr{\n\t\t\t\tIP:   dest.Address.IP(),\n\t\t\t\tPort: int(dest.Port),\n\t\t\t}\n\t\t\tvar mark int\n\t\t\tif d.sockopt != nil {\n\t\t\t\tmark = int(d.sockopt.Mark)\n\t\t\t}\n\t\t\tpConn, err := FakeUDP(addr, mark)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\twriter = NewPacketWriter(pConn, &dest, mark, back)\n\t\t\tdefer writer.(*PacketWriter).Close() // close fake UDP conns\n\t\t}\n\t}\n\n\tif err := dispatcher.DispatchLink(ctx, dest, &transport.Link{\n\t\tReader: reader,\n\t\tWriter: writer},\n\t); err != nil {\n\t\treturn errors.New(\"failed to dispatch request\").Base(err)\n\t}\n\treturn nil // Unlike Dispatch(), DispatchLink() will not return until the outbound finishes Process()\n}\n\nfunc NewPacketWriter(conn net.PacketConn, d *net.Destination, mark int, back *net.UDPAddr) buf.Writer {\n\twriter := &PacketWriter{\n\t\tconn:  conn,\n\t\tconns: make(map[net.Destination]net.PacketConn),\n\t\tmark:  mark,\n\t\tback:  back,\n\t}\n\twriter.conns[*d] = conn\n\treturn writer\n}\n\ntype PacketWriter struct {\n\tconn  net.PacketConn\n\tconns map[net.Destination]net.PacketConn\n\tmark  int\n\tback  *net.UDPAddr\n}\n\nfunc (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor {\n\t\tmb2, b := buf.SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\t\tvar err error\n\t\tif b.UDP != nil && b.UDP.Address.Family().IsIP() {\n\t\t\tconn := w.conns[*b.UDP]\n\t\t\tif conn == nil {\n\t\t\t\tconn, err = FakeUDP(\n\t\t\t\t\t&net.UDPAddr{\n\t\t\t\t\t\tIP:   b.UDP.Address.IP(),\n\t\t\t\t\t\tPort: int(b.UDP.Port),\n\t\t\t\t\t},\n\t\t\t\t\tw.mark,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogInfo(context.Background(), err.Error())\n\t\t\t\t\tb.Release()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tw.conns[*b.UDP] = conn\n\t\t\t}\n\t\t\t_, err = conn.WriteTo(b.Bytes(), w.back)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfo(context.Background(), err.Error())\n\t\t\t\tw.conns[*b.UDP] = nil\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t\tb.Release()\n\t\t} else {\n\t\t\t_, err = w.conn.WriteTo(b.Bytes(), w.back)\n\t\t\tb.Release()\n\t\t\tif err != nil {\n\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (w *PacketWriter) Close() error {\n\tfor _, conn := range w.conns {\n\t\tif conn != nil {\n\t\t\tconn.Close()\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/dokodemo/fakeudp_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage dokodemo\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc FakeUDP(addr *net.UDPAddr, mark int) (net.PacketConn, error) {\n\tvar af int\n\tvar sockaddr syscall.Sockaddr\n\n\tif len(addr.IP) == 4 {\n\t\taf = syscall.AF_INET\n\t\tsockaddr = &syscall.SockaddrInet4{Port: addr.Port}\n\t\tcopy(sockaddr.(*syscall.SockaddrInet4).Addr[:], addr.IP)\n\t} else {\n\t\taf = syscall.AF_INET6\n\t\tsockaddr = &syscall.SockaddrInet6{Port: addr.Port}\n\t\tcopy(sockaddr.(*syscall.SockaddrInet6).Addr[:], addr.IP)\n\t}\n\n\tvar fd int\n\tvar err error\n\n\tif fd, err = syscall.Socket(af, syscall.SOCK_DGRAM, 0); err != nil {\n\t\treturn nil, &net.OpError{Op: \"fake\", Err: fmt.Errorf(\"socket open: %s\", err)}\n\t}\n\n\tif mark != 0 {\n\t\tif err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, mark); err != nil {\n\t\t\tsyscall.Close(fd)\n\t\t\treturn nil, &net.OpError{Op: \"fake\", Err: fmt.Errorf(\"set socket option: SO_MARK: %s\", err)}\n\t\t}\n\t}\n\n\tif err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {\n\t\tsyscall.Close(fd)\n\t\treturn nil, &net.OpError{Op: \"fake\", Err: fmt.Errorf(\"set socket option: IP_TRANSPARENT: %s\", err)}\n\t}\n\n\tsyscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)\n\n\tsyscall.SetsockoptInt(fd, syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)\n\n\tif err = syscall.Bind(fd, sockaddr); err != nil {\n\t\tsyscall.Close(fd)\n\t\treturn nil, &net.OpError{Op: \"fake\", Err: fmt.Errorf(\"socket bind: %s\", err)}\n\t}\n\n\tfdFile := os.NewFile(uintptr(fd), fmt.Sprintf(\"net-udp-fake-%s\", addr.String()))\n\tdefer fdFile.Close()\n\n\tpacketConn, err := net.FilePacketConn(fdFile)\n\tif err != nil {\n\t\tsyscall.Close(fd)\n\t\treturn nil, &net.OpError{Op: \"fake\", Err: fmt.Errorf(\"convert file descriptor to connection: %s\", err)}\n\t}\n\n\treturn packetConn, nil\n}\n"
  },
  {
    "path": "proxy/dokodemo/fakeudp_other.go",
    "content": "//go:build !linux\n// +build !linux\n\npackage dokodemo\n\nimport (\n\t\"fmt\"\n\t\"net\"\n)\n\nfunc FakeUDP(addr *net.UDPAddr, mark int) (net.PacketConn, error) {\n\treturn nil, &net.OpError{Op: \"fake\", Err: fmt.Errorf(\"!linux\")}\n}\n"
  },
  {
    "path": "proxy/freedom/config.go",
    "content": "package freedom\n"
  },
  {
    "path": "proxy/freedom/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/freedom/config.proto\n\npackage freedom\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tinternet \"github.com/xtls/xray-core/transport/internet\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DestinationOverride struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tServer        *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DestinationOverride) Reset() {\n\t*x = DestinationOverride{}\n\tmi := &file_proxy_freedom_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DestinationOverride) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DestinationOverride) ProtoMessage() {}\n\nfunc (x *DestinationOverride) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_freedom_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DestinationOverride.ProtoReflect.Descriptor instead.\nfunc (*DestinationOverride) Descriptor() ([]byte, []int) {\n\treturn file_proxy_freedom_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *DestinationOverride) GetServer() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\ntype Fragment struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPacketsFrom   uint64                 `protobuf:\"varint,1,opt,name=packets_from,json=packetsFrom,proto3\" json:\"packets_from,omitempty\"`\n\tPacketsTo     uint64                 `protobuf:\"varint,2,opt,name=packets_to,json=packetsTo,proto3\" json:\"packets_to,omitempty\"`\n\tLengthMin     uint64                 `protobuf:\"varint,3,opt,name=length_min,json=lengthMin,proto3\" json:\"length_min,omitempty\"`\n\tLengthMax     uint64                 `protobuf:\"varint,4,opt,name=length_max,json=lengthMax,proto3\" json:\"length_max,omitempty\"`\n\tIntervalMin   uint64                 `protobuf:\"varint,5,opt,name=interval_min,json=intervalMin,proto3\" json:\"interval_min,omitempty\"`\n\tIntervalMax   uint64                 `protobuf:\"varint,6,opt,name=interval_max,json=intervalMax,proto3\" json:\"interval_max,omitempty\"`\n\tMaxSplitMin   uint64                 `protobuf:\"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3\" json:\"max_split_min,omitempty\"`\n\tMaxSplitMax   uint64                 `protobuf:\"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3\" json:\"max_split_max,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Fragment) Reset() {\n\t*x = Fragment{}\n\tmi := &file_proxy_freedom_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Fragment) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Fragment) ProtoMessage() {}\n\nfunc (x *Fragment) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_freedom_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Fragment.ProtoReflect.Descriptor instead.\nfunc (*Fragment) Descriptor() ([]byte, []int) {\n\treturn file_proxy_freedom_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Fragment) GetPacketsFrom() uint64 {\n\tif x != nil {\n\t\treturn x.PacketsFrom\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetPacketsTo() uint64 {\n\tif x != nil {\n\t\treturn x.PacketsTo\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetLengthMin() uint64 {\n\tif x != nil {\n\t\treturn x.LengthMin\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetLengthMax() uint64 {\n\tif x != nil {\n\t\treturn x.LengthMax\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetIntervalMin() uint64 {\n\tif x != nil {\n\t\treturn x.IntervalMin\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetIntervalMax() uint64 {\n\tif x != nil {\n\t\treturn x.IntervalMax\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetMaxSplitMin() uint64 {\n\tif x != nil {\n\t\treturn x.MaxSplitMin\n\t}\n\treturn 0\n}\n\nfunc (x *Fragment) GetMaxSplitMax() uint64 {\n\tif x != nil {\n\t\treturn x.MaxSplitMax\n\t}\n\treturn 0\n}\n\ntype Noise struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tLengthMin     uint64                 `protobuf:\"varint,1,opt,name=length_min,json=lengthMin,proto3\" json:\"length_min,omitempty\"`\n\tLengthMax     uint64                 `protobuf:\"varint,2,opt,name=length_max,json=lengthMax,proto3\" json:\"length_max,omitempty\"`\n\tDelayMin      uint64                 `protobuf:\"varint,3,opt,name=delay_min,json=delayMin,proto3\" json:\"delay_min,omitempty\"`\n\tDelayMax      uint64                 `protobuf:\"varint,4,opt,name=delay_max,json=delayMax,proto3\" json:\"delay_max,omitempty\"`\n\tPacket        []byte                 `protobuf:\"bytes,5,opt,name=packet,proto3\" json:\"packet,omitempty\"`\n\tApplyTo       string                 `protobuf:\"bytes,6,opt,name=apply_to,json=applyTo,proto3\" json:\"apply_to,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Noise) Reset() {\n\t*x = Noise{}\n\tmi := &file_proxy_freedom_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Noise) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Noise) ProtoMessage() {}\n\nfunc (x *Noise) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_freedom_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Noise.ProtoReflect.Descriptor instead.\nfunc (*Noise) Descriptor() ([]byte, []int) {\n\treturn file_proxy_freedom_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Noise) GetLengthMin() uint64 {\n\tif x != nil {\n\t\treturn x.LengthMin\n\t}\n\treturn 0\n}\n\nfunc (x *Noise) GetLengthMax() uint64 {\n\tif x != nil {\n\t\treturn x.LengthMax\n\t}\n\treturn 0\n}\n\nfunc (x *Noise) GetDelayMin() uint64 {\n\tif x != nil {\n\t\treturn x.DelayMin\n\t}\n\treturn 0\n}\n\nfunc (x *Noise) GetDelayMax() uint64 {\n\tif x != nil {\n\t\treturn x.DelayMax\n\t}\n\treturn 0\n}\n\nfunc (x *Noise) GetPacket() []byte {\n\tif x != nil {\n\t\treturn x.Packet\n\t}\n\treturn nil\n}\n\nfunc (x *Noise) GetApplyTo() string {\n\tif x != nil {\n\t\treturn x.ApplyTo\n\t}\n\treturn \"\"\n}\n\ntype Config struct {\n\tstate               protoimpl.MessageState  `protogen:\"open.v1\"`\n\tDomainStrategy      internet.DomainStrategy `protobuf:\"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy\" json:\"domain_strategy,omitempty\"`\n\tDestinationOverride *DestinationOverride    `protobuf:\"bytes,3,opt,name=destination_override,json=destinationOverride,proto3\" json:\"destination_override,omitempty\"`\n\tUserLevel           uint32                  `protobuf:\"varint,4,opt,name=user_level,json=userLevel,proto3\" json:\"user_level,omitempty\"`\n\tFragment            *Fragment               `protobuf:\"bytes,5,opt,name=fragment,proto3\" json:\"fragment,omitempty\"`\n\tProxyProtocol       uint32                  `protobuf:\"varint,6,opt,name=proxy_protocol,json=proxyProtocol,proto3\" json:\"proxy_protocol,omitempty\"`\n\tNoises              []*Noise                `protobuf:\"bytes,7,rep,name=noises,proto3\" json:\"noises,omitempty\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_freedom_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_freedom_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_freedom_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Config) GetDomainStrategy() internet.DomainStrategy {\n\tif x != nil {\n\t\treturn x.DomainStrategy\n\t}\n\treturn internet.DomainStrategy(0)\n}\n\nfunc (x *Config) GetDestinationOverride() *DestinationOverride {\n\tif x != nil {\n\t\treturn x.DestinationOverride\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetUserLevel() uint32 {\n\tif x != nil {\n\t\treturn x.UserLevel\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetFragment() *Fragment {\n\tif x != nil {\n\t\treturn x.Fragment\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetProxyProtocol() uint32 {\n\tif x != nil {\n\t\treturn x.ProxyProtocol\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetNoises() []*Noise {\n\tif x != nil {\n\t\treturn x.Noises\n\t}\n\treturn nil\n}\n\nvar File_proxy_freedom_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_freedom_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1aproxy/freedom/config.proto\\x12\\x12xray.proxy.freedom\\x1a!common/protocol/server_spec.proto\\x1a\\x1ftransport/internet/config.proto\\\"S\\n\" +\n\t\"\\x13DestinationOverride\\x12<\\n\" +\n\t\"\\x06server\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x06server\\\"\\x98\\x02\\n\" +\n\t\"\\bFragment\\x12!\\n\" +\n\t\"\\fpackets_from\\x18\\x01 \\x01(\\x04R\\vpacketsFrom\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"packets_to\\x18\\x02 \\x01(\\x04R\\tpacketsTo\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"length_min\\x18\\x03 \\x01(\\x04R\\tlengthMin\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"length_max\\x18\\x04 \\x01(\\x04R\\tlengthMax\\x12!\\n\" +\n\t\"\\finterval_min\\x18\\x05 \\x01(\\x04R\\vintervalMin\\x12!\\n\" +\n\t\"\\finterval_max\\x18\\x06 \\x01(\\x04R\\vintervalMax\\x12\\\"\\n\" +\n\t\"\\rmax_split_min\\x18\\a \\x01(\\x04R\\vmaxSplitMin\\x12\\\"\\n\" +\n\t\"\\rmax_split_max\\x18\\b \\x01(\\x04R\\vmaxSplitMax\\\"\\xb2\\x01\\n\" +\n\t\"\\x05Noise\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"length_min\\x18\\x01 \\x01(\\x04R\\tlengthMin\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"length_max\\x18\\x02 \\x01(\\x04R\\tlengthMax\\x12\\x1b\\n\" +\n\t\"\\tdelay_min\\x18\\x03 \\x01(\\x04R\\bdelayMin\\x12\\x1b\\n\" +\n\t\"\\tdelay_max\\x18\\x04 \\x01(\\x04R\\bdelayMax\\x12\\x16\\n\" +\n\t\"\\x06packet\\x18\\x05 \\x01(\\fR\\x06packet\\x12\\x19\\n\" +\n\t\"\\bapply_to\\x18\\x06 \\x01(\\tR\\aapplyTo\\\"\\xe9\\x02\\n\" +\n\t\"\\x06Config\\x12P\\n\" +\n\t\"\\x0fdomain_strategy\\x18\\x01 \\x01(\\x0e2'.xray.transport.internet.DomainStrategyR\\x0edomainStrategy\\x12Z\\n\" +\n\t\"\\x14destination_override\\x18\\x03 \\x01(\\v2'.xray.proxy.freedom.DestinationOverrideR\\x13destinationOverride\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_level\\x18\\x04 \\x01(\\rR\\tuserLevel\\x128\\n\" +\n\t\"\\bfragment\\x18\\x05 \\x01(\\v2\\x1c.xray.proxy.freedom.FragmentR\\bfragment\\x12%\\n\" +\n\t\"\\x0eproxy_protocol\\x18\\x06 \\x01(\\rR\\rproxyProtocol\\x121\\n\" +\n\t\"\\x06noises\\x18\\a \\x03(\\v2\\x19.xray.proxy.freedom.NoiseR\\x06noisesBX\\n\" +\n\t\"\\x16com.xray.proxy.freedomP\\x01Z'github.com/xtls/xray-core/proxy/freedom\\xaa\\x02\\x12Xray.Proxy.Freedomb\\x06proto3\"\n\nvar (\n\tfile_proxy_freedom_config_proto_rawDescOnce sync.Once\n\tfile_proxy_freedom_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_freedom_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_freedom_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_freedom_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_freedom_config_proto_rawDescData\n}\n\nvar file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_proxy_freedom_config_proto_goTypes = []any{\n\t(*DestinationOverride)(nil),     // 0: xray.proxy.freedom.DestinationOverride\n\t(*Fragment)(nil),                // 1: xray.proxy.freedom.Fragment\n\t(*Noise)(nil),                   // 2: xray.proxy.freedom.Noise\n\t(*Config)(nil),                  // 3: xray.proxy.freedom.Config\n\t(*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint\n\t(internet.DomainStrategy)(0),    // 5: xray.transport.internet.DomainStrategy\n}\nvar file_proxy_freedom_config_proto_depIdxs = []int32{\n\t4, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint\n\t5, // 1: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy\n\t0, // 2: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride\n\t1, // 3: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment\n\t2, // 4: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_freedom_config_proto_init() }\nfunc file_proxy_freedom_config_proto_init() {\n\tif File_proxy_freedom_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_freedom_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_freedom_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_freedom_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_freedom_config_proto = out.File\n\tfile_proxy_freedom_config_proto_goTypes = nil\n\tfile_proxy_freedom_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/freedom/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.freedom;\noption csharp_namespace = \"Xray.Proxy.Freedom\";\noption go_package = \"github.com/xtls/xray-core/proxy/freedom\";\noption java_package = \"com.xray.proxy.freedom\";\noption java_multiple_files = true;\n\nimport \"common/protocol/server_spec.proto\";\nimport \"transport/internet/config.proto\";\n\nmessage DestinationOverride {\n  xray.common.protocol.ServerEndpoint server = 1;\n}\n\nmessage Fragment {\n  uint64 packets_from = 1;\n  uint64 packets_to = 2;\n  uint64 length_min = 3;\n  uint64 length_max = 4;\n  uint64 interval_min = 5;\n  uint64 interval_max = 6;\n  uint64 max_split_min = 7;\n  uint64 max_split_max = 8;\n}\nmessage Noise {\n  uint64 length_min = 1;\n  uint64 length_max = 2;\n  uint64 delay_min = 3;\n  uint64 delay_max = 4;\n  bytes packet = 5;\n  string apply_to = 6;\n}\n\nmessage Config {\n  xray.transport.internet.DomainStrategy domain_strategy = 1;\n  DestinationOverride destination_override = 3;\n  uint32 user_level = 4;\n  Fragment fragment = 5;\n  uint32 proxy_protocol = 6;\n  repeated Noise noises = 7;\n}\n"
  },
  {
    "path": "proxy/freedom/freedom.go",
    "content": "package freedom\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/pires/go-proxyproto\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nvar useSplice bool\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\th := new(Handler)\n\t\tif err := core.RequireFeatures(ctx, func(pm policy.Manager) error {\n\t\t\treturn h.Init(config.(*Config), pm)\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn h, nil\n\t}))\n\tconst defaultFlagValue = \"NOT_DEFINED_AT_ALL\"\n\tvalue := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })\n\tswitch value {\n\tcase defaultFlagValue, \"auto\", \"enable\":\n\t\tuseSplice = true\n\t}\n}\n\n// Handler handles Freedom connections.\ntype Handler struct {\n\tpolicyManager policy.Manager\n\tconfig        *Config\n}\n\n// Init initializes the Handler with necessary parameters.\nfunc (h *Handler) Init(config *Config, pm policy.Manager) error {\n\th.config = config\n\th.policyManager = pm\n\treturn nil\n}\n\nfunc (h *Handler) policy() policy.Session {\n\tp := h.policyManager.ForLevel(h.config.UserLevel)\n\treturn p\n}\n\nfunc isValidAddress(addr *net.IPOrDomain) bool {\n\tif addr == nil {\n\t\treturn false\n\t}\n\n\ta := addr.AsAddress()\n\treturn a != net.AnyIP && a != net.AnyIPv6\n}\n\n// Process implements proxy.Outbound.\nfunc (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified.\")\n\t}\n\tob.Name = \"freedom\"\n\tob.CanSpliceCopy = 1\n\tinbound := session.InboundFromContext(ctx)\n\n\tdestination := ob.Target\n\torigTargetAddr := ob.OriginalTarget.Address\n\tif origTargetAddr == nil {\n\t\torigTargetAddr = ob.Target.Address\n\t}\n\tdialer.SetOutboundGateway(ctx, ob)\n\toutGateway := ob.Gateway\n\tUDPOverride := net.UDPDestination(nil, 0)\n\tif h.config.DestinationOverride != nil {\n\t\tserver := h.config.DestinationOverride.Server\n\t\tif isValidAddress(server.Address) {\n\t\t\tdestination.Address = server.Address.AsAddress()\n\t\t\tUDPOverride.Address = destination.Address\n\t\t}\n\t\tif server.Port != 0 {\n\t\t\tdestination.Port = net.Port(server.Port)\n\t\t\tUDPOverride.Port = destination.Port\n\t\t}\n\t}\n\n\tinput := link.Reader\n\toutput := link.Writer\n\n\tvar conn stat.Connection\n\terr := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\tdialDest := destination\n\t\tif h.config.DomainStrategy.HasStrategy() && dialDest.Address.Family().IsDomain() {\n\t\t\tstrategy := h.config.DomainStrategy\n\t\t\tif destination.Network == net.Network_UDP && origTargetAddr != nil && outGateway == nil {\n\t\t\t\tstrategy = strategy.GetDynamicStrategy(origTargetAddr.Family())\n\t\t\t}\n\t\t\tips, err := internet.LookupForIP(dialDest.Address.Domain(), strategy, outGateway)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to get IP address for domain \", dialDest.Address.Domain())\n\t\t\t\tif h.config.DomainStrategy.ForceIP() {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdialDest = net.Destination{\n\t\t\t\t\tNetwork: dialDest.Network,\n\t\t\t\t\tAddress: net.IPAddress(ips[dice.Roll(len(ips))]),\n\t\t\t\t\tPort:    dialDest.Port,\n\t\t\t\t}\n\t\t\t\terrors.LogInfo(ctx, \"dialing to \", dialDest)\n\t\t\t}\n\t\t}\n\n\t\trawConn, err := dialer.Dial(ctx, dialDest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif h.config.ProxyProtocol > 0 && h.config.ProxyProtocol <= 2 {\n\t\t\tversion := byte(h.config.ProxyProtocol)\n\t\t\tsrcAddr := inbound.Source.RawNetAddr()\n\t\t\tdstAddr := rawConn.RemoteAddr()\n\t\t\theader := proxyproto.HeaderProxyFromAddrs(version, srcAddr, dstAddr)\n\t\t\tif _, err = header.WriteTo(rawConn); err != nil {\n\t\t\t\trawConn.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tconn = rawConn\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to open connection to \", destination).Base(err)\n\t}\n\tdefer conn.Close()\n\terrors.LogInfo(ctx, \"connection opened to \", destination, \", local endpoint \", conn.LocalAddr(), \", remote endpoint \", conn.RemoteAddr())\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tplcy := h.policy()\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, plcy.Timeouts.ConnectionIdle)\n\n\trequestDone := func() error {\n\t\tdefer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)\n\n\t\tvar writer buf.Writer\n\t\tif destination.Network == net.Network_TCP {\n\t\t\tif h.config.Fragment != nil {\n\t\t\t\terrors.LogDebug(ctx, \"FRAGMENT\", h.config.Fragment.PacketsFrom, h.config.Fragment.PacketsTo, h.config.Fragment.LengthMin, h.config.Fragment.LengthMax,\n\t\t\t\t\th.config.Fragment.IntervalMin, h.config.Fragment.IntervalMax, h.config.Fragment.MaxSplitMin, h.config.Fragment.MaxSplitMax)\n\t\t\t\twriter = buf.NewWriter(&FragmentWriter{\n\t\t\t\t\tfragment: h.config.Fragment,\n\t\t\t\t\twriter:   conn,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\twriter = buf.NewWriter(conn)\n\t\t\t}\n\t\t} else {\n\t\t\twriter = NewPacketWriter(conn, h, UDPOverride, destination)\n\t\t\tif h.config.Noises != nil {\n\t\t\t\terrors.LogDebug(ctx, \"NOISE\", h.config.Noises)\n\t\t\t\twriter = &NoisePacketWriter{\n\t\t\t\t\tWriter:      writer,\n\t\t\t\t\tnoises:      h.config.Noises,\n\t\t\t\t\tfirstWrite:  true,\n\t\t\t\t\tUDPOverride: UDPOverride,\n\t\t\t\t\tremoteAddr:  net.DestinationFromAddr(conn.RemoteAddr()).Address,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif err := buf.Copy(input, writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to process request\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tdefer timer.SetTimeout(plcy.Timeouts.UplinkOnly)\n\t\tif destination.Network == net.Network_TCP && useSplice && proxy.IsRAWTransportWithoutSecurity(conn) { // it would be tls conn in special use case of MITM, we need to let link handle traffic\n\t\t\tvar writeConn net.Conn\n\t\t\tvar inTimer *signal.ActivityTimer\n\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {\n\t\t\t\twriteConn = inbound.Conn\n\t\t\t\tinTimer = inbound.Timer\n\t\t\t}\n\t\t\treturn proxy.CopyRawConnIfExist(ctx, conn, writeConn, link.Writer, timer, inTimer)\n\t\t}\n\t\tvar reader buf.Reader\n\t\tif destination.Network == net.Network_TCP {\n\t\t\treader = buf.NewReader(conn)\n\t\t} else {\n\t\t\treader = NewPacketReader(conn, UDPOverride, destination)\n\t\t}\n\t\tif err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to process response\").Base(err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tif err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {\n\tiConn := conn\n\tstatConn, ok := iConn.(*stat.CounterConnection)\n\tif ok {\n\t\tiConn = statConn.Connection\n\t}\n\tvar counter stats.Counter\n\tif statConn != nil {\n\t\tcounter = statConn.ReadCounter\n\t}\n\tif c, ok := iConn.(*internet.PacketConnWrapper); ok {\n\t\tisOverridden := false\n\t\tif UDPOverride.Address != nil || UDPOverride.Port != 0 {\n\t\t\tisOverridden = true\n\t\t}\n\n\t\treturn &PacketReader{\n\t\t\tPacketConnWrapper: c,\n\t\t\tCounter:           counter,\n\t\t\tIsOverridden:      isOverridden,\n\t\t\tInitUnchangedAddr: DialDest.Address,\n\t\t\tInitChangedAddr:   net.DestinationFromAddr(conn.RemoteAddr()).Address,\n\t\t}\n\t}\n\treturn &buf.PacketReader{Reader: conn}\n}\n\ntype PacketReader struct {\n\t*internet.PacketConnWrapper\n\tstats.Counter\n\tIsOverridden      bool\n\tInitUnchangedAddr net.Address\n\tInitChangedAddr   net.Address\n}\n\nfunc (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tb := buf.New()\n\tb.Resize(0, buf.Size)\n\tn, d, err := r.PacketConnWrapper.ReadFrom(b.Bytes())\n\tif err != nil {\n\t\tb.Release()\n\t\treturn nil, err\n\t}\n\tb.Resize(0, int32(n))\n\t// if udp dest addr is changed, we are unable to get the correct src addr\n\t// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr\n\tif !r.IsOverridden {\n\t\taddress := net.IPAddress(d.(*net.UDPAddr).IP)\n\t\tif r.InitChangedAddr == address {\n\t\t\taddress = r.InitUnchangedAddr\n\t\t}\n\t\tb.UDP = &net.Destination{\n\t\t\tAddress: address,\n\t\t\tPort:    net.Port(d.(*net.UDPAddr).Port),\n\t\t\tNetwork: net.Network_UDP,\n\t\t}\n\t}\n\tif r.Counter != nil {\n\t\tr.Counter.Add(int64(n))\n\t}\n\treturn buf.MultiBuffer{b}, nil\n}\n\n// DialDest means the dial target used in the dialer when creating conn\nfunc NewPacketWriter(conn net.Conn, h *Handler, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {\n\tiConn := conn\n\tstatConn, ok := iConn.(*stat.CounterConnection)\n\tif ok {\n\t\tiConn = statConn.Connection\n\t}\n\tvar counter stats.Counter\n\tif statConn != nil {\n\t\tcounter = statConn.WriteCounter\n\t}\n\tif c, ok := iConn.(*internet.PacketConnWrapper); ok {\n\t\t// If DialDest is a domain, it will be resolved in dialer\n\t\t// check this behavior and add it to map\n\t\tresolvedUDPAddr := utils.NewTypedSyncMap[string, net.Address]()\n\t\tif DialDest.Address.Family().IsDomain() {\n\t\t\tresolvedUDPAddr.Store(DialDest.Address.Domain(), net.DestinationFromAddr(conn.RemoteAddr()).Address)\n\t\t}\n\t\treturn &PacketWriter{\n\t\t\tPacketConnWrapper: c,\n\t\t\tCounter:           counter,\n\t\t\tHandler:           h,\n\t\t\tUDPOverride:       UDPOverride,\n\t\t\tResolvedUDPAddr:   resolvedUDPAddr,\n\t\t\tLocalAddr:         net.DestinationFromAddr(conn.LocalAddr()).Address,\n\t\t}\n\n\t}\n\treturn &buf.SequentialWriter{Writer: conn}\n}\n\ntype PacketWriter struct {\n\t*internet.PacketConnWrapper\n\tstats.Counter\n\t*Handler\n\tUDPOverride net.Destination\n\n\t// Dest of udp packets might be a domain, we will resolve them to IP\n\t// But resolver will return a random one if the domain has many IPs\n\t// Resulting in these packets being sent to many different IPs randomly\n\t// So, cache and keep the resolve result\n\tResolvedUDPAddr *utils.TypedSyncMap[string, net.Address]\n\tLocalAddr       net.Address\n}\n\nfunc (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor {\n\t\tmb2, b := buf.SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\t\tvar n int\n\t\tvar err error\n\t\tif b.UDP != nil {\n\t\t\tif w.UDPOverride.Address != nil {\n\t\t\t\tb.UDP.Address = w.UDPOverride.Address\n\t\t\t}\n\t\t\tif w.UDPOverride.Port != 0 {\n\t\t\t\tb.UDP.Port = w.UDPOverride.Port\n\t\t\t}\n\t\t\tif b.UDP.Address.Family().IsDomain() {\n\t\t\t\tif ip, ok := w.ResolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {\n\t\t\t\t\tb.UDP.Address = ip\n\t\t\t\t} else {\n\t\t\t\t\tShouldUseSystemResolver := true\n\t\t\t\t\tif w.Handler.config.DomainStrategy.HasStrategy() {\n\t\t\t\t\t\tips, err := internet.LookupForIP(b.UDP.Address.Domain(), w.Handler.config.DomainStrategy, w.LocalAddr)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t// drop packet if resolve failed when forceIP\n\t\t\t\t\t\t\tif w.Handler.config.DomainStrategy.ForceIP() {\n\t\t\t\t\t\t\t\tb.Release()\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tip = net.IPAddress(ips[dice.Roll(len(ips))])\n\t\t\t\t\t\t\tShouldUseSystemResolver = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ShouldUseSystemResolver {\n\t\t\t\t\t\tudpAddr, err := net.ResolveUDPAddr(\"udp\", b.UDP.NetAddr())\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tb.Release()\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tip = net.IPAddress(udpAddr.IP)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ip != nil {\n\t\t\t\t\t\tb.UDP.Address, _ = w.ResolvedUDPAddr.LoadOrStore(b.UDP.Address.Domain(), ip)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tdestAddr := b.UDP.RawNetAddr()\n\t\t\tif destAddr == nil {\n\t\t\t\tb.Release()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tn, err = w.PacketConnWrapper.WriteTo(b.Bytes(), destAddr)\n\t\t} else {\n\t\t\tn, err = w.PacketConnWrapper.Write(b.Bytes())\n\t\t}\n\t\tb.Release()\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\t\tif w.Counter != nil {\n\t\t\tw.Counter.Add(int64(n))\n\t\t}\n\t}\n\treturn nil\n}\n\ntype NoisePacketWriter struct {\n\tbuf.Writer\n\tnoises      []*Noise\n\tfirstWrite  bool\n\tUDPOverride net.Destination\n\tremoteAddr  net.Address\n}\n\n// MultiBuffer writer with Noise before first packet\nfunc (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tif w.firstWrite {\n\t\tw.firstWrite = false\n\t\t//Do not send Noise for dns requests(just to be safe)\n\t\tif w.UDPOverride.Port == 53 {\n\t\t\treturn w.Writer.WriteMultiBuffer(mb)\n\t\t}\n\t\tvar noise []byte\n\t\tvar err error\n\t\tif w.remoteAddr.Family().IsDomain() {\n\t\t\tpanic(\"impossible, remoteAddr is always IP\")\n\t\t}\n\t\tfor _, n := range w.noises {\n\t\t\tswitch n.ApplyTo {\n\t\t\tcase \"ipv4\":\n\t\t\t\tif w.remoteAddr.Family().IsIPv6() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase \"ipv6\":\n\t\t\t\tif w.remoteAddr.Family().IsIPv4() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase \"ip\":\n\t\t\tdefault:\n\t\t\t\tpanic(\"unreachable, applyTo is ip/ipv4/ipv6\")\n\t\t\t}\n\t\t\t//User input string or base64 encoded string or hex string\n\t\t\tif n.Packet != nil {\n\t\t\t\tnoise = n.Packet\n\t\t\t} else {\n\t\t\t\t//Random noise\n\t\t\t\tnoise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin),\n\t\t\t\t\tint64(n.LengthMax)))\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif n.DelayMin != 0 || n.DelayMax != 0 {\n\t\t\t\ttime.Sleep(time.Duration(crypto.RandBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond)\n\t\t\t}\n\t\t}\n\n\t}\n\treturn w.Writer.WriteMultiBuffer(mb)\n}\n\ntype FragmentWriter struct {\n\tfragment *Fragment\n\twriter   io.Writer\n\tcount    uint64\n}\n\nfunc (f *FragmentWriter) Write(b []byte) (int, error) {\n\tf.count++\n\n\tif f.fragment.PacketsFrom == 0 && f.fragment.PacketsTo == 1 {\n\t\tif f.count != 1 || len(b) <= 5 || b[0] != 22 {\n\t\t\treturn f.writer.Write(b)\n\t\t}\n\t\trecordLen := 5 + ((int(b[3]) << 8) | int(b[4]))\n\t\tif len(b) < recordLen { // maybe already fragmented somehow\n\t\t\treturn f.writer.Write(b)\n\t\t}\n\t\tdata := b[5:recordLen]\n\t\tbuff := make([]byte, 2048)\n\t\tvar hello []byte\n\t\tmaxSplit := crypto.RandBetween(int64(f.fragment.MaxSplitMin), int64(f.fragment.MaxSplitMax))\n\t\tvar splitNum int64\n\t\tfor from := 0; ; {\n\t\t\tto := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))\n\t\t\tsplitNum++\n\t\t\tif to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) {\n\t\t\t\tto = len(data)\n\t\t\t}\n\t\t\tl := to - from\n\t\t\tif 5+l > len(buff) {\n\t\t\t\tbuff = make([]byte, 5+l)\n\t\t\t}\n\t\t\tcopy(buff[:3], b)\n\t\t\tcopy(buff[5:], data[from:to])\n\t\t\tfrom = to\n\t\t\tbuff[3] = byte(l >> 8)\n\t\t\tbuff[4] = byte(l)\n\t\t\tif f.fragment.IntervalMax == 0 { // combine fragmented tlshello if interval is 0\n\t\t\t\thello = append(hello, buff[:5+l]...)\n\t\t\t} else {\n\t\t\t\t_, err := f.writer.Write(buff[:5+l])\n\t\t\t\ttime.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif from == len(data) {\n\t\t\t\tif len(hello) > 0 {\n\t\t\t\t\t_, err := f.writer.Write(hello)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(b) > recordLen {\n\t\t\t\t\tn, err := f.writer.Write(b[recordLen:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn recordLen + n, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn len(b), nil\n\t\t\t}\n\t\t}\n\t}\n\n\tif f.fragment.PacketsFrom != 0 && (f.count < f.fragment.PacketsFrom || f.count > f.fragment.PacketsTo) {\n\t\treturn f.writer.Write(b)\n\t}\n\tmaxSplit := crypto.RandBetween(int64(f.fragment.MaxSplitMin), int64(f.fragment.MaxSplitMax))\n\tvar splitNum int64\n\tfor from := 0; ; {\n\t\tto := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))\n\t\tsplitNum++\n\t\tif to > len(b) || (maxSplit > 0 && splitNum >= maxSplit) {\n\t\t\tto = len(b)\n\t\t}\n\t\tn, err := f.writer.Write(b[from:to])\n\t\tfrom += n\n\t\tif err != nil {\n\t\t\treturn from, err\n\t\t}\n\t\ttime.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)\n\t\tif from >= len(b) {\n\t\t\treturn from, nil\n\t\t}\n\t}\n}\n\nfunc GenerateRandomBytes(n int64) ([]byte, error) {\n\tb := make([]byte, n)\n\t_, err := rand.Read(b)\n\t// Note that err == nil only if we read len(b) bytes.\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b, nil\n}\n"
  },
  {
    "path": "proxy/http/client.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\t\"text/template\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/bytespool\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"golang.org/x/net/http2\"\n)\n\ntype Client struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n\theader        []*Header\n}\n\ntype h2Conn struct {\n\trawConn net.Conn\n\th2Conn  *http2.ClientConn\n}\n\nvar (\n\tcachedH2Mutex sync.Mutex\n\tcachedH2Conns map[net.Destination]h2Conn\n)\n\n// NewClient create a new http client based on the given config.\nfunc NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {\n\tif config.Server == nil {\n\t\treturn nil, errors.New(`no target server found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Server)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err)\n\t}\n\n\tv := core.MustFromContext(ctx)\n\treturn &Client{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\theader:        config.Header,\n\t}, nil\n}\n\n// Process implements proxy.Outbound.Process. We first create a socket tunnel via HTTP CONNECT method, then redirect all inbound traffic to that tunnel.\nfunc (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified.\")\n\t}\n\tob.Name = \"http\"\n\tob.CanSpliceCopy = 2\n\ttarget := ob.Target\n\ttargetAddr := target.NetAddr()\n\n\tif target.Network == net.Network_UDP {\n\t\treturn errors.New(\"UDP is not supported by HTTP outbound\")\n\t}\n\n\tserver := c.server\n\tdest := server.Destination\n\tuser := server.User\n\tvar conn stat.Connection\n\n\tmbuf, _ := link.Reader.ReadMultiBuffer()\n\tlen := mbuf.Len()\n\tfirstPayload := bytespool.Alloc(len)\n\tmbuf, _ = buf.SplitBytes(mbuf, firstPayload)\n\tfirstPayload = firstPayload[:len]\n\n\tbuf.ReleaseMulti(mbuf)\n\tdefer bytespool.Free(firstPayload)\n\n\theader, err := fillRequestHeader(ctx, c.header)\n\tif err != nil {\n\t\treturn errors.New(\"failed to fill out header\").Base(err)\n\t}\n\n\tif err := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\tnetConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, header, firstPayload)\n\t\tif netConn != nil {\n\t\t\tif _, ok := netConn.(*http2Conn); !ok {\n\t\t\t\tif _, err := netConn.Write(firstPayload); err != nil {\n\t\t\t\t\tnetConn.Close()\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tconn = stat.Connection(netConn)\n\t\t}\n\t\treturn err\n\t}); err != nil {\n\t\treturn errors.New(\"failed to find an available destination\").Base(err)\n\t}\n\n\tdefer func() {\n\t\tif err := conn.Close(); err != nil {\n\t\t\terrors.LogInfoInner(ctx, err, \"failed to closed connection\")\n\t\t}\n\t}()\n\n\tp := c.policyManager.ForLevel(0)\n\tif user != nil {\n\t\tp = c.policyManager.ForLevel(user.Level)\n\t}\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, p.Timeouts.ConnectionIdle)\n\n\trequestFunc := func() error {\n\t\tdefer timer.SetTimeout(p.Timeouts.DownlinkOnly)\n\t\treturn buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))\n\t}\n\tresponseFunc := func() error {\n\t\tob.CanSpliceCopy = 1\n\t\tdefer timer.SetTimeout(p.Timeouts.UplinkOnly)\n\t\treturn buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tresponseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestFunc, responseDonePost); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\n// fillRequestHeader will fill out the template of the headers\nfunc fillRequestHeader(ctx context.Context, header []*Header) ([]*Header, error) {\n\tif len(header) == 0 {\n\t\treturn header, nil\n\t}\n\n\tinbound := session.InboundFromContext(ctx)\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\n\tif inbound == nil || ob == nil {\n\t\treturn nil, errors.New(\"missing inbound or outbound metadata from context\")\n\t}\n\n\tdata := struct {\n\t\tSource net.Destination\n\t\tTarget net.Destination\n\t}{\n\t\tSource: inbound.Source,\n\t\tTarget: ob.Target,\n\t}\n\n\tfilled := make([]*Header, len(header))\n\tfor i, h := range header {\n\t\ttmpl, err := template.New(h.Key).Parse(h.Value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar buf bytes.Buffer\n\n\t\tif err = tmpl.Execute(&buf, data); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfilled[i] = &Header{Key: h.Key, Value: buf.String()}\n\t}\n\n\treturn filled, nil\n}\n\n// setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method\nfunc setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, header []*Header, firstPayload []byte) (net.Conn, error) {\n\treq := &http.Request{\n\t\tMethod: http.MethodConnect,\n\t\tURL:    &url.URL{Host: target},\n\t\tHeader: make(http.Header),\n\t\tHost:   target,\n\t}\n\n\tif user != nil && user.Account != nil {\n\t\taccount := user.Account.(*Account)\n\t\tauth := account.GetUsername() + \":\" + account.GetPassword()\n\t\treq.Header.Set(\"Proxy-Authorization\", \"Basic \"+base64.StdEncoding.EncodeToString([]byte(auth)))\n\t}\n\n\tfor _, h := range header {\n\t\treq.Header.Set(h.Key, h.Value)\n\t}\n\tif req.Header.Get(\"User-Agent\") == \"\" {\n\t\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\t}\n\n\tconnectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {\n\t\treq.Header.Set(\"Proxy-Connection\", \"Keep-Alive\")\n\n\t\terr := req.Write(rawConn)\n\t\tif err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresp, err := http.ReadResponse(bufio.NewReader(rawConn), req)\n\t\tif err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\trawConn.Close()\n\t\t\treturn nil, errors.New(\"Proxy responded with non 200 code: \" + resp.Status)\n\t\t}\n\t\treturn rawConn, nil\n\t}\n\n\tconnectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) {\n\t\tpr, pw := io.Pipe()\n\t\treq.Body = pr\n\n\t\tvar pErr error\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(1)\n\n\t\tgo func() {\n\t\t\t_, pErr = pw.Write(firstPayload)\n\t\t\twg.Done()\n\t\t}()\n\n\t\tresp, err := h2clientConn.RoundTrip(req)\n\t\tif err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\n\t\twg.Wait()\n\t\tif pErr != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, pErr\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\trawConn.Close()\n\t\t\treturn nil, errors.New(\"Proxy responded with non 200 code: \" + resp.Status)\n\t\t}\n\t\treturn newHTTP2Conn(rawConn, pw, resp.Body), nil\n\t}\n\n\tcachedH2Mutex.Lock()\n\tcachedConn, cachedConnFound := cachedH2Conns[dest]\n\tcachedH2Mutex.Unlock()\n\n\tif cachedConnFound {\n\t\trc, cc := cachedConn.rawConn, cachedConn.h2Conn\n\t\tif cc.CanTakeNewRequest() {\n\t\t\tproxyConn, err := connectHTTP2(rc, cc)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn proxyConn, nil\n\t\t}\n\t}\n\n\trawConn, err := dialer.Dial(ctx, dest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tiConn := stat.TryUnwrapStatsConn(rawConn)\n\n\tnextProto := \"\"\n\tif tlsConn, ok := iConn.(*tls.Conn); ok {\n\t\tif err := tlsConn.HandshakeContext(ctx); err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tnextProto = tlsConn.ConnectionState().NegotiatedProtocol\n\t} else if tlsConn, ok := iConn.(*tls.UConn); ok {\n\t\tif err := tlsConn.HandshakeContext(ctx); err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tnextProto = tlsConn.ConnectionState().NegotiatedProtocol\n\t}\n\n\tswitch nextProto {\n\tcase \"\", \"http/1.1\":\n\t\treturn connectHTTP1(rawConn)\n\tcase \"h2\":\n\t\tt := http2.Transport{}\n\t\th2clientConn, err := t.NewClientConn(rawConn)\n\t\tif err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\n\t\tproxyConn, err := connectHTTP2(rawConn, h2clientConn)\n\t\tif err != nil {\n\t\t\trawConn.Close()\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcachedH2Mutex.Lock()\n\t\tif cachedH2Conns == nil {\n\t\t\tcachedH2Conns = make(map[net.Destination]h2Conn)\n\t\t}\n\n\t\tcachedH2Conns[dest] = h2Conn{\n\t\t\trawConn: rawConn,\n\t\t\th2Conn:  h2clientConn,\n\t\t}\n\t\tcachedH2Mutex.Unlock()\n\n\t\treturn proxyConn, err\n\tdefault:\n\t\treturn nil, errors.New(\"negotiated unsupported application layer protocol: \" + nextProto)\n\t}\n}\n\nfunc newHTTP2Conn(c net.Conn, pipedReqBody *io.PipeWriter, respBody io.ReadCloser) net.Conn {\n\treturn &http2Conn{Conn: c, in: pipedReqBody, out: respBody}\n}\n\ntype http2Conn struct {\n\tnet.Conn\n\tin  *io.PipeWriter\n\tout io.ReadCloser\n}\n\nfunc (h *http2Conn) Read(p []byte) (n int, err error) {\n\treturn h.out.Read(p)\n}\n\nfunc (h *http2Conn) Write(p []byte) (n int, err error) {\n\treturn h.in.Write(p)\n}\n\nfunc (h *http2Conn) Close() error {\n\th.in.Close()\n\treturn h.out.Close()\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewClient(ctx, config.(*ClientConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/http/config.go",
    "content": "package http\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nfunc (a *Account) Equals(another protocol.Account) bool {\n\tif account, ok := another.(*Account); ok {\n\t\treturn a.Username == account.Username\n\t}\n\treturn false\n}\n\nfunc (a *Account) ToProto() proto.Message {\n\treturn a\n}\n\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\treturn a, nil\n}\n\nfunc (sc *ServerConfig) HasAccount(username, password string) bool {\n\tif sc.Accounts == nil {\n\t\treturn false\n\t}\n\n\tp, found := sc.Accounts[username]\n\tif !found {\n\t\treturn false\n\t}\n\treturn p == password\n}\n"
  },
  {
    "path": "proxy/http/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/http/config.proto\n\npackage http\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Account struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsername      string                 `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword      string                 `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_http_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_http_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_http_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Account) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\n// Config for HTTP proxy server.\ntype ServerConfig struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tAccounts         map[string]string      `protobuf:\"bytes,2,rep,name=accounts,proto3\" json:\"accounts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tAllowTransparent bool                   `protobuf:\"varint,3,opt,name=allow_transparent,json=allowTransparent,proto3\" json:\"allow_transparent,omitempty\"`\n\tUserLevel        uint32                 `protobuf:\"varint,4,opt,name=user_level,json=userLevel,proto3\" json:\"user_level,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *ServerConfig) Reset() {\n\t*x = ServerConfig{}\n\tmi := &file_proxy_http_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerConfig) ProtoMessage() {}\n\nfunc (x *ServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_http_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.\nfunc (*ServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_http_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ServerConfig) GetAccounts() map[string]string {\n\tif x != nil {\n\t\treturn x.Accounts\n\t}\n\treturn nil\n}\n\nfunc (x *ServerConfig) GetAllowTransparent() bool {\n\tif x != nil {\n\t\treturn x.AllowTransparent\n\t}\n\treturn false\n}\n\nfunc (x *ServerConfig) GetUserLevel() uint32 {\n\tif x != nil {\n\t\treturn x.UserLevel\n\t}\n\treturn 0\n}\n\ntype Header struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tKey           string                 `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tValue         string                 `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Header) Reset() {\n\t*x = Header{}\n\tmi := &file_proxy_http_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Header) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Header) ProtoMessage() {}\n\nfunc (x *Header) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_http_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Header.ProtoReflect.Descriptor instead.\nfunc (*Header) Descriptor() ([]byte, []int) {\n\treturn file_proxy_http_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Header) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *Header) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\n// ClientConfig is the protobuf config for HTTP proxy client.\ntype ClientConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Sever is a list of HTTP server addresses.\n\tServer        *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tHeader        []*Header                `protobuf:\"bytes,2,rep,name=header,proto3\" json:\"header,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_proxy_http_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_http_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_http_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *ClientConfig) GetServer() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\nfunc (x *ClientConfig) GetHeader() []*Header {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nvar File_proxy_http_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_http_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x17proxy/http/config.proto\\x12\\x0fxray.proxy.http\\x1a!common/protocol/server_spec.proto\\\"A\\n\" +\n\t\"\\aAccount\\x12\\x1a\\n\" +\n\t\"\\busername\\x18\\x01 \\x01(\\tR\\busername\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x02 \\x01(\\tR\\bpassword\\\"\\xe0\\x01\\n\" +\n\t\"\\fServerConfig\\x12G\\n\" +\n\t\"\\baccounts\\x18\\x02 \\x03(\\v2+.xray.proxy.http.ServerConfig.AccountsEntryR\\baccounts\\x12+\\n\" +\n\t\"\\x11allow_transparent\\x18\\x03 \\x01(\\bR\\x10allowTransparent\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_level\\x18\\x04 \\x01(\\rR\\tuserLevel\\x1a;\\n\" +\n\t\"\\rAccountsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\"0\\n\" +\n\t\"\\x06Header\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value\\\"}\\n\" +\n\t\"\\fClientConfig\\x12<\\n\" +\n\t\"\\x06server\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x06server\\x12/\\n\" +\n\t\"\\x06header\\x18\\x02 \\x03(\\v2\\x17.xray.proxy.http.HeaderR\\x06headerBO\\n\" +\n\t\"\\x13com.xray.proxy.httpP\\x01Z$github.com/xtls/xray-core/proxy/http\\xaa\\x02\\x0fXray.Proxy.Httpb\\x06proto3\"\n\nvar (\n\tfile_proxy_http_config_proto_rawDescOnce sync.Once\n\tfile_proxy_http_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_http_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_http_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_http_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_http_config_proto_rawDesc), len(file_proxy_http_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_http_config_proto_rawDescData\n}\n\nvar file_proxy_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_proxy_http_config_proto_goTypes = []any{\n\t(*Account)(nil),                 // 0: xray.proxy.http.Account\n\t(*ServerConfig)(nil),            // 1: xray.proxy.http.ServerConfig\n\t(*Header)(nil),                  // 2: xray.proxy.http.Header\n\t(*ClientConfig)(nil),            // 3: xray.proxy.http.ClientConfig\n\tnil,                             // 4: xray.proxy.http.ServerConfig.AccountsEntry\n\t(*protocol.ServerEndpoint)(nil), // 5: xray.common.protocol.ServerEndpoint\n}\nvar file_proxy_http_config_proto_depIdxs = []int32{\n\t4, // 0: xray.proxy.http.ServerConfig.accounts:type_name -> xray.proxy.http.ServerConfig.AccountsEntry\n\t5, // 1: xray.proxy.http.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint\n\t2, // 2: xray.proxy.http.ClientConfig.header:type_name -> xray.proxy.http.Header\n\t3, // [3:3] is the sub-list for method output_type\n\t3, // [3:3] is the sub-list for method input_type\n\t3, // [3:3] is the sub-list for extension type_name\n\t3, // [3:3] is the sub-list for extension extendee\n\t0, // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_http_config_proto_init() }\nfunc file_proxy_http_config_proto_init() {\n\tif File_proxy_http_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_http_config_proto_rawDesc), len(file_proxy_http_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_http_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_http_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_http_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_http_config_proto = out.File\n\tfile_proxy_http_config_proto_goTypes = nil\n\tfile_proxy_http_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/http/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.http;\noption csharp_namespace = \"Xray.Proxy.Http\";\noption go_package = \"github.com/xtls/xray-core/proxy/http\";\noption java_package = \"com.xray.proxy.http\";\noption java_multiple_files = true;\n\nimport \"common/protocol/server_spec.proto\";\n\nmessage Account {\n  string username = 1;\n  string password = 2;\n}\n\n// Config for HTTP proxy server.\nmessage ServerConfig {\n  map<string, string> accounts = 2;\n  bool allow_transparent = 3;\n  uint32 user_level = 4;\n}\n\nmessage Header {\n  string key = 1;\n  string value = 2;\n}\n\n// ClientConfig is the protobuf config for HTTP proxy client.\nmessage ClientConfig {\n  // Sever is a list of HTTP server addresses.\n  xray.common.protocol.ServerEndpoint server = 1;\n  repeated Header header = 2;\n}\n"
  },
  {
    "path": "proxy/http/http.go",
    "content": "package http\n"
  },
  {
    "path": "proxy/http/server.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\thttp_proto \"github.com/xtls/xray-core/common/protocol/http\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Server is an HTTP proxy server.\ntype Server struct {\n\tconfig        *ServerConfig\n\tpolicyManager policy.Manager\n}\n\n// NewServer creates a new HTTP inbound handler.\nfunc NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {\n\tv := core.MustFromContext(ctx)\n\ts := &Server{\n\t\tconfig:        config,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\n\treturn s, nil\n}\n\nfunc (s *Server) policy() policy.Session {\n\tconfig := s.config\n\tp := s.policyManager.ForLevel(config.UserLevel)\n\treturn p\n}\n\n// Network implements proxy.Inbound.\nfunc (*Server) Network() []net.Network {\n\treturn []net.Network{net.Network_TCP, net.Network_UNIX}\n}\n\nfunc isTimeout(err error) bool {\n\tnerr, ok := errors.Cause(err).(net.Error)\n\treturn ok && nerr.Timeout()\n}\n\nfunc parseBasicAuth(auth string) (username, password string, ok bool) {\n\tconst prefix = \"Basic \"\n\tif !strings.HasPrefix(auth, prefix) {\n\t\treturn\n\t}\n\tc, err := base64.StdEncoding.DecodeString(auth[len(prefix):])\n\tif err != nil {\n\t\treturn\n\t}\n\tcs := string(c)\n\ts := strings.IndexByte(cs, ':')\n\tif s < 0 {\n\t\treturn\n\t}\n\treturn cs[:s], cs[s+1:], true\n}\n\ntype readerOnly struct {\n\tio.Reader\n}\n\nfunc (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\treturn s.ProcessWithFirstbyte(ctx, network, conn, dispatcher)\n}\n\n// Firstbyte is for forwarded conn from SOCKS inbound\n// Because it needs first byte to choose protocol\n// We need to add it back\n// Other parts are the same as the process function\nfunc (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher, firstbyte ...byte) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"http\"\n\tinbound.CanSpliceCopy = 2\n\tinbound.User = &protocol.MemoryUser{\n\t\tLevel: s.config.UserLevel,\n\t}\n\tif !proxy.IsRAWTransportWithoutSecurity(conn) {\n\t\tinbound.CanSpliceCopy = 3\n\t}\n\tvar reader *bufio.Reader\n\tif len(firstbyte) > 0 {\n\t\treaderWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size)\n\t\tmultiReader := io.MultiReader(bytes.NewReader(firstbyte), readerWithoutFirstbyte)\n\t\treader = bufio.NewReaderSize(multiReader, buf.Size)\n\t} else {\n\t\treader = bufio.NewReaderSize(readerOnly{conn}, buf.Size)\n\t}\n\nStart:\n\tif err := conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to set read deadline\")\n\t}\n\n\trequest, err := http.ReadRequest(reader)\n\tif err != nil {\n\t\ttrace := errors.New(\"failed to read http request\").Base(err)\n\t\tif errors.Cause(err) != io.EOF && !isTimeout(errors.Cause(err)) {\n\t\t\ttrace.AtWarning()\n\t\t}\n\t\treturn trace\n\t}\n\n\tif len(s.config.Accounts) > 0 {\n\t\tuser, pass, ok := parseBasicAuth(request.Header.Get(\"Proxy-Authorization\"))\n\t\tif !ok || !s.config.HasAccount(user, pass) {\n\t\t\treturn common.Error2(conn.Write([]byte(\"HTTP/1.1 407 Proxy Authentication Required\\r\\nProxy-Authenticate: Basic realm=\\\"proxy\\\"\\r\\n\\r\\n\")))\n\t\t}\n\t\tif inbound != nil {\n\t\t\tinbound.User.Email = user\n\t\t}\n\t}\n\n\terrors.LogInfo(ctx, \"request to Method [\", request.Method, \"] Host [\", request.Host, \"] with URL [\", request.URL, \"]\")\n\tif err := conn.SetReadDeadline(time.Time{}); err != nil {\n\t\terrors.LogDebugInner(ctx, err, \"failed to clear read deadline\")\n\t}\n\n\tdefaultPort := net.Port(80)\n\tif strings.EqualFold(request.URL.Scheme, \"https\") {\n\t\tdefaultPort = net.Port(443)\n\t}\n\thost := request.Host\n\tif host == \"\" {\n\t\thost = request.URL.Host\n\t}\n\tdest, err := http_proto.ParseHost(host, defaultPort)\n\tif err != nil {\n\t\treturn errors.New(\"malformed proxy host: \", host).AtWarning().Base(err)\n\t}\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   conn.RemoteAddr(),\n\t\tTo:     request.URL,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t})\n\n\tif strings.EqualFold(request.Method, \"CONNECT\") {\n\t\treturn s.handleConnect(ctx, request, reader, conn, dest, dispatcher, inbound)\n\t}\n\n\tkeepAlive := (strings.TrimSpace(strings.ToLower(request.Header.Get(\"Proxy-Connection\"))) == \"keep-alive\")\n\n\terr = s.handlePlainHTTP(ctx, request, conn, dest, dispatcher)\n\tif err == errWaitAnother {\n\t\tif keepAlive {\n\t\t\tgoto Start\n\t\t}\n\t\terr = nil\n\t}\n\n\treturn err\n}\n\nfunc (s *Server) handleConnect(ctx context.Context, _ *http.Request, buffer *bufio.Reader, conn stat.Connection, dest net.Destination, dispatcher routing.Dispatcher, inbound *session.Inbound) error {\n\t_, err := conn.Write([]byte(\"HTTP/1.1 200 Connection established\\r\\n\\r\\n\"))\n\tif err != nil {\n\t\treturn errors.New(\"failed to write back OK response\").Base(err)\n\t}\n\n\treader := buf.NewReader(conn)\n\tif buffer.Buffered() > 0 {\n\t\tpayload, err := buf.ReadFrom(io.LimitReader(buffer, int64(buffer.Buffered())))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treader = &buf.BufferedReader{Reader: reader, Buffer: payload}\n\t\tbuffer = nil\n\t}\n\n\tif inbound.CanSpliceCopy == 2 {\n\t\tinbound.CanSpliceCopy = 1\n\t}\n\tif err := dispatcher.DispatchLink(ctx, dest, &transport.Link{\n\t\tReader: reader,\n\t\tWriter: buf.NewWriter(conn)},\n\t); err != nil {\n\t\treturn errors.New(\"failed to dispatch request\").Base(err)\n\t}\n\treturn nil\n}\n\nvar errWaitAnother = errors.New(\"keep alive\")\n\nfunc (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, writer io.Writer, dest net.Destination, dispatcher routing.Dispatcher) error {\n\tif !s.config.AllowTransparent && request.URL.Host == \"\" {\n\t\t// RFC 2068 (HTTP/1.1) requires URL to be absolute URL in HTTP proxy.\n\t\tresponse := &http.Response{\n\t\t\tStatus:        \"Bad Request\",\n\t\t\tStatusCode:    400,\n\t\t\tProto:         \"HTTP/1.1\",\n\t\t\tProtoMajor:    1,\n\t\t\tProtoMinor:    1,\n\t\t\tHeader:        http.Header(make(map[string][]string)),\n\t\t\tBody:          nil,\n\t\t\tContentLength: 0,\n\t\t\tClose:         true,\n\t\t}\n\t\tresponse.Header.Set(\"Proxy-Connection\", \"close\")\n\t\tresponse.Header.Set(\"Connection\", \"close\")\n\t\treturn response.Write(writer)\n\t}\n\n\tif len(request.URL.Host) > 0 {\n\t\trequest.Host = request.URL.Host\n\t}\n\thttp_proto.RemoveHopByHopHeaders(request.Header)\n\n\t// Prevent UA from being set to golang's default ones\n\tif request.Header.Get(\"User-Agent\") == \"\" {\n\t\trequest.Header.Set(\"User-Agent\", \"\")\n\t}\n\n\tcontent := &session.Content{\n\t\tProtocol: \"http/1.1\",\n\t}\n\n\tcontent.SetAttribute(\":method\", strings.ToUpper(request.Method))\n\tcontent.SetAttribute(\":path\", request.URL.Path)\n\tfor key := range request.Header {\n\t\tvalue := request.Header.Get(key)\n\t\tcontent.SetAttribute(strings.ToLower(key), value)\n\t}\n\n\tctx = session.ContextWithContent(ctx, content)\n\n\tlink, err := dispatcher.Dispatch(ctx, dest)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Plain HTTP request is not a stream. The request always finishes before response. Hense request has to be closed later.\n\tdefer common.Close(link.Writer)\n\tvar result error = errWaitAnother\n\n\trequestDone := func() error {\n\t\trequest.Header.Set(\"Connection\", \"close\")\n\n\t\trequestWriter := buf.NewBufferedWriter(link.Writer)\n\t\tcommon.Must(requestWriter.SetBuffered(false))\n\t\tif err := request.Write(requestWriter); err != nil {\n\t\t\treturn errors.New(\"failed to write whole request\").Base(err).AtWarning()\n\t\t}\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tresponseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size)\n\t\tresponse, err := readResponseAndHandle100Continue(responseReader, request, writer)\n\t\tif err == nil {\n\t\t\thttp_proto.RemoveHopByHopHeaders(response.Header)\n\t\t\tif response.ContentLength >= 0 {\n\t\t\t\tresponse.Header.Set(\"Proxy-Connection\", \"keep-alive\")\n\t\t\t\tresponse.Header.Set(\"Connection\", \"keep-alive\")\n\t\t\t\tresponse.Header.Set(\"Keep-Alive\", \"timeout=60\")\n\t\t\t\tresponse.Close = false\n\t\t\t} else {\n\t\t\t\tresponse.Close = true\n\t\t\t\tresult = nil\n\t\t\t}\n\t\t\tdefer response.Body.Close()\n\t\t} else {\n\t\t\terrors.LogWarningInner(ctx, err, \"failed to read response from \", request.Host)\n\t\t\tresponse = &http.Response{\n\t\t\t\tStatus:        \"Service Unavailable\",\n\t\t\t\tStatusCode:    503,\n\t\t\t\tProto:         \"HTTP/1.1\",\n\t\t\t\tProtoMajor:    1,\n\t\t\t\tProtoMinor:    1,\n\t\t\t\tHeader:        http.Header(make(map[string][]string)),\n\t\t\t\tBody:          nil,\n\t\t\t\tContentLength: 0,\n\t\t\t\tClose:         true,\n\t\t\t}\n\t\t\tresponse.Header.Set(\"Connection\", \"close\")\n\t\t\tresponse.Header.Set(\"Proxy-Connection\", \"close\")\n\t\t}\n\t\tif err := response.Write(writer); err != nil {\n\t\t\treturn errors.New(\"failed to write response\").Base(err).AtWarning()\n\t\t}\n\t\treturn nil\n\t}\n\n\tif err := task.Run(ctx, requestDone, responseDone); err != nil {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn result\n}\n\n// Sometimes, server might send 1xx response to client\n// it should not be processed by http proxy handler, just forward it to client\nfunc readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) {\n\t// have a little look of response\n\tpeekBytes, err := r.Peek(56)\n\tif err == nil || err == bufio.ErrBufferFull {\n\t\tstr := string(peekBytes)\n\t\tResponseLine := strings.Split(str, \"\\r\\n\")[0]\n\t\t_, status, _ := strings.Cut(ResponseLine, \" \")\n\t\t// only handle 1xx response\n\t\tif strings.HasPrefix(status, \"1\") {\n\t\t\tResponseHeader1xx := []byte{}\n\t\t\t// read until \\r\\n\\r\\n (end of http response header)\n\t\t\tfor {\n\t\t\t\tdata, err := r.ReadSlice('\\n')\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.New(\"failed to read http 1xx response\").Base(err)\n\t\t\t\t}\n\t\t\t\tResponseHeader1xx = append(ResponseHeader1xx, data...)\n\t\t\t\tif bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\\r', '\\n', '\\r', '\\n'}) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif len(ResponseHeader1xx) > 1024 {\n\t\t\t\t\treturn nil, errors.New(\"too big http 1xx response\")\n\t\t\t\t}\n\t\t\t}\n\t\t\twriter.Write(ResponseHeader1xx)\n\t\t}\n\t}\n\treturn http.ReadResponse(r, req)\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewServer(ctx, config.(*ServerConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/hysteria/account/config.go",
    "content": "package account\n\nimport (\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\treturn &MemoryAccount{\n\t\tAuth: a.Auth,\n\t}, nil\n}\n\ntype MemoryAccount struct {\n\tAuth string\n}\n\nfunc (a *MemoryAccount) Equals(another protocol.Account) bool {\n\tif account, ok := another.(*MemoryAccount); ok {\n\t\treturn a.Auth == account.Auth\n\t}\n\treturn false\n}\n\nfunc (a *MemoryAccount) ToProto() proto.Message {\n\treturn &Account{\n\t\tAuth: a.Auth,\n\t}\n}\n\ntype Validator struct {\n\temails map[string]struct{}\n\tusers  map[string]*protocol.MemoryUser\n\n\tmutex sync.Mutex\n}\n\nfunc NewValidator() *Validator {\n\treturn &Validator{\n\t\temails: make(map[string]struct{}),\n\t\tusers:  make(map[string]*protocol.MemoryUser),\n\t}\n}\n\nfunc (v *Validator) Add(u *protocol.MemoryUser) error {\n\tv.mutex.Lock()\n\tdefer v.mutex.Unlock()\n\n\tif u.Email != \"\" {\n\t\tif _, ok := v.emails[u.Email]; ok {\n\t\t\treturn errors.New(\"User \", u.Email, \" already exists.\")\n\t\t}\n\t\tv.emails[u.Email] = struct{}{}\n\t}\n\tv.users[u.Account.(*MemoryAccount).Auth] = u\n\n\treturn nil\n}\n\nfunc (v *Validator) Del(email string) error {\n\tif email == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n\n\tv.mutex.Lock()\n\tdefer v.mutex.Unlock()\n\n\tif _, ok := v.emails[email]; !ok {\n\t\treturn errors.New(\"User \", email, \" not found.\")\n\t}\n\tdelete(v.emails, email)\n\tfor key, user := range v.users {\n\t\tif user.Email == email {\n\t\t\tdelete(v.users, key)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (v *Validator) Get(auth string) *protocol.MemoryUser {\n\tv.mutex.Lock()\n\tdefer v.mutex.Unlock()\n\n\treturn v.users[auth]\n}\n\nfunc (v *Validator) GetByEmail(email string) *protocol.MemoryUser {\n\tif email == \"\" {\n\t\treturn nil\n\t}\n\n\tv.mutex.Lock()\n\tdefer v.mutex.Unlock()\n\n\tif _, ok := v.emails[email]; ok {\n\t\tfor _, user := range v.users {\n\t\t\tif user.Email == email {\n\t\t\t\treturn user\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (v *Validator) GetAll() []*protocol.MemoryUser {\n\tv.mutex.Lock()\n\tdefer v.mutex.Unlock()\n\n\tvar users = make([]*protocol.MemoryUser, 0, len(v.users))\n\tfor _, user := range v.users {\n\t\tusers = append(users, user)\n\t}\n\n\treturn users\n}\n\nfunc (v *Validator) GetCount() int64 {\n\tv.mutex.Lock()\n\tdefer v.mutex.Unlock()\n\n\treturn int64(len(v.users))\n}\n"
  },
  {
    "path": "proxy/hysteria/account/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/hysteria/account/config.proto\n\npackage account\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Account struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAuth          string                 `protobuf:\"bytes,1,opt,name=auth,proto3\" json:\"auth,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_hysteria_account_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_hysteria_account_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_hysteria_account_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Account) GetAuth() string {\n\tif x != nil {\n\t\treturn x.Auth\n\t}\n\treturn \"\"\n}\n\nvar File_proxy_hysteria_account_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_hysteria_account_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#proxy/hysteria/account/config.proto\\x12\\x1bxray.proxy.hysteria.account\\\"\\x1d\\n\" +\n\t\"\\aAccount\\x12\\x12\\n\" +\n\t\"\\x04auth\\x18\\x01 \\x01(\\tR\\x04authBs\\n\" +\n\t\"\\x1fcom.xray.proxy.hysteria.accountP\\x01Z0github.com/xtls/xray-core/proxy/hysteria/account\\xaa\\x02\\x1bXray.Proxy.Hysteria.Accountb\\x06proto3\"\n\nvar (\n\tfile_proxy_hysteria_account_config_proto_rawDescOnce sync.Once\n\tfile_proxy_hysteria_account_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_hysteria_account_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_hysteria_account_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_hysteria_account_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_hysteria_account_config_proto_rawDesc), len(file_proxy_hysteria_account_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_hysteria_account_config_proto_rawDescData\n}\n\nvar file_proxy_hysteria_account_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_hysteria_account_config_proto_goTypes = []any{\n\t(*Account)(nil), // 0: xray.proxy.hysteria.account.Account\n}\nvar file_proxy_hysteria_account_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_hysteria_account_config_proto_init() }\nfunc file_proxy_hysteria_account_config_proto_init() {\n\tif File_proxy_hysteria_account_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_account_config_proto_rawDesc), len(file_proxy_hysteria_account_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_hysteria_account_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_hysteria_account_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_hysteria_account_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_hysteria_account_config_proto = out.File\n\tfile_proxy_hysteria_account_config_proto_goTypes = nil\n\tfile_proxy_hysteria_account_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/hysteria/account/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.hysteria.account;\noption csharp_namespace = \"Xray.Proxy.Hysteria.Account\";\noption go_package = \"github.com/xtls/xray-core/proxy/hysteria/account\";\noption java_package = \"com.xray.proxy.hysteria.account\";\noption java_multiple_files = true;\n\nmessage Account {\n  string auth = 1;\n}"
  },
  {
    "path": "proxy/hysteria/client.go",
    "content": "package hysteria\n\nimport (\n\t\"context\"\n\tgo_errors \"errors\"\n\t\"io\"\n\t\"math/rand\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\thyCtx \"github.com/xtls/xray-core/proxy/hysteria/ctx\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\ntype Client struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n}\n\nfunc NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {\n\tif config.Server == nil {\n\t\treturn nil, errors.New(`no target server found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Server)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err)\n\t}\n\n\tv := core.MustFromContext(ctx)\n\tclient := &Client{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\treturn client, nil\n}\n\nfunc (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified\")\n\t}\n\tob.Name = \"hysteria\"\n\tob.CanSpliceCopy = 3\n\ttarget := ob.Target\n\n\tconn, err := dialer.Dial(hyCtx.ContextWithRequireDatagram(ctx, target.Network == net.Network_UDP), c.server.Destination)\n\tif err != nil {\n\t\treturn errors.New(\"failed to find an available destination\").AtWarning().Base(err)\n\t}\n\tdefer conn.Close()\n\terrors.LogInfo(ctx, \"tunneling request to \", target, \" via \", target.Network, \":\", c.server.Destination.NetAddr())\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tsessionPolicy := c.policyManager.ForLevel(0)\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tif target.Network == net.Network_TCP {\n\t\trequestDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\t\t\tbufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\t\t\terr := WriteTCPRequest(bufferedWriter, target.NetAddr())\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"failed to write request\").Base(err)\n\t\t\t}\n\t\t\tif err := bufferedWriter.SetBuffered(false); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn buf.Copy(link.Reader, bufferedWriter, buf.UpdateActivity(timer))\n\t\t}\n\n\t\tresponseDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\t\t\tok, msg, err := ReadTCPResponse(conn)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(msg)\n\t\t\t}\n\t\t\treturn buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))\n\t\t}\n\n\t\tresponseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))\n\t\tif err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {\n\t\t\treturn errors.New(\"connection ends\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif target.Network == net.Network_UDP {\n\t\tiConn := stat.TryUnwrapStatsConn(conn)\n\t\t_, ok := iConn.(*hysteria.InterUdpConn)\n\t\tif !ok {\n\t\t\treturn errors.New(\"udp requires hysteria udp transport\")\n\t\t}\n\n\t\trequestDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\t\twriter := &UDPWriter{\n\t\t\t\tWriter: conn,\n\t\t\t\tbuf:    make([]byte, MaxUDPSize),\n\t\t\t\taddr:   target.NetAddr(),\n\t\t\t}\n\n\t\t\tif err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\t\treturn errors.New(\"failed to transport all UDP request\").Base(err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\n\t\tresponseDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\t\treader := &UDPReader{\n\t\t\t\tReader: conn,\n\t\t\t\tbuf:    make([]byte, MaxUDPSize),\n\t\t\t\tdf:     &Defragger{},\n\t\t\t}\n\n\t\t\tif err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\t\treturn errors.New(\"failed to transport all UDP response\").Base(err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\n\t\tresponseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))\n\t\tif err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {\n\t\t\treturn errors.New(\"connection ends\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewClient(ctx, config.(*ClientConfig))\n\t}))\n}\n\ntype UDPWriter struct {\n\tWriter io.Writer\n\tbuf    []byte\n\taddr   string\n}\n\nfunc (w *UDPWriter) sendMsg(msg *UDPMessage) error {\n\tmsgN := msg.Serialize(w.buf)\n\tif msgN < 0 {\n\t\treturn nil\n\t}\n\t_, err := w.Writer.Write(w.buf[:msgN])\n\treturn err\n}\n\nfunc (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor {\n\t\tmb2, b := buf.SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\n\t\taddr := w.addr\n\t\tif b.UDP != nil {\n\t\t\taddr = b.UDP.NetAddr()\n\t\t}\n\n\t\tmsg := &UDPMessage{\n\t\t\tSessionID: 0,\n\t\t\tPacketID:  0,\n\t\t\tFragID:    0,\n\t\t\tFragCount: 1,\n\t\t\tAddr:      addr,\n\t\t\tData:      b.Bytes(),\n\t\t}\n\n\t\terr := w.sendMsg(msg)\n\t\tvar errTooLarge *quic.DatagramTooLargeError\n\t\tif go_errors.As(err, &errTooLarge) {\n\t\t\tmsg.PacketID = uint16(rand.Intn(0xFFFF)) + 1\n\t\t\tfMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))\n\t\t\tfor _, fMsg := range fMsgs {\n\t\t\t\terr := w.sendMsg(&fMsg)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Release()\n\t\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t} else if err != nil {\n\t\t\tb.Release()\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\n\t\tb.Release()\n\t}\n\n\treturn nil\n}\n\ntype UDPReader struct {\n\tReader    io.Reader\n\tbuf       []byte\n\tdf        *Defragger\n\tfirstMsg  *UDPMessage\n\tfirstDest *net.Destination\n}\n\nfunc (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tif r.firstMsg != nil {\n\t\tbuffer := buf.New()\n\t\tbuffer.Write(r.firstMsg.Data)\n\t\tbuffer.UDP = r.firstDest\n\n\t\tr.firstMsg = nil\n\n\t\treturn buf.MultiBuffer{buffer}, nil\n\t}\n\tfor {\n\t\tn, err := r.Reader.Read(r.buf)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tmsg, err := ParseUDPMessage(r.buf[:n])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tdfMsg := r.df.Feed(msg)\n\t\tif dfMsg == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tdest, err := net.ParseDestination(\"udp:\" + dfMsg.Addr)\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), dfMsg.Addr, \" ParseDestination err \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tbuffer := buf.New()\n\t\tbuffer.Write(dfMsg.Data)\n\t\tbuffer.UDP = &dest\n\n\t\treturn buf.MultiBuffer{buffer}, nil\n\t}\n}\n"
  },
  {
    "path": "proxy/hysteria/config.go",
    "content": "package hysteria\n\nimport (\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/padding\"\n)\n\nvar (\n\ttcpRequestPadding  = padding.Padding{Min: 64, Max: 512}\n\ttcpResponsePadding = padding.Padding{Min: 128, Max: 1024}\n)\n"
  },
  {
    "path": "proxy/hysteria/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/hysteria/config.proto\n\npackage hysteria\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ClientConfig struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tVersion       int32                    `protobuf:\"varint,1,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tServer        *protocol.ServerEndpoint `protobuf:\"bytes,2,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_proxy_hysteria_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_hysteria_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ClientConfig) GetVersion() int32 {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn 0\n}\n\nfunc (x *ClientConfig) GetServer() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\ntype ServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsers         []*protocol.User       `protobuf:\"bytes,1,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServerConfig) Reset() {\n\t*x = ServerConfig{}\n\tmi := &file_proxy_hysteria_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerConfig) ProtoMessage() {}\n\nfunc (x *ServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_hysteria_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.\nfunc (*ServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_hysteria_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ServerConfig) GetUsers() []*protocol.User {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\nvar File_proxy_hysteria_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_hysteria_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1bproxy/hysteria/config.proto\\x12\\x13xray.proxy.hysteria\\x1a!common/protocol/server_spec.proto\\x1a\\x1acommon/protocol/user.proto\\\"f\\n\" +\n\t\"\\fClientConfig\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x01 \\x01(\\x05R\\aversion\\x12<\\n\" +\n\t\"\\x06server\\x18\\x02 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x06server\\\"@\\n\" +\n\t\"\\fServerConfig\\x120\\n\" +\n\t\"\\x05users\\x18\\x01 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\x05usersB[\\n\" +\n\t\"\\x17com.xray.proxy.hysteriaP\\x01Z(github.com/xtls/xray-core/proxy/hysteria\\xaa\\x02\\x13Xray.Proxy.Hysteriab\\x06proto3\"\n\nvar (\n\tfile_proxy_hysteria_config_proto_rawDescOnce sync.Once\n\tfile_proxy_hysteria_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_hysteria_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_hysteria_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_hysteria_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_hysteria_config_proto_rawDescData\n}\n\nvar file_proxy_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_proxy_hysteria_config_proto_goTypes = []any{\n\t(*ClientConfig)(nil),            // 0: xray.proxy.hysteria.ClientConfig\n\t(*ServerConfig)(nil),            // 1: xray.proxy.hysteria.ServerConfig\n\t(*protocol.ServerEndpoint)(nil), // 2: xray.common.protocol.ServerEndpoint\n\t(*protocol.User)(nil),           // 3: xray.common.protocol.User\n}\nvar file_proxy_hysteria_config_proto_depIdxs = []int32{\n\t2, // 0: xray.proxy.hysteria.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint\n\t3, // 1: xray.proxy.hysteria.ServerConfig.users:type_name -> xray.common.protocol.User\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_hysteria_config_proto_init() }\nfunc file_proxy_hysteria_config_proto_init() {\n\tif File_proxy_hysteria_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_hysteria_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_hysteria_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_hysteria_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_hysteria_config_proto = out.File\n\tfile_proxy_hysteria_config_proto_goTypes = nil\n\tfile_proxy_hysteria_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/hysteria/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.hysteria;\noption csharp_namespace = \"Xray.Proxy.Hysteria\";\noption go_package = \"github.com/xtls/xray-core/proxy/hysteria\";\noption java_package = \"com.xray.proxy.hysteria\";\noption java_multiple_files = true;\n\nimport \"common/protocol/server_spec.proto\";\nimport \"common/protocol/user.proto\";\n\nmessage ClientConfig {\n  int32 version = 1;\n  xray.common.protocol.ServerEndpoint server = 2;\n}\n\nmessage ServerConfig {\n  repeated xray.common.protocol.User users = 1;\n}"
  },
  {
    "path": "proxy/hysteria/ctx/ctx.go",
    "content": "package ctx\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/proxy/hysteria/account\"\n)\n\ntype key int\n\nconst (\n\trequireDatagram key = iota\n\tvalidator\n)\n\nfunc ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context {\n\tif !udp {\n\t\treturn ctx\n\t}\n\treturn context.WithValue(ctx, requireDatagram, struct{}{})\n}\n\nfunc RequireDatagramFromContext(ctx context.Context) bool {\n\t_, ok := ctx.Value(requireDatagram).(struct{})\n\treturn ok\n}\n\nfunc ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {\n\treturn context.WithValue(ctx, validator, v)\n}\n\nfunc ValidatorFromContext(ctx context.Context) *account.Validator {\n\tv, _ := ctx.Value(validator).(*account.Validator)\n\treturn v\n}\n"
  },
  {
    "path": "proxy/hysteria/frag.go",
    "content": "package hysteria\n\nfunc FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {\n\tif m.Size() <= maxSize {\n\t\treturn []UDPMessage{*m}\n\t}\n\tfullPayload := m.Data\n\tmaxPayloadSize := maxSize - m.HeaderSize()\n\toff := 0\n\tfragID := uint8(0)\n\tfragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up\n\tfrags := make([]UDPMessage, fragCount)\n\tfor off < len(fullPayload) {\n\t\tpayloadSize := len(fullPayload) - off\n\t\tif payloadSize > maxPayloadSize {\n\t\t\tpayloadSize = maxPayloadSize\n\t\t}\n\t\tfrag := *m\n\t\tfrag.FragID = fragID\n\t\tfrag.FragCount = fragCount\n\t\tfrag.Data = fullPayload[off : off+payloadSize]\n\t\tfrags[fragID] = frag\n\t\toff += payloadSize\n\t\tfragID++\n\t}\n\treturn frags\n}\n\n// Defragger handles the defragmentation of UDP messages.\n// The current implementation can only handle one packet ID at a time.\n// If another packet arrives before a packet has received all fragments\n// in their entirety, any previous state is discarded.\ntype Defragger struct {\n\tpktID uint16\n\tfrags []*UDPMessage\n\tcount uint8\n\tsize  int // data size\n}\n\nfunc (d *Defragger) Feed(m *UDPMessage) *UDPMessage {\n\tif m.FragCount <= 1 {\n\t\treturn m\n\t}\n\tif m.FragID >= m.FragCount {\n\t\t// wtf is this?\n\t\treturn nil\n\t}\n\tif m.PacketID != d.pktID || m.FragCount != uint8(len(d.frags)) {\n\t\t// new message, clear previous state\n\t\td.pktID = m.PacketID\n\t\td.frags = make([]*UDPMessage, m.FragCount)\n\t\td.frags[m.FragID] = m\n\t\td.count = 1\n\t\td.size = len(m.Data)\n\t} else if d.frags[m.FragID] == nil {\n\t\td.frags[m.FragID] = m\n\t\td.count++\n\t\td.size += len(m.Data)\n\t\tif int(d.count) == len(d.frags) {\n\t\t\t// all fragments received, assemble\n\t\t\tdata := make([]byte, d.size)\n\t\t\toff := 0\n\t\t\tfor _, frag := range d.frags {\n\t\t\t\toff += copy(data[off:], frag.Data)\n\t\t\t}\n\t\t\tm.Data = data\n\t\t\tm.FragID = 0\n\t\t\tm.FragCount = 1\n\t\t\treturn m\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/hysteria/protocol.go",
    "content": "package hysteria\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/apernet/quic-go/quicvarint\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nconst (\n\t// Max length values are for preventing DoS attacks\n\n\tMaxAddressLength = 2048\n\tMaxMessageLength = 2048\n\tMaxPaddingLength = 4096\n\n\tMaxUDPSize = 4096\n\n\tmaxVarInt1 = 63\n\tmaxVarInt2 = 16383\n\tmaxVarInt4 = 1073741823\n\tmaxVarInt8 = 4611686018427387903\n)\n\n// TCPRequest format:\n// Address length (QUIC varint)\n// Address (bytes)\n// Padding length (QUIC varint)\n// Padding (bytes)\n\nfunc ReadTCPRequest(r io.Reader) (string, error) {\n\tbReader := quicvarint.NewReader(r)\n\taddrLen, err := quicvarint.Read(bReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif addrLen == 0 || addrLen > MaxAddressLength {\n\t\treturn \"\", errors.New(\"invalid address length\")\n\t}\n\taddrBuf := make([]byte, addrLen)\n\t_, err = io.ReadFull(r, addrBuf)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tpaddingLen, err := quicvarint.Read(bReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif paddingLen > MaxPaddingLength {\n\t\treturn \"\", errors.New(\"invalid padding length\")\n\t}\n\tif paddingLen > 0 {\n\t\t_, err = io.CopyN(io.Discard, r, int64(paddingLen))\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn string(addrBuf), nil\n}\n\nfunc WriteTCPRequest(w io.Writer, addr string) error {\n\tpadding := tcpRequestPadding.String()\n\tpaddingLen := len(padding)\n\taddrLen := len(addr)\n\tsz := int(quicvarint.Len(uint64(addrLen))) + addrLen +\n\t\tint(quicvarint.Len(uint64(paddingLen))) + paddingLen\n\tbuf := make([]byte, sz)\n\ti := varintPut(buf, uint64(addrLen))\n\ti += copy(buf[i:], addr)\n\ti += varintPut(buf[i:], uint64(paddingLen))\n\tcopy(buf[i:], padding)\n\t_, err := w.Write(buf)\n\treturn err\n}\n\n// TCPResponse format:\n// Status (byte, 0=ok, 1=error)\n// Message length (QUIC varint)\n// Message (bytes)\n// Padding length (QUIC varint)\n// Padding (bytes)\n\nfunc ReadTCPResponse(r io.Reader) (bool, string, error) {\n\tvar status [1]byte\n\tif _, err := io.ReadFull(r, status[:]); err != nil {\n\t\treturn false, \"\", err\n\t}\n\tbReader := quicvarint.NewReader(r)\n\tmsgLen, err := quicvarint.Read(bReader)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\tif msgLen > MaxMessageLength {\n\t\treturn false, \"\", errors.New(\"invalid message length\")\n\t}\n\tvar msgBuf []byte\n\t// No message is fine\n\tif msgLen > 0 {\n\t\tmsgBuf = make([]byte, msgLen)\n\t\t_, err = io.ReadFull(r, msgBuf)\n\t\tif err != nil {\n\t\t\treturn false, \"\", err\n\t\t}\n\t}\n\tpaddingLen, err := quicvarint.Read(bReader)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\tif paddingLen > MaxPaddingLength {\n\t\treturn false, \"\", errors.New(\"invalid padding length\")\n\t}\n\tif paddingLen > 0 {\n\t\t_, err = io.CopyN(io.Discard, r, int64(paddingLen))\n\t\tif err != nil {\n\t\t\treturn false, \"\", err\n\t\t}\n\t}\n\treturn status[0] == 0, string(msgBuf), nil\n}\n\nfunc WriteTCPResponse(w io.Writer, ok bool, msg string) error {\n\tpadding := tcpResponsePadding.String()\n\tpaddingLen := len(padding)\n\tmsgLen := len(msg)\n\tsz := 1 + int(quicvarint.Len(uint64(msgLen))) + msgLen +\n\t\tint(quicvarint.Len(uint64(paddingLen))) + paddingLen\n\tbuf := make([]byte, sz)\n\tif ok {\n\t\tbuf[0] = 0\n\t} else {\n\t\tbuf[0] = 1\n\t}\n\ti := varintPut(buf[1:], uint64(msgLen))\n\ti += copy(buf[1+i:], msg)\n\ti += varintPut(buf[1+i:], uint64(paddingLen))\n\tcopy(buf[1+i:], padding)\n\t_, err := w.Write(buf)\n\treturn err\n}\n\n// UDPMessage format:\n// Session ID (uint32 BE)\n// Packet ID (uint16 BE)\n// Fragment ID (uint8)\n// Fragment count (uint8)\n// Address length (QUIC varint)\n// Address (bytes)\n// Data...\n\ntype UDPMessage struct {\n\tSessionID uint32 // 4\n\tPacketID  uint16 // 2\n\tFragID    uint8  // 1\n\tFragCount uint8  // 1\n\tAddr      string // varint + bytes\n\tData      []byte\n}\n\nfunc (m *UDPMessage) HeaderSize() int {\n\tlAddr := len(m.Addr)\n\treturn 4 + 2 + 1 + 1 + int(quicvarint.Len(uint64(lAddr))) + lAddr\n}\n\nfunc (m *UDPMessage) Size() int {\n\treturn m.HeaderSize() + len(m.Data)\n}\n\nfunc (m *UDPMessage) Serialize(buf []byte) int {\n\t// Make sure the buffer is big enough\n\tif len(buf) < m.Size() {\n\t\treturn -1\n\t}\n\t// binary.BigEndian.PutUint32(buf, m.SessionID)\n\tbinary.BigEndian.PutUint16(buf[4:], m.PacketID)\n\tbuf[6] = m.FragID\n\tbuf[7] = m.FragCount\n\ti := varintPut(buf[8:], uint64(len(m.Addr)))\n\ti += copy(buf[8+i:], m.Addr)\n\ti += copy(buf[8+i:], m.Data)\n\treturn 8 + i\n}\n\nfunc ParseUDPMessage(msg []byte) (*UDPMessage, error) {\n\tm := &UDPMessage{}\n\tbuf := bytes.NewBuffer(msg)\n\tif err := binary.Read(buf, binary.BigEndian, &m.SessionID); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(buf, binary.BigEndian, &m.PacketID); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(buf, binary.BigEndian, &m.FragID); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(buf, binary.BigEndian, &m.FragCount); err != nil {\n\t\treturn nil, err\n\t}\n\tlAddr, err := quicvarint.Read(buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif lAddr == 0 || lAddr > MaxMessageLength {\n\t\treturn nil, errors.New(\"invalid address length\")\n\t}\n\tbs := buf.Bytes()\n\tif len(bs) <= int(lAddr) {\n\t\t// We use <= instead of < here as we expect at least one byte of data after the address\n\t\treturn nil, errors.New(\"invalid message length\")\n\t}\n\tm.Addr = string(bs[:lAddr])\n\tm.Data = bs[lAddr:]\n\treturn m, nil\n}\n\n// varintPut is like quicvarint.Append, but instead of appending to a slice,\n// it writes to a fixed-size buffer. Returns the number of bytes written.\nfunc varintPut(b []byte, i uint64) int {\n\tif i <= maxVarInt1 {\n\t\tb[0] = uint8(i)\n\t\treturn 1\n\t}\n\tif i <= maxVarInt2 {\n\t\tb[0] = uint8(i>>8) | 0x40\n\t\tb[1] = uint8(i)\n\t\treturn 2\n\t}\n\tif i <= maxVarInt4 {\n\t\tb[0] = uint8(i>>24) | 0x80\n\t\tb[1] = uint8(i >> 16)\n\t\tb[2] = uint8(i >> 8)\n\t\tb[3] = uint8(i)\n\t\treturn 4\n\t}\n\tif i <= maxVarInt8 {\n\t\tb[0] = uint8(i>>56) | 0xc0\n\t\tb[1] = uint8(i >> 48)\n\t\tb[2] = uint8(i >> 40)\n\t\tb[3] = uint8(i >> 32)\n\t\tb[4] = uint8(i >> 24)\n\t\tb[5] = uint8(i >> 16)\n\t\tb[6] = uint8(i >> 8)\n\t\tb[7] = uint8(i)\n\t\treturn 8\n\t}\n\tpanic(fmt.Sprintf(\"%#x doesn't fit into 62 bits\", i))\n}\n"
  },
  {
    "path": "proxy/hysteria/server.go",
    "content": "package hysteria\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/proxy/hysteria/account\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\ntype Server struct {\n\tconfig        *ServerConfig\n\tvalidator     *account.Validator\n\tpolicyManager policy.Manager\n}\n\nfunc NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {\n\tvalidator := account.NewValidator()\n\tfor _, user := range config.Users {\n\t\tu, err := user.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get hysteria user\").Base(err).AtError()\n\t\t}\n\n\t\tif err := validator.Add(u); err != nil {\n\t\t\treturn nil, errors.New(\"failed to add user\").Base(err).AtError()\n\t\t}\n\t}\n\n\tv := core.MustFromContext(ctx)\n\ts := &Server{\n\t\tconfig:        config,\n\t\tvalidator:     validator,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\n\treturn s, nil\n}\n\nfunc (s *Server) HysteriaInboundValidator() *account.Validator {\n\treturn s.validator\n}\n\nfunc (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {\n\treturn s.validator.Add(u)\n}\n\nfunc (s *Server) RemoveUser(ctx context.Context, e string) error {\n\treturn s.validator.Del(e)\n}\n\nfunc (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {\n\treturn s.validator.GetByEmail(email)\n}\n\nfunc (s *Server) GetUsers(ctx context.Context) []*protocol.MemoryUser {\n\treturn s.validator.GetAll()\n}\n\nfunc (s *Server) GetUsersCount(context.Context) int64 {\n\treturn s.validator.GetCount()\n}\n\nfunc (s *Server) Network() []net.Network {\n\treturn []net.Network{net.Network_TCP}\n}\n\nfunc (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"hysteria\"\n\tinbound.CanSpliceCopy = 3\n\n\tvar useremail string\n\tvar userlevel uint32\n\ttype User interface{ User() *protocol.MemoryUser }\n\tif v, ok := conn.(User); ok {\n\t\tinbound.User = v.User()\n\t\tif inbound.User != nil {\n\t\t\tuseremail = inbound.User.Email\n\t\t\tuserlevel = inbound.User.Level\n\t\t}\n\t}\n\n\tiConn := stat.TryUnwrapStatsConn(conn)\n\tif _, ok := iConn.(*hysteria.InterUdpConn); ok {\n\t\tr := io.Reader(conn)\n\t\tb := make([]byte, MaxUDPSize)\n\t\tdf := &Defragger{}\n\t\tvar firstMsg *UDPMessage\n\t\tvar firstDest net.Destination\n\n\t\tfor {\n\t\t\tn, err := r.Read(b)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tmsg, err := ParseUDPMessage(b[:n])\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdfMsg := df.Feed(msg)\n\t\t\tif dfMsg == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfirstMsg = dfMsg\n\t\t\tfirstDest, err = net.ParseDestination(\"udp:\" + firstMsg.Addr)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogDebug(context.Background(), dfMsg.Addr, \" ParseDestination err \", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\n\t\treader := &UDPReader{\n\t\t\tReader:    r,\n\t\t\tbuf:       b,\n\t\t\tdf:        df,\n\t\t\tfirstMsg:  firstMsg,\n\t\t\tfirstDest: &firstDest,\n\t\t}\n\n\t\twriter := &UDPWriter{\n\t\t\tWriter: conn,\n\t\t\tbuf:    make([]byte, MaxUDPSize),\n\t\t\taddr:   firstMsg.Addr,\n\t\t}\n\n\t\treturn dispatcher.DispatchLink(ctx, firstDest, &transport.Link{\n\t\t\tReader: reader,\n\t\t\tWriter: writer,\n\t\t})\n\t} else {\n\t\tsessionPolicy := s.policyManager.ForLevel(userlevel)\n\n\t\tcommon.Must(conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)))\n\t\taddr, err := ReadTCPRequest(conn)\n\t\tif err != nil {\n\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\tFrom:   conn.RemoteAddr(),\n\t\t\t\tTo:     \"\",\n\t\t\t\tStatus: log.AccessRejected,\n\t\t\t\tReason: err,\n\t\t\t})\n\t\t\treturn errors.New(\"failed to create request from: \", conn.RemoteAddr()).Base(err)\n\t\t}\n\t\tcommon.Must(conn.SetReadDeadline(time.Time{}))\n\n\t\tdest, err := net.ParseDestination(\"tcp:\" + addr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\tFrom:   conn.RemoteAddr(),\n\t\t\tTo:     dest,\n\t\t\tStatus: log.AccessAccepted,\n\t\t\tReason: \"\",\n\t\t\tEmail:  useremail,\n\t\t})\n\t\terrors.LogInfo(ctx, \"tunnelling request to \", dest)\n\n\t\tbufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\t\terr = WriteTCPResponse(bufferedWriter, true, \"\")\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to write response\").Base(err)\n\t\t}\n\t\tif err := bufferedWriter.SetBuffered(false); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn dispatcher.DispatchLink(ctx, dest, &transport.Link{\n\t\t\tReader: buf.NewReader(conn),\n\t\t\tWriter: bufferedWriter,\n\t\t})\n\t}\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewServer(ctx, config.(*ServerConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/loopback/config.go",
    "content": "package loopback\n"
  },
  {
    "path": "proxy/loopback/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/loopback/config.proto\n\npackage loopback\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tInboundTag    string                 `protobuf:\"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3\" json:\"inbound_tag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_loopback_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_loopback_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_loopback_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetInboundTag() string {\n\tif x != nil {\n\t\treturn x.InboundTag\n\t}\n\treturn \"\"\n}\n\nvar File_proxy_loopback_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_loopback_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1bproxy/loopback/config.proto\\x12\\x13xray.proxy.loopback\\\")\\n\" +\n\t\"\\x06Config\\x12\\x1f\\n\" +\n\t\"\\vinbound_tag\\x18\\x01 \\x01(\\tR\\n\" +\n\t\"inboundTagB[\\n\" +\n\t\"\\x17com.xray.proxy.loopbackP\\x01Z(github.com/xtls/xray-core/proxy/loopback\\xaa\\x02\\x13Xray.Proxy.Loopbackb\\x06proto3\"\n\nvar (\n\tfile_proxy_loopback_config_proto_rawDescOnce sync.Once\n\tfile_proxy_loopback_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_loopback_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_loopback_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_loopback_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_loopback_config_proto_rawDesc), len(file_proxy_loopback_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_loopback_config_proto_rawDescData\n}\n\nvar file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_loopback_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.proxy.loopback.Config\n}\nvar file_proxy_loopback_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_loopback_config_proto_init() }\nfunc file_proxy_loopback_config_proto_init() {\n\tif File_proxy_loopback_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_loopback_config_proto_rawDesc), len(file_proxy_loopback_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_loopback_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_loopback_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_loopback_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_loopback_config_proto = out.File\n\tfile_proxy_loopback_config_proto_goTypes = nil\n\tfile_proxy_loopback_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/loopback/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.loopback;\noption csharp_namespace = \"Xray.Proxy.Loopback\";\noption go_package = \"github.com/xtls/xray-core/proxy/loopback\";\noption java_package = \"com.xray.proxy.loopback\";\noption java_multiple_files = true;\n\nmessage Config {\n  string inbound_tag = 1;\n}\n"
  },
  {
    "path": "proxy/loopback/loopback.go",
    "content": "package loopback\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\ntype Loopback struct {\n\tconfig             *Config\n\tdispatcherInstance routing.Dispatcher\n}\n\nfunc (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified.\")\n\t}\n\tob.Name = \"loopback\"\n\tdestination := ob.Target\n\n\terrors.LogInfo(ctx, \"opening connection to \", destination)\n\n\tinput := link.Reader\n\toutput := link.Writer\n\n\tvar conn net.Conn\n\terr := retry.ExponentialBackoff(2, 100).On(func() error {\n\t\tdialDest := destination\n\n\t\tcontent := new(session.Content)\n\t\tcontent.SkipDNSResolve = true\n\n\t\tctx = session.ContextWithContent(ctx, content)\n\n\t\tinbound := session.InboundFromContext(ctx)\n\n\t\tinbound.Tag = l.config.InboundTag\n\n\t\tctx = session.ContextWithInbound(ctx, inbound)\n\n\t\trawConn, err := l.dispatcherInstance.Dispatch(ctx, dialDest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar readerOpt cnc.ConnectionOption\n\t\tif dialDest.Network == net.Network_TCP {\n\t\t\treaderOpt = cnc.ConnectionOutputMulti(rawConn.Reader)\n\t\t} else {\n\t\t\treaderOpt = cnc.ConnectionOutputMultiUDP(rawConn.Reader)\n\t\t}\n\n\t\tconn = cnc.NewConnection(cnc.ConnectionInputMulti(rawConn.Writer), readerOpt)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to open connection to \", destination).Base(err)\n\t}\n\tdefer conn.Close()\n\n\trequestDone := func() error {\n\t\tvar writer buf.Writer\n\t\tif destination.Network == net.Network_TCP {\n\t\t\twriter = buf.NewWriter(conn)\n\t\t} else {\n\t\t\twriter = &buf.SequentialWriter{Writer: conn}\n\t\t}\n\n\t\tif err := buf.Copy(input, writer); err != nil {\n\t\t\treturn errors.New(\"failed to process request\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tvar reader buf.Reader\n\t\tif destination.Network == net.Network_TCP {\n\t\t\treader = buf.NewReader(conn)\n\t\t} else {\n\t\t\treader = buf.NewPacketReader(conn)\n\t\t}\n\t\tif err := buf.Copy(reader, output); err != nil {\n\t\t\treturn errors.New(\"failed to process response\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error {\n\tl.dispatcherInstance = dispatcherInstance\n\tl.config = config\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tl := new(Loopback)\n\t\terr := core.RequireFeatures(ctx, func(dispatcherInstance routing.Dispatcher) error {\n\t\t\treturn l.init(config.(*Config), dispatcherInstance)\n\t\t})\n\t\treturn l, err\n\t}))\n}\n"
  },
  {
    "path": "proxy/proxy.go",
    "content": "// Package proxy contains all proxies used by Xray.\n//\n// To implement an inbound or outbound proxy, one needs to do the following:\n// 1. Implement the interface(s) below.\n// 2. Register a config creator through common.RegisterConfig.\npackage proxy\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"math/big\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/pires/go-proxyproto\"\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/proxy/vless/encryption\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nvar (\n\tTls13SupportedVersions  = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}\n\tTlsClientHandShakeStart = []byte{0x16, 0x03}\n\tTlsServerHandShakeStart = []byte{0x16, 0x03, 0x03}\n\tTlsApplicationDataStart = []byte{0x17, 0x03, 0x03}\n\n\tTls13CipherSuiteDic = map[uint16]string{\n\t\t0x1301: \"TLS_AES_128_GCM_SHA256\",\n\t\t0x1302: \"TLS_AES_256_GCM_SHA384\",\n\t\t0x1303: \"TLS_CHACHA20_POLY1305_SHA256\",\n\t\t0x1304: \"TLS_AES_128_CCM_SHA256\",\n\t\t0x1305: \"TLS_AES_128_CCM_8_SHA256\",\n\t}\n)\n\nconst (\n\tTlsHandshakeTypeClientHello byte = 0x01\n\tTlsHandshakeTypeServerHello byte = 0x02\n\n\tCommandPaddingContinue byte = 0x00\n\tCommandPaddingEnd      byte = 0x01\n\tCommandPaddingDirect   byte = 0x02\n)\n\n// An Inbound processes inbound connections.\ntype Inbound interface {\n\t// Network returns a list of networks that this inbound supports. Connections with not-supported networks will not be passed into Process().\n\tNetwork() []net.Network\n\n\t// Process processes a connection of given network. If necessary, the Inbound can dispatch the connection to an Outbound.\n\tProcess(context.Context, net.Network, stat.Connection, routing.Dispatcher) error\n}\n\n// An Outbound process outbound connections.\ntype Outbound interface {\n\t// Process processes the given connection. The given dialer may be used to dial a system outbound connection.\n\tProcess(context.Context, *transport.Link, internet.Dialer) error\n}\n\n// UserManager is the interface for Inbounds and Outbounds that can manage their users.\ntype UserManager interface {\n\t// AddUser adds a new user.\n\tAddUser(context.Context, *protocol.MemoryUser) error\n\n\t// RemoveUser removes a user by email.\n\tRemoveUser(context.Context, string) error\n\n\t// Get user by email.\n\tGetUser(context.Context, string) *protocol.MemoryUser\n\n\t// Get all users.\n\tGetUsers(context.Context) []*protocol.MemoryUser\n\n\t// Get users count.\n\tGetUsersCount(context.Context) int64\n}\n\ntype GetInbound interface {\n\tGetInbound() Inbound\n}\n\ntype GetOutbound interface {\n\tGetOutbound() Outbound\n}\n\n// TrafficState is used to track uplink and downlink of one connection\n// It is used by XTLS to determine if switch to raw copy mode, It is used by Vision to calculate padding\ntype TrafficState struct {\n\tUserUUID               []byte\n\tNumberOfPacketToFilter int\n\tEnableXtls             bool\n\tIsTLS12orAbove         bool\n\tIsTLS                  bool\n\tCipher                 uint16\n\tRemainingServerHello   int32\n\tInbound                InboundState\n\tOutbound               OutboundState\n}\n\ntype InboundState struct {\n\t// reader link state\n\tWithinPaddingBuffers   bool\n\tUplinkReaderDirectCopy bool\n\tRemainingCommand       int32\n\tRemainingContent       int32\n\tRemainingPadding       int32\n\tCurrentCommand         int\n\t// write link state\n\tIsPadding                bool\n\tDownlinkWriterDirectCopy bool\n}\n\ntype OutboundState struct {\n\t// reader link state\n\tWithinPaddingBuffers     bool\n\tDownlinkReaderDirectCopy bool\n\tRemainingCommand         int32\n\tRemainingContent         int32\n\tRemainingPadding         int32\n\tCurrentCommand           int\n\t// write link state\n\tIsPadding              bool\n\tUplinkWriterDirectCopy bool\n}\n\nfunc NewTrafficState(userUUID []byte) *TrafficState {\n\treturn &TrafficState{\n\t\tUserUUID:               userUUID,\n\t\tNumberOfPacketToFilter: 8,\n\t\tEnableXtls:             false,\n\t\tIsTLS12orAbove:         false,\n\t\tIsTLS:                  false,\n\t\tCipher:                 0,\n\t\tRemainingServerHello:   -1,\n\t\tInbound: InboundState{\n\t\t\tWithinPaddingBuffers:     true,\n\t\t\tUplinkReaderDirectCopy:   false,\n\t\t\tRemainingCommand:         -1,\n\t\t\tRemainingContent:         -1,\n\t\t\tRemainingPadding:         -1,\n\t\t\tCurrentCommand:           0,\n\t\t\tIsPadding:                true,\n\t\t\tDownlinkWriterDirectCopy: false,\n\t\t},\n\t\tOutbound: OutboundState{\n\t\t\tWithinPaddingBuffers:     true,\n\t\t\tDownlinkReaderDirectCopy: false,\n\t\t\tRemainingCommand:         -1,\n\t\t\tRemainingContent:         -1,\n\t\t\tRemainingPadding:         -1,\n\t\t\tCurrentCommand:           0,\n\t\t\tIsPadding:                true,\n\t\t\tUplinkWriterDirectCopy:   false,\n\t\t},\n\t}\n}\n\n// VisionReader is used to read xtls vision protocol\n// Note Vision probably only make sense as the inner most layer of reader, since it need assess traffic state from origin proxy traffic\ntype VisionReader struct {\n\tbuf.Reader\n\ttrafficState *TrafficState\n\tctx          context.Context\n\tisUplink     bool\n\tconn         net.Conn\n\tinput        *bytes.Reader\n\trawInput     *bytes.Buffer\n\tob           *session.Outbound\n\n\t// internal\n\tdirectReadCounter stats.Counter\n}\n\nfunc NewVisionReader(reader buf.Reader, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, ob *session.Outbound) *VisionReader {\n\treturn &VisionReader{\n\t\tReader:       reader,\n\t\ttrafficState: trafficState,\n\t\tctx:          ctx,\n\t\tisUplink:     isUplink,\n\t\tconn:         conn,\n\t\tinput:        input,\n\t\trawInput:     rawInput,\n\t\tob:           ob,\n\t}\n}\n\nfunc (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tbuffer, err := w.Reader.ReadMultiBuffer()\n\tif buffer.IsEmpty() {\n\t\treturn buffer, err\n\t}\n\n\tvar withinPaddingBuffers *bool\n\tvar remainingContent *int32\n\tvar remainingPadding *int32\n\tvar currentCommand *int\n\tvar switchToDirectCopy *bool\n\tif w.isUplink {\n\t\twithinPaddingBuffers = &w.trafficState.Inbound.WithinPaddingBuffers\n\t\tremainingContent = &w.trafficState.Inbound.RemainingContent\n\t\tremainingPadding = &w.trafficState.Inbound.RemainingPadding\n\t\tcurrentCommand = &w.trafficState.Inbound.CurrentCommand\n\t\tswitchToDirectCopy = &w.trafficState.Inbound.UplinkReaderDirectCopy\n\t} else {\n\t\twithinPaddingBuffers = &w.trafficState.Outbound.WithinPaddingBuffers\n\t\tremainingContent = &w.trafficState.Outbound.RemainingContent\n\t\tremainingPadding = &w.trafficState.Outbound.RemainingPadding\n\t\tcurrentCommand = &w.trafficState.Outbound.CurrentCommand\n\t\tswitchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy\n\t}\n\n\tif *switchToDirectCopy {\n\t\tif w.directReadCounter != nil {\n\t\t\tw.directReadCounter.Add(int64(buffer.Len()))\n\t\t}\n\t\treturn buffer, err\n\t}\n\n\tif *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 {\n\t\tmb2 := make(buf.MultiBuffer, 0, len(buffer))\n\t\tfor _, b := range buffer {\n\t\t\tnewbuffer := XtlsUnpadding(b, w.trafficState, w.isUplink, w.ctx)\n\t\t\tif newbuffer.Len() > 0 {\n\t\t\t\tmb2 = append(mb2, newbuffer)\n\t\t\t}\n\t\t}\n\t\tbuffer = mb2\n\t\tif *remainingContent > 0 || *remainingPadding > 0 || *currentCommand == 0 {\n\t\t\t*withinPaddingBuffers = true\n\t\t} else if *currentCommand == 1 {\n\t\t\t*withinPaddingBuffers = false\n\t\t} else if *currentCommand == 2 {\n\t\t\t*withinPaddingBuffers = false\n\t\t\t*switchToDirectCopy = true\n\t\t} else {\n\t\t\terrors.LogDebug(w.ctx, \"XtlsRead unknown command \", *currentCommand, buffer.Len())\n\t\t}\n\t}\n\tif w.trafficState.NumberOfPacketToFilter > 0 {\n\t\tXtlsFilterTls(buffer, w.trafficState, w.ctx)\n\t}\n\n\tif *switchToDirectCopy {\n\t\t// XTLS Vision processes TLS-like conn's input and rawInput\n\t\tif inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() {\n\t\t\tbuffer, _ = buf.MergeMulti(buffer, inputBuffer)\n\t\t}\n\t\tif rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() {\n\t\t\tbuffer, _ = buf.MergeMulti(buffer, rawInputBuffer)\n\t\t}\n\t\t*w.input = bytes.Reader{} // release memory\n\t\tw.input = nil\n\t\t*w.rawInput = bytes.Buffer{} // release memory\n\t\tw.rawInput = nil\n\n\t\tif inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {\n\t\t\t// if w.isUplink && inbound.CanSpliceCopy == 2 { // TODO: enable uplink splice\n\t\t\t// \tinbound.CanSpliceCopy = 1\n\t\t\t// }\n\t\t\tif !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob\n\t\t\t\tw.ob.CanSpliceCopy = 1\n\t\t\t}\n\t\t}\n\t\treaderConn, readCounter, _ := UnwrapRawConn(w.conn)\n\t\tw.directReadCounter = readCounter\n\t\tw.Reader = buf.NewReader(readerConn)\n\t}\n\treturn buffer, err\n}\n\n// VisionWriter is used to write xtls vision protocol\n// Note Vision probably only make sense as the inner most layer of writer, since it need assess traffic state from origin proxy traffic\ntype VisionWriter struct {\n\tbuf.Writer\n\ttrafficState *TrafficState\n\tctx          context.Context\n\tisUplink     bool\n\tconn         net.Conn\n\tob           *session.Outbound\n\n\t// internal\n\twriteOnceUserUUID  []byte\n\tdirectWriteCounter stats.Counter\n\n\ttestseed []uint32\n}\n\nfunc NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound, testseed []uint32) *VisionWriter {\n\tw := make([]byte, len(trafficState.UserUUID))\n\tcopy(w, trafficState.UserUUID)\n\tif len(testseed) < 4 {\n\t\ttestseed = []uint32{900, 500, 900, 256}\n\t}\n\treturn &VisionWriter{\n\t\tWriter:            writer,\n\t\ttrafficState:      trafficState,\n\t\tctx:               ctx,\n\t\twriteOnceUserUUID: w,\n\t\tisUplink:          isUplink,\n\t\tconn:              conn,\n\t\tob:                ob,\n\t\ttestseed:          testseed,\n\t}\n}\n\nfunc (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tvar isPadding *bool\n\tvar switchToDirectCopy *bool\n\tif w.isUplink {\n\t\tisPadding = &w.trafficState.Outbound.IsPadding\n\t\tswitchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy\n\t} else {\n\t\tisPadding = &w.trafficState.Inbound.IsPadding\n\t\tswitchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy\n\t}\n\n\tif *switchToDirectCopy {\n\t\tif inbound := session.InboundFromContext(w.ctx); inbound != nil {\n\t\t\tif !w.isUplink && inbound.CanSpliceCopy == 2 {\n\t\t\t\tinbound.CanSpliceCopy = 1\n\t\t\t}\n\t\t\t// if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice\n\t\t\t// \tw.ob.CanSpliceCopy = 1\n\t\t\t// }\n\t\t}\n\t\trawConn, _, writerCounter := UnwrapRawConn(w.conn)\n\t\tw.Writer = buf.NewWriter(rawConn)\n\t\tw.directWriteCounter = writerCounter\n\t\t*switchToDirectCopy = false\n\t}\n\tif !mb.IsEmpty() && w.directWriteCounter != nil {\n\t\tw.directWriteCounter.Add(int64(mb.Len()))\n\t}\n\n\tif w.trafficState.NumberOfPacketToFilter > 0 {\n\t\tXtlsFilterTls(mb, w.trafficState, w.ctx)\n\t}\n\n\tif *isPadding {\n\t\tif len(mb) == 1 && mb[0] == nil {\n\t\t\tmb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header\n\t\t\treturn w.Writer.WriteMultiBuffer(mb)\n\t\t}\n\t\tisComplete := IsCompleteRecord(mb)\n\t\tmb = ReshapeMultiBuffer(w.ctx, mb)\n\t\tlongPadding := w.trafficState.IsTLS\n\t\tfor i, b := range mb {\n\t\t\tif w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) && isComplete {\n\t\t\t\tif w.trafficState.EnableXtls {\n\t\t\t\t\t*switchToDirectCopy = true\n\t\t\t\t}\n\t\t\t\tvar command byte = CommandPaddingContinue\n\t\t\t\tif i == len(mb)-1 {\n\t\t\t\t\tcommand = CommandPaddingEnd\n\t\t\t\t\tif w.trafficState.EnableXtls {\n\t\t\t\t\t\tcommand = CommandPaddingDirect\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed)\n\t\t\t\t*isPadding = false // padding going to end\n\t\t\t\tlongPadding = false\n\t\t\t\tcontinue\n\t\t\t} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early\n\t\t\t\t*isPadding = false\n\t\t\t\tmb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvar command byte = CommandPaddingContinue\n\t\t\tif i == len(mb)-1 && !*isPadding {\n\t\t\t\tcommand = CommandPaddingEnd\n\t\t\t\tif w.trafficState.EnableXtls {\n\t\t\t\t\tcommand = CommandPaddingDirect\n\t\t\t\t}\n\t\t\t}\n\t\t\tmb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)\n\t\t}\n\t}\n\treturn w.Writer.WriteMultiBuffer(mb)\n}\n\n// IsCompleteRecord Is complete tls data record\nfunc IsCompleteRecord(buffer buf.MultiBuffer) bool {\n\tb := make([]byte, buffer.Len())\n\tif buffer.Copy(b) != int(buffer.Len()) {\n\t\tpanic(\"impossible bytes allocation\")\n\t}\n\tvar headerLen int = 5\n\tvar recordLen int\n\n\ttotalLen := len(b)\n\ti := 0\n\tfor i < totalLen {\n\t\t// record header: 0x17 0x3 0x3 + 2 bytes length\n\t\tif headerLen > 0 {\n\t\t\tdata := b[i]\n\t\t\ti++\n\t\t\tswitch headerLen {\n\t\t\tcase 5:\n\t\t\t\tif data != 0x17 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\tcase 4:\n\t\t\t\tif data != 0x03 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\tcase 3:\n\t\t\t\tif data != 0x03 {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\tcase 2:\n\t\t\t\trecordLen = int(data) << 8\n\t\t\tcase 1:\n\t\t\t\trecordLen = recordLen | int(data)\n\t\t\t}\n\t\t\theaderLen--\n\t\t} else if recordLen > 0 {\n\t\t\tremaining := totalLen - i\n\t\t\tif remaining < recordLen {\n\t\t\t\treturn false\n\t\t\t} else {\n\t\t\t\ti += recordLen\n\t\t\t\trecordLen = 0\n\t\t\t\theaderLen = 5\n\t\t\t}\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\tif headerLen == 5 && recordLen == 0 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// ReshapeMultiBuffer prepare multi buffer for padding structure (max 21 bytes)\nfunc ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer {\n\tneedReshape := 0\n\tfor _, b := range buffer {\n\t\tif b.Len() >= buf.Size-21 {\n\t\t\tneedReshape += 1\n\t\t}\n\t}\n\tif needReshape == 0 {\n\t\treturn buffer\n\t}\n\tmb2 := make(buf.MultiBuffer, 0, len(buffer)+needReshape)\n\ttoPrint := \"\"\n\tfor i, buffer1 := range buffer {\n\t\tif buffer1.Len() >= buf.Size-21 {\n\t\t\tindex := int32(bytes.LastIndex(buffer1.Bytes(), TlsApplicationDataStart))\n\t\t\tif index < 21 || index > buf.Size-21 {\n\t\t\t\tindex = buf.Size / 2\n\t\t\t}\n\t\t\tbuffer2 := buf.New()\n\t\t\tbuffer2.Write(buffer1.BytesFrom(index))\n\t\t\tbuffer1.Resize(0, index)\n\t\t\tmb2 = append(mb2, buffer1, buffer2)\n\t\t\ttoPrint += \" \" + strconv.Itoa(int(buffer1.Len())) + \" \" + strconv.Itoa(int(buffer2.Len()))\n\t\t} else {\n\t\t\tmb2 = append(mb2, buffer1)\n\t\t\ttoPrint += \" \" + strconv.Itoa(int(buffer1.Len()))\n\t\t}\n\t\tbuffer[i] = nil\n\t}\n\tbuffer = buffer[:0]\n\terrors.LogDebug(ctx, \"ReshapeMultiBuffer \", toPrint)\n\treturn mb2\n}\n\n// XtlsPadding add padding to eliminate length signature during tls handshake\nfunc XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context, testseed []uint32) *buf.Buffer {\n\tvar contentLen int32 = 0\n\tvar paddingLen int32 = 0\n\tif b != nil {\n\t\tcontentLen = b.Len()\n\t}\n\tif contentLen < int32(testseed[0]) && longPadding {\n\t\tl, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[1])))\n\t\tif err != nil {\n\t\t\terrors.LogDebugInner(ctx, err, \"failed to generate padding\")\n\t\t}\n\t\tpaddingLen = int32(l.Int64()) + int32(testseed[2]) - contentLen\n\t} else {\n\t\tl, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[3])))\n\t\tif err != nil {\n\t\t\terrors.LogDebugInner(ctx, err, \"failed to generate padding\")\n\t\t}\n\t\tpaddingLen = int32(l.Int64())\n\t}\n\tif paddingLen > buf.Size-21-contentLen {\n\t\tpaddingLen = buf.Size - 21 - contentLen\n\t}\n\tnewbuffer := buf.New()\n\tif userUUID != nil {\n\t\tnewbuffer.Write(*userUUID)\n\t\t*userUUID = nil\n\t}\n\tnewbuffer.Write([]byte{command, byte(contentLen >> 8), byte(contentLen), byte(paddingLen >> 8), byte(paddingLen)})\n\tif b != nil {\n\t\tnewbuffer.Write(b.Bytes())\n\t\tb.Release()\n\t\tb = nil\n\t}\n\tnewbuffer.Extend(paddingLen)\n\terrors.LogDebug(ctx, \"XtlsPadding \", contentLen, \" \", paddingLen, \" \", command)\n\treturn newbuffer\n}\n\n// XtlsUnpadding remove padding and parse command\nfunc XtlsUnpadding(b *buf.Buffer, s *TrafficState, isUplink bool, ctx context.Context) *buf.Buffer {\n\tvar remainingCommand *int32\n\tvar remainingContent *int32\n\tvar remainingPadding *int32\n\tvar currentCommand *int\n\tif isUplink {\n\t\tremainingCommand = &s.Inbound.RemainingCommand\n\t\tremainingContent = &s.Inbound.RemainingContent\n\t\tremainingPadding = &s.Inbound.RemainingPadding\n\t\tcurrentCommand = &s.Inbound.CurrentCommand\n\t} else {\n\t\tremainingCommand = &s.Outbound.RemainingCommand\n\t\tremainingContent = &s.Outbound.RemainingContent\n\t\tremainingPadding = &s.Outbound.RemainingPadding\n\t\tcurrentCommand = &s.Outbound.CurrentCommand\n\t}\n\tif *remainingCommand == -1 && *remainingContent == -1 && *remainingPadding == -1 { // initial state\n\t\tif b.Len() >= 21 && bytes.Equal(s.UserUUID, b.BytesTo(16)) {\n\t\t\tb.Advance(16)\n\t\t\t*remainingCommand = 5\n\t\t} else {\n\t\t\treturn b\n\t\t}\n\t}\n\tnewbuffer := buf.New()\n\tfor b.Len() > 0 {\n\t\tif *remainingCommand > 0 {\n\t\t\tdata, err := b.ReadByte()\n\t\t\tif err != nil {\n\t\t\t\treturn newbuffer\n\t\t\t}\n\t\t\tswitch *remainingCommand {\n\t\t\tcase 5:\n\t\t\t\t*currentCommand = int(data)\n\t\t\tcase 4:\n\t\t\t\t*remainingContent = int32(data) << 8\n\t\t\tcase 3:\n\t\t\t\t*remainingContent = *remainingContent | int32(data)\n\t\t\tcase 2:\n\t\t\t\t*remainingPadding = int32(data) << 8\n\t\t\tcase 1:\n\t\t\t\t*remainingPadding = *remainingPadding | int32(data)\n\t\t\t\terrors.LogDebug(ctx, \"Xtls Unpadding new block, content \", *remainingContent, \" padding \", *remainingPadding, \" command \", *currentCommand)\n\t\t\t}\n\t\t\t*remainingCommand--\n\t\t} else if *remainingContent > 0 {\n\t\t\tlen := *remainingContent\n\t\t\tif b.Len() < len {\n\t\t\t\tlen = b.Len()\n\t\t\t}\n\t\t\tdata, err := b.ReadBytes(len)\n\t\t\tif err != nil {\n\t\t\t\treturn newbuffer\n\t\t\t}\n\t\t\tnewbuffer.Write(data)\n\t\t\t*remainingContent -= len\n\t\t} else { // remainingPadding > 0\n\t\t\tlen := *remainingPadding\n\t\t\tif b.Len() < len {\n\t\t\t\tlen = b.Len()\n\t\t\t}\n\t\t\tb.Advance(len)\n\t\t\t*remainingPadding -= len\n\t\t}\n\t\tif *remainingCommand <= 0 && *remainingContent <= 0 && *remainingPadding <= 0 { // this block done\n\t\t\tif *currentCommand == 0 {\n\t\t\t\t*remainingCommand = 5\n\t\t\t} else {\n\t\t\t\t*remainingCommand = -1 // set to initial state\n\t\t\t\t*remainingContent = -1\n\t\t\t\t*remainingPadding = -1\n\t\t\t\tif b.Len() > 0 { // shouldn't happen\n\t\t\t\t\tnewbuffer.Write(b.Bytes())\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tb.Release()\n\tb = nil\n\treturn newbuffer\n}\n\n// XtlsFilterTls filter and recognize tls 1.3 and other info\nfunc XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx context.Context) {\n\tfor _, b := range buffer {\n\t\tif b == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttrafficState.NumberOfPacketToFilter--\n\t\tif b.Len() >= 6 {\n\t\t\tstartsBytes := b.BytesTo(6)\n\t\t\tif bytes.Equal(TlsServerHandShakeStart, startsBytes[:3]) && startsBytes[5] == TlsHandshakeTypeServerHello {\n\t\t\t\ttrafficState.RemainingServerHello = (int32(startsBytes[3])<<8 | int32(startsBytes[4])) + 5\n\t\t\t\ttrafficState.IsTLS12orAbove = true\n\t\t\t\ttrafficState.IsTLS = true\n\t\t\t\tif b.Len() >= 79 && trafficState.RemainingServerHello >= 79 {\n\t\t\t\t\tsessionIdLen := int32(b.Byte(43))\n\t\t\t\t\tcipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3)\n\t\t\t\t\ttrafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1])\n\t\t\t\t} else {\n\t\t\t\t\terrors.LogDebug(ctx, \"XtlsFilterTls short server hello, tls 1.2 or older? \", b.Len(), \" \", trafficState.RemainingServerHello)\n\t\t\t\t}\n\t\t\t} else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello {\n\t\t\t\ttrafficState.IsTLS = true\n\t\t\t\terrors.LogDebug(ctx, \"XtlsFilterTls found tls client hello! \", buffer.Len())\n\t\t\t}\n\t\t}\n\t\tif trafficState.RemainingServerHello > 0 {\n\t\t\tend := trafficState.RemainingServerHello\n\t\t\tif end > b.Len() {\n\t\t\t\tend = b.Len()\n\t\t\t}\n\t\t\ttrafficState.RemainingServerHello -= b.Len()\n\t\t\tif bytes.Contains(b.BytesTo(end), Tls13SupportedVersions) {\n\t\t\t\tv, ok := Tls13CipherSuiteDic[trafficState.Cipher]\n\t\t\t\tif !ok {\n\t\t\t\t\tv = \"Old cipher: \" + strconv.FormatUint(uint64(trafficState.Cipher), 16)\n\t\t\t\t} else if v != \"TLS_AES_128_CCM_8_SHA256\" {\n\t\t\t\t\ttrafficState.EnableXtls = true\n\t\t\t\t}\n\t\t\t\terrors.LogDebug(ctx, \"XtlsFilterTls found tls 1.3! \", b.Len(), \" \", v)\n\t\t\t\ttrafficState.NumberOfPacketToFilter = 0\n\t\t\t\treturn\n\t\t\t} else if trafficState.RemainingServerHello <= 0 {\n\t\t\t\terrors.LogDebug(ctx, \"XtlsFilterTls found tls 1.2! \", b.Len())\n\t\t\t\ttrafficState.NumberOfPacketToFilter = 0\n\t\t\t\treturn\n\t\t\t}\n\t\t\terrors.LogDebug(ctx, \"XtlsFilterTls inconclusive server hello \", b.Len(), \" \", trafficState.RemainingServerHello)\n\t\t}\n\t\tif trafficState.NumberOfPacketToFilter <= 0 {\n\t\t\terrors.LogDebug(ctx, \"XtlsFilterTls stop filtering\", buffer.Len())\n\t\t}\n\t}\n}\n\n// UnwrapRawConn support unwrap encryption, stats, mask wrappers, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it\nfunc UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {\n\tvar readCounter, writerCounter stats.Counter\n\tif conn != nil {\n\t\tisEncryption := false\n\t\tif commonConn, ok := conn.(*encryption.CommonConn); ok {\n\t\t\tconn = commonConn.Conn\n\t\t\tisEncryption = true\n\t\t}\n\t\tif xorConn, ok := conn.(*encryption.XorConn); ok {\n\t\t\treturn xorConn, nil, nil // full-random xorConn should not be penetrated\n\t\t}\n\t\tif statConn, ok := conn.(*stat.CounterConnection); ok {\n\t\t\tconn = statConn.Connection\n\t\t\treadCounter = statConn.ReadCounter\n\t\t\twriterCounter = statConn.WriteCounter\n\t\t}\n\n\t\tif !isEncryption { // avoids double penetration\n\t\t\tif xc, ok := conn.(*tls.Conn); ok {\n\t\t\t\tconn = xc.NetConn()\n\t\t\t} else if utlsConn, ok := conn.(*tls.UConn); ok {\n\t\t\t\tconn = utlsConn.NetConn()\n\t\t\t} else if realityConn, ok := conn.(*reality.Conn); ok {\n\t\t\t\tconn = realityConn.NetConn()\n\t\t\t} else if realityUConn, ok := conn.(*reality.UConn); ok {\n\t\t\t\tconn = realityUConn.NetConn()\n\t\t\t}\n\t\t}\n\n\t\tconn = finalmask.UnwrapTcpMask(conn)\n\n\t\tif pc, ok := conn.(*proxyproto.Conn); ok {\n\t\t\tconn = pc.Raw()\n\t\t\t// 8192 > 4096, there is no need to process pc's bufReader\n\t\t}\n\t\tif uc, ok := conn.(*internet.UnixConnWrapper); ok {\n\t\t\tconn = uc.UnixConn\n\t\t}\n\t}\n\treturn conn, readCounter, writerCounter\n}\n\n// CopyRawConnIfExist use the most efficient copy method.\n// - If caller don't want to turn on splice, do not pass in both reader conn and writer conn\n// - writer are from *transport.Link\nfunc CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net.Conn, writer buf.Writer, timer *signal.ActivityTimer, inTimer *signal.ActivityTimer) error {\n\treaderConn, readCounter, _ := UnwrapRawConn(readerConn)\n\twriterConn, _, writeCounter := UnwrapRawConn(writerConn)\n\treader := buf.NewReader(readerConn)\n\tif runtime.GOOS != \"linux\" && runtime.GOOS != \"android\" {\n\t\treturn readV(ctx, reader, writer, timer, readCounter)\n\t}\n\ttc, ok := writerConn.(*net.TCPConn)\n\tif !ok || readerConn == nil || writerConn == nil {\n\t\treturn readV(ctx, reader, writer, timer, readCounter)\n\t}\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound == nil || inbound.CanSpliceCopy == 3 {\n\t\treturn readV(ctx, reader, writer, timer, readCounter)\n\t}\n\toutbounds := session.OutboundsFromContext(ctx)\n\tif len(outbounds) == 0 {\n\t\treturn readV(ctx, reader, writer, timer, readCounter)\n\t}\n\tfor _, ob := range outbounds {\n\t\tif ob.CanSpliceCopy == 3 {\n\t\t\treturn readV(ctx, reader, writer, timer, readCounter)\n\t\t}\n\t}\n\n\tfor {\n\t\tinbound := session.InboundFromContext(ctx)\n\t\toutbounds := session.OutboundsFromContext(ctx)\n\t\tvar splice = inbound.CanSpliceCopy == 1\n\t\tfor _, ob := range outbounds {\n\t\t\tif ob.CanSpliceCopy != 1 {\n\t\t\t\tsplice = false\n\t\t\t}\n\t\t}\n\t\tif splice {\n\t\t\terrors.LogDebug(ctx, \"CopyRawConn splice\")\n\t\t\tstatWriter, _ := writer.(*dispatcher.SizeStatWriter)\n\t\t\t//runtime.Gosched() // necessary\n\t\t\ttime.Sleep(time.Millisecond)     // without this, there will be a rare ssl error for freedom splice\n\t\t\ttimer.SetTimeout(24 * time.Hour) // prevent leak, just in case\n\t\t\tif inTimer != nil {\n\t\t\t\tinTimer.SetTimeout(24 * time.Hour)\n\t\t\t}\n\t\t\tw, err := tc.ReadFrom(readerConn)\n\t\t\tif readCounter != nil {\n\t\t\t\treadCounter.Add(w) // outbound stats\n\t\t\t}\n\t\t\tif writeCounter != nil {\n\t\t\t\twriteCounter.Add(w) // inbound stats\n\t\t\t}\n\t\t\tif statWriter != nil {\n\t\t\t\tstatWriter.Counter.Add(w) // user stats\n\t\t\t}\n\t\t\tif err != nil && errors.Cause(err) != io.EOF {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tbuffer, err := reader.ReadMultiBuffer()\n\t\tif !buffer.IsEmpty() {\n\t\t\tif readCounter != nil {\n\t\t\t\treadCounter.Add(int64(buffer.Len()))\n\t\t\t}\n\t\t\ttimer.Update()\n\t\t\tif werr := writer.WriteMultiBuffer(buffer); werr != nil {\n\t\t\t\treturn werr\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tif errors.Cause(err) == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {\n\terrors.LogDebug(ctx, \"CopyRawConn (maybe) readv\")\n\tif err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {\n\t\treturn errors.New(\"failed to process response\").Base(err)\n\t}\n\treturn nil\n}\n\nfunc IsRAWTransportWithoutSecurity(conn stat.Connection) bool {\n\tiConn := stat.TryUnwrapStatsConn(conn)\n\tiConn = finalmask.UnwrapTcpMask(iConn)\n\t_, ok1 := iConn.(*proxyproto.Conn)\n\t_, ok2 := iConn.(*net.TCPConn)\n\t_, ok3 := iConn.(*internet.UnixConnWrapper)\n\treturn ok1 || ok2 || ok3\n}\n"
  },
  {
    "path": "proxy/shadowsocks/client.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Client is a inbound handler for Shadowsocks protocol\ntype Client struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n}\n\n// NewClient create a new Shadowsocks client.\nfunc NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {\n\tif config.Server == nil {\n\t\treturn nil, errors.New(`no target server found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Server)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err)\n\t}\n\n\tv := core.MustFromContext(ctx)\n\tclient := &Client{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\treturn client, nil\n}\n\n// Process implements OutboundHandler.Process().\nfunc (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified\")\n\t}\n\tob.Name = \"shadowsocks\"\n\tob.CanSpliceCopy = 3\n\tdestination := ob.Target\n\tnetwork := destination.Network\n\n\tserver := c.server\n\tdest := server.Destination\n\tdest.Network = network\n\tvar conn stat.Connection\n\n\terr := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\trawConn, err := dialer.Dial(ctx, dest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconn = rawConn\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to find an available destination\").AtWarning().Base(err)\n\t}\n\terrors.LogInfo(ctx, \"tunneling request to \", destination, \" via \", network, \":\", server.Destination.NetAddr())\n\n\tdefer conn.Close()\n\n\trequest := &protocol.RequestHeader{\n\t\tVersion: Version,\n\t\tAddress: destination.Address,\n\t\tPort:    destination.Port,\n\t}\n\tif destination.Network == net.Network_TCP {\n\t\trequest.Command = protocol.RequestCommandTCP\n\t} else {\n\t\trequest.Command = protocol.RequestCommandUDP\n\t}\n\n\tuser := server.User\n\t_, ok := user.Account.(*MemoryAccount)\n\tif !ok {\n\t\treturn errors.New(\"user account is not valid\")\n\t}\n\trequest.User = user\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tsessionPolicy := c.policyManager.ForLevel(user.Level)\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tif request.Command == protocol.RequestCommandTCP {\n\t\trequestDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\t\t\tbufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\t\t\tbodyWriter, err := WriteTCPRequest(request, bufferedWriter)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"failed to write request\").Base(err)\n\t\t\t}\n\n\t\t\tif err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {\n\t\t\t\treturn errors.New(\"failed to write A request payload\").Base(err).AtWarning()\n\t\t\t}\n\n\t\t\tif err := bufferedWriter.SetBuffered(false); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer))\n\t\t}\n\n\t\tresponseDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\t\tresponseReader, err := ReadTCPResponse(user, conn)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn buf.Copy(responseReader, link.Writer, buf.UpdateActivity(timer))\n\t\t}\n\n\t\tresponseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))\n\t\tif err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {\n\t\t\treturn errors.New(\"connection ends\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif request.Command == protocol.RequestCommandUDP {\n\n\t\trequestDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\t\twriter := &UDPWriter{\n\t\t\t\tWriter:  conn,\n\t\t\t\tRequest: request,\n\t\t\t}\n\n\t\t\tif err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\t\treturn errors.New(\"failed to transport all UDP request\").Base(err)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tresponseDone := func() error {\n\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\t\treader := &UDPReader{\n\t\t\t\tReader: conn,\n\t\t\t\tUser:   user,\n\t\t\t}\n\n\t\t\tif err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\t\treturn errors.New(\"failed to transport all UDP response\").Base(err)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tresponseDoneAndCloseWriter := task.OnSuccess(responseDone, task.Close(link.Writer))\n\t\tif err := task.Run(ctx, requestDone, responseDoneAndCloseWriter); err != nil {\n\t\t\treturn errors.New(\"connection ends\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewClient(ctx, config.(*ClientConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/shadowsocks/config.go",
    "content": "package shadowsocks\n\nimport (\n\t\"bytes\"\n\t\"crypto/cipher\"\n\t\"crypto/md5\"\n\t\"crypto/sha1\"\n\t\"io\"\n\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"golang.org/x/crypto/chacha20poly1305\"\n\t\"golang.org/x/crypto/hkdf\"\n)\n\n// MemoryAccount is an account type converted from Account.\ntype MemoryAccount struct {\n\tCipher     Cipher\n\tCipherType CipherType\n\tKey        []byte\n\tPassword   string\n}\n\nvar ErrIVNotUnique = errors.New(\"IV is not unique\")\n\n// Equals implements protocol.Account.Equals().\nfunc (a *MemoryAccount) Equals(another protocol.Account) bool {\n\tif account, ok := another.(*MemoryAccount); ok {\n\t\treturn bytes.Equal(a.Key, account.Key)\n\t}\n\treturn false\n}\n\nfunc (a *MemoryAccount) ToProto() proto.Message {\n\treturn &Account{\n\t\tCipherType: a.CipherType,\n\t\tPassword:   a.Password,\n\t}\n}\n\nfunc createAesGcm(key []byte) cipher.AEAD {\n\treturn crypto.NewAesGcm(key)\n}\n\nfunc createChaCha20Poly1305(key []byte) cipher.AEAD {\n\tChaChaPoly1305, err := chacha20poly1305.New(key)\n\tcommon.Must(err)\n\treturn ChaChaPoly1305\n}\n\nfunc createXChaCha20Poly1305(key []byte) cipher.AEAD {\n\tXChaChaPoly1305, err := chacha20poly1305.NewX(key)\n\tcommon.Must(err)\n\treturn XChaChaPoly1305\n}\n\nfunc (a *Account) getCipher() (Cipher, error) {\n\tswitch a.CipherType {\n\tcase CipherType_AES_128_GCM:\n\t\treturn &AEADCipher{\n\t\t\tKeyBytes:        16,\n\t\t\tIVBytes:         16,\n\t\t\tAEADAuthCreator: createAesGcm,\n\t\t}, nil\n\tcase CipherType_AES_256_GCM:\n\t\treturn &AEADCipher{\n\t\t\tKeyBytes:        32,\n\t\t\tIVBytes:         32,\n\t\t\tAEADAuthCreator: createAesGcm,\n\t\t}, nil\n\tcase CipherType_CHACHA20_POLY1305:\n\t\treturn &AEADCipher{\n\t\t\tKeyBytes:        32,\n\t\t\tIVBytes:         32,\n\t\t\tAEADAuthCreator: createChaCha20Poly1305,\n\t\t}, nil\n\tcase CipherType_XCHACHA20_POLY1305:\n\t\treturn &AEADCipher{\n\t\t\tKeyBytes:        32,\n\t\t\tIVBytes:         32,\n\t\t\tAEADAuthCreator: createXChaCha20Poly1305,\n\t\t}, nil\n\tcase CipherType_NONE:\n\t\treturn NoneCipher{}, nil\n\tdefault:\n\t\treturn nil, errors.New(\"Unsupported cipher.\")\n\t}\n}\n\n// AsAccount implements protocol.AsAccount.\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\tCipher, err := a.getCipher()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get cipher\").Base(err)\n\t}\n\treturn &MemoryAccount{\n\t\tCipher:     Cipher,\n\t\tCipherType: a.CipherType,\n\t\tKey:        passwordToCipherKey([]byte(a.Password), Cipher.KeySize()),\n\t\tPassword:   a.Password,\n\t}, nil\n}\n\n// Cipher is an interface for all Shadowsocks ciphers.\ntype Cipher interface {\n\tKeySize() int32\n\tIVSize() int32\n\tNewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error)\n\tNewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error)\n\tIsAEAD() bool\n\tEncodePacket(key []byte, b *buf.Buffer) error\n\tDecodePacket(key []byte, b *buf.Buffer) error\n}\n\ntype AEADCipher struct {\n\tKeyBytes        int32\n\tIVBytes         int32\n\tAEADAuthCreator func(key []byte) cipher.AEAD\n}\n\nfunc (*AEADCipher) IsAEAD() bool {\n\treturn true\n}\n\nfunc (c *AEADCipher) KeySize() int32 {\n\treturn c.KeyBytes\n}\n\nfunc (c *AEADCipher) IVSize() int32 {\n\treturn c.IVBytes\n}\n\nfunc (c *AEADCipher) createAuthenticator(key []byte, iv []byte) *crypto.AEADAuthenticator {\n\tsubkey := make([]byte, c.KeyBytes)\n\thkdfSHA1(key, iv, subkey)\n\taead := c.AEADAuthCreator(subkey)\n\tnonce := crypto.GenerateAEADNonceWithSize(aead.NonceSize())\n\treturn &crypto.AEADAuthenticator{\n\t\tAEAD:           aead,\n\t\tNonceGenerator: nonce,\n\t}\n}\n\nfunc (c *AEADCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {\n\tauth := c.createAuthenticator(key, iv)\n\treturn crypto.NewAuthenticationWriter(auth, &crypto.AEADChunkSizeParser{\n\t\tAuth: auth,\n\t}, writer, protocol.TransferTypeStream, nil), nil\n}\n\nfunc (c *AEADCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {\n\tauth := c.createAuthenticator(key, iv)\n\treturn crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{\n\t\tAuth: auth,\n\t}, reader, protocol.TransferTypeStream, nil), nil\n}\n\nfunc (c *AEADCipher) EncodePacket(key []byte, b *buf.Buffer) error {\n\tivLen := c.IVSize()\n\tpayloadLen := b.Len()\n\tauth := c.createAuthenticator(key, b.BytesTo(ivLen))\n\n\tb.Extend(int32(auth.Overhead()))\n\t_, err := auth.Seal(b.BytesTo(ivLen), b.BytesRange(ivLen, payloadLen))\n\treturn err\n}\n\nfunc (c *AEADCipher) DecodePacket(key []byte, b *buf.Buffer) error {\n\tif b.Len() <= c.IVSize() {\n\t\treturn errors.New(\"insufficient data: \", b.Len())\n\t}\n\tivLen := c.IVSize()\n\tpayloadLen := b.Len()\n\tauth := c.createAuthenticator(key, b.BytesTo(ivLen))\n\n\tbbb, err := auth.Open(b.BytesTo(ivLen), b.BytesRange(ivLen, payloadLen))\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.Resize(ivLen, int32(len(bbb)))\n\treturn nil\n}\n\ntype NoneCipher struct{}\n\nfunc (NoneCipher) KeySize() int32 { return 0 }\nfunc (NoneCipher) IVSize() int32  { return 0 }\nfunc (NoneCipher) IsAEAD() bool {\n\treturn false\n}\n\nfunc (NoneCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) {\n\treturn buf.NewReader(reader), nil\n}\n\nfunc (NoneCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) {\n\treturn buf.NewWriter(writer), nil\n}\n\nfunc (NoneCipher) EncodePacket(key []byte, b *buf.Buffer) error {\n\treturn nil\n}\n\nfunc (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error {\n\treturn nil\n}\n\nfunc passwordToCipherKey(password []byte, keySize int32) []byte {\n\tkey := make([]byte, 0, keySize)\n\n\tmd5Sum := md5.Sum(password)\n\tkey = append(key, md5Sum[:]...)\n\n\tfor int32(len(key)) < keySize {\n\t\tmd5Hash := md5.New()\n\t\tcommon.Must2(md5Hash.Write(md5Sum[:]))\n\t\tcommon.Must2(md5Hash.Write(password))\n\t\tmd5Hash.Sum(md5Sum[:0])\n\n\t\tkey = append(key, md5Sum[:]...)\n\t}\n\treturn key\n}\n\nfunc hkdfSHA1(secret, salt, outKey []byte) {\n\tr := hkdf.New(sha1.New, secret, salt, []byte(\"ss-subkey\"))\n\tcommon.Must2(io.ReadFull(r, outKey))\n}\n"
  },
  {
    "path": "proxy/shadowsocks/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/shadowsocks/config.proto\n\npackage shadowsocks\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype CipherType int32\n\nconst (\n\tCipherType_UNKNOWN            CipherType = 0\n\tCipherType_AES_128_GCM        CipherType = 5\n\tCipherType_AES_256_GCM        CipherType = 6\n\tCipherType_CHACHA20_POLY1305  CipherType = 7\n\tCipherType_XCHACHA20_POLY1305 CipherType = 8\n\tCipherType_NONE               CipherType = 9\n)\n\n// Enum value maps for CipherType.\nvar (\n\tCipherType_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t5: \"AES_128_GCM\",\n\t\t6: \"AES_256_GCM\",\n\t\t7: \"CHACHA20_POLY1305\",\n\t\t8: \"XCHACHA20_POLY1305\",\n\t\t9: \"NONE\",\n\t}\n\tCipherType_value = map[string]int32{\n\t\t\"UNKNOWN\":            0,\n\t\t\"AES_128_GCM\":        5,\n\t\t\"AES_256_GCM\":        6,\n\t\t\"CHACHA20_POLY1305\":  7,\n\t\t\"XCHACHA20_POLY1305\": 8,\n\t\t\"NONE\":               9,\n\t}\n)\n\nfunc (x CipherType) Enum() *CipherType {\n\tp := new(CipherType)\n\t*p = x\n\treturn p\n}\n\nfunc (x CipherType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (CipherType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_proxy_shadowsocks_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (CipherType) Type() protoreflect.EnumType {\n\treturn &file_proxy_shadowsocks_config_proto_enumTypes[0]\n}\n\nfunc (x CipherType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use CipherType.Descriptor instead.\nfunc (CipherType) EnumDescriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype Account struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      string                 `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tCipherType    CipherType             `protobuf:\"varint,2,opt,name=cipher_type,json=cipherType,proto3,enum=xray.proxy.shadowsocks.CipherType\" json:\"cipher_type,omitempty\"`\n\tIvCheck       bool                   `protobuf:\"varint,3,opt,name=iv_check,json=ivCheck,proto3\" json:\"iv_check,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_shadowsocks_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Account) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetCipherType() CipherType {\n\tif x != nil {\n\t\treturn x.CipherType\n\t}\n\treturn CipherType_UNKNOWN\n}\n\nfunc (x *Account) GetIvCheck() bool {\n\tif x != nil {\n\t\treturn x.IvCheck\n\t}\n\treturn false\n}\n\ntype ServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsers         []*protocol.User       `protobuf:\"bytes,1,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tNetwork       []net.Network          `protobuf:\"varint,2,rep,packed,name=network,proto3,enum=xray.common.net.Network\" json:\"network,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServerConfig) Reset() {\n\t*x = ServerConfig{}\n\tmi := &file_proxy_shadowsocks_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerConfig) ProtoMessage() {}\n\nfunc (x *ServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.\nfunc (*ServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ServerConfig) GetUsers() []*protocol.User {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\nfunc (x *ServerConfig) GetNetwork() []net.Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn nil\n}\n\ntype ClientConfig struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tServer        *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_proxy_shadowsocks_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *ClientConfig) GetServer() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\nvar File_proxy_shadowsocks_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_shadowsocks_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1eproxy/shadowsocks/config.proto\\x12\\x16xray.proxy.shadowsocks\\x1a\\x18common/net/network.proto\\x1a\\x1acommon/protocol/user.proto\\x1a!common/protocol/server_spec.proto\\\"\\x85\\x01\\n\" +\n\t\"\\aAccount\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x01 \\x01(\\tR\\bpassword\\x12C\\n\" +\n\t\"\\vcipher_type\\x18\\x02 \\x01(\\x0e2\\\".xray.proxy.shadowsocks.CipherTypeR\\n\" +\n\t\"cipherType\\x12\\x19\\n\" +\n\t\"\\biv_check\\x18\\x03 \\x01(\\bR\\aivCheck\\\"t\\n\" +\n\t\"\\fServerConfig\\x120\\n\" +\n\t\"\\x05users\\x18\\x01 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\x05users\\x122\\n\" +\n\t\"\\anetwork\\x18\\x02 \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\anetwork\\\"L\\n\" +\n\t\"\\fClientConfig\\x12<\\n\" +\n\t\"\\x06server\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x06server*t\\n\" +\n\t\"\\n\" +\n\t\"CipherType\\x12\\v\\n\" +\n\t\"\\aUNKNOWN\\x10\\x00\\x12\\x0f\\n\" +\n\t\"\\vAES_128_GCM\\x10\\x05\\x12\\x0f\\n\" +\n\t\"\\vAES_256_GCM\\x10\\x06\\x12\\x15\\n\" +\n\t\"\\x11CHACHA20_POLY1305\\x10\\a\\x12\\x16\\n\" +\n\t\"\\x12XCHACHA20_POLY1305\\x10\\b\\x12\\b\\n\" +\n\t\"\\x04NONE\\x10\\tBd\\n\" +\n\t\"\\x1acom.xray.proxy.shadowsocksP\\x01Z+github.com/xtls/xray-core/proxy/shadowsocks\\xaa\\x02\\x16Xray.Proxy.Shadowsocksb\\x06proto3\"\n\nvar (\n\tfile_proxy_shadowsocks_config_proto_rawDescOnce sync.Once\n\tfile_proxy_shadowsocks_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_shadowsocks_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_shadowsocks_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_shadowsocks_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_shadowsocks_config_proto_rawDesc), len(file_proxy_shadowsocks_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_shadowsocks_config_proto_rawDescData\n}\n\nvar file_proxy_shadowsocks_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_proxy_shadowsocks_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_proxy_shadowsocks_config_proto_goTypes = []any{\n\t(CipherType)(0),                 // 0: xray.proxy.shadowsocks.CipherType\n\t(*Account)(nil),                 // 1: xray.proxy.shadowsocks.Account\n\t(*ServerConfig)(nil),            // 2: xray.proxy.shadowsocks.ServerConfig\n\t(*ClientConfig)(nil),            // 3: xray.proxy.shadowsocks.ClientConfig\n\t(*protocol.User)(nil),           // 4: xray.common.protocol.User\n\t(net.Network)(0),                // 5: xray.common.net.Network\n\t(*protocol.ServerEndpoint)(nil), // 6: xray.common.protocol.ServerEndpoint\n}\nvar file_proxy_shadowsocks_config_proto_depIdxs = []int32{\n\t0, // 0: xray.proxy.shadowsocks.Account.cipher_type:type_name -> xray.proxy.shadowsocks.CipherType\n\t4, // 1: xray.proxy.shadowsocks.ServerConfig.users:type_name -> xray.common.protocol.User\n\t5, // 2: xray.proxy.shadowsocks.ServerConfig.network:type_name -> xray.common.net.Network\n\t6, // 3: xray.proxy.shadowsocks.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint\n\t4, // [4:4] is the sub-list for method output_type\n\t4, // [4:4] is the sub-list for method input_type\n\t4, // [4:4] is the sub-list for extension type_name\n\t4, // [4:4] is the sub-list for extension extendee\n\t0, // [0:4] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_shadowsocks_config_proto_init() }\nfunc file_proxy_shadowsocks_config_proto_init() {\n\tif File_proxy_shadowsocks_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_shadowsocks_config_proto_rawDesc), len(file_proxy_shadowsocks_config_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_shadowsocks_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_shadowsocks_config_proto_depIdxs,\n\t\tEnumInfos:         file_proxy_shadowsocks_config_proto_enumTypes,\n\t\tMessageInfos:      file_proxy_shadowsocks_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_shadowsocks_config_proto = out.File\n\tfile_proxy_shadowsocks_config_proto_goTypes = nil\n\tfile_proxy_shadowsocks_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/shadowsocks/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.shadowsocks;\noption csharp_namespace = \"Xray.Proxy.Shadowsocks\";\noption go_package = \"github.com/xtls/xray-core/proxy/shadowsocks\";\noption java_package = \"com.xray.proxy.shadowsocks\";\noption java_multiple_files = true;\n\nimport \"common/net/network.proto\";\nimport \"common/protocol/user.proto\";\nimport \"common/protocol/server_spec.proto\";\n\nmessage Account {\n  string password = 1;\n  CipherType cipher_type = 2;\n\n  bool iv_check = 3;\n}\n\nenum CipherType {\n  UNKNOWN = 0;\n  AES_128_GCM = 5;\n  AES_256_GCM = 6;\n  CHACHA20_POLY1305 = 7;\n  XCHACHA20_POLY1305 = 8;\n  NONE = 9;\n}\n\nmessage ServerConfig {\n  repeated xray.common.protocol.User users = 1;\n  repeated xray.common.net.Network network = 2;\n}\n\nmessage ClientConfig {\n  xray.common.protocol.ServerEndpoint server = 1;\n}\n"
  },
  {
    "path": "proxy/shadowsocks/config_test.go",
    "content": "package shadowsocks_test\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks\"\n)\n\nfunc TestAEADCipherUDP(t *testing.T) {\n\trawAccount := &shadowsocks.Account{\n\t\tCipherType: shadowsocks.CipherType_AES_128_GCM,\n\t\tPassword:   \"test\",\n\t}\n\taccount, err := rawAccount.AsAccount()\n\tcommon.Must(err)\n\n\tcipher := account.(*shadowsocks.MemoryAccount).Cipher\n\n\tkey := make([]byte, cipher.KeySize())\n\tcommon.Must2(rand.Read(key))\n\n\tpayload := make([]byte, 1024)\n\tcommon.Must2(rand.Read(payload))\n\n\tb1 := buf.New()\n\tcommon.Must2(b1.ReadFullFrom(rand.Reader, cipher.IVSize()))\n\tcommon.Must2(b1.Write(payload))\n\tcommon.Must(cipher.EncodePacket(key, b1))\n\n\tcommon.Must(cipher.DecodePacket(key, b1))\n\tif diff := cmp.Diff(b1.Bytes(), payload); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n}\n"
  },
  {
    "path": "proxy/shadowsocks/protocol.go",
    "content": "package shadowsocks\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\tgoerrors \"errors\"\n\t\"hash/crc32\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/drain\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nconst (\n\tVersion = 1\n)\n\nvar addrParser = protocol.NewAddressParser(\n\tprotocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),\n\tprotocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),\n\tprotocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),\n\tprotocol.WithAddressTypeParser(func(b byte) byte {\n\t\treturn b & 0x0F\n\t}),\n)\n\ntype FullReader struct {\n\treader io.Reader\n\tbuffer []byte\n}\n\nfunc (r *FullReader) Read(p []byte) (n int, err error) {\n\tif r.buffer != nil {\n\t\tn := copy(p, r.buffer)\n\t\tif n == len(r.buffer) {\n\t\t\tr.buffer = nil\n\t\t} else {\n\t\t\tr.buffer = r.buffer[n:]\n\t\t}\n\t\tif n == len(p) {\n\t\t\treturn n, nil\n\t\t} else {\n\t\t\tm, err := r.reader.Read(p[n:])\n\t\t\treturn n + m, err\n\t\t}\n\t}\n\treturn r.reader.Read(p)\n}\n\n// ReadTCPSession reads a Shadowsocks TCP session from the given reader, returns its header and remaining parts.\nfunc ReadTCPSession(validator *Validator, reader io.Reader) (*protocol.RequestHeader, buf.Reader, error) {\n\tbehaviorSeed := validator.GetBehaviorSeed()\n\tdrainer, errDrain := drain.NewBehaviorSeedLimitedDrainer(int64(behaviorSeed), 16+38, 3266, 64)\n\n\tif errDrain != nil {\n\t\treturn nil, nil, errors.New(\"failed to initialize drainer\").Base(errDrain)\n\t}\n\n\tvar r buf.Reader\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\tif _, err := buffer.ReadFullFrom(reader, 50); err != nil {\n\t\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\t\treturn nil, nil, drain.WithError(drainer, reader, errors.New(\"failed to read 50 bytes\").Base(err))\n\t}\n\n\tbs := buffer.Bytes()\n\tuser, aead, _, ivLen, err := validator.Get(bs, protocol.RequestCommandTCP)\n\n\tswitch err {\n\tcase ErrNotFound:\n\t\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\t\treturn nil, nil, drain.WithError(drainer, reader, errors.New(\"failed to match an user\").Base(err))\n\tcase ErrIVNotUnique:\n\t\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\t\treturn nil, nil, drain.WithError(drainer, reader, errors.New(\"failed iv check\").Base(err))\n\tdefault:\n\t\treader = &FullReader{reader, bs[ivLen:]}\n\t\tdrainer.AcknowledgeReceive(int(ivLen))\n\n\t\tif aead != nil {\n\t\t\tauth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:           aead,\n\t\t\t\tNonceGenerator: crypto.GenerateAEADNonceWithSize(aead.NonceSize()),\n\t\t\t}\n\t\t\tr = crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{\n\t\t\t\tAuth: auth,\n\t\t\t}, reader, protocol.TransferTypeStream, nil)\n\t\t} else {\n\t\t\taccount := user.Account.(*MemoryAccount)\n\t\t\tiv := append([]byte(nil), buffer.BytesTo(ivLen)...)\n\t\t\tr, err = account.Cipher.NewDecryptionReader(account.Key, iv, reader)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, drain.WithError(drainer, reader, errors.New(\"failed to initialize decoding stream\").Base(err).AtError())\n\t\t\t}\n\t\t}\n\t}\n\n\tbr := &buf.BufferedReader{Reader: r}\n\n\trequest := &protocol.RequestHeader{\n\t\tVersion: Version,\n\t\tUser:    user,\n\t\tCommand: protocol.RequestCommandTCP,\n\t}\n\n\tbuffer.Clear()\n\n\taddr, port, err := addrParser.ReadAddressPort(buffer, br)\n\tif err != nil {\n\t\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\t\treturn nil, nil, drain.WithError(drainer, reader, errors.New(\"failed to read address\").Base(err))\n\t}\n\n\trequest.Address = addr\n\trequest.Port = port\n\n\tif request.Address == nil {\n\t\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\t\treturn nil, nil, drain.WithError(drainer, reader, errors.New(\"invalid remote address.\"))\n\t}\n\n\treturn request, br, nil\n}\n\n// WriteTCPRequest writes Shadowsocks request into the given writer, and returns a writer for body.\nfunc WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {\n\tuser := request.User\n\taccount := user.Account.(*MemoryAccount)\n\n\tvar iv []byte\n\tif account.Cipher.IVSize() > 0 {\n\t\tiv = make([]byte, account.Cipher.IVSize())\n\t\tcommon.Must2(rand.Read(iv))\n\t\tif err := buf.WriteAllBytes(writer, iv, nil); err != nil {\n\t\t\treturn nil, errors.New(\"failed to write IV\")\n\t\t}\n\t}\n\n\tw, err := account.Cipher.NewEncryptionWriter(account.Key, iv, writer)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to create encoding stream\").Base(err).AtError()\n\t}\n\n\theader := buf.New()\n\n\tif err := addrParser.WriteAddressPort(header, request.Address, request.Port); err != nil {\n\t\treturn nil, errors.New(\"failed to write address\").Base(err)\n\t}\n\n\tif err := w.WriteMultiBuffer(buf.MultiBuffer{header}); err != nil {\n\t\treturn nil, errors.New(\"failed to write header\").Base(err)\n\t}\n\n\treturn w, nil\n}\n\nfunc ReadTCPResponse(user *protocol.MemoryUser, reader io.Reader) (buf.Reader, error) {\n\taccount := user.Account.(*MemoryAccount)\n\n\thashkdf := hmac.New(sha256.New, []byte(\"SSBSKDF\"))\n\thashkdf.Write(account.Key)\n\n\tbehaviorSeed := crc32.ChecksumIEEE(hashkdf.Sum(nil))\n\n\tdrainer, err := drain.NewBehaviorSeedLimitedDrainer(int64(behaviorSeed), 16+38, 3266, 64)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to initialize drainer\").Base(err)\n\t}\n\n\tvar iv []byte\n\tif account.Cipher.IVSize() > 0 {\n\t\tiv = make([]byte, account.Cipher.IVSize())\n\t\tif n, err := io.ReadFull(reader, iv); err != nil {\n\t\t\treturn nil, errors.New(\"failed to read IV\").Base(err)\n\t\t} else { // nolint: golint\n\t\t\tdrainer.AcknowledgeReceive(n)\n\t\t}\n\t}\n\n\treturn account.Cipher.NewDecryptionReader(account.Key, iv, reader)\n}\n\nfunc WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {\n\tuser := request.User\n\taccount := user.Account.(*MemoryAccount)\n\n\tvar iv []byte\n\tif account.Cipher.IVSize() > 0 {\n\t\tiv = make([]byte, account.Cipher.IVSize())\n\t\tcommon.Must2(rand.Read(iv))\n\t\tif err := buf.WriteAllBytes(writer, iv, nil); err != nil {\n\t\t\treturn nil, errors.New(\"failed to write IV.\").Base(err)\n\t\t}\n\t}\n\n\treturn account.Cipher.NewEncryptionWriter(account.Key, iv, writer)\n}\n\nfunc EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buffer, error) {\n\tuser := request.User\n\taccount := user.Account.(*MemoryAccount)\n\n\tbuffer := buf.New()\n\tivLen := account.Cipher.IVSize()\n\tif ivLen > 0 {\n\t\tcommon.Must2(buffer.ReadFullFrom(rand.Reader, ivLen))\n\t}\n\n\tif err := addrParser.WriteAddressPort(buffer, request.Address, request.Port); err != nil {\n\t\treturn nil, errors.New(\"failed to write address\").Base(err)\n\t}\n\n\tbuffer.Write(payload)\n\n\tif err := account.Cipher.EncodePacket(account.Key, buffer); err != nil {\n\t\treturn nil, errors.New(\"failed to encrypt UDP payload\").Base(err)\n\t}\n\n\treturn buffer, nil\n}\n\nfunc DecodeUDPPacket(validator *Validator, payload *buf.Buffer) (*protocol.RequestHeader, *buf.Buffer, error) {\n\trawPayload := payload.Bytes()\n\tuser, _, d, _, err := validator.Get(rawPayload, protocol.RequestCommandUDP)\n\n\tif goerrors.Is(err, ErrIVNotUnique) {\n\t\treturn nil, nil, errors.New(\"failed iv check\").Base(err)\n\t}\n\n\tif goerrors.Is(err, ErrNotFound) {\n\t\treturn nil, nil, errors.New(\"failed to match an user\").Base(err)\n\t}\n\n\tif err != nil {\n\t\treturn nil, nil, errors.New(\"unexpected error\").Base(err)\n\t}\n\n\taccount, ok := user.Account.(*MemoryAccount)\n\tif !ok {\n\t\treturn nil, nil, errors.New(\"expected MemoryAccount returned from validator\")\n\t}\n\n\tif account.Cipher.IsAEAD() {\n\t\tpayload.Clear()\n\t\tpayload.Write(d)\n\t} else {\n\t\tif account.Cipher.IVSize() > 0 {\n\t\t\tiv := make([]byte, account.Cipher.IVSize())\n\t\t\tcopy(iv, payload.BytesTo(account.Cipher.IVSize()))\n\t\t}\n\t\tif err = account.Cipher.DecodePacket(account.Key, payload); err != nil {\n\t\t\treturn nil, nil, errors.New(\"failed to decrypt UDP payload\").Base(err)\n\t\t}\n\t}\n\n\tpayload.SetByte(0, payload.Byte(0)&0x0F)\n\n\taddr, port, err := addrParser.ReadAddressPort(nil, payload)\n\tif err != nil {\n\t\treturn nil, nil, errors.New(\"failed to parse address\").Base(err)\n\t}\n\n\trequest := &protocol.RequestHeader{\n\t\tVersion: Version,\n\t\tUser:    user,\n\t\tCommand: protocol.RequestCommandUDP,\n\t\tAddress: addr,\n\t\tPort:    port,\n\t}\n\n\treturn request, payload, nil\n}\n\ntype UDPReader struct {\n\tReader io.Reader\n\tUser   *protocol.MemoryUser\n}\n\nfunc (v *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tbuffer := buf.New()\n\t_, err := buffer.ReadFrom(v.Reader)\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, err\n\t}\n\tvalidator := new(Validator)\n\tvalidator.Add(v.User)\n\n\tu, payload, err := DecodeUDPPacket(validator, buffer)\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, err\n\t}\n\tdest := u.Destination()\n\tpayload.UDP = &dest\n\treturn buf.MultiBuffer{payload}, nil\n}\n\ntype UDPWriter struct {\n\tWriter  io.Writer\n\tRequest *protocol.RequestHeader\n}\n\nfunc (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor {\n\t\tmb2, b := buf.SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\t\trequest := w.Request\n\t\tif b.UDP != nil {\n\t\t\trequest = &protocol.RequestHeader{\n\t\t\t\tUser:    w.Request.User,\n\t\t\t\tAddress: b.UDP.Address,\n\t\t\t\tPort:    b.UDP.Port,\n\t\t\t}\n\t\t}\n\t\tpacket, err := EncodeUDPPacket(request, b.Bytes())\n\t\tb.Release()\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\t\t_, err = w.Writer.Write(packet.Bytes())\n\t\tpacket.Release()\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/shadowsocks/protocol_test.go",
    "content": "package shadowsocks_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t. \"github.com/xtls/xray-core/proxy/shadowsocks\"\n)\n\nfunc toAccount(a *Account) protocol.Account {\n\taccount, err := a.AsAccount()\n\tcommon.Must(err)\n\treturn account\n}\n\nfunc equalRequestHeader(x, y *protocol.RequestHeader) bool {\n\treturn cmp.Equal(x, y, cmp.Comparer(func(x, y protocol.RequestHeader) bool {\n\t\treturn x == y\n\t}))\n}\n\nfunc TestUDPEncodingDecoding(t *testing.T) {\n\ttestRequests := []protocol.RequestHeader{\n\t\t{\n\t\t\tVersion: Version,\n\t\t\tCommand: protocol.RequestCommandUDP,\n\t\t\tAddress: net.LocalHostIP,\n\t\t\tPort:    1234,\n\t\t\tUser: &protocol.MemoryUser{\n\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\tAccount: toAccount(&Account{\n\t\t\t\t\tPassword:   \"password\",\n\t\t\t\t\tCipherType: CipherType_AES_128_GCM,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tVersion: Version,\n\t\t\tCommand: protocol.RequestCommandUDP,\n\t\t\tAddress: net.LocalHostIP,\n\t\t\tPort:    1234,\n\t\t\tUser: &protocol.MemoryUser{\n\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\tAccount: toAccount(&Account{\n\t\t\t\t\tPassword:   \"123\",\n\t\t\t\t\tCipherType: CipherType_NONE,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, request := range testRequests {\n\t\tdata := buf.New()\n\t\tcommon.Must2(data.WriteString(\"test string\"))\n\t\tencodedData, err := EncodeUDPPacket(&request, data.Bytes())\n\t\tcommon.Must(err)\n\n\t\tvalidator := new(Validator)\n\t\tvalidator.Add(request.User)\n\t\tdecodedRequest, decodedData, err := DecodeUDPPacket(validator, encodedData)\n\t\tcommon.Must(err)\n\n\t\tif r := cmp.Diff(decodedData.Bytes(), data.Bytes()); r != \"\" {\n\t\t\tt.Error(\"data: \", r)\n\t\t}\n\n\t\tif equalRequestHeader(decodedRequest, &request) == false {\n\t\t\tt.Error(\"different request\")\n\t\t}\n\t}\n}\n\nfunc TestUDPDecodingWithPayloadTooShort(t *testing.T) {\n\ttestAccounts := []protocol.Account{\n\t\ttoAccount(&Account{\n\t\t\tPassword:   \"password\",\n\t\t\tCipherType: CipherType_AES_128_GCM,\n\t\t}),\n\t\ttoAccount(&Account{\n\t\t\tPassword:   \"password\",\n\t\t\tCipherType: CipherType_NONE,\n\t\t}),\n\t}\n\n\tfor _, account := range testAccounts {\n\t\tdata := buf.New()\n\t\tdata.WriteString(\"short payload\")\n\t\tvalidator := new(Validator)\n\t\tvalidator.Add(&protocol.MemoryUser{\n\t\t\tAccount: account,\n\t\t})\n\t\t_, _, err := DecodeUDPPacket(validator, data)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error\")\n\t\t}\n\t}\n}\n\nfunc TestTCPRequest(t *testing.T) {\n\tcases := []struct {\n\t\trequest *protocol.RequestHeader\n\t\tpayload []byte\n\t}{\n\t\t{\n\t\t\trequest: &protocol.RequestHeader{\n\t\t\t\tVersion: Version,\n\t\t\t\tCommand: protocol.RequestCommandTCP,\n\t\t\t\tAddress: net.LocalHostIP,\n\t\t\t\tPort:    1234,\n\t\t\t\tUser: &protocol.MemoryUser{\n\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\tAccount: toAccount(&Account{\n\t\t\t\t\t\tPassword:   \"tcp-password\",\n\t\t\t\t\t\tCipherType: CipherType_AES_128_GCM,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tpayload: []byte(\"test string\"),\n\t\t},\n\t\t{\n\t\t\trequest: &protocol.RequestHeader{\n\t\t\t\tVersion: Version,\n\t\t\t\tCommand: protocol.RequestCommandTCP,\n\t\t\t\tAddress: net.LocalHostIPv6,\n\t\t\t\tPort:    1234,\n\t\t\t\tUser: &protocol.MemoryUser{\n\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\tAccount: toAccount(&Account{\n\t\t\t\t\t\tPassword:   \"password\",\n\t\t\t\t\t\tCipherType: CipherType_AES_256_GCM,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tpayload: []byte(\"test string\"),\n\t\t},\n\t\t{\n\t\t\trequest: &protocol.RequestHeader{\n\t\t\t\tVersion: Version,\n\t\t\t\tCommand: protocol.RequestCommandTCP,\n\t\t\t\tAddress: net.DomainAddress(\"example.com\"),\n\t\t\t\tPort:    1234,\n\t\t\t\tUser: &protocol.MemoryUser{\n\t\t\t\t\tEmail: \"love@example.com\",\n\t\t\t\t\tAccount: toAccount(&Account{\n\t\t\t\t\t\tPassword:   \"password\",\n\t\t\t\t\t\tCipherType: CipherType_CHACHA20_POLY1305,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tpayload: []byte(\"test string\"),\n\t\t},\n\t}\n\n\trunTest := func(request *protocol.RequestHeader, payload []byte) {\n\t\tdata := buf.New()\n\t\tcommon.Must2(data.Write(payload))\n\n\t\tcache := buf.New()\n\t\tdefer cache.Release()\n\n\t\twriter, err := WriteTCPRequest(request, cache)\n\t\tcommon.Must(err)\n\n\t\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data}))\n\n\t\tvalidator := new(Validator)\n\t\tvalidator.Add(request.User)\n\t\tdecodedRequest, reader, err := ReadTCPSession(validator, cache)\n\t\tcommon.Must(err)\n\t\tif equalRequestHeader(decodedRequest, request) == false {\n\t\t\tt.Error(\"different request\")\n\t\t}\n\n\t\tdecodedData, err := reader.ReadMultiBuffer()\n\t\tcommon.Must(err)\n\t\tif r := cmp.Diff(decodedData[0].Bytes(), payload); r != \"\" {\n\t\t\tt.Error(\"data: \", r)\n\t\t}\n\t}\n\n\tfor _, test := range cases {\n\t\trunTest(test.request, test.payload)\n\t}\n}\n\nfunc TestUDPReaderWriter(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tAccount: toAccount(&Account{\n\t\t\tPassword:   \"test-password\",\n\t\t\tCipherType: CipherType_CHACHA20_POLY1305,\n\t\t}),\n\t}\n\tcache := buf.New()\n\tdefer cache.Release()\n\n\twriter := &UDPWriter{\n\t\tWriter: cache,\n\t\tRequest: &protocol.RequestHeader{\n\t\t\tVersion: Version,\n\t\t\tAddress: net.DomainAddress(\"example.com\"),\n\t\t\tPort:    123,\n\t\t\tUser:    user,\n\t\t},\n\t}\n\n\treader := &UDPReader{\n\t\tReader: cache,\n\t\tUser:   user,\n\t}\n\n\t{\n\t\tb := buf.New()\n\t\tcommon.Must2(b.WriteString(\"test payload\"))\n\t\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))\n\n\t\tpayload, err := reader.ReadMultiBuffer()\n\t\tcommon.Must(err)\n\t\tif payload[0].String() != \"test payload\" {\n\t\t\tt.Error(\"unexpected output: \", payload[0].String())\n\t\t}\n\t}\n\n\t{\n\t\tb := buf.New()\n\t\tcommon.Must2(b.WriteString(\"test payload 2\"))\n\t\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{b}))\n\n\t\tpayload, err := reader.ReadMultiBuffer()\n\t\tcommon.Must(err)\n\t\tif payload[0].String() != \"test payload 2\" {\n\t\t\tt.Error(\"unexpected output: \", payload[0].String())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "proxy/shadowsocks/server.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\tudp_proto \"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n)\n\ntype Server struct {\n\tconfig        *ServerConfig\n\tvalidator     *Validator\n\tpolicyManager policy.Manager\n\tcone          bool\n}\n\n// NewServer create a new Shadowsocks server.\nfunc NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {\n\tvalidator := new(Validator)\n\tfor _, user := range config.Users {\n\t\tu, err := user.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get shadowsocks user\").Base(err).AtError()\n\t\t}\n\n\t\tif err := validator.Add(u); err != nil {\n\t\t\treturn nil, errors.New(\"failed to add user\").Base(err).AtError()\n\t\t}\n\t}\n\n\tv := core.MustFromContext(ctx)\n\ts := &Server{\n\t\tconfig:        config,\n\t\tvalidator:     validator,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tcone:          ctx.Value(\"cone\").(bool),\n\t}\n\n\treturn s, nil\n}\n\n// AddUser implements proxy.UserManager.AddUser().\nfunc (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {\n\treturn s.validator.Add(u)\n}\n\n// RemoveUser implements proxy.UserManager.RemoveUser().\nfunc (s *Server) RemoveUser(ctx context.Context, e string) error {\n\treturn s.validator.Del(e)\n}\n\n// GetUser implements proxy.UserManager.GetUser().\nfunc (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {\n\treturn s.validator.GetByEmail(email)\n}\n\n// GetUsers implements proxy.UserManager.GetUsers().\nfunc (s *Server) GetUsers(ctx context.Context) []*protocol.MemoryUser {\n\treturn s.validator.GetAll()\n}\n\n// GetUsersCount implements proxy.UserManager.GetUsersCount().\nfunc (s *Server) GetUsersCount(context.Context) int64 {\n\treturn s.validator.GetCount()\n}\n\nfunc (s *Server) Network() []net.Network {\n\tlist := s.config.Network\n\tif len(list) == 0 {\n\t\tlist = append(list, net.Network_TCP)\n\t}\n\treturn list\n}\n\nfunc (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"shadowsocks\"\n\tinbound.CanSpliceCopy = 3\n\n\tswitch network {\n\tcase net.Network_TCP:\n\t\treturn s.handleConnection(ctx, conn, dispatcher)\n\tcase net.Network_UDP:\n\t\treturn s.handleUDPPayload(ctx, conn, dispatcher)\n\tdefault:\n\t\treturn errors.New(\"unknown network: \", network)\n\t}\n}\n\nfunc (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tudpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {\n\t\trequest := protocol.RequestHeaderFromContext(ctx)\n\t\tpayload := packet.Payload\n\t\tif request == nil {\n\t\t\tpayload.Release()\n\t\t\treturn\n\t\t}\n\n\t\tif payload.UDP != nil {\n\t\t\trequest = &protocol.RequestHeader{\n\t\t\t\tUser:    request.User,\n\t\t\t\tAddress: payload.UDP.Address,\n\t\t\t\tPort:    payload.UDP.Port,\n\t\t\t}\n\t\t}\n\n\t\tdata, err := EncodeUDPPacket(request, payload.Bytes())\n\t\tpayload.Release()\n\t\tif err != nil {\n\t\t\terrors.LogWarningInner(ctx, err, \"failed to encode UDP packet\")\n\t\t\treturn\n\t\t}\n\n\t\tconn.Write(data.Bytes())\n\t\tdata.Release()\n\t})\n\tdefer udpServer.RemoveRay()\n\n\tinbound := session.InboundFromContext(ctx)\n\tvar dest *net.Destination\n\treader := buf.NewPacketReader(conn)\n\tfor {\n\t\tmpayload, err := reader.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, payload := range mpayload {\n\t\t\tvar request *protocol.RequestHeader\n\t\t\tvar data *buf.Buffer\n\t\t\tvar err error\n\n\t\t\tif inbound.User != nil {\n\t\t\t\tvalidator := new(Validator)\n\t\t\t\tvalidator.Add(inbound.User)\n\t\t\t\trequest, data, err = DecodeUDPPacket(validator, payload)\n\t\t\t} else {\n\t\t\t\trequest, data, err = DecodeUDPPacket(s.validator, payload)\n\t\t\t\tif err == nil {\n\t\t\t\t\tinbound.User = request.User\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tif inbound.Source.IsValid() {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"dropping invalid UDP packet from: \", inbound.Source)\n\t\t\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\t\t\tFrom:   inbound.Source,\n\t\t\t\t\t\tTo:     \"\",\n\t\t\t\t\t\tStatus: log.AccessRejected,\n\t\t\t\t\t\tReason: err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tpayload.Release()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdestination := request.Destination()\n\n\t\t\tcurrentPacketCtx := ctx\n\t\t\tif inbound.Source.IsValid() {\n\t\t\t\tcurrentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\t\t\tFrom:   inbound.Source,\n\t\t\t\t\tTo:     destination,\n\t\t\t\t\tStatus: log.AccessAccepted,\n\t\t\t\t\tReason: \"\",\n\t\t\t\t\tEmail:  request.User.Email,\n\t\t\t\t})\n\t\t\t}\n\t\t\terrors.LogInfo(ctx, \"tunnelling request to \", destination)\n\n\t\t\tdata.UDP = &destination\n\n\t\t\tif !s.cone || dest == nil {\n\t\t\t\tdest = &destination\n\t\t\t}\n\n\t\t\tcurrentPacketCtx = protocol.ContextWithRequestHeader(currentPacketCtx, request)\n\t\t\tudpServer.Dispatch(currentPacketCtx, *dest, data)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Server) handleConnection(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tsessionPolicy := s.policyManager.ForLevel(0)\n\tif err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {\n\t\treturn errors.New(\"unable to set read deadline\").Base(err).AtWarning()\n\t}\n\n\tbufferedReader := buf.BufferedReader{Reader: buf.NewReader(conn)}\n\trequest, bodyReader, err := ReadTCPSession(s.validator, &bufferedReader)\n\tif err != nil {\n\t\tlog.Record(&log.AccessMessage{\n\t\t\tFrom:   conn.RemoteAddr(),\n\t\t\tTo:     \"\",\n\t\t\tStatus: log.AccessRejected,\n\t\t\tReason: err,\n\t\t})\n\t\treturn errors.New(\"failed to create request from: \", conn.RemoteAddr()).Base(err)\n\t}\n\tconn.SetReadDeadline(time.Time{})\n\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound == nil {\n\t\tpanic(\"no inbound metadata\")\n\t}\n\tinbound.User = request.User\n\n\tdest := request.Destination()\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   conn.RemoteAddr(),\n\t\tTo:     dest,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t\tEmail:  request.User.Email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to \", dest)\n\n\tsessionPolicy = s.policyManager.ForLevel(request.User.Level)\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)\n\tlink, err := dispatcher.Dispatch(ctx, dest)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresponseDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\tbufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\t\tresponseWriter, err := WriteTCPResponse(request, bufferedWriter)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to write response\").Base(err)\n\t\t}\n\n\t\t{\n\t\t\tpayload, err := link.Reader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := responseWriter.WriteMultiBuffer(payload); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif err := bufferedWriter.SetBuffered(false); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := buf.Copy(link.Reader, responseWriter, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to transport all TCP response\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\trequestDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\tif err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to transport all TCP request\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\trequestDoneAndCloseWriter := task.OnSuccess(requestDone, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestDoneAndCloseWriter, responseDone); err != nil {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewServer(ctx, config.(*ServerConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/shadowsocks/shadowsocks.go",
    "content": "// Package shadowsocks provides compatible functionality to Shadowsocks.\n//\n// Shadowsocks client and server are implemented as outbound and inbound respectively in Xray's term.\n//\n// R.I.P Shadowsocks\npackage shadowsocks\n"
  },
  {
    "path": "proxy/shadowsocks/validator.go",
    "content": "package shadowsocks\n\nimport (\n\t\"crypto/cipher\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"hash/crc64\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\n// Validator stores valid Shadowsocks users.\ntype Validator struct {\n\tsync.RWMutex\n\tusers []*protocol.MemoryUser\n\n\tbehaviorSeed  uint64\n\tbehaviorFused bool\n}\n\nvar ErrNotFound = errors.New(\"Not Found\")\n\n// Add a Shadowsocks user.\nfunc (v *Validator) Add(u *protocol.MemoryUser) error {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\taccount := u.Account.(*MemoryAccount)\n\tif !account.Cipher.IsAEAD() && len(v.users) > 0 {\n\t\treturn errors.New(\"The cipher is not support Single-port Multi-user\")\n\t}\n\tv.users = append(v.users, u)\n\n\tif !v.behaviorFused {\n\t\thashkdf := hmac.New(sha256.New, []byte(\"SSBSKDF\"))\n\t\thashkdf.Write(account.Key)\n\t\tv.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))\n\t}\n\n\treturn nil\n}\n\n// Del a Shadowsocks user with a non-empty Email.\nfunc (v *Validator) Del(email string) error {\n\tif email == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n\n\tv.Lock()\n\tdefer v.Unlock()\n\n\temail = strings.ToLower(email)\n\tidx := -1\n\tfor i, u := range v.users {\n\t\tif strings.EqualFold(u.Email, email) {\n\t\t\tidx = i\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif idx == -1 {\n\t\treturn errors.New(\"User \", email, \" not found.\")\n\t}\n\tulen := len(v.users)\n\n\tv.users[idx] = v.users[ulen-1]\n\tv.users[ulen-1] = nil\n\tv.users = v.users[:ulen-1]\n\n\treturn nil\n}\n\n// GetByEmail Get a Shadowsocks user with a non-empty Email.\nfunc (v *Validator) GetByEmail(email string) *protocol.MemoryUser {\n\tif email == \"\" {\n\t\treturn nil\n\t}\n\n\tv.Lock()\n\tdefer v.Unlock()\n\n\temail = strings.ToLower(email)\n\tfor _, u := range v.users {\n\t\tif strings.EqualFold(u.Email, email) {\n\t\t\treturn u\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetAll get all users\nfunc (v *Validator) GetAll() []*protocol.MemoryUser {\n\tv.Lock()\n\tdefer v.Unlock()\n\tdst := make([]*protocol.MemoryUser, len(v.users))\n\tcopy(dst, v.users)\n\treturn dst\n}\n\n// GetCount get users count\nfunc (v *Validator) GetCount() int64 {\n\tv.Lock()\n\tdefer v.Unlock()\n\treturn int64(len(v.users))\n}\n\n// Get a Shadowsocks user.\nfunc (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol.MemoryUser, aead cipher.AEAD, ret []byte, ivLen int32, err error) {\n\tv.RLock()\n\tdefer v.RUnlock()\n\n\tfor _, user := range v.users {\n\t\tif account := user.Account.(*MemoryAccount); account.Cipher.IsAEAD() {\n\t\t\t// AEAD payload decoding requires the payload to be over 32 bytes\n\t\t\tif len(bs) < 32 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\taeadCipher := account.Cipher.(*AEADCipher)\n\t\t\tivLen = aeadCipher.IVSize()\n\t\t\tiv := bs[:ivLen]\n\t\t\tsubkey := make([]byte, 32)\n\t\t\tsubkey = subkey[:aeadCipher.KeyBytes]\n\t\t\thkdfSHA1(account.Key, iv, subkey)\n\t\t\taead = aeadCipher.AEADAuthCreator(subkey)\n\n\t\t\tvar matchErr error\n\t\t\tswitch command {\n\t\t\tcase protocol.RequestCommandTCP:\n\t\t\t\tdata := make([]byte, 4+aead.NonceSize())\n\t\t\t\tret, matchErr = aead.Open(data[:0], data[4:], bs[ivLen:ivLen+18], nil)\n\t\t\tcase protocol.RequestCommandUDP:\n\t\t\t\tdata := make([]byte, 8192)\n\t\t\t\tret, matchErr = aead.Open(data[:0], data[8192-aead.NonceSize():8192], bs[ivLen:], nil)\n\t\t\t}\n\n\t\t\tif matchErr == nil {\n\t\t\t\tu = user\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tu = user\n\t\t\tivLen = user.Account.(*MemoryAccount).Cipher.IVSize()\n\t\t\t// err = user.Account.(*MemoryAccount).CheckIV(bs[:ivLen]) // The IV size of None Cipher is 0.\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn nil, nil, nil, 0, ErrNotFound\n}\n\nfunc (v *Validator) GetBehaviorSeed() uint64 {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\tv.behaviorFused = true\n\tif v.behaviorSeed == 0 {\n\t\tv.behaviorSeed = dice.RollUint64()\n\t}\n\treturn v.behaviorSeed\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/config.go",
    "content": "package shadowsocks_2022\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\n// MemoryAccount is an account type converted from Account.\ntype MemoryAccount struct {\n\tKey string\n}\n\n// AsAccount implements protocol.AsAccount.\nfunc (u *Account) AsAccount() (protocol.Account, error) {\n\treturn &MemoryAccount{\n\t\tKey: u.GetKey(),\n\t}, nil\n}\n\n// Equals implements protocol.Account.Equals().\nfunc (a *MemoryAccount) Equals(another protocol.Account) bool {\n\tif account, ok := another.(*MemoryAccount); ok {\n\t\treturn a.Key == account.Key\n\t}\n\treturn false\n}\n\nfunc (a *MemoryAccount) ToProto() proto.Message {\n\treturn &Account{\n\t\tKey: a.Key,\n\t}\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/shadowsocks_2022/config.proto\n\npackage shadowsocks_2022\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMethod        string                 `protobuf:\"bytes,1,opt,name=method,proto3\" json:\"method,omitempty\"`\n\tKey           string                 `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tEmail         string                 `protobuf:\"bytes,3,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tLevel         int32                  `protobuf:\"varint,4,opt,name=level,proto3\" json:\"level,omitempty\"`\n\tNetwork       []net.Network          `protobuf:\"varint,5,rep,packed,name=network,proto3,enum=xray.common.net.Network\" json:\"network,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServerConfig) Reset() {\n\t*x = ServerConfig{}\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerConfig) ProtoMessage() {}\n\nfunc (x *ServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.\nfunc (*ServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ServerConfig) GetMethod() string {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn \"\"\n}\n\nfunc (x *ServerConfig) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *ServerConfig) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *ServerConfig) GetLevel() int32 {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn 0\n}\n\nfunc (x *ServerConfig) GetNetwork() []net.Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn nil\n}\n\ntype MultiUserServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMethod        string                 `protobuf:\"bytes,1,opt,name=method,proto3\" json:\"method,omitempty\"`\n\tKey           string                 `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tUsers         []*protocol.User       `protobuf:\"bytes,3,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tNetwork       []net.Network          `protobuf:\"varint,4,rep,packed,name=network,proto3,enum=xray.common.net.Network\" json:\"network,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *MultiUserServerConfig) Reset() {\n\t*x = MultiUserServerConfig{}\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *MultiUserServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MultiUserServerConfig) ProtoMessage() {}\n\nfunc (x *MultiUserServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MultiUserServerConfig.ProtoReflect.Descriptor instead.\nfunc (*MultiUserServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *MultiUserServerConfig) GetMethod() string {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn \"\"\n}\n\nfunc (x *MultiUserServerConfig) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *MultiUserServerConfig) GetUsers() []*protocol.User {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\nfunc (x *MultiUserServerConfig) GetNetwork() []net.Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn nil\n}\n\ntype RelayDestination struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tKey           string                 `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tAddress       *net.IPOrDomain        `protobuf:\"bytes,2,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tPort          uint32                 `protobuf:\"varint,3,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tEmail         string                 `protobuf:\"bytes,4,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tLevel         int32                  `protobuf:\"varint,5,opt,name=level,proto3\" json:\"level,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RelayDestination) Reset() {\n\t*x = RelayDestination{}\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RelayDestination) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RelayDestination) ProtoMessage() {}\n\nfunc (x *RelayDestination) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RelayDestination.ProtoReflect.Descriptor instead.\nfunc (*RelayDestination) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *RelayDestination) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *RelayDestination) GetAddress() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *RelayDestination) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nfunc (x *RelayDestination) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *RelayDestination) GetLevel() int32 {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn 0\n}\n\ntype RelayServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMethod        string                 `protobuf:\"bytes,1,opt,name=method,proto3\" json:\"method,omitempty\"`\n\tKey           string                 `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tDestinations  []*RelayDestination    `protobuf:\"bytes,3,rep,name=destinations,proto3\" json:\"destinations,omitempty\"`\n\tNetwork       []net.Network          `protobuf:\"varint,4,rep,packed,name=network,proto3,enum=xray.common.net.Network\" json:\"network,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RelayServerConfig) Reset() {\n\t*x = RelayServerConfig{}\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RelayServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RelayServerConfig) ProtoMessage() {}\n\nfunc (x *RelayServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RelayServerConfig.ProtoReflect.Descriptor instead.\nfunc (*RelayServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *RelayServerConfig) GetMethod() string {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn \"\"\n}\n\nfunc (x *RelayServerConfig) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *RelayServerConfig) GetDestinations() []*RelayDestination {\n\tif x != nil {\n\t\treturn x.Destinations\n\t}\n\treturn nil\n}\n\nfunc (x *RelayServerConfig) GetNetwork() []net.Network {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn nil\n}\n\ntype Account struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tKey           string                 `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Account) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\ntype ClientConfig struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddress           *net.IPOrDomain        `protobuf:\"bytes,1,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tPort              uint32                 `protobuf:\"varint,2,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tMethod            string                 `protobuf:\"bytes,3,opt,name=method,proto3\" json:\"method,omitempty\"`\n\tKey               string                 `protobuf:\"bytes,4,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tUdpOverTcp        bool                   `protobuf:\"varint,5,opt,name=udp_over_tcp,json=udpOverTcp,proto3\" json:\"udp_over_tcp,omitempty\"`\n\tUdpOverTcpVersion uint32                 `protobuf:\"varint,6,opt,name=udp_over_tcp_version,json=udpOverTcpVersion,proto3\" json:\"udp_over_tcp_version,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_shadowsocks_2022_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ClientConfig) GetAddress() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ClientConfig) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nfunc (x *ClientConfig) GetMethod() string {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientConfig) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientConfig) GetUdpOverTcp() bool {\n\tif x != nil {\n\t\treturn x.UdpOverTcp\n\t}\n\treturn false\n}\n\nfunc (x *ClientConfig) GetUdpOverTcpVersion() uint32 {\n\tif x != nil {\n\t\treturn x.UdpOverTcpVersion\n\t}\n\treturn 0\n}\n\nvar File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_shadowsocks_2022_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#proxy/shadowsocks_2022/config.proto\\x12\\x1bxray.proxy.shadowsocks_2022\\x1a\\x18common/net/network.proto\\x1a\\x18common/net/address.proto\\x1a\\x1acommon/protocol/user.proto\\\"\\x98\\x01\\n\" +\n\t\"\\fServerConfig\\x12\\x16\\n\" +\n\t\"\\x06method\\x18\\x01 \\x01(\\tR\\x06method\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x02 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05email\\x18\\x03 \\x01(\\tR\\x05email\\x12\\x14\\n\" +\n\t\"\\x05level\\x18\\x04 \\x01(\\x05R\\x05level\\x122\\n\" +\n\t\"\\anetwork\\x18\\x05 \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\anetwork\\\"\\xa7\\x01\\n\" +\n\t\"\\x15MultiUserServerConfig\\x12\\x16\\n\" +\n\t\"\\x06method\\x18\\x01 \\x01(\\tR\\x06method\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x02 \\x01(\\tR\\x03key\\x120\\n\" +\n\t\"\\x05users\\x18\\x03 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\x05users\\x122\\n\" +\n\t\"\\anetwork\\x18\\x04 \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\anetwork\\\"\\x9b\\x01\\n\" +\n\t\"\\x10RelayDestination\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x125\\n\" +\n\t\"\\aaddress\\x18\\x02 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x03 \\x01(\\rR\\x04port\\x12\\x14\\n\" +\n\t\"\\x05email\\x18\\x04 \\x01(\\tR\\x05email\\x12\\x14\\n\" +\n\t\"\\x05level\\x18\\x05 \\x01(\\x05R\\x05level\\\"\\xc4\\x01\\n\" +\n\t\"\\x11RelayServerConfig\\x12\\x16\\n\" +\n\t\"\\x06method\\x18\\x01 \\x01(\\tR\\x06method\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x02 \\x01(\\tR\\x03key\\x12Q\\n\" +\n\t\"\\fdestinations\\x18\\x03 \\x03(\\v2-.xray.proxy.shadowsocks_2022.RelayDestinationR\\fdestinations\\x122\\n\" +\n\t\"\\anetwork\\x18\\x04 \\x03(\\x0e2\\x18.xray.common.net.NetworkR\\anetwork\\\"\\x1b\\n\" +\n\t\"\\aAccount\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\\"\\xd6\\x01\\n\" +\n\t\"\\fClientConfig\\x125\\n\" +\n\t\"\\aaddress\\x18\\x01 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x02 \\x01(\\rR\\x04port\\x12\\x16\\n\" +\n\t\"\\x06method\\x18\\x03 \\x01(\\tR\\x06method\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x04 \\x01(\\tR\\x03key\\x12 \\n\" +\n\t\"\\fudp_over_tcp\\x18\\x05 \\x01(\\bR\\n\" +\n\t\"udpOverTcp\\x12/\\n\" +\n\t\"\\x14udp_over_tcp_version\\x18\\x06 \\x01(\\rR\\x11udpOverTcpVersionBr\\n\" +\n\t\"\\x1fcom.xray.proxy.shadowsocks_2022P\\x01Z0github.com/xtls/xray-core/proxy/shadowsocks_2022\\xaa\\x02\\x1aXray.Proxy.Shadowsocks2022b\\x06proto3\"\n\nvar (\n\tfile_proxy_shadowsocks_2022_config_proto_rawDescOnce sync.Once\n\tfile_proxy_shadowsocks_2022_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_shadowsocks_2022_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_shadowsocks_2022_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_shadowsocks_2022_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_shadowsocks_2022_config_proto_rawDesc), len(file_proxy_shadowsocks_2022_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_shadowsocks_2022_config_proto_rawDescData\n}\n\nvar file_proxy_shadowsocks_2022_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)\nvar file_proxy_shadowsocks_2022_config_proto_goTypes = []any{\n\t(*ServerConfig)(nil),          // 0: xray.proxy.shadowsocks_2022.ServerConfig\n\t(*MultiUserServerConfig)(nil), // 1: xray.proxy.shadowsocks_2022.MultiUserServerConfig\n\t(*RelayDestination)(nil),      // 2: xray.proxy.shadowsocks_2022.RelayDestination\n\t(*RelayServerConfig)(nil),     // 3: xray.proxy.shadowsocks_2022.RelayServerConfig\n\t(*Account)(nil),               // 4: xray.proxy.shadowsocks_2022.Account\n\t(*ClientConfig)(nil),          // 5: xray.proxy.shadowsocks_2022.ClientConfig\n\t(net.Network)(0),              // 6: xray.common.net.Network\n\t(*protocol.User)(nil),         // 7: xray.common.protocol.User\n\t(*net.IPOrDomain)(nil),        // 8: xray.common.net.IPOrDomain\n}\nvar file_proxy_shadowsocks_2022_config_proto_depIdxs = []int32{\n\t6, // 0: xray.proxy.shadowsocks_2022.ServerConfig.network:type_name -> xray.common.net.Network\n\t7, // 1: xray.proxy.shadowsocks_2022.MultiUserServerConfig.users:type_name -> xray.common.protocol.User\n\t6, // 2: xray.proxy.shadowsocks_2022.MultiUserServerConfig.network:type_name -> xray.common.net.Network\n\t8, // 3: xray.proxy.shadowsocks_2022.RelayDestination.address:type_name -> xray.common.net.IPOrDomain\n\t2, // 4: xray.proxy.shadowsocks_2022.RelayServerConfig.destinations:type_name -> xray.proxy.shadowsocks_2022.RelayDestination\n\t6, // 5: xray.proxy.shadowsocks_2022.RelayServerConfig.network:type_name -> xray.common.net.Network\n\t8, // 6: xray.proxy.shadowsocks_2022.ClientConfig.address:type_name -> xray.common.net.IPOrDomain\n\t7, // [7:7] is the sub-list for method output_type\n\t7, // [7:7] is the sub-list for method input_type\n\t7, // [7:7] is the sub-list for extension type_name\n\t7, // [7:7] is the sub-list for extension extendee\n\t0, // [0:7] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_shadowsocks_2022_config_proto_init() }\nfunc file_proxy_shadowsocks_2022_config_proto_init() {\n\tif File_proxy_shadowsocks_2022_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_shadowsocks_2022_config_proto_rawDesc), len(file_proxy_shadowsocks_2022_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   6,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_shadowsocks_2022_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_shadowsocks_2022_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_shadowsocks_2022_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_shadowsocks_2022_config_proto = out.File\n\tfile_proxy_shadowsocks_2022_config_proto_goTypes = nil\n\tfile_proxy_shadowsocks_2022_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.shadowsocks_2022;\noption csharp_namespace = \"Xray.Proxy.Shadowsocks2022\";\noption go_package = \"github.com/xtls/xray-core/proxy/shadowsocks_2022\";\noption java_package = \"com.xray.proxy.shadowsocks_2022\";\noption java_multiple_files = true;\n\nimport \"common/net/network.proto\";\nimport \"common/net/address.proto\";\nimport \"common/protocol/user.proto\";\n\nmessage ServerConfig {\n  string method = 1;\n  string key = 2;\n  string email = 3;\n  int32 level = 4;\n  repeated xray.common.net.Network network = 5;\n}\n\nmessage MultiUserServerConfig {\n  string method = 1;\n  string key = 2;\n  repeated xray.common.protocol.User users = 3;\n  repeated xray.common.net.Network network = 4;\n}\n\nmessage RelayDestination {\n  string key = 1;\n  xray.common.net.IPOrDomain address = 2;\n  uint32 port = 3;\n  string email = 4;\n  int32 level = 5;\n}\n\nmessage RelayServerConfig {\n  string method = 1;\n  string key = 2;\n  repeated RelayDestination destinations = 3;\n  repeated xray.common.net.Network network = 4;\n}\n\nmessage Account {\n  string key = 1;\n}\n\nmessage ClientConfig {\n  xray.common.net.IPOrDomain address = 1;\n  uint32 port = 2;\n  string method = 3;\n  string key = 4;\n  bool udp_over_tcp = 5;\n  uint32 udp_over_tcp_version = 6;\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/inbound.go",
    "content": "package shadowsocks_2022\n\nimport (\n\t\"context\"\n\n\tshadowsocks \"github.com/sagernet/sing-shadowsocks\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\tC \"github.com/sagernet/sing/common\"\n\tB \"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/singbridge\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewServer(ctx, config.(*ServerConfig))\n\t}))\n}\n\ntype Inbound struct {\n\tnetworks []net.Network\n\tservice  shadowsocks.Service\n\temail    string\n\tlevel    int\n}\n\nfunc NewServer(ctx context.Context, config *ServerConfig) (*Inbound, error) {\n\tnetworks := config.Network\n\tif len(networks) == 0 {\n\t\tnetworks = []net.Network{\n\t\t\tnet.Network_TCP,\n\t\t\tnet.Network_UDP,\n\t\t}\n\t}\n\tinbound := &Inbound{\n\t\tnetworks: networks,\n\t\temail:    config.Email,\n\t\tlevel:    int(config.Level),\n\t}\n\tif !C.Contains(shadowaead_2022.List, config.Method) {\n\t\treturn nil, errors.New(\"unsupported method \", config.Method)\n\t}\n\tservice, err := shadowaead_2022.NewServiceWithPassword(config.Method, config.Key, 500, inbound, nil)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\tinbound.service = service\n\treturn inbound, nil\n}\n\nfunc (i *Inbound) Network() []net.Network {\n\treturn i.networks\n}\n\nfunc (i *Inbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"shadowsocks-2022\"\n\tinbound.CanSpliceCopy = 3\n\n\tvar metadata M.Metadata\n\tif inbound.Source.IsValid() {\n\t\tmetadata.Source = M.ParseSocksaddr(inbound.Source.NetAddr())\n\t}\n\n\tctx = session.ContextWithDispatcher(ctx, dispatcher)\n\n\tif network == net.Network_TCP {\n\t\treturn singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata))\n\t} else {\n\t\treader := buf.NewReader(connection)\n\t\tpc := &natPacketConn{connection}\n\t\tfor {\n\t\t\tmb, err := reader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\treturn singbridge.ReturnError(err)\n\t\t\t}\n\t\t\tfor _, buffer := range mb {\n\t\t\t\tpacket := B.As(buffer.Bytes()).ToOwned()\n\t\t\t\tbuffer.Release()\n\t\t\t\terr = i.service.NewPacket(ctx, pc, packet, metadata)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpacket.Release()\n\t\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.User = &protocol.MemoryUser{\n\t\tEmail: i.email,\n\t\tLevel: uint32(i.level),\n\t}\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   metadata.Source,\n\t\tTo:     metadata.Destination,\n\t\tStatus: log.AccessAccepted,\n\t\tEmail:  i.email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to tcp:\", metadata.Destination)\n\tdispatcher := session.DispatcherFromContext(ctx)\n\tlink, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn singbridge.CopyConn(ctx, nil, link, conn)\n}\n\nfunc (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.User = &protocol.MemoryUser{\n\t\tEmail: i.email,\n\t\tLevel: uint32(i.level),\n\t}\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   metadata.Source,\n\t\tTo:     metadata.Destination,\n\t\tStatus: log.AccessAccepted,\n\t\tEmail:  i.email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to udp:\", metadata.Destination)\n\tdispatcher := session.DispatcherFromContext(ctx)\n\tdestination := singbridge.ToDestination(metadata.Destination, net.Network_UDP)\n\tlink, err := dispatcher.Dispatch(ctx, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\toutConn := &singbridge.PacketConnWrapper{\n\t\tReader: link.Reader,\n\t\tWriter: link.Writer,\n\t\tDest:   destination,\n\t}\n\treturn bufio.CopyPacketConn(ctx, conn, outConn)\n}\n\nfunc (i *Inbound) NewError(ctx context.Context, err error) {\n\tif E.IsClosed(err) {\n\t\treturn\n\t}\n\terrors.LogWarning(ctx, err.Error())\n}\n\ntype natPacketConn struct {\n\tnet.Conn\n}\n\nfunc (c *natPacketConn) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {\n\t_, err = buffer.ReadFrom(c)\n\treturn\n}\n\nfunc (c *natPacketConn) WritePacket(buffer *B.Buffer, addr M.Socksaddr) error {\n\t_, err := buffer.WriteTo(c)\n\treturn err\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/inbound_multi.go",
    "content": "package shadowsocks_2022\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\tC \"github.com/sagernet/sing/common\"\n\tA \"github.com/sagernet/sing/common/auth\"\n\tB \"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/singbridge\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*MultiUserServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewMultiServer(ctx, config.(*MultiUserServerConfig))\n\t}))\n}\n\ntype MultiUserInbound struct {\n\tsync.Mutex\n\tnetworks []net.Network\n\tusers    []*protocol.MemoryUser\n\tservice  *shadowaead_2022.MultiService[int]\n}\n\nfunc NewMultiServer(ctx context.Context, config *MultiUserServerConfig) (*MultiUserInbound, error) {\n\tnetworks := config.Network\n\tif len(networks) == 0 {\n\t\tnetworks = []net.Network{\n\t\t\tnet.Network_TCP,\n\t\t\tnet.Network_UDP,\n\t\t}\n\t}\n\tmemUsers := []*protocol.MemoryUser{}\n\tfor i, user := range config.Users {\n\t\tif user.Email == \"\" {\n\t\t\tu := uuid.New()\n\t\t\tuser.Email = \"unnamed-user-\" + strconv.Itoa(i) + \"-\" + u.String()\n\t\t}\n\t\tu, err := user.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get shadowsocks user\").Base(err).AtError()\n\t\t}\n\t\tmemUsers = append(memUsers, u)\n\t}\n\n\tinbound := &MultiUserInbound{\n\t\tnetworks: networks,\n\t\tusers:    memUsers,\n\t}\n\tif config.Key == \"\" {\n\t\treturn nil, errors.New(\"missing key\")\n\t}\n\tpsk, err := base64.StdEncoding.DecodeString(config.Key)\n\tif err != nil {\n\t\treturn nil, errors.New(\"parse config\").Base(err)\n\t}\n\tservice, err := shadowaead_2022.NewMultiService[int](config.Method, psk, 500, inbound, nil)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\terr = service.UpdateUsersWithPasswords(\n\t\tC.MapIndexed(memUsers, func(index int, it *protocol.MemoryUser) int { return index }),\n\t\tC.Map(memUsers, func(it *protocol.MemoryUser) string { return it.Account.(*MemoryAccount).Key }),\n\t)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\n\tinbound.service = service\n\treturn inbound, nil\n}\n\n// AddUser implements proxy.UserManager.AddUser().\nfunc (i *MultiUserInbound) AddUser(ctx context.Context, u *protocol.MemoryUser) error {\n\ti.Lock()\n\tdefer i.Unlock()\n\n\tif u.Email != \"\" {\n\t\tfor idx := range i.users {\n\t\t\tif i.users[idx].Email == u.Email {\n\t\t\t\treturn errors.New(\"User \", u.Email, \" already exists.\")\n\t\t\t}\n\t\t}\n\t}\n\ti.users = append(i.users, u)\n\n\t// sync to multi service\n\t// Considering implements shadowsocks2022 in xray-core may have better performance.\n\ti.service.UpdateUsersWithPasswords(\n\t\tC.MapIndexed(i.users, func(index int, it *protocol.MemoryUser) int { return index }),\n\t\tC.Map(i.users, func(it *protocol.MemoryUser) string { return it.Account.(*MemoryAccount).Key }),\n\t)\n\n\treturn nil\n}\n\n// RemoveUser implements proxy.UserManager.RemoveUser().\nfunc (i *MultiUserInbound) RemoveUser(ctx context.Context, email string) error {\n\tif email == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n\n\ti.Lock()\n\tdefer i.Unlock()\n\n\tidx := -1\n\tfor ii, u := range i.users {\n\t\tif strings.EqualFold(u.Email, email) {\n\t\t\tidx = ii\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif idx == -1 {\n\t\treturn errors.New(\"User \", email, \" not found.\")\n\t}\n\n\tulen := len(i.users)\n\n\ti.users[idx] = i.users[ulen-1]\n\ti.users[ulen-1] = nil\n\ti.users = i.users[:ulen-1]\n\n\t// sync to multi service\n\t// Considering implements shadowsocks2022 in xray-core may have better performance.\n\ti.service.UpdateUsersWithPasswords(\n\t\tC.MapIndexed(i.users, func(index int, it *protocol.MemoryUser) int { return index }),\n\t\tC.Map(i.users, func(it *protocol.MemoryUser) string { return it.Account.(*MemoryAccount).Key }),\n\t)\n\n\treturn nil\n}\n\n// GetUser implements proxy.UserManager.GetUser().\nfunc (i *MultiUserInbound) GetUser(ctx context.Context, email string) *protocol.MemoryUser {\n\tif email == \"\" {\n\t\treturn nil\n\t}\n\n\ti.Lock()\n\tdefer i.Unlock()\n\n\tfor _, u := range i.users {\n\t\tif strings.EqualFold(u.Email, email) {\n\t\t\treturn u\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetUsers implements proxy.UserManager.GetUsers().\nfunc (i *MultiUserInbound) GetUsers(ctx context.Context) []*protocol.MemoryUser {\n\ti.Lock()\n\tdefer i.Unlock()\n\tdst := make([]*protocol.MemoryUser, len(i.users))\n\tcopy(dst, i.users)\n\treturn dst\n}\n\n// GetUsersCount implements proxy.UserManager.GetUsersCount().\nfunc (i *MultiUserInbound) GetUsersCount(context.Context) int64 {\n\ti.Lock()\n\tdefer i.Unlock()\n\treturn int64(len(i.users))\n}\n\nfunc (i *MultiUserInbound) Network() []net.Network {\n\treturn i.networks\n}\n\nfunc (i *MultiUserInbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"shadowsocks-2022-multi\"\n\tinbound.CanSpliceCopy = 3\n\n\tvar metadata M.Metadata\n\tif inbound.Source.IsValid() {\n\t\tmetadata.Source = M.ParseSocksaddr(inbound.Source.NetAddr())\n\t}\n\n\tctx = session.ContextWithDispatcher(ctx, dispatcher)\n\n\tif network == net.Network_TCP {\n\t\treturn singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata))\n\t} else {\n\t\treader := buf.NewReader(connection)\n\t\tpc := &natPacketConn{connection}\n\t\tfor {\n\t\t\tmb, err := reader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\treturn singbridge.ReturnError(err)\n\t\t\t}\n\t\t\tfor _, buffer := range mb {\n\t\t\t\tpacket := B.As(buffer.Bytes()).ToOwned()\n\t\t\t\tbuffer.Release()\n\t\t\t\terr = i.service.NewPacket(ctx, pc, packet, metadata)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpacket.Release()\n\t\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (i *MultiUserInbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tinbound := session.InboundFromContext(ctx)\n\tuserInt, _ := A.UserFromContext[int](ctx)\n\tuser := i.users[userInt]\n\tinbound.User = user\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   metadata.Source,\n\t\tTo:     metadata.Destination,\n\t\tStatus: log.AccessAccepted,\n\t\tEmail:  user.Email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to tcp:\", metadata.Destination)\n\tdispatcher := session.DispatcherFromContext(ctx)\n\tdestination := singbridge.ToDestination(metadata.Destination, net.Network_TCP)\n\tif !destination.IsValid() {\n\t\treturn errors.New(\"invalid destination\")\n\t}\n\n\tlink, err := dispatcher.Dispatch(ctx, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn singbridge.CopyConn(ctx, conn, link, conn)\n}\n\nfunc (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tinbound := session.InboundFromContext(ctx)\n\tuserInt, _ := A.UserFromContext[int](ctx)\n\tuser := i.users[userInt]\n\tinbound.User = user\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   metadata.Source,\n\t\tTo:     metadata.Destination,\n\t\tStatus: log.AccessAccepted,\n\t\tEmail:  user.Email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to udp:\", metadata.Destination)\n\tdispatcher := session.DispatcherFromContext(ctx)\n\tdestination := singbridge.ToDestination(metadata.Destination, net.Network_UDP)\n\tlink, err := dispatcher.Dispatch(ctx, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\toutConn := &singbridge.PacketConnWrapper{\n\t\tReader: link.Reader,\n\t\tWriter: link.Writer,\n\t\tDest:   destination,\n\t}\n\treturn bufio.CopyPacketConn(ctx, conn, outConn)\n}\n\nfunc (i *MultiUserInbound) NewError(ctx context.Context, err error) {\n\tif E.IsClosed(err) {\n\t\treturn\n\t}\n\terrors.LogWarning(ctx, err.Error())\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/inbound_relay.go",
    "content": "package shadowsocks_2022\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\tC \"github.com/sagernet/sing/common\"\n\tA \"github.com/sagernet/sing/common/auth\"\n\tB \"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/singbridge\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*RelayServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewRelayServer(ctx, config.(*RelayServerConfig))\n\t}))\n}\n\ntype RelayInbound struct {\n\tnetworks     []net.Network\n\tdestinations []*RelayDestination\n\tservice      *shadowaead_2022.RelayService[int]\n}\n\nfunc NewRelayServer(ctx context.Context, config *RelayServerConfig) (*RelayInbound, error) {\n\tnetworks := config.Network\n\tif len(networks) == 0 {\n\t\tnetworks = []net.Network{\n\t\t\tnet.Network_TCP,\n\t\t\tnet.Network_UDP,\n\t\t}\n\t}\n\tinbound := &RelayInbound{\n\t\tnetworks:     networks,\n\t\tdestinations: config.Destinations,\n\t}\n\tif !C.Contains(shadowaead_2022.List, config.Method) || !strings.Contains(config.Method, \"aes\") {\n\t\treturn nil, errors.New(\"unsupported method \", config.Method)\n\t}\n\tservice, err := shadowaead_2022.NewRelayServiceWithPassword[int](config.Method, config.Key, 500, inbound)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\n\tfor i, destination := range config.Destinations {\n\t\tif destination.Email == \"\" {\n\t\t\tu := uuid.New()\n\t\t\tdestination.Email = \"unnamed-destination-\" + strconv.Itoa(i) + \"-\" + u.String()\n\t\t}\n\t}\n\terr = service.UpdateUsersWithPasswords(\n\t\tC.MapIndexed(config.Destinations, func(index int, it *RelayDestination) int { return index }),\n\t\tC.Map(config.Destinations, func(it *RelayDestination) string { return it.Key }),\n\t\tC.Map(config.Destinations, func(it *RelayDestination) M.Socksaddr {\n\t\t\treturn singbridge.ToSocksaddr(net.Destination{\n\t\t\t\tAddress: it.Address.AsAddress(),\n\t\t\t\tPort:    net.Port(it.Port),\n\t\t\t})\n\t\t}),\n\t)\n\tif err != nil {\n\t\treturn nil, errors.New(\"create service\").Base(err)\n\t}\n\tinbound.service = service\n\treturn inbound, nil\n}\n\nfunc (i *RelayInbound) Network() []net.Network {\n\treturn i.networks\n}\n\nfunc (i *RelayInbound) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"shadowsocks-2022-relay\"\n\tinbound.CanSpliceCopy = 3\n\n\tvar metadata M.Metadata\n\tif inbound.Source.IsValid() {\n\t\tmetadata.Source = M.ParseSocksaddr(inbound.Source.NetAddr())\n\t}\n\n\tctx = session.ContextWithDispatcher(ctx, dispatcher)\n\n\tif network == net.Network_TCP {\n\t\treturn singbridge.ReturnError(i.service.NewConnection(ctx, connection, metadata))\n\t} else {\n\t\treader := buf.NewReader(connection)\n\t\tpc := &natPacketConn{connection}\n\t\tfor {\n\t\t\tmb, err := reader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\treturn singbridge.ReturnError(err)\n\t\t\t}\n\t\t\tfor _, buffer := range mb {\n\t\t\t\tpacket := B.As(buffer.Bytes()).ToOwned()\n\t\t\t\tbuffer.Release()\n\t\t\t\terr = i.service.NewPacket(ctx, pc, packet, metadata)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpacket.Release()\n\t\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (i *RelayInbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {\n\tinbound := session.InboundFromContext(ctx)\n\tuserInt, _ := A.UserFromContext[int](ctx)\n\tuser := i.destinations[userInt]\n\tinbound.User = &protocol.MemoryUser{\n\t\tEmail: user.Email,\n\t\tLevel: uint32(user.Level),\n\t}\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   metadata.Source,\n\t\tTo:     metadata.Destination,\n\t\tStatus: log.AccessAccepted,\n\t\tEmail:  user.Email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to tcp:\", metadata.Destination)\n\tdispatcher := session.DispatcherFromContext(ctx)\n\tlink, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn singbridge.CopyConn(ctx, nil, link, conn)\n}\n\nfunc (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {\n\tinbound := session.InboundFromContext(ctx)\n\tuserInt, _ := A.UserFromContext[int](ctx)\n\tuser := i.destinations[userInt]\n\tinbound.User = &protocol.MemoryUser{\n\t\tEmail: user.Email,\n\t\tLevel: uint32(user.Level),\n\t}\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   metadata.Source,\n\t\tTo:     metadata.Destination,\n\t\tStatus: log.AccessAccepted,\n\t\tEmail:  user.Email,\n\t})\n\terrors.LogInfo(ctx, \"tunnelling request to udp:\", metadata.Destination)\n\tdispatcher := session.DispatcherFromContext(ctx)\n\tdestination := singbridge.ToDestination(metadata.Destination, net.Network_UDP)\n\tlink, err := dispatcher.Dispatch(ctx, destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\toutConn := &singbridge.PacketConnWrapper{\n\t\tReader: link.Reader,\n\t\tWriter: link.Writer,\n\t\tDest:   destination,\n\t}\n\treturn bufio.CopyPacketConn(ctx, conn, outConn)\n}\n\nfunc (i *RelayInbound) NewError(ctx context.Context, err error) {\n\tif E.IsClosed(err) {\n\t\treturn\n\t}\n\terrors.LogWarning(ctx, err.Error())\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/outbound.go",
    "content": "package shadowsocks_2022\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tshadowsocks \"github.com/sagernet/sing-shadowsocks\"\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\tC \"github.com/sagernet/sing/common\"\n\tB \"github.com/sagernet/sing/common/buf\"\n\t\"github.com/sagernet/sing/common/bufio\"\n\tN \"github.com/sagernet/sing/common/network\"\n\t\"github.com/sagernet/sing/common/uot\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/singbridge\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewClient(ctx, config.(*ClientConfig))\n\t}))\n}\n\ntype Outbound struct {\n\tctx       context.Context\n\tserver    net.Destination\n\tmethod    shadowsocks.Method\n\tuotClient *uot.Client\n}\n\nfunc NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {\n\to := &Outbound{\n\t\tctx: ctx,\n\t\tserver: net.Destination{\n\t\t\tAddress: config.Address.AsAddress(),\n\t\t\tPort:    net.Port(config.Port),\n\t\t\tNetwork: net.Network_TCP,\n\t\t},\n\t}\n\tif C.Contains(shadowaead_2022.List, config.Method) {\n\t\tif config.Key == \"\" {\n\t\t\treturn nil, errors.New(\"missing psk\")\n\t\t}\n\t\tmethod, err := shadowaead_2022.NewWithPassword(config.Method, config.Key, nil)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"create method\").Base(err)\n\t\t}\n\t\to.method = method\n\t} else {\n\t\treturn nil, errors.New(\"unknown method \", config.Method)\n\t}\n\tif config.UdpOverTcp {\n\t\to.uotClient = &uot.Client{Version: uint8(config.UdpOverTcpVersion)}\n\t}\n\treturn o, nil\n}\n\nfunc (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\tvar inboundConn net.Conn\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound != nil {\n\t\tinboundConn = inbound.Conn\n\t}\n\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified\")\n\t}\n\tob.Name = \"shadowsocks-2022\"\n\tob.CanSpliceCopy = 3\n\tdestination := ob.Target\n\tnetwork := destination.Network\n\n\terrors.LogInfo(ctx, \"tunneling request to \", destination, \" via \", o.server.NetAddr())\n\n\tserverDestination := o.server\n\tif o.uotClient != nil {\n\t\tserverDestination.Network = net.Network_TCP\n\t} else {\n\t\tserverDestination.Network = network\n\t}\n\tconnection, err := dialer.Dial(ctx, serverDestination)\n\tif err != nil {\n\t\treturn errors.New(\"failed to connect to server\").Base(err)\n\t}\n\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tctx, _ = context.WithCancel(context.Background())\n\t}\n\n\tif network == net.Network_TCP {\n\t\tserverConn := o.method.DialEarlyConn(connection, singbridge.ToSocksaddr(destination))\n\t\tvar handshake bool\n\t\tif timeoutReader, isTimeoutReader := link.Reader.(buf.TimeoutReader); isTimeoutReader {\n\t\t\tmb, err := timeoutReader.ReadMultiBufferTimeout(time.Millisecond * 100)\n\t\t\tif err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {\n\t\t\t\treturn errors.New(\"read payload\").Base(err)\n\t\t\t}\n\t\t\tpayload := B.New()\n\t\t\tfor {\n\t\t\t\tpayload.Reset()\n\t\t\t\tnb, n := buf.SplitBytes(mb, payload.FreeBytes())\n\t\t\t\tif n > 0 {\n\t\t\t\t\tpayload.Truncate(n)\n\t\t\t\t\t_, err = serverConn.Write(payload.Bytes())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpayload.Release()\n\t\t\t\t\t\treturn errors.New(\"write payload\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\thandshake = true\n\t\t\t\t}\n\t\t\t\tif nb.IsEmpty() {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tmb = nb\n\t\t\t}\n\t\t\tpayload.Release()\n\t\t}\n\t\tif !handshake {\n\t\t\t_, err = serverConn.Write(nil)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"client handshake\").Base(err)\n\t\t\t}\n\t\t}\n\t\treturn singbridge.CopyConn(ctx, inboundConn, link, serverConn)\n\t} else {\n\t\tvar packetConn N.PacketConn\n\t\tif pc, isPacketConn := inboundConn.(N.PacketConn); isPacketConn {\n\t\t\tpacketConn = pc\n\t\t} else if nc, isNetPacket := inboundConn.(net.PacketConn); isNetPacket {\n\t\t\tpacketConn = bufio.NewPacketConn(nc)\n\t\t} else {\n\t\t\tpacketConn = &singbridge.PacketConnWrapper{\n\t\t\t\tReader: link.Reader,\n\t\t\t\tWriter: link.Writer,\n\t\t\t\tConn:   inboundConn,\n\t\t\t\tDest:   destination,\n\t\t\t}\n\t\t}\n\n\t\tif o.uotClient != nil {\n\t\t\tuConn, err := o.uotClient.DialEarlyConn(o.method.DialEarlyConn(connection, uot.RequestDestination(o.uotClient.Version)), false, singbridge.ToSocksaddr(destination))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, uConn))\n\t\t} else {\n\t\t\tserverConn := o.method.DialPacketConn(connection)\n\t\t\treturn singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "proxy/shadowsocks_2022/shadowsocks_2022.go",
    "content": "package shadowsocks_2022\n"
  },
  {
    "path": "proxy/socks/client.go",
    "content": "package socks\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Client is a Socks5 client.\ntype Client struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n}\n\n// NewClient create a new Socks5 client based on the given config.\nfunc NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {\n\tif config.Server == nil {\n\t\treturn nil, errors.New(`no target server found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Server)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err)\n\t}\n\n\tv := core.MustFromContext(ctx)\n\tc := &Client{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\n\treturn c, nil\n}\n\n// Process implements proxy.Outbound.Process.\nfunc (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified.\")\n\t}\n\tob.Name = \"socks\"\n\tob.CanSpliceCopy = 2\n\t// Destination of the inner request.\n\tdestination := ob.Target\n\n\t// Outbound server.\n\tserver := c.server\n\tdest := server.Destination\n\t// Connection to the outbound server.\n\tvar conn stat.Connection\n\n\tif err := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\trawConn, err := dialer.Dial(ctx, dest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconn = rawConn\n\n\t\treturn nil\n\t}); err != nil {\n\t\treturn errors.New(\"failed to find an available destination\").Base(err)\n\t}\n\n\tdefer func() {\n\t\tif err := conn.Close(); err != nil {\n\t\t\terrors.LogInfoInner(ctx, err, \"failed to closed connection\")\n\t\t}\n\t}()\n\n\tp := c.policyManager.ForLevel(0)\n\n\trequest := &protocol.RequestHeader{\n\t\tVersion: socks5Version,\n\t\tCommand: protocol.RequestCommandTCP,\n\t\tAddress: destination.Address,\n\t\tPort:    destination.Port,\n\t}\n\n\tif destination.Network == net.Network_UDP {\n\t\trequest.Command = protocol.RequestCommandUDP\n\t}\n\n\tuser := server.User\n\tif user != nil {\n\t\trequest.User = user\n\t\tp = c.policyManager.ForLevel(user.Level)\n\t}\n\n\tif err := conn.SetDeadline(time.Now().Add(p.Timeouts.Handshake)); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to set deadline for handshake\")\n\t}\n\tudpRequest, err := ClientHandshake(request, conn, conn)\n\tif err != nil {\n\t\treturn errors.New(\"failed to establish connection to server\").AtWarning().Base(err)\n\t}\n\tif udpRequest != nil {\n\t\tif udpRequest.Address == net.AnyIP || udpRequest.Address == net.AnyIPv6 {\n\t\t\tudpRequest.Address = dest.Address\n\t\t}\n\t}\n\n\tif err := conn.SetDeadline(time.Time{}); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to clear deadline after handshake\")\n\t}\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, p.Timeouts.ConnectionIdle)\n\n\tvar requestFunc func() error\n\tvar responseFunc func() error\n\tif request.Command == protocol.RequestCommandTCP {\n\t\trequestFunc = func() error {\n\t\t\tdefer timer.SetTimeout(p.Timeouts.DownlinkOnly)\n\t\t\treturn buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))\n\t\t}\n\t\tresponseFunc = func() error {\n\t\t\tob.CanSpliceCopy = 1\n\t\t\tdefer timer.SetTimeout(p.Timeouts.UplinkOnly)\n\t\t\treturn buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))\n\t\t}\n\t} else if request.Command == protocol.RequestCommandUDP {\n\t\tudpConn, err := dialer.Dial(ctx, udpRequest.Destination())\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create UDP connection\").Base(err)\n\t\t}\n\t\tdefer udpConn.Close()\n\t\trequestFunc = func() error {\n\t\t\tdefer timer.SetTimeout(p.Timeouts.DownlinkOnly)\n\t\t\twriter := &UDPWriter{Writer: udpConn, Request: request}\n\t\t\treturn buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))\n\t\t}\n\t\tresponseFunc = func() error {\n\t\t\tob.CanSpliceCopy = 1\n\t\t\tdefer timer.SetTimeout(p.Timeouts.UplinkOnly)\n\t\t\treader := &UDPReader{Reader: udpConn}\n\t\t\treturn buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))\n\t\t}\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tresponseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestFunc, responseDonePost); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewClient(ctx, config.(*ClientConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/socks/config.go",
    "content": "package socks\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nfunc (a *Account) Equals(another protocol.Account) bool {\n\tif account, ok := another.(*Account); ok {\n\t\treturn a.Username == account.Username\n\t}\n\treturn false\n}\n\nfunc (a *Account) ToProto() proto.Message {\n\treturn a\n}\n\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\treturn a, nil\n}\n\nfunc (c *ServerConfig) HasAccount(username, password string) bool {\n\tif c.Accounts == nil {\n\t\treturn false\n\t}\n\tstoredPassed, found := c.Accounts[username]\n\tif !found {\n\t\treturn false\n\t}\n\treturn storedPassed == password\n}\n"
  },
  {
    "path": "proxy/socks/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/socks/config.proto\n\npackage socks\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// AuthType is the authentication type of Socks proxy.\ntype AuthType int32\n\nconst (\n\t// NO_AUTH is for anonymous authentication.\n\tAuthType_NO_AUTH AuthType = 0\n\t// PASSWORD is for username/password authentication.\n\tAuthType_PASSWORD AuthType = 1\n)\n\n// Enum value maps for AuthType.\nvar (\n\tAuthType_name = map[int32]string{\n\t\t0: \"NO_AUTH\",\n\t\t1: \"PASSWORD\",\n\t}\n\tAuthType_value = map[string]int32{\n\t\t\"NO_AUTH\":  0,\n\t\t\"PASSWORD\": 1,\n\t}\n)\n\nfunc (x AuthType) Enum() *AuthType {\n\tp := new(AuthType)\n\t*p = x\n\treturn p\n}\n\nfunc (x AuthType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (AuthType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_proxy_socks_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (AuthType) Type() protoreflect.EnumType {\n\treturn &file_proxy_socks_config_proto_enumTypes[0]\n}\n\nfunc (x AuthType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use AuthType.Descriptor instead.\nfunc (AuthType) EnumDescriptor() ([]byte, []int) {\n\treturn file_proxy_socks_config_proto_rawDescGZIP(), []int{0}\n}\n\n// Account represents a Socks account.\ntype Account struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsername      string                 `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword      string                 `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_socks_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_socks_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_socks_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Account) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\n// ServerConfig is the protobuf config for Socks server.\ntype ServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAuthType      AuthType               `protobuf:\"varint,1,opt,name=auth_type,json=authType,proto3,enum=xray.proxy.socks.AuthType\" json:\"auth_type,omitempty\"`\n\tAccounts      map[string]string      `protobuf:\"bytes,2,rep,name=accounts,proto3\" json:\"accounts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tAddress       *net.IPOrDomain        `protobuf:\"bytes,3,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tUdpEnabled    bool                   `protobuf:\"varint,4,opt,name=udp_enabled,json=udpEnabled,proto3\" json:\"udp_enabled,omitempty\"`\n\tUserLevel     uint32                 `protobuf:\"varint,6,opt,name=user_level,json=userLevel,proto3\" json:\"user_level,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServerConfig) Reset() {\n\t*x = ServerConfig{}\n\tmi := &file_proxy_socks_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerConfig) ProtoMessage() {}\n\nfunc (x *ServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_socks_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.\nfunc (*ServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_socks_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ServerConfig) GetAuthType() AuthType {\n\tif x != nil {\n\t\treturn x.AuthType\n\t}\n\treturn AuthType_NO_AUTH\n}\n\nfunc (x *ServerConfig) GetAccounts() map[string]string {\n\tif x != nil {\n\t\treturn x.Accounts\n\t}\n\treturn nil\n}\n\nfunc (x *ServerConfig) GetAddress() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *ServerConfig) GetUdpEnabled() bool {\n\tif x != nil {\n\t\treturn x.UdpEnabled\n\t}\n\treturn false\n}\n\nfunc (x *ServerConfig) GetUserLevel() uint32 {\n\tif x != nil {\n\t\treturn x.UserLevel\n\t}\n\treturn 0\n}\n\n// ClientConfig is the protobuf config for Socks client.\ntype ClientConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Sever is a list of Socks server addresses.\n\tServer        *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_proxy_socks_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_socks_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_socks_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *ClientConfig) GetServer() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\nvar File_proxy_socks_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_socks_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x18proxy/socks/config.proto\\x12\\x10xray.proxy.socks\\x1a\\x18common/net/address.proto\\x1a!common/protocol/server_spec.proto\\\"A\\n\" +\n\t\"\\aAccount\\x12\\x1a\\n\" +\n\t\"\\busername\\x18\\x01 \\x01(\\tR\\busername\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x02 \\x01(\\tR\\bpassword\\\"\\xc5\\x02\\n\" +\n\t\"\\fServerConfig\\x127\\n\" +\n\t\"\\tauth_type\\x18\\x01 \\x01(\\x0e2\\x1a.xray.proxy.socks.AuthTypeR\\bauthType\\x12H\\n\" +\n\t\"\\baccounts\\x18\\x02 \\x03(\\v2,.xray.proxy.socks.ServerConfig.AccountsEntryR\\baccounts\\x125\\n\" +\n\t\"\\aaddress\\x18\\x03 \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x1f\\n\" +\n\t\"\\vudp_enabled\\x18\\x04 \\x01(\\bR\\n\" +\n\t\"udpEnabled\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_level\\x18\\x06 \\x01(\\rR\\tuserLevel\\x1a;\\n\" +\n\t\"\\rAccountsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\"L\\n\" +\n\t\"\\fClientConfig\\x12<\\n\" +\n\t\"\\x06server\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x06server*%\\n\" +\n\t\"\\bAuthType\\x12\\v\\n\" +\n\t\"\\aNO_AUTH\\x10\\x00\\x12\\f\\n\" +\n\t\"\\bPASSWORD\\x10\\x01BR\\n\" +\n\t\"\\x14com.xray.proxy.socksP\\x01Z%github.com/xtls/xray-core/proxy/socks\\xaa\\x02\\x10Xray.Proxy.Socksb\\x06proto3\"\n\nvar (\n\tfile_proxy_socks_config_proto_rawDescOnce sync.Once\n\tfile_proxy_socks_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_socks_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_socks_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_socks_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_socks_config_proto_rawDesc), len(file_proxy_socks_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_socks_config_proto_rawDescData\n}\n\nvar file_proxy_socks_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_proxy_socks_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_proxy_socks_config_proto_goTypes = []any{\n\t(AuthType)(0),                   // 0: xray.proxy.socks.AuthType\n\t(*Account)(nil),                 // 1: xray.proxy.socks.Account\n\t(*ServerConfig)(nil),            // 2: xray.proxy.socks.ServerConfig\n\t(*ClientConfig)(nil),            // 3: xray.proxy.socks.ClientConfig\n\tnil,                             // 4: xray.proxy.socks.ServerConfig.AccountsEntry\n\t(*net.IPOrDomain)(nil),          // 5: xray.common.net.IPOrDomain\n\t(*protocol.ServerEndpoint)(nil), // 6: xray.common.protocol.ServerEndpoint\n}\nvar file_proxy_socks_config_proto_depIdxs = []int32{\n\t0, // 0: xray.proxy.socks.ServerConfig.auth_type:type_name -> xray.proxy.socks.AuthType\n\t4, // 1: xray.proxy.socks.ServerConfig.accounts:type_name -> xray.proxy.socks.ServerConfig.AccountsEntry\n\t5, // 2: xray.proxy.socks.ServerConfig.address:type_name -> xray.common.net.IPOrDomain\n\t6, // 3: xray.proxy.socks.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint\n\t4, // [4:4] is the sub-list for method output_type\n\t4, // [4:4] is the sub-list for method input_type\n\t4, // [4:4] is the sub-list for extension type_name\n\t4, // [4:4] is the sub-list for extension extendee\n\t0, // [0:4] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_socks_config_proto_init() }\nfunc file_proxy_socks_config_proto_init() {\n\tif File_proxy_socks_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_socks_config_proto_rawDesc), len(file_proxy_socks_config_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_socks_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_socks_config_proto_depIdxs,\n\t\tEnumInfos:         file_proxy_socks_config_proto_enumTypes,\n\t\tMessageInfos:      file_proxy_socks_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_socks_config_proto = out.File\n\tfile_proxy_socks_config_proto_goTypes = nil\n\tfile_proxy_socks_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/socks/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.socks;\noption csharp_namespace = \"Xray.Proxy.Socks\";\noption go_package = \"github.com/xtls/xray-core/proxy/socks\";\noption java_package = \"com.xray.proxy.socks\";\noption java_multiple_files = true;\n\nimport \"common/net/address.proto\";\nimport \"common/protocol/server_spec.proto\";\n\n// Account represents a Socks account.\nmessage Account {\n  string username = 1;\n  string password = 2;\n}\n\n// AuthType is the authentication type of Socks proxy.\nenum AuthType {\n  // NO_AUTH is for anonymous authentication.\n  NO_AUTH = 0;\n  // PASSWORD is for username/password authentication.\n  PASSWORD = 1;\n}\n\n// ServerConfig is the protobuf config for Socks server.\nmessage ServerConfig {\n  AuthType auth_type = 1;\n  map<string, string> accounts = 2;\n  xray.common.net.IPOrDomain address = 3;\n  bool udp_enabled = 4;\n  uint32 user_level = 6;\n}\n\n// ClientConfig is the protobuf config for Socks client.\nmessage ClientConfig {\n  // Sever is a list of Socks server addresses.\n  xray.common.protocol.ServerEndpoint server = 1;\n}\n"
  },
  {
    "path": "proxy/socks/protocol.go",
    "content": "package socks\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nconst (\n\tsocks5Version = 0x05\n\tsocks4Version = 0x04\n\n\tcmdTCPConnect    = 0x01\n\tcmdTCPBind       = 0x02\n\tcmdUDPAssociate  = 0x03\n\tcmdTorResolve    = 0xF0\n\tcmdTorResolvePTR = 0xF1\n\n\tsocks4RequestGranted  = 90\n\tsocks4RequestRejected = 91\n\n\tauthNotRequired = 0x00\n\t// authGssAPI           = 0x01\n\tauthPassword         = 0x02\n\tauthNoMatchingMethod = 0xFF\n\n\tstatusSuccess       = 0x00\n\tstatusCmdNotSupport = 0x07\n)\n\nvar addrParser = protocol.NewAddressParser(\n\tprotocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),\n\tprotocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),\n\tprotocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),\n)\n\ntype ServerSession struct {\n\tconfig       *ServerConfig\n\taddress      net.Address\n\tport         net.Port\n\tlocalAddress net.Address\n}\n\nfunc (s *ServerSession) handshake4(cmd byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {\n\tif s.config.AuthType == AuthType_PASSWORD {\n\t\twriteSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0))\n\t\treturn nil, errors.New(\"socks 4 is not allowed when auth is required.\")\n\t}\n\n\tvar port net.Port\n\tvar address net.Address\n\n\t{\n\t\tbuffer := buf.StackNew()\n\t\tif _, err := buffer.ReadFullFrom(reader, 6); err != nil {\n\t\t\tbuffer.Release()\n\t\t\treturn nil, errors.New(\"insufficient header\").Base(err)\n\t\t}\n\t\tport = net.PortFromBytes(buffer.BytesRange(0, 2))\n\t\taddress = net.IPAddress(buffer.BytesRange(2, 6))\n\t\tbuffer.Release()\n\t}\n\n\tif _, err := ReadUntilNull(reader); /* user id */ err != nil {\n\t\treturn nil, err\n\t}\n\tif address.IP()[0] == 0x00 {\n\t\tdomain, err := ReadUntilNull(reader)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to read domain for socks 4a\").Base(err)\n\t\t}\n\t\taddress = net.ParseAddress(domain)\n\t}\n\n\tswitch cmd {\n\tcase cmdTCPConnect:\n\t\trequest := &protocol.RequestHeader{\n\t\t\tCommand: protocol.RequestCommandTCP,\n\t\t\tAddress: address,\n\t\t\tPort:    port,\n\t\t\tVersion: socks4Version,\n\t\t}\n\t\tif err := writeSocks4Response(writer, socks4RequestGranted, net.AnyIP, net.Port(0)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn request, nil\n\tdefault:\n\t\twriteSocks4Response(writer, socks4RequestRejected, net.AnyIP, net.Port(0))\n\t\treturn nil, errors.New(\"unsupported command: \", cmd)\n\t}\n}\n\nfunc (s *ServerSession) auth5(nMethod byte, reader io.Reader, writer io.Writer) (username string, err error) {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tif _, err = buffer.ReadFullFrom(reader, int32(nMethod)); err != nil {\n\t\treturn \"\", errors.New(\"failed to read auth methods\").Base(err)\n\t}\n\n\tvar expectedAuth byte = authNotRequired\n\tif s.config.AuthType == AuthType_PASSWORD {\n\t\texpectedAuth = authPassword\n\t}\n\n\tif !hasAuthMethod(expectedAuth, buffer.BytesRange(0, int32(nMethod))) {\n\t\twriteSocks5AuthenticationResponse(writer, socks5Version, authNoMatchingMethod)\n\t\treturn \"\", errors.New(\"no matching auth method\")\n\t}\n\n\tif err := writeSocks5AuthenticationResponse(writer, socks5Version, expectedAuth); err != nil {\n\t\treturn \"\", errors.New(\"failed to write auth response\").Base(err)\n\t}\n\n\tif expectedAuth == authPassword {\n\t\tusername, password, err := ReadUsernamePassword(reader)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.New(\"failed to read username and password for authentication\").Base(err)\n\t\t}\n\n\t\tif !s.config.HasAccount(username, password) {\n\t\t\twriteSocks5AuthenticationResponse(writer, 0x01, 0xFF)\n\t\t\treturn \"\", errors.New(\"invalid username or password\")\n\t\t}\n\n\t\tif err := writeSocks5AuthenticationResponse(writer, 0x01, 0x00); err != nil {\n\t\t\treturn \"\", errors.New(\"failed to write auth response\").Base(err)\n\t\t}\n\t\treturn username, nil\n\t}\n\n\treturn \"\", nil\n}\n\nfunc (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {\n\tvar (\n\t\tusername string\n\t\terr      error\n\t)\n\tif username, err = s.auth5(nMethod, reader, writer); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar cmd byte\n\t{\n\t\tbuffer := buf.StackNew()\n\t\tif _, err := buffer.ReadFullFrom(reader, 3); err != nil {\n\t\t\tbuffer.Release()\n\t\t\treturn nil, errors.New(\"failed to read request\").Base(err)\n\t\t}\n\t\tcmd = buffer.Byte(1)\n\t\tbuffer.Release()\n\t}\n\n\trequest := new(protocol.RequestHeader)\n\tif username != \"\" {\n\t\trequest.User = &protocol.MemoryUser{Email: username}\n\t}\n\tswitch cmd {\n\tcase cmdTCPConnect, cmdTorResolve, cmdTorResolvePTR:\n\t\t// We don't have a solution for Tor case now. Simply treat it as connect command.\n\t\trequest.Command = protocol.RequestCommandTCP\n\tcase cmdUDPAssociate:\n\t\tif !s.config.UdpEnabled {\n\t\t\twriteSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))\n\t\t\treturn nil, errors.New(\"UDP is not enabled.\")\n\t\t}\n\t\trequest.Command = protocol.RequestCommandUDP\n\tcase cmdTCPBind:\n\t\twriteSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))\n\t\treturn nil, errors.New(\"TCP bind is not supported.\")\n\tdefault:\n\t\twriteSocks5Response(writer, statusCmdNotSupport, net.AnyIP, net.Port(0))\n\t\treturn nil, errors.New(\"unknown command \", cmd)\n\t}\n\n\trequest.Version = socks5Version\n\n\taddr, port, err := addrParser.ReadAddressPort(nil, reader)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read address\").Base(err)\n\t}\n\trequest.Address = addr\n\trequest.Port = port\n\n\tresponseAddress := s.address\n\tresponsePort := s.port\n\t//nolint:gocritic // Use if else chain for clarity\n\tif request.Command == protocol.RequestCommandUDP {\n\t\tif s.config.Address != nil {\n\t\t\t// Use configured IP as remote address in the response to UDP Associate\n\t\t\tresponseAddress = s.config.Address.AsAddress()\n\t\t} else {\n\t\t\t// Use conn.LocalAddr() IP as remote address in the response by default\n\t\t\tresponseAddress = s.localAddress\n\t\t}\n\t}\n\tif err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn request, nil\n}\n\n// Handshake performs a Socks4/4a/5 handshake.\nfunc (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {\n\tbuffer := buf.StackNew()\n\tif _, err := buffer.ReadFullFrom(reader, 2); err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, errors.New(\"insufficient header\").Base(err)\n\t}\n\n\tversion := buffer.Byte(0)\n\tcmd := buffer.Byte(1)\n\tbuffer.Release()\n\n\tswitch version {\n\tcase socks4Version:\n\t\treturn s.handshake4(cmd, reader, writer)\n\tcase socks5Version:\n\t\treturn s.handshake5(cmd, reader, writer)\n\tdefault:\n\t\treturn nil, errors.New(\"unknown Socks version: \", version)\n\t}\n}\n\n// ReadUsernamePassword reads Socks 5 username/password message from the given reader.\n// +----+------+----------+------+----------+\n// |VER | ULEN |  UNAME   | PLEN |  PASSWD  |\n// +----+------+----------+------+----------+\n// | 1  |  1   | 1 to 255 |  1   | 1 to 255 |\n// +----+------+----------+------+----------+\nfunc ReadUsernamePassword(reader io.Reader) (string, string, error) {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tif _, err := buffer.ReadFullFrom(reader, 2); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tnUsername := int32(buffer.Byte(1))\n\n\tbuffer.Clear()\n\tif _, err := buffer.ReadFullFrom(reader, nUsername); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tusername := buffer.String()\n\n\tbuffer.Clear()\n\tif _, err := buffer.ReadFullFrom(reader, 1); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tnPassword := int32(buffer.Byte(0))\n\n\tbuffer.Clear()\n\tif _, err := buffer.ReadFullFrom(reader, nPassword); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tpassword := buffer.String()\n\treturn username, password, nil\n}\n\n// ReadUntilNull reads content from given reader, until a null (0x00) byte.\nfunc ReadUntilNull(reader io.Reader) (string, error) {\n\tb := buf.StackNew()\n\tdefer b.Release()\n\n\tfor {\n\t\t_, err := b.ReadFullFrom(reader, 1)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif b.Byte(b.Len()-1) == 0x00 {\n\t\t\tb.Resize(0, b.Len()-1)\n\t\t\treturn b.String(), nil\n\t\t}\n\t\tif b.IsFull() {\n\t\t\treturn \"\", errors.New(\"buffer overrun\")\n\t\t}\n\t}\n}\n\nfunc hasAuthMethod(expectedAuth byte, authCandidates []byte) bool {\n\tfor _, a := range authCandidates {\n\t\tif a == expectedAuth {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc writeSocks5AuthenticationResponse(writer io.Writer, version byte, auth byte) error {\n\treturn buf.WriteAllBytes(writer, []byte{version, auth}, nil)\n}\n\nfunc writeSocks5Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error {\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\tcommon.Must2(buffer.Write([]byte{socks5Version, errCode, 0x00 /* reserved */}))\n\tif err := addrParser.WriteAddressPort(buffer, address, port); err != nil {\n\t\treturn err\n\t}\n\n\treturn buf.WriteAllBytes(writer, buffer.Bytes(), nil)\n}\n\nfunc writeSocks4Response(writer io.Writer, errCode byte, address net.Address, port net.Port) error {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tcommon.Must(buffer.WriteByte(0x00))\n\tcommon.Must(buffer.WriteByte(errCode))\n\tportBytes := buffer.Extend(2)\n\tbinary.BigEndian.PutUint16(portBytes, port.Value())\n\tcommon.Must2(buffer.Write(address.IP()))\n\treturn buf.WriteAllBytes(writer, buffer.Bytes(), nil)\n}\n\nfunc DecodeUDPPacket(packet *buf.Buffer) (*protocol.RequestHeader, error) {\n\tif packet.Len() < 5 {\n\t\treturn nil, errors.New(\"insufficient length of packet.\")\n\t}\n\trequest := &protocol.RequestHeader{\n\t\tVersion: socks5Version,\n\t\tCommand: protocol.RequestCommandUDP,\n\t}\n\n\t// packet[0] and packet[1] are reserved\n\tif packet.Byte(2) != 0 /* fragments */ {\n\t\treturn nil, errors.New(\"discarding fragmented payload.\")\n\t}\n\n\tpacket.Advance(3)\n\n\taddr, port, err := addrParser.ReadAddressPort(nil, packet)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read UDP header\").Base(err)\n\t}\n\trequest.Address = addr\n\trequest.Port = port\n\treturn request, nil\n}\n\nfunc EncodeUDPPacket(request *protocol.RequestHeader, data []byte) (*buf.Buffer, error) {\n\tb := buf.New()\n\tcommon.Must2(b.Write([]byte{0, 0, 0 /* Fragment */}))\n\tif err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil {\n\t\tb.Release()\n\t\treturn nil, err\n\t}\n\t// if data is too large, return an empty buffer (drop too big data)\n\tif b.Available() < int32(len(data)) {\n\t\tb.Clear()\n\t\treturn b, nil\n\t}\n\tcommon.Must2(b.Write(data))\n\treturn b, nil\n}\n\ntype UDPReader struct {\n\tReader io.Reader\n}\n\nfunc (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tbuffer := buf.New()\n\t_, err := buffer.ReadFrom(r.Reader)\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, err\n\t}\n\tu, err := DecodeUDPPacket(buffer)\n\tif err != nil {\n\t\tbuffer.Release()\n\t\treturn nil, err\n\t}\n\tdest := u.Destination()\n\tbuffer.UDP = &dest\n\treturn buf.MultiBuffer{buffer}, nil\n}\n\ntype UDPWriter struct {\n\tWriter  io.Writer\n\tRequest *protocol.RequestHeader\n}\n\nfunc (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor {\n\t\tmb2, b := buf.SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\t\trequest := w.Request\n\t\tif b.UDP != nil {\n\t\t\trequest = &protocol.RequestHeader{\n\t\t\t\tAddress: b.UDP.Address,\n\t\t\t\tPort:    b.UDP.Port,\n\t\t\t}\n\t\t}\n\t\tpacket, err := EncodeUDPPacket(request, b.Bytes())\n\t\tb.Release()\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\t\t_, err = w.Writer.Write(packet.Bytes())\n\t\tpacket.Release()\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {\n\tauthByte := byte(authNotRequired)\n\tif request.User != nil {\n\t\tauthByte = byte(authPassword)\n\t}\n\n\tb := buf.New()\n\tdefer b.Release()\n\n\tcommon.Must2(b.Write([]byte{socks5Version, 0x01, authByte}))\n\tif err := buf.WriteAllBytes(writer, b.Bytes(), nil); err != nil {\n\t\treturn nil, err\n\t}\n\n\tb.Clear()\n\tif _, err := b.ReadFullFrom(reader, 2); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif b.Byte(0) != socks5Version {\n\t\treturn nil, errors.New(\"unexpected server version: \", b.Byte(0)).AtWarning()\n\t}\n\tif b.Byte(1) != authByte {\n\t\treturn nil, errors.New(\"auth method not supported.\").AtWarning()\n\t}\n\n\tif authByte == authPassword {\n\t\tb.Clear()\n\t\taccount := request.User.Account.(*Account)\n\t\tcommon.Must(b.WriteByte(0x01))\n\t\tcommon.Must(b.WriteByte(byte(len(account.Username))))\n\t\tcommon.Must2(b.WriteString(account.Username))\n\t\tcommon.Must(b.WriteByte(byte(len(account.Password))))\n\t\tcommon.Must2(b.WriteString(account.Password))\n\t\tif err := buf.WriteAllBytes(writer, b.Bytes(), nil); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tb.Clear()\n\t\tif _, err := b.ReadFullFrom(reader, 2); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif b.Byte(1) != 0x00 {\n\t\t\treturn nil, errors.New(\"server rejects account: \", b.Byte(1))\n\t\t}\n\t}\n\n\tb.Clear()\n\n\tcommand := byte(cmdTCPConnect)\n\tif request.Command == protocol.RequestCommandUDP {\n\t\tcommand = byte(cmdUDPAssociate)\n\t}\n\tcommon.Must2(b.Write([]byte{socks5Version, command, 0x00 /* reserved */}))\n\tif request.Command == protocol.RequestCommandUDP {\n\t\tcommon.Must2(b.Write([]byte{1, 0, 0, 0, 0, 0, 0 /* RFC 1928 */}))\n\t} else {\n\t\tif err := addrParser.WriteAddressPort(b, request.Address, request.Port); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := buf.WriteAllBytes(writer, b.Bytes(), nil); err != nil {\n\t\treturn nil, err\n\t}\n\n\tb.Clear()\n\tif _, err := b.ReadFullFrom(reader, 3); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp := b.Byte(1)\n\tif resp != 0x00 {\n\t\treturn nil, errors.New(\"server rejects request: \", resp)\n\t}\n\n\tb.Clear()\n\n\taddress, port, err := addrParser.ReadAddressPort(b, reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif request.Command == protocol.RequestCommandUDP {\n\t\tudpRequest := &protocol.RequestHeader{\n\t\t\tVersion: socks5Version,\n\t\t\tCommand: protocol.RequestCommandUDP,\n\t\t\tAddress: address,\n\t\t\tPort:    port,\n\t\t}\n\t\treturn udpRequest, nil\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "proxy/socks/protocol_test.go",
    "content": "package socks_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t. \"github.com/xtls/xray-core/proxy/socks\"\n)\n\nfunc TestUDPEncoding(t *testing.T) {\n\tb := buf.New()\n\n\trequest := &protocol.RequestHeader{\n\t\tAddress: net.IPAddress([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}),\n\t\tPort:    1024,\n\t}\n\twriter := &UDPWriter{Writer: b, Request: request}\n\n\tcontent := []byte{'a'}\n\tpayload := buf.New()\n\tpayload.Write(content)\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{payload}))\n\n\treader := &UDPReader{Reader: b}\n\n\tdecodedPayload, err := reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif r := cmp.Diff(decodedPayload[0].Bytes(), content); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestReadUsernamePassword(t *testing.T) {\n\ttestCases := []struct {\n\t\tInput    []byte\n\t\tUsername string\n\t\tPassword string\n\t\tError    bool\n\t}{\n\t\t{\n\t\t\tInput:    []byte{0x05, 0x01, 'a', 0x02, 'b', 'c'},\n\t\t\tUsername: \"a\",\n\t\t\tPassword: \"bc\",\n\t\t},\n\t\t{\n\t\t\tInput: []byte{0x05, 0x18, 'a', 0x02, 'b', 'c'},\n\t\t\tError: true,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\treader := bytes.NewReader(testCase.Input)\n\t\tusername, password, err := ReadUsernamePassword(reader)\n\t\tif testCase.Error {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect error, but actually nil\")\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect no error, but actually \", err.Error())\n\t\t\t}\n\t\t\tif testCase.Username != username {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect username \", testCase.Username, \" but actually \", username)\n\t\t\t}\n\t\t\tif testCase.Password != password {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect password \", testCase.Password, \" but actually \", password)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestReadUntilNull(t *testing.T) {\n\ttestCases := []struct {\n\t\tInput  []byte\n\t\tOutput string\n\t\tError  bool\n\t}{\n\t\t{\n\t\t\tInput:  []byte{'a', 'b', 0x00},\n\t\t\tOutput: \"ab\",\n\t\t},\n\t\t{\n\t\t\tInput: []byte{'a'},\n\t\t\tError: true,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\treader := bytes.NewReader(testCase.Input)\n\t\tvalue, err := ReadUntilNull(reader)\n\t\tif testCase.Error {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect error, but actually nil\")\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect no error, but actually \", err.Error())\n\t\t\t}\n\t\t\tif testCase.Output != value {\n\t\t\t\tt.Error(\"for input: \", testCase.Input, \" expect output \", testCase.Output, \" but actually \", value)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc BenchmarkReadUsernamePassword(b *testing.B) {\n\tinput := []byte{0x05, 0x01, 'a', 0x02, 'b', 'c'}\n\tbuffer := buf.New()\n\tbuffer.Write(input)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _, err := ReadUsernamePassword(buffer)\n\t\tcommon.Must(err)\n\t\tbuffer.Clear()\n\t\tbuffer.Extend(int32(len(input)))\n\t}\n}\n"
  },
  {
    "path": "proxy/socks/server.go",
    "content": "package socks\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\tudp_proto \"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/proxy/http\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n)\n\n// Server is a SOCKS 5 proxy server\ntype Server struct {\n\tconfig        *ServerConfig\n\tpolicyManager policy.Manager\n\tcone          bool\n\tudpFilter     *UDPFilter\n\thttpServer    *http.Server\n}\n\n// NewServer creates a new Server object.\nfunc NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {\n\tv := core.MustFromContext(ctx)\n\ts := &Server{\n\t\tconfig:        config,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tcone:          ctx.Value(\"cone\").(bool),\n\t}\n\thttpConfig := &http.ServerConfig{\n\t\tUserLevel: config.UserLevel,\n\t}\n\tif config.AuthType == AuthType_PASSWORD {\n\t\thttpConfig.Accounts = config.Accounts\n\t\ts.udpFilter = new(UDPFilter) // We only use this when auth is enabled\n\t}\n\ts.httpServer, _ = http.NewServer(ctx, httpConfig)\n\treturn s, nil\n}\n\nfunc (s *Server) policy() policy.Session {\n\tconfig := s.config\n\tp := s.policyManager.ForLevel(config.UserLevel)\n\treturn p\n}\n\n// Network implements proxy.Inbound.\nfunc (s *Server) Network() []net.Network {\n\tlist := []net.Network{net.Network_TCP}\n\tif s.config.UdpEnabled {\n\t\tlist = append(list, net.Network_UDP)\n\t}\n\treturn list\n}\n\n// Process implements proxy.Inbound.\nfunc (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"socks\"\n\tinbound.CanSpliceCopy = 2\n\tinbound.User = &protocol.MemoryUser{\n\t\tLevel: s.config.UserLevel,\n\t}\n\tif !proxy.IsRAWTransportWithoutSecurity(conn) {\n\t\tinbound.CanSpliceCopy = 3\n\t}\n\n\tswitch network {\n\tcase net.Network_TCP:\n\t\tfirstbyte := make([]byte, 1)\n\t\tif n, err := conn.Read(firstbyte); n == 0 {\n\t\t\tif goerrors.Is(err, io.EOF) {\n\t\t\t\terrors.LogInfo(ctx, \"Connection closed immediately, likely health check connection\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn errors.New(\"failed to read from connection\").Base(err)\n\t\t}\n\t\tif firstbyte[0] != 5 && firstbyte[0] != 4 { // Check if it is Socks5/4/4a\n\t\t\terrors.LogDebug(ctx, \"Not Socks request, try to parse as HTTP request\")\n\t\t\treturn s.httpServer.ProcessWithFirstbyte(ctx, network, conn, dispatcher, firstbyte...)\n\t\t}\n\t\treturn s.processTCP(ctx, conn, dispatcher, firstbyte)\n\tcase net.Network_UDP:\n\t\treturn s.handleUDPPayload(ctx, conn, dispatcher)\n\tdefault:\n\t\treturn errors.New(\"unknown network: \", network)\n\t}\n}\n\nfunc (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher, firstbyte []byte) error {\n\tplcy := s.policy()\n\tif err := conn.SetReadDeadline(time.Now().Add(plcy.Timeouts.Handshake)); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to set deadline\")\n\t}\n\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound == nil || !inbound.Gateway.IsValid() {\n\t\treturn errors.New(\"inbound gateway not specified\")\n\t}\n\n\tsvrSession := &ServerSession{\n\t\tconfig:       s.config,\n\t\taddress:      inbound.Gateway.Address,\n\t\tport:         inbound.Gateway.Port,\n\t\tlocalAddress: net.IPAddress(conn.LocalAddr().(*net.TCPAddr).IP),\n\t}\n\n\t// Firstbyte is for forwarded conn from SOCKS inbound\n\t// Because it needs first byte to choose protocol\n\t// We need to add it back\n\treader := &buf.BufferedReader{\n\t\tReader: buf.NewReader(conn),\n\t\tBuffer: buf.MultiBuffer{buf.FromBytes(firstbyte)},\n\t}\n\trequest, err := svrSession.Handshake(reader, conn)\n\tif err != nil {\n\t\tif inbound.Source.IsValid() {\n\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\tFrom:   inbound.Source,\n\t\t\t\tTo:     \"\",\n\t\t\t\tStatus: log.AccessRejected,\n\t\t\t\tReason: err,\n\t\t\t})\n\t\t}\n\t\treturn errors.New(\"failed to read request\").Base(err)\n\t}\n\tif request.User != nil {\n\t\tinbound.User.Email = request.User.Email\n\t}\n\n\tif err := conn.SetReadDeadline(time.Time{}); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to clear deadline\")\n\t}\n\n\tif request.Command == protocol.RequestCommandTCP {\n\t\tdest := request.Destination()\n\t\terrors.LogInfo(ctx, \"TCP Connect request to \", dest)\n\t\tif inbound.Source.IsValid() {\n\t\t\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\t\tFrom:   inbound.Source,\n\t\t\t\tTo:     dest,\n\t\t\t\tStatus: log.AccessAccepted,\n\t\t\t\tReason: \"\",\n\t\t\t})\n\t\t}\n\t\tif inbound.CanSpliceCopy == 2 {\n\t\t\tinbound.CanSpliceCopy = 1\n\t\t}\n\t\tif err := dispatcher.DispatchLink(ctx, dest, &transport.Link{\n\t\t\tReader: reader,\n\t\t\tWriter: buf.NewWriter(conn)},\n\t\t); err != nil {\n\t\t\treturn errors.New(\"failed to dispatch request\").Base(err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif request.Command == protocol.RequestCommandUDP {\n\t\tif s.udpFilter != nil {\n\t\t\ts.udpFilter.Add(conn.RemoteAddr())\n\t\t}\n\t\treturn s.handleUDP(conn)\n\t}\n\n\treturn nil\n}\n\nfunc (*Server) handleUDP(c io.Reader) error {\n\t// The TCP connection closes after this method returns. We need to wait until\n\t// the client closes it.\n\treturn common.Error2(io.Copy(buf.DiscardBytes, c))\n}\n\nfunc (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tif s.udpFilter != nil && !s.udpFilter.Check(conn.RemoteAddr()) {\n\t\terrors.LogDebug(ctx, \"Unauthorized UDP access from \", conn.RemoteAddr().String())\n\t\treturn nil\n\t}\n\tudpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {\n\t\tpayload := packet.Payload\n\t\terrors.LogDebug(ctx, \"writing back UDP response with \", payload.Len(), \" bytes\")\n\n\t\trequest := protocol.RequestHeaderFromContext(ctx)\n\t\tif request == nil {\n\t\t\tpayload.Release()\n\t\t\treturn\n\t\t}\n\n\t\tif payload.UDP != nil {\n\t\t\trequest = &protocol.RequestHeader{\n\t\t\t\tUser:    request.User,\n\t\t\t\tAddress: payload.UDP.Address,\n\t\t\t\tPort:    payload.UDP.Port,\n\t\t\t}\n\t\t}\n\n\t\tudpMessage, err := EncodeUDPPacket(request, payload.Bytes())\n\t\tpayload.Release()\n\n\t\tif err != nil {\n\t\t\terrors.LogWarningInner(ctx, err, \"failed to write UDP response\")\n\t\t\treturn\n\t\t}\n\n\t\tconn.Write(udpMessage.Bytes())\n\t\tudpMessage.Release()\n\t})\n\tdefer udpServer.RemoveRay()\n\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound != nil && inbound.Source.IsValid() {\n\t\terrors.LogInfo(ctx, \"client UDP connection from \", inbound.Source)\n\t}\n\n\tvar dest *net.Destination\n\n\treader := buf.NewPacketReader(conn)\n\tfor {\n\t\tmpayload, err := reader.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, payload := range mpayload {\n\t\t\trequest, err := DecodeUDPPacket(payload)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to parse UDP request\")\n\t\t\t\tpayload.Release()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif payload.IsEmpty() {\n\t\t\t\tpayload.Release()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdestination := request.Destination()\n\n\t\t\tcurrentPacketCtx := ctx\n\t\t\terrors.LogDebug(ctx, \"send packet to \", destination, \" with \", payload.Len(), \" bytes\")\n\t\t\tif inbound != nil && inbound.Source.IsValid() {\n\t\t\t\tcurrentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\t\t\tFrom:   inbound.Source,\n\t\t\t\t\tTo:     destination,\n\t\t\t\t\tStatus: log.AccessAccepted,\n\t\t\t\t\tReason: \"\",\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tpayload.UDP = &destination\n\n\t\t\tif !s.cone || dest == nil {\n\t\t\t\tdest = &destination\n\t\t\t}\n\n\t\t\tcurrentPacketCtx = protocol.ContextWithRequestHeader(currentPacketCtx, request)\n\t\t\tudpServer.Dispatch(currentPacketCtx, *dest, payload)\n\t\t}\n\t}\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewServer(ctx, config.(*ServerConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/socks/socks.go",
    "content": "// Package socks provides implements of Socks protocol 4, 4a and 5.\npackage socks\n"
  },
  {
    "path": "proxy/socks/udpfilter.go",
    "content": "package socks\n\nimport (\n\t\"net\"\n\t\"sync\"\n)\n\n/*\nIn the sock implementation of * ray, UDP authentication is flawed and can be bypassed.\nTracking a UDP connection may be a bit troublesome.\nHere is a simple solution.\nWe create a filter, add remote IP to the pool when it try to establish a UDP connection with auth.\nAnd drop UDP packets from unauthorized IP.\nAfter discussion, we believe it is not necessary to add a timeout mechanism to this filter.\n*/\n\ntype UDPFilter struct {\n\tips sync.Map\n}\n\nfunc (f *UDPFilter) Add(addr net.Addr) bool {\n\tip, _, _ := net.SplitHostPort(addr.String())\n\tf.ips.Store(ip, true)\n\treturn true\n}\n\nfunc (f *UDPFilter) Check(addr net.Addr) bool {\n\tip, _, _ := net.SplitHostPort(addr.String())\n\t_, ok := f.ips.Load(ip)\n\treturn ok\n}\n"
  },
  {
    "path": "proxy/trojan/client.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Client is a inbound handler for trojan protocol\ntype Client struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n}\n\n// NewClient create a new trojan client.\nfunc NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {\n\tif config.Server == nil {\n\t\treturn nil, errors.New(`no target server found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Server)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err)\n\t}\n\n\tv := core.MustFromContext(ctx)\n\tclient := &Client{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\treturn client, nil\n}\n\n// Process implements OutboundHandler.Process().\nfunc (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified\")\n\t}\n\tob.Name = \"trojan\"\n\tob.CanSpliceCopy = 3\n\tdestination := ob.Target\n\tnetwork := destination.Network\n\n\tserver := c.server\n\tvar conn stat.Connection\n\n\terr := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\trawConn, err := dialer.Dial(ctx, server.Destination)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tconn = rawConn\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to find an available destination\").AtWarning().Base(err)\n\t}\n\terrors.LogInfo(ctx, \"tunneling request to \", destination, \" via \", server.Destination.NetAddr())\n\n\tdefer conn.Close()\n\n\tuser := server.User\n\taccount, ok := user.Account.(*MemoryAccount)\n\tif !ok {\n\t\treturn errors.New(\"user account is not valid\")\n\t}\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tsessionPolicy := c.policyManager.ForLevel(user.Level)\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tpostRequest := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\tbufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\n\t\tconnWriter := &ConnWriter{\n\t\t\tWriter:  bufferWriter,\n\t\t\tTarget:  destination,\n\t\t\tAccount: account,\n\t\t}\n\n\t\tvar bodyWriter buf.Writer\n\t\tif destination.Network == net.Network_UDP {\n\t\t\tbodyWriter = &PacketWriter{Writer: connWriter, Target: destination}\n\t\t} else {\n\t\t\tbodyWriter = connWriter\n\t\t}\n\n\t\t// write some request payload to buffer\n\t\tif err = buf.CopyOnceTimeout(link.Reader, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {\n\t\t\treturn errors.New(\"failed to write A request payload\").Base(err).AtWarning()\n\t\t}\n\n\t\t// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer\n\t\tif err = bufferWriter.SetBuffered(false); err != nil {\n\t\t\treturn errors.New(\"failed to flush payload\").Base(err).AtWarning()\n\t\t}\n\n\t\t// Send header if not sent yet\n\t\tif _, err = connWriter.Write([]byte{}); err != nil {\n\t\t\treturn err.(*errors.Error).AtWarning()\n\t\t}\n\n\t\tif err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to transfer request payload\").Base(err).AtInfo()\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tgetResponse := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\tvar reader buf.Reader\n\t\tif network == net.Network_UDP {\n\t\t\treader = &PacketReader{\n\t\t\t\tReader: conn,\n\t\t\t}\n\t\t} else {\n\t\t\treader = buf.NewReader(conn)\n\t\t}\n\t\treturn buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tresponseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer))\n\tif err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewClient(ctx, config.(*ClientConfig))\n\t}))\n}\n"
  },
  {
    "path": "proxy/trojan/config.go",
    "content": "package trojan\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\n// MemoryAccount is an account type converted from Account.\ntype MemoryAccount struct {\n\tPassword string\n\tKey      []byte\n}\n\n// AsAccount implements protocol.AsAccount.\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\tpassword := a.GetPassword()\n\tkey := hexSha224(password)\n\treturn &MemoryAccount{\n\t\tPassword: password,\n\t\tKey:      key,\n\t}, nil\n}\n\n// Equals implements protocol.Account.Equals().\nfunc (a *MemoryAccount) Equals(another protocol.Account) bool {\n\tif account, ok := another.(*MemoryAccount); ok {\n\t\treturn a.Password == account.Password\n\t}\n\treturn false\n}\n\nfunc (a *MemoryAccount) ToProto() proto.Message {\n\treturn &Account{\n\t\tPassword: a.Password,\n\t}\n}\n\nfunc hexSha224(password string) []byte {\n\tbuf := make([]byte, 56)\n\thash := sha256.New224()\n\tcommon.Must2(hash.Write([]byte(password)))\n\thex.Encode(buf, hash.Sum(nil))\n\treturn buf\n}\n\nfunc hexString(data []byte) string {\n\tstr := \"\"\n\tfor _, v := range data {\n\t\tstr += fmt.Sprintf(\"%02x\", v)\n\t}\n\treturn str\n}\n"
  },
  {
    "path": "proxy/trojan/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/trojan/config.proto\n\npackage trojan\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Account struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      string                 `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_trojan_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_trojan_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_trojan_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Account) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\ntype Fallback struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tAlpn          string                 `protobuf:\"bytes,2,opt,name=alpn,proto3\" json:\"alpn,omitempty\"`\n\tPath          string                 `protobuf:\"bytes,3,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tType          string                 `protobuf:\"bytes,4,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tDest          string                 `protobuf:\"bytes,5,opt,name=dest,proto3\" json:\"dest,omitempty\"`\n\tXver          uint64                 `protobuf:\"varint,6,opt,name=xver,proto3\" json:\"xver,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Fallback) Reset() {\n\t*x = Fallback{}\n\tmi := &file_proxy_trojan_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Fallback) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Fallback) ProtoMessage() {}\n\nfunc (x *Fallback) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_trojan_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Fallback.ProtoReflect.Descriptor instead.\nfunc (*Fallback) Descriptor() ([]byte, []int) {\n\treturn file_proxy_trojan_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Fallback) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetAlpn() string {\n\tif x != nil {\n\t\treturn x.Alpn\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetDest() string {\n\tif x != nil {\n\t\treturn x.Dest\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetXver() uint64 {\n\tif x != nil {\n\t\treturn x.Xver\n\t}\n\treturn 0\n}\n\ntype ClientConfig struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tServer        *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_proxy_trojan_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_trojan_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_trojan_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *ClientConfig) GetServer() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\ntype ServerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUsers         []*protocol.User       `protobuf:\"bytes,1,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tFallbacks     []*Fallback            `protobuf:\"bytes,2,rep,name=fallbacks,proto3\" json:\"fallbacks,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ServerConfig) Reset() {\n\t*x = ServerConfig{}\n\tmi := &file_proxy_trojan_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerConfig) ProtoMessage() {}\n\nfunc (x *ServerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_trojan_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.\nfunc (*ServerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_trojan_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *ServerConfig) GetUsers() []*protocol.User {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\nfunc (x *ServerConfig) GetFallbacks() []*Fallback {\n\tif x != nil {\n\t\treturn x.Fallbacks\n\t}\n\treturn nil\n}\n\nvar File_proxy_trojan_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_trojan_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x19proxy/trojan/config.proto\\x12\\x11xray.proxy.trojan\\x1a\\x1acommon/protocol/user.proto\\x1a!common/protocol/server_spec.proto\\\"%\\n\" +\n\t\"\\aAccount\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x01 \\x01(\\tR\\bpassword\\\"\\x82\\x01\\n\" +\n\t\"\\bFallback\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x12\\n\" +\n\t\"\\x04alpn\\x18\\x02 \\x01(\\tR\\x04alpn\\x12\\x12\\n\" +\n\t\"\\x04path\\x18\\x03 \\x01(\\tR\\x04path\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x04 \\x01(\\tR\\x04type\\x12\\x12\\n\" +\n\t\"\\x04dest\\x18\\x05 \\x01(\\tR\\x04dest\\x12\\x12\\n\" +\n\t\"\\x04xver\\x18\\x06 \\x01(\\x04R\\x04xver\\\"L\\n\" +\n\t\"\\fClientConfig\\x12<\\n\" +\n\t\"\\x06server\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x06server\\\"{\\n\" +\n\t\"\\fServerConfig\\x120\\n\" +\n\t\"\\x05users\\x18\\x01 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\x05users\\x129\\n\" +\n\t\"\\tfallbacks\\x18\\x02 \\x03(\\v2\\x1b.xray.proxy.trojan.FallbackR\\tfallbacksBU\\n\" +\n\t\"\\x15com.xray.proxy.trojanP\\x01Z&github.com/xtls/xray-core/proxy/trojan\\xaa\\x02\\x11Xray.Proxy.Trojanb\\x06proto3\"\n\nvar (\n\tfile_proxy_trojan_config_proto_rawDescOnce sync.Once\n\tfile_proxy_trojan_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_trojan_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_trojan_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_trojan_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_trojan_config_proto_rawDesc), len(file_proxy_trojan_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_trojan_config_proto_rawDescData\n}\n\nvar file_proxy_trojan_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_proxy_trojan_config_proto_goTypes = []any{\n\t(*Account)(nil),                 // 0: xray.proxy.trojan.Account\n\t(*Fallback)(nil),                // 1: xray.proxy.trojan.Fallback\n\t(*ClientConfig)(nil),            // 2: xray.proxy.trojan.ClientConfig\n\t(*ServerConfig)(nil),            // 3: xray.proxy.trojan.ServerConfig\n\t(*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint\n\t(*protocol.User)(nil),           // 5: xray.common.protocol.User\n}\nvar file_proxy_trojan_config_proto_depIdxs = []int32{\n\t4, // 0: xray.proxy.trojan.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint\n\t5, // 1: xray.proxy.trojan.ServerConfig.users:type_name -> xray.common.protocol.User\n\t1, // 2: xray.proxy.trojan.ServerConfig.fallbacks:type_name -> xray.proxy.trojan.Fallback\n\t3, // [3:3] is the sub-list for method output_type\n\t3, // [3:3] is the sub-list for method input_type\n\t3, // [3:3] is the sub-list for extension type_name\n\t3, // [3:3] is the sub-list for extension extendee\n\t0, // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_trojan_config_proto_init() }\nfunc file_proxy_trojan_config_proto_init() {\n\tif File_proxy_trojan_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_trojan_config_proto_rawDesc), len(file_proxy_trojan_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_trojan_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_trojan_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_trojan_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_trojan_config_proto = out.File\n\tfile_proxy_trojan_config_proto_goTypes = nil\n\tfile_proxy_trojan_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/trojan/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.trojan;\noption csharp_namespace = \"Xray.Proxy.Trojan\";\noption go_package = \"github.com/xtls/xray-core/proxy/trojan\";\noption java_package = \"com.xray.proxy.trojan\";\noption java_multiple_files = true;\n\nimport \"common/protocol/user.proto\";\nimport \"common/protocol/server_spec.proto\";\n\nmessage Account {\n  string password = 1;\n}\n\nmessage Fallback {\n  string name = 1;\n  string alpn = 2;\n  string path = 3;\n  string type = 4;\n  string dest = 5;\n  uint64 xver = 6;\n}\n\nmessage ClientConfig {\n  xray.common.protocol.ServerEndpoint server = 1;\n}\n\nmessage ServerConfig {\n  repeated xray.common.protocol.User users = 1;\n  repeated Fallback fallbacks = 2;\n}\n"
  },
  {
    "path": "proxy/trojan/protocol.go",
    "content": "package trojan\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nvar (\n\tcrlf = []byte{'\\r', '\\n'}\n\n\taddrParser = protocol.NewAddressParser(\n\t\tprotocol.AddressFamilyByte(0x01, net.AddressFamilyIPv4),\n\t\tprotocol.AddressFamilyByte(0x04, net.AddressFamilyIPv6),\n\t\tprotocol.AddressFamilyByte(0x03, net.AddressFamilyDomain),\n\t)\n)\n\nconst (\n\tmaxLength = 8192\n\n\tcommandTCP byte = 1\n\tcommandUDP byte = 3\n)\n\n// ConnWriter is TCP Connection Writer Wrapper for trojan protocol\ntype ConnWriter struct {\n\tio.Writer\n\tTarget     net.Destination\n\tAccount    *MemoryAccount\n\theaderSent bool\n}\n\n// Write implements io.Writer\nfunc (c *ConnWriter) Write(p []byte) (n int, err error) {\n\tif !c.headerSent {\n\t\tif err := c.writeHeader(); err != nil {\n\t\t\treturn 0, errors.New(\"failed to write request header\").Base(err)\n\t\t}\n\t}\n\n\treturn c.Writer.Write(p)\n}\n\n// WriteMultiBuffer implements buf.Writer\nfunc (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\n\tfor _, b := range mb {\n\t\tif !b.IsEmpty() {\n\t\t\tif _, err := c.Write(b.Bytes()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *ConnWriter) writeHeader() error {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tcommand := commandTCP\n\tif c.Target.Network == net.Network_UDP {\n\t\tcommand = commandUDP\n\t}\n\n\tif _, err := buffer.Write(c.Account.Key); err != nil {\n\t\treturn err\n\t}\n\tif _, err := buffer.Write(crlf); err != nil {\n\t\treturn err\n\t}\n\tif err := buffer.WriteByte(command); err != nil {\n\t\treturn err\n\t}\n\tif err := addrParser.WriteAddressPort(&buffer, c.Target.Address, c.Target.Port); err != nil {\n\t\treturn err\n\t}\n\tif _, err := buffer.Write(crlf); err != nil {\n\t\treturn err\n\t}\n\n\t_, err := c.Writer.Write(buffer.Bytes())\n\tif err == nil {\n\t\tc.headerSent = true\n\t}\n\n\treturn err\n}\n\n// PacketWriter UDP Connection Writer Wrapper for trojan protocol\ntype PacketWriter struct {\n\tio.Writer\n\tTarget net.Destination\n}\n\n// WriteMultiBuffer implements buf.Writer\nfunc (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor {\n\t\tmb2, b := buf.SplitFirst(mb)\n\t\tmb = mb2\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\t\ttarget := &w.Target\n\t\tif b.UDP != nil {\n\t\t\ttarget = b.UDP\n\t\t}\n\t\tif _, err := w.writePacket(b.Bytes(), *target); err != nil {\n\t\t\tb.Release()\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn err\n\t\t}\n\t\tb.Release()\n\t}\n\treturn nil\n}\n\nfunc (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tlength := len(payload)\n\tlengthBuf := [2]byte{}\n\tbinary.BigEndian.PutUint16(lengthBuf[:], uint16(length))\n\tif err := addrParser.WriteAddressPort(&buffer, dest.Address, dest.Port); err != nil {\n\t\treturn 0, err\n\t}\n\tif _, err := buffer.Write(lengthBuf[:]); err != nil {\n\t\treturn 0, err\n\t}\n\tif _, err := buffer.Write(crlf); err != nil {\n\t\treturn 0, err\n\t}\n\tif _, err := buffer.Write(payload); err != nil {\n\t\treturn 0, err\n\t}\n\t_, err := w.Write(buffer.Bytes())\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn length, nil\n}\n\n// ConnReader is TCP Connection Reader Wrapper for trojan protocol\ntype ConnReader struct {\n\tio.Reader\n\tTarget       net.Destination\n\tFlow         string\n\theaderParsed bool\n}\n\n// ParseHeader parses the trojan protocol header\nfunc (c *ConnReader) ParseHeader() error {\n\tvar crlf [2]byte\n\tvar command [1]byte\n\tvar hash [56]byte\n\tif _, err := io.ReadFull(c.Reader, hash[:]); err != nil {\n\t\treturn errors.New(\"failed to read user hash\").Base(err)\n\t}\n\n\tif _, err := io.ReadFull(c.Reader, crlf[:]); err != nil {\n\t\treturn errors.New(\"failed to read crlf\").Base(err)\n\t}\n\n\tif _, err := io.ReadFull(c.Reader, command[:]); err != nil {\n\t\treturn errors.New(\"failed to read command\").Base(err)\n\t}\n\n\tnetwork := net.Network_TCP\n\tif command[0] == commandUDP {\n\t\tnetwork = net.Network_UDP\n\t}\n\n\taddr, port, err := addrParser.ReadAddressPort(nil, c.Reader)\n\tif err != nil {\n\t\treturn errors.New(\"failed to read address and port\").Base(err)\n\t}\n\tc.Target = net.Destination{Network: network, Address: addr, Port: port}\n\n\tif _, err := io.ReadFull(c.Reader, crlf[:]); err != nil {\n\t\treturn errors.New(\"failed to read crlf\").Base(err)\n\t}\n\n\tc.headerParsed = true\n\treturn nil\n}\n\n// Read implements io.Reader\nfunc (c *ConnReader) Read(p []byte) (int, error) {\n\tif !c.headerParsed {\n\t\tif err := c.ParseHeader(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\treturn c.Reader.Read(p)\n}\n\n// ReadMultiBuffer implements buf.Reader\nfunc (c *ConnReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tb := buf.New()\n\t_, err := b.ReadFrom(c)\n\treturn buf.MultiBuffer{b}, err\n}\n\n// PacketReader is UDP Connection Reader Wrapper for trojan protocol\ntype PacketReader struct {\n\tio.Reader\n}\n\n// ReadMultiBuffer implements buf.Reader\nfunc (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\taddr, port, err := addrParser.ReadAddressPort(nil, r)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to read address and port\").Base(err)\n\t}\n\n\tvar lengthBuf [2]byte\n\tif _, err := io.ReadFull(r, lengthBuf[:]); err != nil {\n\t\treturn nil, errors.New(\"failed to read payload length\").Base(err)\n\t}\n\n\tremain := int(binary.BigEndian.Uint16(lengthBuf[:]))\n\tif remain > maxLength {\n\t\treturn nil, errors.New(\"oversize payload\")\n\t}\n\n\tvar crlf [2]byte\n\tif _, err := io.ReadFull(r, crlf[:]); err != nil {\n\t\treturn nil, errors.New(\"failed to read crlf\").Base(err)\n\t}\n\n\tdest := net.UDPDestination(addr, port)\n\tvar mb buf.MultiBuffer\n\tfor remain > 0 {\n\t\tlength := buf.Size\n\t\tif remain < length {\n\t\t\tlength = remain\n\t\t}\n\n\t\tb := buf.New()\n\t\tb.UDP = &dest\n\t\tmb = append(mb, b)\n\t\tn, err := b.ReadFullFrom(r, int32(length))\n\t\tif err != nil {\n\t\t\tbuf.ReleaseMulti(mb)\n\t\t\treturn nil, errors.New(\"failed to read payload\").Base(err)\n\t\t}\n\n\t\tremain -= int(n)\n\t}\n\n\treturn mb, nil\n}\n"
  },
  {
    "path": "proxy/trojan/protocol_test.go",
    "content": "package trojan_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t. \"github.com/xtls/xray-core/proxy/trojan\"\n)\n\nfunc toAccount(a *Account) protocol.Account {\n\taccount, err := a.AsAccount()\n\tcommon.Must(err)\n\treturn account\n}\n\nfunc TestTCPRequest(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tEmail: \"love@example.com\",\n\t\tAccount: toAccount(&Account{\n\t\t\tPassword: \"password\",\n\t\t}),\n\t}\n\tpayload := []byte(\"test string\")\n\tdata := buf.New()\n\tcommon.Must2(data.Write(payload))\n\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\tdestination := net.Destination{Network: net.Network_TCP, Address: net.LocalHostIP, Port: 1234}\n\twriter := &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data}))\n\n\treader := &ConnReader{Reader: buffer}\n\tcommon.Must(reader.ParseHeader())\n\n\tif r := cmp.Diff(reader.Target, destination); r != \"\" {\n\t\tt.Error(\"destination: \", r)\n\t}\n\n\tdecodedData, err := reader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif r := cmp.Diff(decodedData[0].Bytes(), payload); r != \"\" {\n\t\tt.Error(\"data: \", r)\n\t}\n}\n\nfunc TestUDPRequest(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tEmail: \"love@example.com\",\n\t\tAccount: toAccount(&Account{\n\t\t\tPassword: \"password\",\n\t\t}),\n\t}\n\tpayload := []byte(\"test string\")\n\tdata := buf.New()\n\tcommon.Must2(data.Write(payload))\n\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\tdestination := net.Destination{Network: net.Network_UDP, Address: net.LocalHostIP, Port: 1234}\n\twriter := &PacketWriter{Writer: &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}, Target: destination}\n\tcommon.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data}))\n\n\tconnReader := &ConnReader{Reader: buffer}\n\tcommon.Must(connReader.ParseHeader())\n\n\tpacketReader := &PacketReader{Reader: connReader}\n\tmb, err := packetReader.ReadMultiBuffer()\n\tcommon.Must(err)\n\n\tif mb.IsEmpty() {\n\t\tt.Error(\"no request data\")\n\t}\n\n\tmb2, b := buf.SplitFirst(mb)\n\tdefer buf.ReleaseMulti(mb2)\n\n\tdest := *b.UDP\n\tif r := cmp.Diff(dest, destination); r != \"\" {\n\t\tt.Error(\"destination: \", r)\n\t}\n\n\tif r := cmp.Diff(b.Bytes(), payload); r != \"\" {\n\t\tt.Error(\"data: \", r)\n\t}\n}\n"
  },
  {
    "path": "proxy/trojan/server.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\tudp_proto \"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewServer(ctx, config.(*ServerConfig))\n\t}))\n}\n\n// Server is an inbound connection handler that handles messages in trojan protocol.\ntype Server struct {\n\tpolicyManager policy.Manager\n\tvalidator     *Validator\n\tfallbacks     map[string]map[string]map[string]*Fallback // or nil\n\tcone          bool\n}\n\n// NewServer creates a new trojan inbound handler.\nfunc NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {\n\tvalidator := new(Validator)\n\tfor _, user := range config.Users {\n\t\tu, err := user.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get trojan user\").Base(err).AtError()\n\t\t}\n\n\t\tif err := validator.Add(u); err != nil {\n\t\t\treturn nil, errors.New(\"failed to add user\").Base(err).AtError()\n\t\t}\n\t}\n\n\tv := core.MustFromContext(ctx)\n\tserver := &Server{\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tvalidator:     validator,\n\t\tcone:          ctx.Value(\"cone\").(bool),\n\t}\n\n\tif config.Fallbacks != nil {\n\t\tserver.fallbacks = make(map[string]map[string]map[string]*Fallback)\n\t\tfor _, fb := range config.Fallbacks {\n\t\t\tif server.fallbacks[fb.Name] == nil {\n\t\t\t\tserver.fallbacks[fb.Name] = make(map[string]map[string]*Fallback)\n\t\t\t}\n\t\t\tif server.fallbacks[fb.Name][fb.Alpn] == nil {\n\t\t\t\tserver.fallbacks[fb.Name][fb.Alpn] = make(map[string]*Fallback)\n\t\t\t}\n\t\t\tserver.fallbacks[fb.Name][fb.Alpn][fb.Path] = fb\n\t\t}\n\t\tif server.fallbacks[\"\"] != nil {\n\t\t\tfor name, apfb := range server.fallbacks {\n\t\t\t\tif name != \"\" {\n\t\t\t\t\tfor alpn := range server.fallbacks[\"\"] {\n\t\t\t\t\t\tif apfb[alpn] == nil {\n\t\t\t\t\t\t\tapfb[alpn] = make(map[string]*Fallback)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, apfb := range server.fallbacks {\n\t\t\tif apfb[\"\"] != nil {\n\t\t\t\tfor alpn, pfb := range apfb {\n\t\t\t\t\tif alpn != \"\" { // && alpn != \"h2\" {\n\t\t\t\t\t\tfor path, fb := range apfb[\"\"] {\n\t\t\t\t\t\t\tif pfb[path] == nil {\n\t\t\t\t\t\t\t\tpfb[path] = fb\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif server.fallbacks[\"\"] != nil {\n\t\t\tfor name, apfb := range server.fallbacks {\n\t\t\t\tif name != \"\" {\n\t\t\t\t\tfor alpn, pfb := range server.fallbacks[\"\"] {\n\t\t\t\t\t\tfor path, fb := range pfb {\n\t\t\t\t\t\t\tif apfb[alpn][path] == nil {\n\t\t\t\t\t\t\t\tapfb[alpn][path] = fb\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn server, nil\n}\n\n// AddUser implements proxy.UserManager.AddUser().\nfunc (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {\n\treturn s.validator.Add(u)\n}\n\n// RemoveUser implements proxy.UserManager.RemoveUser().\nfunc (s *Server) RemoveUser(ctx context.Context, e string) error {\n\treturn s.validator.Del(e)\n}\n\n// GetUser implements proxy.UserManager.GetUser().\nfunc (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {\n\treturn s.validator.GetByEmail(email)\n}\n\n// GetUsers implements proxy.UserManager.GetUsers().\nfunc (s *Server) GetUsers(ctx context.Context) []*protocol.MemoryUser {\n\treturn s.validator.GetAll()\n}\n\n// GetUsersCount implements proxy.UserManager.GetUsersCount().\nfunc (s *Server) GetUsersCount(context.Context) int64 {\n\treturn s.validator.GetCount()\n}\n\n// Network implements proxy.Inbound.Network().\nfunc (s *Server) Network() []net.Network {\n\treturn []net.Network{net.Network_TCP, net.Network_UNIX}\n}\n\n// Process implements proxy.Inbound.Process().\nfunc (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\tiConn := stat.TryUnwrapStatsConn(conn)\n\n\tsessionPolicy := s.policyManager.ForLevel(0)\n\tif err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {\n\t\treturn errors.New(\"unable to set read deadline\").Base(err).AtWarning()\n\t}\n\n\tfirst := buf.FromBytes(make([]byte, buf.Size))\n\tfirst.Clear()\n\tfirstLen, err := first.ReadFrom(conn)\n\tif err != nil {\n\t\treturn errors.New(\"failed to read first request\").Base(err)\n\t}\n\terrors.LogInfo(ctx, \"firstLen = \", firstLen)\n\n\tbufferedReader := &buf.BufferedReader{\n\t\tReader: buf.NewReader(conn),\n\t\tBuffer: buf.MultiBuffer{first},\n\t}\n\n\tvar user *protocol.MemoryUser\n\n\tnapfb := s.fallbacks\n\tisfb := napfb != nil\n\n\tshouldFallback := false\n\tif firstLen < 58 || first.Byte(56) != '\\r' {\n\t\t// invalid protocol\n\t\terr = errors.New(\"not trojan protocol\")\n\t\tlog.Record(&log.AccessMessage{\n\t\t\tFrom:   conn.RemoteAddr(),\n\t\t\tTo:     \"\",\n\t\t\tStatus: log.AccessRejected,\n\t\t\tReason: err,\n\t\t})\n\n\t\tshouldFallback = true\n\t} else {\n\t\tuser = s.validator.Get(hexString(first.BytesTo(56)))\n\t\tif user == nil {\n\t\t\t// invalid user, let's fallback\n\t\t\terr = errors.New(\"not a valid user\")\n\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\tFrom:   conn.RemoteAddr(),\n\t\t\t\tTo:     \"\",\n\t\t\t\tStatus: log.AccessRejected,\n\t\t\t\tReason: err,\n\t\t\t})\n\n\t\t\tshouldFallback = true\n\t\t}\n\t}\n\n\tif isfb && shouldFallback {\n\t\treturn s.fallback(ctx, err, sessionPolicy, conn, iConn, napfb, first, firstLen, bufferedReader)\n\t} else if shouldFallback {\n\t\treturn errors.New(\"invalid protocol or invalid user\")\n\t}\n\n\tclientReader := &ConnReader{Reader: bufferedReader}\n\tif err := clientReader.ParseHeader(); err != nil {\n\t\tlog.Record(&log.AccessMessage{\n\t\t\tFrom:   conn.RemoteAddr(),\n\t\t\tTo:     \"\",\n\t\t\tStatus: log.AccessRejected,\n\t\t\tReason: err,\n\t\t})\n\t\treturn errors.New(\"failed to create request from: \", conn.RemoteAddr()).Base(err)\n\t}\n\n\tdestination := clientReader.Target\n\tif err := conn.SetReadDeadline(time.Time{}); err != nil {\n\t\treturn errors.New(\"unable to set read deadline\").Base(err).AtWarning()\n\t}\n\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"trojan\"\n\tinbound.CanSpliceCopy = 3\n\tinbound.User = user\n\tsessionPolicy = s.policyManager.ForLevel(user.Level)\n\n\tif destination.Network == net.Network_UDP { // handle udp request\n\t\treturn s.handleUDPPayload(ctx, sessionPolicy, &PacketReader{Reader: clientReader}, &PacketWriter{Writer: conn}, dispatcher)\n\t}\n\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   conn.RemoteAddr(),\n\t\tTo:     destination,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t\tEmail:  user.Email,\n\t})\n\n\terrors.LogInfo(ctx, \"received request for \", destination)\n\treturn s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher)\n}\n\nfunc (s *Server) handleUDPPayload(ctx context.Context, sessionPolicy policy.Session, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\ttimer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)\n\tdefer timer.SetTimeout(0)\n\tudpServer := udp.NewDispatcher(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {\n\t\tudpPayload := packet.Payload\n\t\tif udpPayload.UDP == nil {\n\t\t\tudpPayload.UDP = &packet.Source\n\t\t}\n\n\t\tif err := clientWriter.WriteMultiBuffer(buf.MultiBuffer{udpPayload}); err != nil {\n\t\t\terrors.LogWarningInner(ctx, err, \"failed to write response\")\n\t\t\tcancel()\n\t\t} else {\n\t\t\ttimer.Update()\n\t\t}\n\t})\n\tdefer udpServer.RemoveRay()\n\n\tinbound := session.InboundFromContext(ctx)\n\tuser := inbound.User\n\n\tvar dest *net.Destination\n\n\trequestDone := func() error {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t\tmb, err := clientReader.ReadMultiBuffer()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.Cause(err) != io.EOF {\n\t\t\t\t\t\treturn errors.New(\"unexpected EOF\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tmb2, b := buf.SplitFirst(mb)\n\t\t\t\tif b == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttimer.Update()\n\t\t\t\tdestination := *b.UDP\n\n\t\t\t\tcurrentPacketCtx := ctx\n\t\t\t\tif inbound.Source.IsValid() {\n\t\t\t\t\tcurrentPacketCtx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\t\t\t\tFrom:   inbound.Source,\n\t\t\t\t\t\tTo:     destination,\n\t\t\t\t\t\tStatus: log.AccessAccepted,\n\t\t\t\t\t\tReason: \"\",\n\t\t\t\t\t\tEmail:  user.Email,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\terrors.LogInfo(ctx, \"tunnelling request to \", destination)\n\n\t\t\t\tif !s.cone || dest == nil {\n\t\t\t\t\tdest = &destination\n\t\t\t\t}\n\n\t\t\t\tudpServer.Dispatch(currentPacketCtx, *dest, b) // first packet\n\t\t\t\tfor _, payload := range mb2 {\n\t\t\t\t\tudpServer.Dispatch(currentPacketCtx, *dest, payload)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif err := task.Run(ctx, requestDone); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session,\n\tdestination net.Destination,\n\tclientReader buf.Reader,\n\tclientWriter buf.Writer, dispatcher routing.Dispatcher,\n) error {\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)\n\tctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)\n\n\tlink, err := dispatcher.Dispatch(ctx, destination)\n\tif err != nil {\n\t\treturn errors.New(\"failed to dispatch request to \", destination).Base(err)\n\t}\n\n\trequestDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\t\tif buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)) != nil {\n\t\t\treturn errors.New(\"failed to transfer request\").Base(err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\tif err := buf.Copy(link.Reader, clientWriter, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to write response\").Base(err)\n\t\t}\n\t\treturn nil\n\t}\n\n\trequestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestDonePost, responseDone); err != nil {\n\t\tcommon.Must(common.Interrupt(link.Reader))\n\t\tcommon.Must(common.Interrupt(link.Writer))\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *Server) fallback(ctx context.Context, err error, sessionPolicy policy.Session, connection stat.Connection, iConn stat.Connection, napfb map[string]map[string]map[string]*Fallback, first *buf.Buffer, firstLen int64, reader buf.Reader) error {\n\tif err := connection.SetReadDeadline(time.Time{}); err != nil {\n\t\terrors.LogWarningInner(ctx, err, \"unable to set back read deadline\")\n\t}\n\terrors.LogInfoInner(ctx, err, \"fallback starts\")\n\n\tname := \"\"\n\talpn := \"\"\n\tif tlsConn, ok := iConn.(*tls.Conn); ok {\n\t\tcs := tlsConn.ConnectionState()\n\t\tname = cs.ServerName\n\t\talpn = cs.NegotiatedProtocol\n\t\terrors.LogInfo(ctx, \"realName = \"+name)\n\t\terrors.LogInfo(ctx, \"realAlpn = \"+alpn)\n\t} else if realityConn, ok := iConn.(*reality.Conn); ok {\n\t\tcs := realityConn.ConnectionState()\n\t\tname = cs.ServerName\n\t\talpn = cs.NegotiatedProtocol\n\t\terrors.LogInfo(ctx, \"realName = \"+name)\n\t\terrors.LogInfo(ctx, \"realAlpn = \"+alpn)\n\t}\n\tname = strings.ToLower(name)\n\talpn = strings.ToLower(alpn)\n\n\tif len(napfb) > 1 || napfb[\"\"] == nil {\n\t\tif name != \"\" && napfb[name] == nil {\n\t\t\tmatch := \"\"\n\t\t\tfor n := range napfb {\n\t\t\t\tif n != \"\" && strings.Contains(name, n) && len(n) > len(match) {\n\t\t\t\t\tmatch = n\n\t\t\t\t}\n\t\t\t}\n\t\t\tname = match\n\t\t}\n\t}\n\n\tif napfb[name] == nil {\n\t\tname = \"\"\n\t}\n\tapfb := napfb[name]\n\tif apfb == nil {\n\t\treturn errors.New(`failed to find the default \"name\" config`).AtWarning()\n\t}\n\n\tif apfb[alpn] == nil {\n\t\talpn = \"\"\n\t}\n\tpfb := apfb[alpn]\n\tif pfb == nil {\n\t\treturn errors.New(`failed to find the default \"alpn\" config`).AtWarning()\n\t}\n\n\tpath := \"\"\n\tif len(pfb) > 1 || pfb[\"\"] == nil {\n\t\tif firstLen >= 18 && first.Byte(4) != '*' { // not h2c\n\t\t\tfirstBytes := first.Bytes()\n\t\t\tfor i := 4; i <= 8; i++ { // 5 -> 9\n\t\t\t\tif firstBytes[i] == '/' && firstBytes[i-1] == ' ' {\n\t\t\t\t\tsearch := len(firstBytes)\n\t\t\t\t\tif search > 64 {\n\t\t\t\t\t\tsearch = 64 // up to about 60\n\t\t\t\t\t}\n\t\t\t\t\tfor j := i + 1; j < search; j++ {\n\t\t\t\t\t\tk := firstBytes[j]\n\t\t\t\t\t\tif k == '\\r' || k == '\\n' { // avoid logging \\r or \\n\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif k == '?' || k == ' ' {\n\t\t\t\t\t\t\tpath = string(firstBytes[i:j])\n\t\t\t\t\t\t\terrors.LogInfo(ctx, \"realPath = \"+path)\n\t\t\t\t\t\t\tif pfb[path] == nil {\n\t\t\t\t\t\t\t\tpath = \"\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfb := pfb[path]\n\tif fb == nil {\n\t\treturn errors.New(`failed to find the default \"path\" config`).AtWarning()\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)\n\tctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)\n\n\tvar conn net.Conn\n\tif err := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\tvar dialer net.Dialer\n\t\tconn, err = dialer.DialContext(ctx, fb.Type, fb.Dest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn errors.New(\"failed to dial to \" + fb.Dest).Base(err).AtWarning()\n\t}\n\tdefer conn.Close()\n\n\tserverReader := buf.NewReader(conn)\n\tserverWriter := buf.NewWriter(conn)\n\n\tpostRequest := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\t\tif fb.Xver != 0 {\n\t\t\tipType := 4\n\t\t\tremoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String())\n\t\t\tif err != nil {\n\t\t\t\tipType = 0\n\t\t\t}\n\t\t\tlocalAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String())\n\t\t\tif err != nil {\n\t\t\t\tipType = 0\n\t\t\t}\n\t\t\tif ipType == 4 {\n\t\t\t\tfor i := 0; i < len(remoteAddr); i++ {\n\t\t\t\t\tif remoteAddr[i] == ':' {\n\t\t\t\t\t\tipType = 6\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tpro := buf.New()\n\t\t\tdefer pro.Release()\n\t\t\tswitch fb.Xver {\n\t\t\tcase 1:\n\t\t\t\tif ipType == 0 {\n\t\t\t\t\tcommon.Must2(pro.Write([]byte(\"PROXY UNKNOWN\\r\\n\")))\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif ipType == 4 {\n\t\t\t\t\tcommon.Must2(pro.Write([]byte(\"PROXY TCP4 \" + remoteAddr + \" \" + localAddr + \" \" + remotePort + \" \" + localPort + \"\\r\\n\")))\n\t\t\t\t} else {\n\t\t\t\t\tcommon.Must2(pro.Write([]byte(\"PROXY TCP6 \" + remoteAddr + \" \" + localAddr + \" \" + remotePort + \" \" + localPort + \"\\r\\n\")))\n\t\t\t\t}\n\t\t\tcase 2:\n\t\t\t\tcommon.Must2(pro.Write([]byte(\"\\x0D\\x0A\\x0D\\x0A\\x00\\x0D\\x0A\\x51\\x55\\x49\\x54\\x0A\"))) // signature\n\t\t\t\tif ipType == 0 {\n\t\t\t\t\tcommon.Must2(pro.Write([]byte(\"\\x20\\x00\\x00\\x00\"))) // v2 + LOCAL + UNSPEC + UNSPEC + 0 bytes\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif ipType == 4 {\n\t\t\t\t\tcommon.Must2(pro.Write([]byte(\"\\x21\\x11\\x00\\x0C\"))) // v2 + PROXY + AF_INET + STREAM + 12 bytes\n\t\t\t\t\tcommon.Must2(pro.Write(net.ParseIP(remoteAddr).To4()))\n\t\t\t\t\tcommon.Must2(pro.Write(net.ParseIP(localAddr).To4()))\n\t\t\t\t} else {\n\t\t\t\t\tcommon.Must2(pro.Write([]byte(\"\\x21\\x21\\x00\\x24\"))) // v2 + PROXY + AF_INET6 + STREAM + 36 bytes\n\t\t\t\t\tcommon.Must2(pro.Write(net.ParseIP(remoteAddr).To16()))\n\t\t\t\t\tcommon.Must2(pro.Write(net.ParseIP(localAddr).To16()))\n\t\t\t\t}\n\t\t\t\tp1, _ := strconv.ParseUint(remotePort, 10, 16)\n\t\t\t\tp2, _ := strconv.ParseUint(localPort, 10, 16)\n\t\t\t\tcommon.Must2(pro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)}))\n\t\t\t}\n\t\t\tif err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil {\n\t\t\t\treturn errors.New(\"failed to set PROXY protocol v\", fb.Xver).Base(err).AtWarning()\n\t\t\t}\n\t\t}\n\t\tif err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to fallback request payload\").Base(err).AtInfo()\n\t\t}\n\t\treturn nil\n\t}\n\n\twriter := buf.NewWriter(connection)\n\n\tgetResponse := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\t\tif err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to deliver response payload\").Base(err).AtInfo()\n\t\t}\n\t\treturn nil\n\t}\n\n\tif err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil {\n\t\tcommon.Must(common.Interrupt(serverReader))\n\t\tcommon.Must(common.Interrupt(serverWriter))\n\t\treturn errors.New(\"fallback ends\").Base(err).AtInfo()\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/trojan/trojan.go",
    "content": "package trojan\n"
  },
  {
    "path": "proxy/trojan/validator.go",
    "content": "package trojan\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\n// Validator stores valid trojan users.\ntype Validator struct {\n\t// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.\n\temail sync.Map\n\tusers sync.Map\n}\n\n// Add a trojan user, Email must be empty or unique.\nfunc (v *Validator) Add(u *protocol.MemoryUser) error {\n\tif u.Email != \"\" {\n\t\t_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)\n\t\tif loaded {\n\t\t\treturn errors.New(\"User \", u.Email, \" already exists.\")\n\t\t}\n\t}\n\tv.users.Store(hexString(u.Account.(*MemoryAccount).Key), u)\n\treturn nil\n}\n\n// Del a trojan user with a non-empty Email.\nfunc (v *Validator) Del(e string) error {\n\tif e == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n\tle := strings.ToLower(e)\n\tu, _ := v.email.Load(le)\n\tif u == nil {\n\t\treturn errors.New(\"User \", e, \" not found.\")\n\t}\n\tv.email.Delete(le)\n\tv.users.Delete(hexString(u.(*protocol.MemoryUser).Account.(*MemoryAccount).Key))\n\treturn nil\n}\n\n// Get a trojan user with hashed key, nil if user doesn't exist.\nfunc (v *Validator) Get(hash string) *protocol.MemoryUser {\n\tu, _ := v.users.Load(hash)\n\tif u != nil {\n\t\treturn u.(*protocol.MemoryUser)\n\t}\n\treturn nil\n}\n\n// Get a trojan user with hashed key, nil if user doesn't exist.\nfunc (v *Validator) GetByEmail(email string) *protocol.MemoryUser {\n\temail = strings.ToLower(email)\n\tu, _ := v.email.Load(email)\n\tif u != nil {\n\t\treturn u.(*protocol.MemoryUser)\n\t}\n\treturn nil\n}\n\n// Get all users\nfunc (v *Validator) GetAll() []*protocol.MemoryUser {\n\tvar u = make([]*protocol.MemoryUser, 0, 100)\n\tv.email.Range(func(key, value interface{}) bool {\n\t\tu = append(u, value.(*protocol.MemoryUser))\n\t\treturn true\n\t})\n\treturn u\n}\n\n// Get users count\nfunc (v *Validator) GetCount() int64 {\n\tvar c int64 = 0\n\tv.email.Range(func(key, value interface{}) bool {\n\t\tc++\n\t\treturn true\n\t})\n\treturn c\n}\n"
  },
  {
    "path": "proxy/tun/README.md",
    "content": "# TUN network layer 3 input support\n\nTUN interface support bridges the gap between network layer 3 and layer 7, introducing raw network input.\n\nThis functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \\\nPrimary targets are Linux based router devices. Like most popular OpenWRT option. \\\nSupport for Windows, macOS, Android and iOS is also implemented (see below).\n\n## PLEASE READ FOLLOWING CAREFULLY\n\nIf you are not sure what this is and do you need it or not - you don't. \\\nThis functionality is intended to be configured by network professionals, who understand the deployment case and scenarios. \\\nPlainly enabling it in the config probably will result nothing, or lock your router up in infinite network loop.\n\n## DETAILS\n\nCurrent implementation does not contain options to configure network level addresses, routing or rules.\nEnabling the feature will result only tun interface up, and that's it. \\\nThis is explicit decision, significantly simplifying implementation, and allowing any number of custom configurations, consumers could come up with. Network interface is OS level entity, and OS is what should manage it. \\\nWorking configuration, is tun enabled in Xray config with specific name (e.g. xray0), and OS level configuration to manage \"xray0\" interface, applying routing and rules on interface up.\nThis way consistency of system level routing and rules is ensured from single place of responsibility - the OS itself. \\\nExamples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README.\n\nDue to this inbound not actually being a proxy, the configuration ignore required listen and port options, and never listen on any port. \\\nHere is simple Xray config snippet to enable the inbound:\n```\n{\n  \"inbounds\": [\n    {\n      \"port\": 0,\n      \"protocol\": \"tun\",\n      \"settings\": {\n        \"name\": \"xray0\",\n        \"MTU\": 1492\n      }\n    }\n  ],\n```\n\n## SUPPORTED FEATURES\n\n- IPv4 and IPv6\n- TCP and UDP\n\n## LIMITATION\n\n- No ICMP support\n- Connections are established to any host, as connection success is only a mark of successful accepting packet for proxying. Hosts that are not accepting connections or don't even exists, will look like they opened a connection (SYN-ACK), and never send back a single byte, closing connection (RST) after some time. This is the side effect of the whole process actually being a proxy, and not real network layer 3 vpn\n\n## CONSIDERATIONS\n\nThis feature being network level interface that require raw routing, bring some ambiguities that need to be taken in account. \\\nXray-core itself is connecting to its uplinks on a network level, therefore, it's really simple to lock network up in an infinite loop, when trying to pass \"everything through Xray\". \\\nYou can't just route 0.0.0.0/0 through xray0 interface, as that will result Xray-core itself try to reach its uplink through xray0 interface, resulting infinite network loop.\nThere are several ways to address this:\n\n- Approach 1: \\\n  Add precise static route to Xray upstreams, having them always routed through static internet gateway.\n  E.g. when 123.123.123.123 is the Xray VLESS uplink, this network configuration will work just fine:\n  ```\n  ip route add 123.123.123.123/32 via <provider internet gateway ip>\n  ip route add 0.0.0.0/0 dev xray0\n  ```\n  This has disadvantages, - a lot of conditions must be kept static: internet gateway address, xray uplink ip address, and so on.\n- Approach 1-b: \\\n  Route only specific networks through Xray, keeping the default gateway unchanged.\n  This can be done in many different ways, using ip sets, routing daemons like BGP peers, etc... All you need to do is to route the paths through xray0 dev.\n  The disadvantage in this case is smaller, - you need to make sure the uplink will not become part of those sets and that's it. Can easily be done with route metric priorities.\n- Approach 2: \\\n  Separate main route table and Xray route table with default gateways pointing to different destinations.\n  This way you can achieve full protection of hosts behind the router, keeping router configuration as flexible as desired. \\\n  There are two ways to do that: \\\n  Either configure xray0 interface to appear and operate as default gateway in a separate route table, e.g. 1001. Then mark and route protected traffic by ip rules to that table. \\\n  It's a simplest way to make a \"non-damaging\" configuration, when the only thing you need to do to enable/disable proxying is to flip the ip rules off. Which is also a disadvantage of itself - if by accident ip rules will get disabled, the traffic will spill out of the internet interface unprotected. \\\n  Or, other way around, move default routing to a separate route table, so that all usual routing information is set in e.g. route table 1000,\n  and Xray interface operate in the main route table. This will allow proper flexibility, but you need to ensure traffic from the Xray process, running on the router, is marked to get routed through table 1000. This again can be achieved in same ways, using ip rules and iptable rules combinations. \\\n  Big advantage of that, is that traffic of the route itself is going to be wrapped into the proxy, including DNS queries, without any additional effort. Although, the disadvantage of that is, that in any case proxying stops (uplink dies, Xray hangs, encryption start to flap), it will result complete internet inaccessibility. \\\n  Any approach is applicable, and if you know what you are doing (which is expected, if you read until here) you do understand which one you want and can manage. \\\n\n### Important:\n\nTUN is network level entity, therefore communication through it, is always ip to ip, there are no host names. \\\nTherefore, DNS resolution will always happen before traffic even enter the interface (it will be separate ip-to-ip packets/connections to resolve hostnames). \\\nYou always need to consider that DNS queries in any configuration you chose, most likely, will originate from the router itself (hosts behind the router access router DNS, router DNS fire queries to the outside).\nWithout proper addressing that, DNS queries will expose actual destinations/websites accessed through the router. \\\nTo address that you can as ignore (not use) DNS of the router (just delegate some public DNS in DHCP configuration to your devices), or make sure routing rules are configured the way, DNS resolution of the router itself runs through Xray interface/routing table.\n\nYou also need to remember that local traffic of the router (e.g. DNS, firmware updates, etc.), is subject of firewall rules as outcoming/incoming traffic (not forward).\nIf you have restrictive firewall, you need to allow input/output traffic through xray0 interface, for it to properly dispatch and reach the OS back.\n\nAdditionally, working with two route tables is not taken lightly by Linux, and sometimes make it panic about \"martian packets\", which it calls the packets arriving through interfaces it does not expect they could arrive from. \\\nIt was just a warning until recent kernel versions, but now traffic is often dropped. \\\nIn simple case this can be just disabled with\n```\n/usr/sbin/sysctl -w net.ipv4.conf.all.rp_filter=0\n```\nBut proper approach should be defining your route tables fully and consistently, adding all routes corresponding to traffic that flow through them.\n\n## EXAMPLES\n\nsystemd-networkd \\\nconfiguration file you can place in /etc/systemd/networkd as 90-xray0.network\nwhich will configure xray0 interface and routing using route table 1001, when the interface will appear in the system (Xray starts). And deconfigure when disappears.\n```\n[Match]\nName = xray0\n\n[Network]\nKeepConfiguration = yes\n\n[Link]\nActivationPolicy = manual\nRequiredForOnline = no\n\n[Route]\nTable = 1001\nDestination = 0.0.0.0/0\n\n[RoutingPolicyRule]\nFrom = 192.168.0.0/24\nTable = 1001\n```\nRoutingPolicyRule will add the record into ip rules, that will funnel all traffic from 192.168.0.0/24 through the table 1001 \\\nPlease note that for ideal configuration of the routing you will also need to add the route to 192.168.0.0/24 to the route table 1001.\nYou can do that e.g. in the file, describing your adapter serving local network (e.g. 10-br0.network), additionally to its native properties like:\n```\n[Match]\nName = br0\nType = bridge\n\n[Network]\n...skip...\nAddress = 192.168.0.1/24\n...skip...\n\n[Route]\nTable = 1001\nDestination = 192.168.0.0/24\nPreferredSource = 192.168.0.1\nScope = link\n```\nAll in all systemd-networkd and its derivatives (like netplan or NetworkManager) provide all means to configure your networking, according to your wish, that will ensure network consistency of xray0 interface coming up and down, relative to other network configuration like internet interfaces, nat rules and so on.\n\n## WINDOWS SUPPORT\n\nWindows version of the same functionality is implemented through Wintun library. \\\nTo make it start, wintun.dll specific for your Windows/arch must be present next to Xray.exe binary.\n\nAfter the start network adapter with the name you chose in the config will be created in the system, and exist while Xray is running.\n\nYou can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \\\nMinimal configuration that will work for local machine is routing passing the traffic on-link through the interface.\nYou will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver.\nYou can find the interface id with the command\n```\nroute print\n```\nit will be in the list of interfaces on the top of the output\n```\n===========================================================================\nInterface List\n  8...cc cc cc cc cc cc ......Realtek PCIe GbE Family Controller\n 47...........................Xray Tunnel\n  1...........................Software Loopback Interface 1\n===========================================================================\n```\nIn this case the interface id is \"47\". \\\nThen you can add on-link route through the adapter with (example) command\n```\nroute add 1.1.1.1 mask 255.0.0.0 0.0.0.0 if 47\n```\nNote on ipv6 support. \\\nDespite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \\\nSo everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine\n\n## MAC OS X SUPPORT\n\nDarwin (Mac OS X) support of the same functionality is implemented through utun (userspace tunnel).\n\nInterface name in the configuration must comply to the scheme \"utunN\", where N is some number. \\\nMost running OS'es create some amount of utun interfaces in advance for own needs. Please either check the interfaces you already have occupied by issuing following command:\n```\nifconfig\n```\nProduced list will have all system interfaces listed, from which you will see how many \"utun\" ones already exists.\nIt's not required to select next available number, e.g. if you have utun1-utun7 interfaces, it's not required to have \"utun8\" in the config. You can choose any available name, even utun20, to get surely available interface number.\n\nTo attach routing to the interface, route command like following can be executed:\n```\nsudo route add -net 1.1.1.0/24 -iface utun10\n```\n```\nsudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10\nsudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10\n```\nImportant to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure.\n\n## ANDROID SUPPORT\n\nAndroid uses the VpnService API which provides a TUN file descriptor to the application.\n\nObtain the fd from VpnService:\n```kotlin\nval tunFd = vpnInterface.fd\n```\n\nSet the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Kotlin/Java or by exposing a Go function via gomobile bindings.\n\nBuild using gomobile for Android library integration:\n```\ngomobile bind -target=android\n```\n\n## iOS SUPPORT\n\niOS uses the same utun packet format as macOS, but the file descriptor is provided by NetworkExtension.\n\nObtain the fd from NetworkExtension:\n```swift\nvar buf = [CChar](repeating: 0, count: Int(IFNAMSIZ))\nlet utunPrefix = \"utun\".utf8CString.dropLast()\nlet tunFd = ((0 ... 1024).first { (_ fd: Int32) -> Bool in var len = socklen_t(buf.count)\n    return getsockopt(fd, 2, 2, &buf, &len) == 0 && buf.starts(with: utunPrefix)\n}!\n```\n\nSet the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Swift/Objective-C or by exposing a Go function via gomobile bindings.\n\nBuild using gomobile for iOS framework integration:\n```\ngomobile bind -target=ios\n```"
  },
  {
    "path": "proxy/tun/config.go",
    "content": "package tun\n"
  },
  {
    "path": "proxy/tun/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/tun/config.proto\n\npackage tun\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tMTU           uint32                 `protobuf:\"varint,2,opt,name=MTU,proto3\" json:\"MTU,omitempty\"`\n\tUserLevel     uint32                 `protobuf:\"varint,3,opt,name=user_level,json=userLevel,proto3\" json:\"user_level,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_tun_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_tun_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_tun_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMTU() uint32 {\n\tif x != nil {\n\t\treturn x.MTU\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetUserLevel() uint32 {\n\tif x != nil {\n\t\treturn x.UserLevel\n\t}\n\treturn 0\n}\n\nvar File_proxy_tun_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_tun_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x16proxy/tun/config.proto\\x12\\x0exray.proxy.tun\\\"M\\n\" +\n\t\"\\x06Config\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x10\\n\" +\n\t\"\\x03MTU\\x18\\x02 \\x01(\\rR\\x03MTU\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_level\\x18\\x03 \\x01(\\rR\\tuserLevelBL\\n\" +\n\t\"\\x12com.xray.proxy.tunP\\x01Z#github.com/xtls/xray-core/proxy/tun\\xaa\\x02\\x0eXray.Proxy.Tunb\\x06proto3\"\n\nvar (\n\tfile_proxy_tun_config_proto_rawDescOnce sync.Once\n\tfile_proxy_tun_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_tun_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_tun_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_tun_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_tun_config_proto_rawDesc), len(file_proxy_tun_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_tun_config_proto_rawDescData\n}\n\nvar file_proxy_tun_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_tun_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.proxy.tun.Config\n}\nvar file_proxy_tun_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_tun_config_proto_init() }\nfunc file_proxy_tun_config_proto_init() {\n\tif File_proxy_tun_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_tun_config_proto_rawDesc), len(file_proxy_tun_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_tun_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_tun_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_tun_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_tun_config_proto = out.File\n\tfile_proxy_tun_config_proto_goTypes = nil\n\tfile_proxy_tun_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/tun/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.tun;\noption csharp_namespace = \"Xray.Proxy.Tun\";\noption go_package = \"github.com/xtls/xray-core/proxy/tun\";\noption java_package = \"com.xray.proxy.tun\";\noption java_multiple_files = true;\n\nmessage Config {\n  string name = 1;\n  uint32 MTU = 2;\n  uint32 user_level = 3;\n}\n"
  },
  {
    "path": "proxy/tun/handler.go",
    "content": "package tun\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\tc \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Handler is managing object that tie together tun interface, ip stack and dispatch connections to the routing\ntype Handler struct {\n\tctx             context.Context\n\tconfig          *Config\n\tstack           Stack\n\tpolicyManager   policy.Manager\n\tdispatcher      routing.Dispatcher\n\ttag             string\n\tsniffingRequest session.SniffingRequest\n}\n\n// ConnectionHandler interface with the only method that stack is going to push new connections to\ntype ConnectionHandler interface {\n\tHandleConnection(conn net.Conn, destination net.Destination)\n}\n\n// Handler implements ConnectionHandler\nvar _ ConnectionHandler = (*Handler)(nil)\n\nfunc (t *Handler) policy() policy.Session {\n\tp := t.policyManager.ForLevel(t.config.UserLevel)\n\treturn p\n}\n\n// Init the Handler instance with necessary parameters\nfunc (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {\n\tvar err error\n\n\t// Retrieve tag and sniffing config from context (set by AlwaysOnInboundHandler)\n\tif inbound := session.InboundFromContext(ctx); inbound != nil {\n\t\tt.tag = inbound.Tag\n\t}\n\tif content := session.ContentFromContext(ctx); content != nil {\n\t\tt.sniffingRequest = content.SniffingRequest\n\t}\n\n\tt.ctx = core.ToBackgroundDetachedContext(ctx)\n\tt.policyManager = pm\n\tt.dispatcher = dispatcher\n\n\ttunName := t.config.Name\n\ttunOptions := TunOptions{\n\t\tName: tunName,\n\t\tMTU:  t.config.MTU,\n\t}\n\ttunInterface, err := NewTun(tunOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terrors.LogInfo(t.ctx, tunName, \" created\")\n\n\ttunStackOptions := StackOptions{\n\t\tTun:         tunInterface,\n\t\tIdleTimeout: pm.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,\n\t}\n\ttunStack, err := NewStack(t.ctx, tunStackOptions, t)\n\tif err != nil {\n\t\t_ = tunInterface.Close()\n\t\treturn err\n\t}\n\n\terr = tunStack.Start()\n\tif err != nil {\n\t\t_ = tunStack.Close()\n\t\t_ = tunInterface.Close()\n\t\treturn err\n\t}\n\n\terr = tunInterface.Start()\n\tif err != nil {\n\t\t_ = tunStack.Close()\n\t\t_ = tunInterface.Close()\n\t\treturn err\n\t}\n\n\tt.stack = tunStack\n\n\terrors.LogInfo(t.ctx, tunName, \" up\")\n\treturn nil\n}\n\n// HandleConnection pass the connection coming from the ip stack to the routing dispatcher\nfunc (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {\n\t// when handling is done with any outcome, always signal back to the incoming connection\n\t// to close, send completion packets back to the network, and cleanup\n\tdefer conn.Close()\n\n\tctx, cancel := context.WithCancel(t.ctx)\n\tdefer cancel()\n\tctx = c.ContextWithID(ctx, session.NewID())\n\n\tsource := net.DestinationFromAddr(conn.RemoteAddr())\n\tinbound := session.Inbound{\n\t\tName:          \"tun\",\n\t\tTag:           t.tag,\n\t\tCanSpliceCopy: 3,\n\t\tSource:        source,\n\t\tUser: &protocol.MemoryUser{\n\t\t\tLevel: t.config.UserLevel,\n\t\t},\n\t}\n\n\tctx = session.ContextWithInbound(ctx, &inbound)\n\tctx = session.ContextWithContent(ctx, &session.Content{\n\t\tSniffingRequest: t.sniffingRequest,\n\t})\n\tctx = session.SubContextFromMuxInbound(ctx)\n\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   inbound.Source,\n\t\tTo:     destination,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t})\n\terrors.LogInfo(ctx, \"processing from \", source, \" to \", destination)\n\n\tlink := &transport.Link{\n\t\tReader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},\n\t\tWriter: buf.NewWriter(conn),\n\t}\n\tif err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {\n\t\terrors.LogError(ctx, errors.New(\"connection closed\").Base(err))\n\t}\n}\n\n// Network implements proxy.Inbound\n// and exists only to comply to proxy interface, declaring it doesn't listen on any network,\n// making the process not open any port for this inbound (input will be network interface)\nfunc (t *Handler) Network() []net.Network {\n\treturn []net.Network{}\n}\n\n// Process implements proxy.Inbound\n// and exists only to comply to proxy interface, which should never get any inputs due to no listening ports\nfunc (t *Handler) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tt := &Handler{config: config.(*Config)}\n\t\terr := core.RequireFeatures(ctx, func(pm policy.Manager, dispatcher routing.Dispatcher) error {\n\t\t\treturn t.Init(ctx, pm, dispatcher)\n\t\t})\n\t\treturn t, err\n\t}))\n}\n"
  },
  {
    "path": "proxy/tun/stack.go",
    "content": "package tun\n\nimport (\n\t\"time\"\n)\n\n// Stack interface implement ip protocol stack, bridging raw network packets and data streams\ntype Stack interface {\n\tStart() error\n\tClose() error\n}\n\n// StackOptions for the stack implementation\ntype StackOptions struct {\n\tTun         Tun\n\tIdleTimeout time.Duration\n}\n"
  },
  {
    "path": "proxy/tun/stack_gvisor.go",
    "content": "package tun\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"gvisor.dev/gvisor/pkg/buffer\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/checksum\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/header\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv4\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv6\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/tcp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/udp\"\n\t\"gvisor.dev/gvisor/pkg/waiter\"\n)\n\nconst (\n\tdefaultNIC tcpip.NICID = 1\n\n\ttcpRXBufMinSize = tcp.MinBufferSize\n\ttcpRXBufDefSize = tcp.DefaultSendBufferSize\n\ttcpRXBufMaxSize = 8 << 20 // 8MiB\n\n\ttcpTXBufMinSize = tcp.MinBufferSize\n\ttcpTXBufDefSize = tcp.DefaultReceiveBufferSize\n\ttcpTXBufMaxSize = 6 << 20 // 6MiB\n)\n\n// stackGVisor is ip stack implemented by gVisor package\ntype stackGVisor struct {\n\tctx         context.Context\n\ttun         GVisorTun\n\tidleTimeout time.Duration\n\thandler     *Handler\n\tstack       *stack.Stack\n\tendpoint    stack.LinkEndpoint\n}\n\n// GVisorTun implements a bridge to connect gVisor ip stack to tun interface\ntype GVisorTun interface {\n\tnewEndpoint() (stack.LinkEndpoint, error)\n}\n\n// NewStack builds new ip stack (using gVisor)\nfunc NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {\n\tgStack := &stackGVisor{\n\t\tctx:         ctx,\n\t\ttun:         options.Tun.(GVisorTun),\n\t\tidleTimeout: options.IdleTimeout,\n\t\thandler:     handler,\n\t}\n\n\treturn gStack, nil\n}\n\n// Start is called by Handler to bring stack to life\nfunc (t *stackGVisor) Start() error {\n\tlinkEndpoint, err := t.tun.newEndpoint()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tipStack, err := createStack(linkEndpoint)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttcpForwarder := tcp.NewForwarder(ipStack, 0, 65535, func(r *tcp.ForwarderRequest) {\n\t\tgo func(r *tcp.ForwarderRequest) {\n\t\t\tvar wq waiter.Queue\n\t\t\tvar id = r.ID()\n\n\t\t\t// Perform a TCP three-way handshake.\n\t\t\tep, err := r.CreateEndpoint(&wq)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogError(t.ctx, err.String())\n\t\t\t\tr.Complete(true)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\toptions := ep.SocketOptions()\n\t\t\toptions.SetKeepAlive(false)\n\t\t\toptions.SetReuseAddress(true)\n\t\t\toptions.SetReusePort(true)\n\n\t\t\tt.handler.HandleConnection(\n\t\t\t\tgonet.NewTCPConn(&wq, ep),\n\t\t\t\t// local address on the gVisor side is connection destination\n\t\t\t\tnet.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),\n\t\t\t)\n\n\t\t\t// close the socket\n\t\t\tep.Close()\n\t\t\t// send connection complete upstream\n\t\t\tr.Complete(false)\n\t\t}(r)\n\t})\n\tipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)\n\n\t// Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support\n\tudpForwarder := newUdpConnectionHandler(t.handler.HandleConnection, t.writeRawUDPPacket)\n\tipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {\n\t\tdata := pkt.Data().AsRange().ToSlice()\n\t\tif len(data) == 0 {\n\t\t\treturn false\n\t\t}\n\t\t// source/destination of the packet we process as incoming, on gVisor side are Remote/Local\n\t\t// in other terms, src is the side behind tun, dst is the side behind gVisor\n\t\t// this function handle packets passing from the tun to the gVisor, therefore the src/dst assignement\n\t\tsrc := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))\n\t\tdst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))\n\n\t\treturn udpForwarder.HandlePacket(src, dst, data)\n\t})\n\n\tt.stack = ipStack\n\tt.endpoint = linkEndpoint\n\n\treturn nil\n}\n\nfunc (t *stackGVisor) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {\n\tudpLen := header.UDPMinimumSize + len(payload)\n\tsrcIP := tcpip.AddrFromSlice(src.Address.IP())\n\tdstIP := tcpip.AddrFromSlice(dst.Address.IP())\n\n\t// build packet with appropriate IP header size\n\tisIPv4 := dst.Address.Family().IsIPv4()\n\tipHdrSize := header.IPv6MinimumSize\n\tipProtocol := header.IPv6ProtocolNumber\n\tif isIPv4 {\n\t\tipHdrSize = header.IPv4MinimumSize\n\t\tipProtocol = header.IPv4ProtocolNumber\n\t}\n\n\tpkt := stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\tReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,\n\t\tPayload:            buffer.MakeWithData(payload),\n\t})\n\tdefer pkt.DecRef()\n\n\t// Build UDP header\n\tudpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))\n\tudpHdr.Encode(&header.UDPFields{\n\t\tSrcPort: uint16(src.Port),\n\t\tDstPort: uint16(dst.Port),\n\t\tLength:  uint16(udpLen),\n\t})\n\n\t// Calculate and set UDP checksum\n\txsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))\n\tudpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))\n\n\t// Build IP header\n\tif isIPv4 {\n\t\tipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))\n\t\tipHdr.Encode(&header.IPv4Fields{\n\t\t\tTotalLength: uint16(header.IPv4MinimumSize + udpLen),\n\t\t\tTTL:         64,\n\t\t\tProtocol:    uint8(header.UDPProtocolNumber),\n\t\t\tSrcAddr:     srcIP,\n\t\t\tDstAddr:     dstIP,\n\t\t})\n\t\tipHdr.SetChecksum(^ipHdr.CalculateChecksum())\n\t} else {\n\t\tipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))\n\t\tipHdr.Encode(&header.IPv6Fields{\n\t\t\tPayloadLength:     uint16(udpLen),\n\t\t\tTransportProtocol: header.UDPProtocolNumber,\n\t\t\tHopLimit:          64,\n\t\t\tSrcAddr:           srcIP,\n\t\t\tDstAddr:           dstIP,\n\t\t})\n\t}\n\n\t// dispatch the packet\n\terr := t.stack.WriteRawPacket(defaultNIC, ipProtocol, buffer.MakeWithView(pkt.ToView()))\n\tif err != nil {\n\t\treturn errors.New(\"failed to write raw udp packet back to stack\", err)\n\t}\n\n\treturn nil\n}\n\n// Close is called by Handler to shut down the stack\nfunc (t *stackGVisor) Close() error {\n\tif t.stack == nil {\n\t\treturn nil\n\t}\n\tt.endpoint.Attach(nil)\n\tt.stack.Close()\n\tfor _, endpoint := range t.stack.CleanupEndpoints() {\n\t\tendpoint.Abort()\n\t}\n\n\treturn nil\n}\n\n// createStack configure gVisor ip stack\nfunc createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {\n\topts := stack.Options{\n\t\tNetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},\n\t\tTransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},\n\t\tHandleLocal:        false,\n\t}\n\tgStack := stack.New(opts)\n\n\terr := gStack.CreateNIC(defaultNIC, ep)\n\tif err != nil {\n\t\treturn nil, errors.New(err.String())\n\t}\n\n\tgStack.SetRouteTable([]tcpip.Route{\n\t\t{Destination: header.IPv4EmptySubnet, NIC: defaultNIC},\n\t\t{Destination: header.IPv6EmptySubnet, NIC: defaultNIC},\n\t})\n\n\terr = gStack.SetSpoofing(defaultNIC, true)\n\tif err != nil {\n\t\treturn nil, errors.New(err.String())\n\t}\n\terr = gStack.SetPromiscuousMode(defaultNIC, true)\n\tif err != nil {\n\t\treturn nil, errors.New(err.String())\n\t}\n\n\tcOpt := tcpip.CongestionControlOption(\"cubic\")\n\tgStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt)\n\tsOpt := tcpip.TCPSACKEnabled(true)\n\tgStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)\n\tmOpt := tcpip.TCPModerateReceiveBufferOption(true)\n\tgStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)\n\n\t// Disable RACK/TLP loss recovery to fix connection stalls under high load\n\trOpt := tcpip.TCPRecovery(0)\n\tgStack.SetTransportProtocolOption(tcp.ProtocolNumber, &rOpt)\n\n\ttcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{\n\t\tMin:     tcpRXBufMinSize,\n\t\tDefault: tcpRXBufDefSize,\n\t\tMax:     tcpRXBufMaxSize,\n\t}\n\terr = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRXBufOpt)\n\tif err != nil {\n\t\treturn nil, errors.New(err.String())\n\t}\n\n\ttcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{\n\t\tMin:     tcpTXBufMinSize,\n\t\tDefault: tcpTXBufDefSize,\n\t\tMax:     tcpTXBufMaxSize,\n\t}\n\terr = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTXBufOpt)\n\tif err != nil {\n\t\treturn nil, errors.New(err.String())\n\t}\n\n\treturn gStack, nil\n}\n"
  },
  {
    "path": "proxy/tun/stack_gvisor_endpoint.go",
    "content": "package tun\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/header\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\nvar ErrQueueEmpty = errors.New(\"queue is empty\")\n\ntype GVisorDevice interface {\n\tWritePacket(packet *stack.PacketBuffer) tcpip.Error\n\tReadPacket() (byte, *stack.PacketBuffer, error)\n\tWait()\n}\n\n// LinkEndpoint implements GVisor stack.LinkEndpoint\nvar _ stack.LinkEndpoint = (*LinkEndpoint)(nil)\n\ntype LinkEndpoint struct {\n\tdeviceMTU        uint32\n\tdevice           GVisorDevice\n\tdispatcherCancel context.CancelFunc\n}\n\nfunc (e *LinkEndpoint) MTU() uint32 {\n\treturn e.deviceMTU\n}\n\nfunc (e *LinkEndpoint) SetMTU(_ uint32) {\n\t// not Implemented, as it is not expected GVisor will be asking tun device to be modified\n}\n\nfunc (e *LinkEndpoint) MaxHeaderLength() uint16 {\n\treturn 0\n}\n\nfunc (e *LinkEndpoint) LinkAddress() tcpip.LinkAddress {\n\treturn \"\"\n}\n\nfunc (e *LinkEndpoint) SetLinkAddress(_ tcpip.LinkAddress) {\n\t// not Implemented, as it is not expected GVisor will be asking tun device to be modified\n}\n\nfunc (e *LinkEndpoint) Capabilities() stack.LinkEndpointCapabilities {\n\treturn stack.CapabilityRXChecksumOffload\n}\n\nfunc (e *LinkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {\n\tif e.dispatcherCancel != nil {\n\t\te.dispatcherCancel()\n\t\te.dispatcherCancel = nil\n\t}\n\n\tif dispatcher != nil {\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tgo e.dispatchLoop(ctx, dispatcher)\n\t\te.dispatcherCancel = cancel\n\t}\n}\n\nfunc (e *LinkEndpoint) IsAttached() bool {\n\treturn e.dispatcherCancel != nil\n}\n\nfunc (e *LinkEndpoint) Wait() {\n\n}\n\nfunc (e *LinkEndpoint) ARPHardwareType() header.ARPHardwareType {\n\treturn header.ARPHardwareNone\n}\n\nfunc (e *LinkEndpoint) AddHeader(buffer *stack.PacketBuffer) {\n\t// tun interface doesn't have link layer header, it will be added by the OS\n}\n\nfunc (e *LinkEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {\n\treturn true\n}\n\nfunc (e *LinkEndpoint) Close() {\n\tif e.dispatcherCancel != nil {\n\t\te.dispatcherCancel()\n\t\te.dispatcherCancel = nil\n\t}\n}\n\nfunc (e *LinkEndpoint) SetOnCloseAction(_ func()) {\n\n}\n\nfunc (e *LinkEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {\n\tvar n int\n\tvar err tcpip.Error\n\n\tfor _, packetBuffer := range packetBufferList.AsSlice() {\n\t\terr = e.device.WritePacket(packetBuffer)\n\t\tif err != nil {\n\t\t\treturn n, &tcpip.ErrAborted{}\n\t\t}\n\t\tn++\n\t}\n\n\treturn n, nil\n}\n\nfunc (e *LinkEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {\n\tvar networkProtocolNumber tcpip.NetworkProtocolNumber\n\tvar version byte\n\tvar packet *stack.PacketBuffer\n\tvar err error\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tversion, packet, err = e.device.ReadPacket()\n\t\t\t// on \"queue empty\", ask device to yield slightly and continue\n\t\t\tif errors.Is(err, ErrQueueEmpty) {\n\t\t\t\te.device.Wait()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// stop dispatcher loop on any other interface failure\n\t\t\tif err != nil {\n\t\t\t\te.Attach(nil)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// extract network protocol number from the packet first byte\n\t\t\t// (which is returned separately, since it is so incredibly hard to extract one byte from\n\t\t\t// stack.PacketBuffer without additional memory allocation and full copying it back and forth)\n\t\t\tswitch version {\n\t\t\tcase 4:\n\t\t\t\tnetworkProtocolNumber = header.IPv4ProtocolNumber\n\t\t\tcase 6:\n\t\t\t\tnetworkProtocolNumber = header.IPv6ProtocolNumber\n\t\t\tdefault:\n\t\t\t\t// discard unknown network protocol packet\n\t\t\t\tpacket.DecRef()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// dispatch the buffer to the stack\n\t\t\tdispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)\n\t\t\t// signal the buffer that it can be released\n\t\t\tpacket.DecRef()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "proxy/tun/tun.go",
    "content": "package tun\n\n// Tun interface implements tun interface interaction\ntype Tun interface {\n\tStart() error\n\tClose() error\n}\n\n// TunOptions for tun interface implementation\ntype TunOptions struct {\n\tName string\n\tMTU  uint32\n}\n"
  },
  {
    "path": "proxy/tun/tun_android.go",
    "content": "//go:build android\n\npackage tun\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"golang.org/x/sys/unix\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/fdbased\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\ntype AndroidTun struct {\n\ttunFd   int\n\toptions TunOptions\n}\n\n// DefaultTun implements Tun\nvar _ Tun = (*AndroidTun)(nil)\n\n// DefaultTun implements GVisorTun\nvar _ GVisorTun = (*AndroidTun)(nil)\n\n// NewTun builds new tun interface handler\nfunc NewTun(options TunOptions) (Tun, error) {\n\tfd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return \"0\" }))\n\terrors.LogInfo(context.Background(), \"read Android Tun Fd \", fd, err)\n\n\terr = unix.SetNonblock(fd, true)\n\tif err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, err\n\t}\n\n\treturn &AndroidTun{\n\t\ttunFd:   fd,\n\t\toptions: options,\n\t}, nil\n}\n\nfunc (t *AndroidTun) Start() error {\n\treturn nil\n}\n\nfunc (t *AndroidTun) Close() error {\n\treturn nil\n}\n\nfunc (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {\n\treturn fdbased.New(&fdbased.Options{\n\t\tFDs:               []int{t.tunFd},\n\t\tMTU:               t.options.MTU,\n\t\tRXChecksumOffload: true,\n\t})\n}\n"
  },
  {
    "path": "proxy/tun/tun_darwin.go",
    "content": "//go:build darwin\n\npackage tun\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"golang.org/x/sys/unix\"\n\t\"gvisor.dev/gvisor/pkg/buffer\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\nconst (\n\tutunControlName = \"com.apple.net.utun_control\"\n\tsysprotoControl = 2\n\tgateway         = \"169.254.10.1/30\"\n\tutunHeaderSize  = 4\n)\n\nconst (\n\tSIOCAIFADDR6          = 2155899162 // netinet6/in6_var.h\n\tIN6_IFF_NODAD         = 0x0020     // netinet6/in6_var.h\n\tIN6_IFF_SECURED       = 0x0400     // netinet6/in6_var.h\n\tND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h\n)\n\n//go:linkname procyield runtime.procyield\nfunc procyield(cycles uint32)\n\ntype DarwinTun struct {\n\ttunFile *os.File\n\toptions TunOptions\n\townsFd  bool // true for macOS (we created the fd), false for iOS (fd from system)\n}\n\nvar _ Tun = (*DarwinTun)(nil)\nvar _ GVisorTun = (*DarwinTun)(nil)\nvar _ GVisorDevice = (*DarwinTun)(nil)\n\nfunc NewTun(options TunOptions) (Tun, error) {\n\t// Check if fd is provided via environment (iOS mode)\n\tfdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return \"\" })\n\tif fdStr != \"\" {\n\t\t// iOS: use provided fd from NetworkExtension\n\t\tfd, err := strconv.Atoi(fdStr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn &DarwinTun{\n\t\t\ttunFile: os.NewFile(uintptr(fd), \"utun\"),\n\t\t\toptions: options,\n\t\t\townsFd:  false,\n\t\t}, nil\n\t}\n\n\t// macOS: create our own utun interface\n\ttunFile, err := open(options.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = setup(options.Name, options.MTU)\n\tif err != nil {\n\t\t_ = tunFile.Close()\n\t\treturn nil, err\n\t}\n\n\treturn &DarwinTun{\n\t\ttunFile: tunFile,\n\t\toptions: options,\n\t\townsFd:  true,\n\t}, nil\n}\n\nfunc (t *DarwinTun) Start() error {\n\treturn nil\n}\n\nfunc (t *DarwinTun) Close() error {\n\tif t.ownsFd {\n\t\treturn t.tunFile.Close()\n\t}\n\t// iOS: don't close the fd, it's owned by NetworkExtension\n\treturn nil\n}\n\n// WritePacket implements GVisorDevice method to write one packet to the tun device\nfunc (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {\n\t// request memory to write from reusable buffer pool\n\tb := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)\n\tdefer b.Release()\n\n\t// prepare Darwin specific packet header\n\t_, _ = b.Write([]byte{0x0, 0x0, 0x0, 0x0})\n\t// copy the bytes of slices that compose the packet into the allocated buffer\n\tfor _, packetElement := range packet.AsSlices() {\n\t\t_, _ = b.Write(packetElement)\n\t}\n\t// fill Darwin specific header from the first raw packet byte, that we can access now\n\tvar family byte\n\tswitch b.Byte(4) >> 4 {\n\tcase 4:\n\t\tfamily = unix.AF_INET\n\tcase 6:\n\t\tfamily = unix.AF_INET6\n\tdefault:\n\t\treturn &tcpip.ErrAborted{}\n\t}\n\tb.SetByte(3, family)\n\n\tif _, err := t.tunFile.Write(b.Bytes()); err != nil {\n\t\tif errors.Is(err, unix.EAGAIN) {\n\t\t\treturn &tcpip.ErrWouldBlock{}\n\t\t}\n\t\treturn &tcpip.ErrAborted{}\n\t}\n\treturn nil\n}\n\n// ReadPacket implements GVisorDevice method to read one packet from the tun device\n// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,\n// which will make the stack call Wait which should implement desired push-back\nfunc (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {\n\t// request memory to write from reusable buffer pool\n\tb := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)\n\n\t// read the bytes to the interface file\n\tn, err := b.ReadFrom(t.tunFile)\n\tif errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {\n\t\tb.Release()\n\t\treturn 0, nil, ErrQueueEmpty\n\t}\n\tif err != nil {\n\t\tb.Release()\n\t\treturn 0, nil, err\n\t}\n\n\t// discard empty or sub-empty packets\n\tif n <= utunHeaderSize {\n\t\tb.Release()\n\t\treturn 0, nil, ErrQueueEmpty\n\t}\n\n\t// network protocol version from first byte of the raw packet, the one that follows Darwin specific header\n\tversion := b.Byte(utunHeaderSize) >> 4\n\tpacketBuffer := buffer.MakeWithData(b.BytesFrom(utunHeaderSize))\n\treturn version, stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\tPayload:           packetBuffer,\n\t\tIsForwardedPacket: true,\n\t\tOnRelease: func() {\n\t\t\tb.Release()\n\t\t},\n\t}), nil\n}\n\n// Wait some cpu cycles\nfunc (t *DarwinTun) Wait() {\n\tprocyield(1)\n}\n\nfunc (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {\n\treturn &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil\n}\n\n// open the interface, by creating new utunN if in the system and returning its file descriptor\nfunc open(name string) (*os.File, error) {\n\tifIndex := -1\n\t_, err := fmt.Sscanf(name, \"utun%d\", &ifIndex)\n\tif err != nil || ifIndex < 0 {\n\t\treturn nil, errors.New(\"interface name must be utunN, where N is a number, e.g. utun9, utun11 and so on\")\n\t}\n\n\tfd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysprotoControl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctlInfo := &unix.CtlInfo{}\n\tcopy(ctlInfo.Name[:], utunControlName)\n\tif err := unix.IoctlCtlInfo(fd, ctlInfo); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, err\n\t}\n\n\tsockaddr := &unix.SockaddrCtl{\n\t\tID:   ctlInfo.Id,\n\t\tUnit: uint32(ifIndex) + 1,\n\t}\n\tif err := unix.Connect(fd, sockaddr); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, err\n\t}\n\n\tif err := unix.SetNonblock(fd, true); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, err\n\t}\n\n\treturn os.NewFile(uintptr(fd), name), nil\n}\n\n// setup the interface by name\nfunc setup(name string, MTU uint32) error {\n\tif err := setMTU(name, MTU); err != nil {\n\t\treturn err\n\t}\n\n\t/*\n\t * Darwin routing require tunnel type interface to have local and remote address, to be routable.\n\t * To simplify inevitable task, assign the interface static ip address, which in current implementation\n\t * is just some random ip from link-local pool, allowing to not bother about existing routing intersection.\n\t */\n\tsyntheticIP, _ := netip.ParsePrefix(gateway)\n\tif err := setIPAddress(name, syntheticIP); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// setMTU sets MTU on the interface by given name\nfunc setMTU(name string, mtu uint32) error {\n\tsocket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(socket)\n\n\tifr := unix.IfreqMTU{MTU: int32(mtu)}\n\tcopy(ifr.Name[:], name)\n\treturn unix.IoctlSetIfreqMTU(socket, &ifr)\n}\n\ntype ifAliasReq4 struct {\n\tName    [unix.IFNAMSIZ]byte\n\tAddr    unix.RawSockaddrInet4\n\tDstaddr unix.RawSockaddrInet4\n\tMask    unix.RawSockaddrInet4\n}\n\ntype ifAliasReq6 struct {\n\tName     [unix.IFNAMSIZ]byte\n\tAddr     unix.RawSockaddrInet6\n\tDstaddr  unix.RawSockaddrInet6\n\tMask     unix.RawSockaddrInet6\n\tFlags    uint32\n\tLifetime addrLifetime6\n}\n\ntype addrLifetime6 struct {\n\tExpire    float64\n\tPreferred float64\n\tVltime    uint32\n\tPltime    uint32\n}\n\n// setIPAddress sets ipv4 and ipv6 addresses to the interface, required for the routing to work\nfunc setIPAddress(name string, gateway netip.Prefix) error {\n\tsocket4, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(socket4)\n\n\t// assume local ip address is next one from the remote address\n\tlocal4 := gateway.Addr().As4()\n\tlocal4[3]++\n\n\t// fill the configuration for ipv4\n\tifReq4 := ifAliasReq4{\n\t\tAddr: unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   local4,\n\t\t},\n\t\tDstaddr: unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   gateway.Addr().As4(),\n\t\t},\n\t\tMask: unix.RawSockaddrInet4{\n\t\t\tLen:    unix.SizeofSockaddrInet4,\n\t\t\tFamily: unix.AF_INET,\n\t\t\tAddr:   netip.MustParseAddr(net.IP(net.CIDRMask(gateway.Bits(), 32)).String()).As4(),\n\t\t},\n\t}\n\tcopy(ifReq4.Name[:], name)\n\tif err = ioctlPtr(socket4, unix.SIOCAIFADDR, unsafe.Pointer(&ifReq4)); err != nil {\n\t\treturn os.NewSyscallError(\"SIOCAIFADDR\", err)\n\t}\n\n\tsocket6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(socket6)\n\n\t// link-local ipv6 address with suffix from ipv6\n\tlocal6 := netip.AddrFrom16([16]byte{0: 0xfe, 1: 0x80, 12: local4[0], 13: local4[1], 14: local4[2], 15: local4[3]})\n\n\t// fill the configuration for ipv6\n\t// only link-local address without the destination is enough for it\n\tifReq6 := ifAliasReq6{\n\t\tAddr: unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   local6.As16(),\n\t\t},\n\t\tMask: unix.RawSockaddrInet6{\n\t\t\tLen:    unix.SizeofSockaddrInet6,\n\t\t\tFamily: unix.AF_INET6,\n\t\t\tAddr:   netip.MustParseAddr(net.IP(net.CIDRMask(64, 128)).String()).As16(),\n\t\t},\n\t\tFlags: IN6_IFF_NODAD,\n\t\tLifetime: addrLifetime6{\n\t\t\tVltime: ND6_INFINITE_LIFETIME,\n\t\t\tPltime: ND6_INFINITE_LIFETIME,\n\t\t},\n\t}\n\t// assign link-local ipv6 address to the interface.\n\t// this will additionally trigger OS level autoconfiguration, which will result two different link-local\n\t// addresses - the requested one, and autoconfigured one.\n\t// this really has no known side effects, just look excessive. and actually considered pretty normal way to\n\t// enable the ipv6 on the interface by macOS concepts.\n\tcopy(ifReq6.Name[:], name)\n\tif err = ioctlPtr(socket6, SIOCAIFADDR6, unsafe.Pointer(&ifReq6)); err != nil {\n\t\treturn os.NewSyscallError(\"SIOCAIFADDR6\", err)\n\t}\n\n\treturn nil\n}\n\nfunc ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {\n\t_, _, errno := unix.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))\n\tif errno != 0 {\n\t\treturn errno\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/tun/tun_default.go",
    "content": "//go:build !linux && !windows && !android && !darwin\n\npackage tun\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\ntype DefaultTun struct {\n}\n\n// DefaultTun implements Tun\nvar _ Tun = (*DefaultTun)(nil)\n\n// DefaultTun implements GVisorTun\nvar _ GVisorTun = (*DefaultTun)(nil)\n\n// NewTun builds new tun interface handler\nfunc NewTun(options TunOptions) (Tun, error) {\n\treturn nil, errors.New(\"Tun is not supported on your platform\")\n}\n\nfunc (t *DefaultTun) Start() error {\n\treturn errors.New(\"Tun is not supported on your platform\")\n}\n\nfunc (t *DefaultTun) Close() error {\n\treturn errors.New(\"Tun is not supported on your platform\")\n}\n\nfunc (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) {\n\treturn nil, errors.New(\"Tun is not supported on your platform\")\n}\n"
  },
  {
    "path": "proxy/tun/tun_linux.go",
    "content": "//go:build linux && !android\n\npackage tun\n\nimport (\n\t\"github.com/vishvananda/netlink\"\n\t\"golang.org/x/sys/unix\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/fdbased\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\n// LinuxTun is an object that handles tun network interface on linux\n// current version is heavily stripped to do nothing more,\n// then create a network interface, to be provided as file descriptor to gVisor ip stack\ntype LinuxTun struct {\n\ttunFd   int\n\ttunLink netlink.Link\n\toptions TunOptions\n}\n\n// LinuxTun implements Tun\nvar _ Tun = (*LinuxTun)(nil)\n\n// LinuxTun implements GVisorTun\nvar _ GVisorTun = (*LinuxTun)(nil)\n\n// NewTun builds new tun interface handler (linux specific)\nfunc NewTun(options TunOptions) (Tun, error) {\n\ttunFd, err := open(options.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttunLink, err := setup(options.Name, int(options.MTU))\n\tif err != nil {\n\t\t_ = unix.Close(tunFd)\n\t\treturn nil, err\n\t}\n\n\tlinuxTun := &LinuxTun{\n\t\ttunFd:   tunFd,\n\t\ttunLink: tunLink,\n\t\toptions: options,\n\t}\n\n\treturn linuxTun, nil\n}\n\n// open the file that implements tun interface in the OS\nfunc open(name string) (int, error) {\n\tfd, err := unix.Open(\"/dev/net/tun\", unix.O_RDWR, 0)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\tifr, err := unix.NewIfreq(name)\n\tif err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn 0, err\n\t}\n\n\tflags := unix.IFF_TUN | unix.IFF_NO_PI\n\tifr.SetUint16(uint16(flags))\n\terr = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)\n\tif err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn 0, err\n\t}\n\n\terr = unix.SetNonblock(fd, true)\n\tif err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn 0, err\n\t}\n\n\treturn fd, nil\n}\n\n// setup the interface through netlink socket\nfunc setup(name string, MTU int) (netlink.Link, error) {\n\ttunLink, err := netlink.LinkByName(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = netlink.LinkSetMTU(tunLink, MTU)\n\tif err != nil {\n\t\t_ = netlink.LinkSetDown(tunLink)\n\t\treturn nil, err\n\t}\n\n\treturn tunLink, nil\n}\n\n// Start is called by handler to bring tun interface to life\nfunc (t *LinuxTun) Start() error {\n\terr := netlink.LinkSetUp(t.tunLink)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Close is called to shut down the tun interface\nfunc (t *LinuxTun) Close() error {\n\t_ = netlink.LinkSetDown(t.tunLink)\n\t_ = unix.Close(t.tunFd)\n\n\treturn nil\n}\n\n// newEndpoint builds new gVisor stack.LinkEndpoint from the tun interface file descriptor\nfunc (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {\n\treturn fdbased.New(&fdbased.Options{\n\t\tFDs:               []int{t.tunFd},\n\t\tMTU:               t.options.MTU,\n\t\tRXChecksumOffload: true,\n\t})\n}\n"
  },
  {
    "path": "proxy/tun/tun_windows.go",
    "content": "//go:build windows\n\npackage tun\n\nimport (\n\t\"crypto/md5\"\n\t\"errors\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n\t\"golang.zx2c4.com/wintun\"\n\t\"gvisor.dev/gvisor/pkg/buffer\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\n//go:linkname procyield runtime.procyield\nfunc procyield(cycles uint32)\n\n// WindowsTun is an object that handles tun network interface on Windows\n// current version is heavily stripped to do nothing more,\n// then create a network interface, to be provided as endpoint to gVisor ip stack\ntype WindowsTun struct {\n\toptions  TunOptions\n\tadapter  *wintun.Adapter\n\tsession  wintun.Session\n\treadWait windows.Handle\n\tMTU      uint32\n}\n\n// WindowsTun implements Tun\nvar _ Tun = (*WindowsTun)(nil)\n\n// WindowsTun implements GVisorTun\nvar _ GVisorTun = (*WindowsTun)(nil)\n\n// WindowsTun implements GVisorDevice\nvar _ GVisorDevice = (*WindowsTun)(nil)\n\n// NewTun creates a Wintun interface with the given name. Should a Wintun\n// interface with the same name exist, it tried to be reused.\nfunc NewTun(options TunOptions) (Tun, error) {\n\t// instantiate wintun adapter\n\tadapter, err := open(options.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// start the interface with ring buffer capacity of 8 MiB\n\tsession, err := adapter.StartSession(0x800000)\n\tif err != nil {\n\t\t_ = adapter.Close()\n\t\treturn nil, err\n\t}\n\n\ttun := &WindowsTun{\n\t\toptions:  options,\n\t\tadapter:  adapter,\n\t\tsession:  session,\n\t\treadWait: session.ReadWaitEvent(),\n\t\t// there is currently no iphndl.dll support, which is the netlink library for windows\n\t\t// so there is nowhere to change MTU for the Wintun interface, and we take its default value\n\t\tMTU: wintun.PacketSizeMax,\n\t}\n\n\treturn tun, nil\n}\n\nfunc open(name string) (*wintun.Adapter, error) {\n\t// generate a deterministic GUID from the adapter name\n\tid := md5.Sum([]byte(name))\n\tguid := (*windows.GUID)(unsafe.Pointer(&id[0]))\n\t// try to open existing adapter by name\n\tadapter, err := wintun.OpenAdapter(name)\n\tif err == nil {\n\t\treturn adapter, nil\n\t}\n\t// try to create adapter anew\n\tadapter, err = wintun.CreateAdapter(name, \"Xray\", guid)\n\tif err == nil {\n\t\treturn adapter, nil\n\t}\n\treturn nil, err\n}\n\nfunc (t *WindowsTun) Start() error {\n\treturn nil\n}\n\nfunc (t *WindowsTun) Close() error {\n\tt.session.End()\n\t_ = t.adapter.Close()\n\n\treturn nil\n}\n\n// WritePacket implements GVisorDevice method to write one packet to the tun device\nfunc (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error {\n\t// request buffer from Wintun\n\tpacket, err := t.session.AllocateSendPacket(packetBuffer.Size())\n\tif err != nil {\n\t\treturn &tcpip.ErrAborted{}\n\t}\n\n\t// copy the bytes of slices that compose the packet into the allocated buffer\n\tvar index int\n\tfor _, packetElement := range packetBuffer.AsSlices() {\n\t\tindex += copy(packet[index:], packetElement)\n\t}\n\n\t// signal Wintun to send that buffer as the packet\n\tt.session.SendPacket(packet)\n\n\treturn nil\n}\n\n// ReadPacket implements GVisorDevice method to read one packet from the tun device\n// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,\n// which will make the stack call Wait which should implement desired push-back\nfunc (t *WindowsTun) ReadPacket() (byte, *stack.PacketBuffer, error) {\n\tpacket, err := t.session.ReceivePacket()\n\tif errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {\n\t\treturn 0, nil, ErrQueueEmpty\n\t}\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\n\tversion := packet[0] >> 4\n\tpacketBuffer := buffer.MakeWithView(buffer.NewViewWithData(packet))\n\treturn version, stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\tPayload:           packetBuffer,\n\t\tIsForwardedPacket: true,\n\t\tOnRelease: func() {\n\t\t\tt.session.ReleaseReceivePacket(packet)\n\t\t},\n\t}), nil\n}\n\nfunc (t *WindowsTun) Wait() {\n\tprocyield(1)\n\t_, _ = windows.WaitForSingleObject(t.readWait, windows.INFINITE)\n}\n\nfunc (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {\n\treturn &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil\n}\n"
  },
  {
    "path": "proxy/tun/udp_fullcone.go",
    "content": "package tun\n\nimport (\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// sub-handler specifically for udp connections under main handler\ntype udpConnectionHandler struct {\n\tsync.Mutex\n\n\tudpConns map[net.Destination]*udpConn\n\n\thandleConnection func(conn net.Conn, dest net.Destination)\n\twritePacket      func(data []byte, src net.Destination, dst net.Destination) error\n}\n\nfunc newUdpConnectionHandler(handleConnection func(conn net.Conn, dest net.Destination), writePacket func(data []byte, src net.Destination, dst net.Destination) error) *udpConnectionHandler {\n\thandler := &udpConnectionHandler{\n\t\tudpConns:         make(map[net.Destination]*udpConn),\n\t\thandleConnection: handleConnection,\n\t\twritePacket:      writePacket,\n\t}\n\n\treturn handler\n}\n\n// HandlePacket handles UDP packets coming from tun, to forward to the dispatcher\n// this custom handler support FullCone NAT of returning packets, binding connection only by the source addr:port\nfunc (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destination, data []byte) bool {\n\tu.Lock()\n\tconn, found := u.udpConns[src]\n\tif !found {\n\t\tegress := make(chan []byte, 16)\n\t\tconn = &udpConn{handler: u, egress: egress, src: src, dst: dst}\n\t\tu.udpConns[src] = conn\n\n\t\tgo u.handleConnection(conn, dst)\n\t}\n\tu.Unlock()\n\n\t// send packet data to the egress channel, if it has buffer, or discard\n\tselect {\n\tcase conn.egress <- data:\n\tdefault:\n\t}\n\n\treturn true\n}\n\nfunc (u *udpConnectionHandler) connectionFinished(src net.Destination) {\n\tu.Lock()\n\tconn, found := u.udpConns[src]\n\tif found {\n\t\tdelete(u.udpConns, src)\n\t\tclose(conn.egress)\n\t}\n\tu.Unlock()\n}\n\n// udp connection abstraction\ntype udpConn struct {\n\tnet.Conn\n\tbuf.Writer\n\n\thandler *udpConnectionHandler\n\n\tegress chan []byte\n\tsrc    net.Destination\n\tdst    net.Destination\n}\n\n// Read packets from the connection\nfunc (c *udpConn) Read(p []byte) (int, error) {\n\tdata, ok := <-c.egress\n\tif !ok {\n\t\treturn 0, io.EOF\n\t}\n\n\tn := copy(p, data)\n\treturn n, nil\n}\n\n// Write returning packets back\nfunc (c *udpConn) Write(p []byte) (int, error) {\n\t// sending packets back mean sending payload with source/destination reversed\n\terr := c.handler.writePacket(p, c.dst, c.src)\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\n\treturn len(p), nil\n}\n\nfunc (c *udpConn) Close() error {\n\tc.handler.connectionFinished(c.src)\n\n\treturn nil\n}\n\nfunc (c *udpConn) LocalAddr() net.Addr {\n\treturn &net.UDPAddr{IP: c.dst.Address.IP(), Port: int(c.dst.Port.Value())}\n}\n\nfunc (c *udpConn) RemoteAddr() net.Addr {\n\treturn &net.UDPAddr{IP: c.src.Address.IP(), Port: int(c.src.Port.Value())}\n}\n\n// Write returning packets back\nfunc (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tfor _, b := range mb {\n\t\tdst := c.dst\n\t\tif b.UDP != nil {\n\t\t\tdst = *b.UDP\n\t\t}\n\n\t\t// validate address family matches between buffer packet and the connection\n\t\tif dst.Address.Family() != c.dst.Address.Family() {\n\t\t\tcontinue\n\t\t}\n\n\t\t// sending packets back mean sending payload with source/destination reversed\n\t\terr := c.handler.writePacket(b.Bytes(), dst, c.src)\n\t\tif err != nil {\n\t\t\t// udp doesn't guarantee delivery, so in any failure we just continue to the next packet\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/vless/account.go",
    "content": "package vless\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\n// AsAccount implements protocol.Account.AsAccount().\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\tid, err := uuid.ParseString(a.Id)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse ID\").Base(err).AtError()\n\t}\n\treturn &MemoryAccount{\n\t\tID:         protocol.NewID(id),\n\t\tFlow:       a.Flow,       // needs parser here?\n\t\tEncryption: a.Encryption, // needs parser here?\n\t\tXorMode:    a.XorMode,\n\t\tSeconds:    a.Seconds,\n\t\tPadding:    a.Padding,\n\t\tReverse:    a.Reverse,\n\t\tTestpre:    a.Testpre,\n\t\tTestseed:   a.Testseed,\n\t}, nil\n}\n\n// MemoryAccount is an in-memory form of VLess account.\ntype MemoryAccount struct {\n\t// ID of the account.\n\tID *protocol.ID\n\t// Flow of the account. May be \"xtls-rprx-vision\".\n\tFlow string\n\n\tEncryption string\n\tXorMode    uint32\n\tSeconds    uint32\n\tPadding    string\n\n\tReverse *Reverse\n\n\tTestpre  uint32\n\tTestseed []uint32\n}\n\n// Equals implements protocol.Account.Equals().\nfunc (a *MemoryAccount) Equals(account protocol.Account) bool {\n\tvlessAccount, ok := account.(*MemoryAccount)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn a.ID.Equals(vlessAccount.ID)\n}\n\nfunc (a *MemoryAccount) ToProto() proto.Message {\n\treturn &Account{\n\t\tId:         a.ID.String(),\n\t\tFlow:       a.Flow,\n\t\tEncryption: a.Encryption,\n\t\tXorMode:    a.XorMode,\n\t\tSeconds:    a.Seconds,\n\t\tPadding:    a.Padding,\n\t\tReverse:    a.Reverse,\n\t\tTestpre:    a.Testpre,\n\t\tTestseed:   a.Testseed,\n\t}\n}\n"
  },
  {
    "path": "proxy/vless/account.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vless/account.proto\n\npackage vless\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Reverse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag           string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Reverse) Reset() {\n\t*x = Reverse{}\n\tmi := &file_proxy_vless_account_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Reverse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Reverse) ProtoMessage() {}\n\nfunc (x *Reverse) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vless_account_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Reverse.ProtoReflect.Descriptor instead.\nfunc (*Reverse) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vless_account_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Reverse) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\ntype Account struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// ID of the account, in the form of a UUID, e.g., \"66ad4540-b58c-4ad2-9926-ea63445a9b57\".\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\t// Flow settings. May be \"xtls-rprx-vision\".\n\tFlow          string   `protobuf:\"bytes,2,opt,name=flow,proto3\" json:\"flow,omitempty\"`\n\tEncryption    string   `protobuf:\"bytes,3,opt,name=encryption,proto3\" json:\"encryption,omitempty\"`\n\tXorMode       uint32   `protobuf:\"varint,4,opt,name=xorMode,proto3\" json:\"xorMode,omitempty\"`\n\tSeconds       uint32   `protobuf:\"varint,5,opt,name=seconds,proto3\" json:\"seconds,omitempty\"`\n\tPadding       string   `protobuf:\"bytes,6,opt,name=padding,proto3\" json:\"padding,omitempty\"`\n\tReverse       *Reverse `protobuf:\"bytes,7,opt,name=reverse,proto3\" json:\"reverse,omitempty\"`\n\tTestpre       uint32   `protobuf:\"varint,8,opt,name=testpre,proto3\" json:\"testpre,omitempty\"`\n\tTestseed      []uint32 `protobuf:\"varint,9,rep,packed,name=testseed,proto3\" json:\"testseed,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_vless_account_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vless_account_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vless_account_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Account) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetFlow() string {\n\tif x != nil {\n\t\treturn x.Flow\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetEncryption() string {\n\tif x != nil {\n\t\treturn x.Encryption\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetXorMode() uint32 {\n\tif x != nil {\n\t\treturn x.XorMode\n\t}\n\treturn 0\n}\n\nfunc (x *Account) GetSeconds() uint32 {\n\tif x != nil {\n\t\treturn x.Seconds\n\t}\n\treturn 0\n}\n\nfunc (x *Account) GetPadding() string {\n\tif x != nil {\n\t\treturn x.Padding\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetReverse() *Reverse {\n\tif x != nil {\n\t\treturn x.Reverse\n\t}\n\treturn nil\n}\n\nfunc (x *Account) GetTestpre() uint32 {\n\tif x != nil {\n\t\treturn x.Testpre\n\t}\n\treturn 0\n}\n\nfunc (x *Account) GetTestseed() []uint32 {\n\tif x != nil {\n\t\treturn x.Testseed\n\t}\n\treturn nil\n}\n\nvar File_proxy_vless_account_proto protoreflect.FileDescriptor\n\nconst file_proxy_vless_account_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x19proxy/vless/account.proto\\x12\\x10xray.proxy.vless\\\"\\x1b\\n\" +\n\t\"\\aReverse\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\\"\\x86\\x02\\n\" +\n\t\"\\aAccount\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\tR\\x02id\\x12\\x12\\n\" +\n\t\"\\x04flow\\x18\\x02 \\x01(\\tR\\x04flow\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"encryption\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"encryption\\x12\\x18\\n\" +\n\t\"\\axorMode\\x18\\x04 \\x01(\\rR\\axorMode\\x12\\x18\\n\" +\n\t\"\\aseconds\\x18\\x05 \\x01(\\rR\\aseconds\\x12\\x18\\n\" +\n\t\"\\apadding\\x18\\x06 \\x01(\\tR\\apadding\\x123\\n\" +\n\t\"\\areverse\\x18\\a \\x01(\\v2\\x19.xray.proxy.vless.ReverseR\\areverse\\x12\\x18\\n\" +\n\t\"\\atestpre\\x18\\b \\x01(\\rR\\atestpre\\x12\\x1a\\n\" +\n\t\"\\btestseed\\x18\\t \\x03(\\rR\\btestseedBR\\n\" +\n\t\"\\x14com.xray.proxy.vlessP\\x01Z%github.com/xtls/xray-core/proxy/vless\\xaa\\x02\\x10Xray.Proxy.Vlessb\\x06proto3\"\n\nvar (\n\tfile_proxy_vless_account_proto_rawDescOnce sync.Once\n\tfile_proxy_vless_account_proto_rawDescData []byte\n)\n\nfunc file_proxy_vless_account_proto_rawDescGZIP() []byte {\n\tfile_proxy_vless_account_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vless_account_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vless_account_proto_rawDesc), len(file_proxy_vless_account_proto_rawDesc)))\n\t})\n\treturn file_proxy_vless_account_proto_rawDescData\n}\n\nvar file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_proxy_vless_account_proto_goTypes = []any{\n\t(*Reverse)(nil), // 0: xray.proxy.vless.Reverse\n\t(*Account)(nil), // 1: xray.proxy.vless.Account\n}\nvar file_proxy_vless_account_proto_depIdxs = []int32{\n\t0, // 0: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vless_account_proto_init() }\nfunc file_proxy_vless_account_proto_init() {\n\tif File_proxy_vless_account_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vless_account_proto_rawDesc), len(file_proxy_vless_account_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vless_account_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vless_account_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vless_account_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vless_account_proto = out.File\n\tfile_proxy_vless_account_proto_goTypes = nil\n\tfile_proxy_vless_account_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vless/account.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vless;\noption csharp_namespace = \"Xray.Proxy.Vless\";\noption go_package = \"github.com/xtls/xray-core/proxy/vless\";\noption java_package = \"com.xray.proxy.vless\";\noption java_multiple_files = true;\n\nmessage Reverse {\n  string tag = 1;\n}\n\nmessage Account {\n  // ID of the account, in the form of a UUID, e.g., \"66ad4540-b58c-4ad2-9926-ea63445a9b57\".\n  string id = 1;\n  // Flow settings. May be \"xtls-rprx-vision\".\n  string flow = 2;\n\n  string encryption = 3;\n  uint32 xorMode = 4;\n  uint32 seconds = 5;\n  string padding = 6;\n\n  Reverse reverse = 7;\n\n  uint32 testpre = 8;\n  repeated uint32 testseed = 9;\n}\n"
  },
  {
    "path": "proxy/vless/encoding/addons.go",
    "content": "package encoding\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc EncodeHeaderAddons(buffer *buf.Buffer, addons *Addons) error {\n\tswitch addons.Flow {\n\tcase vless.XRV:\n\t\tbytes, err := proto.Marshal(addons)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to marshal addons protobuf value\").Base(err)\n\t\t}\n\t\tif err := buffer.WriteByte(byte(len(bytes))); err != nil {\n\t\t\treturn errors.New(\"failed to write addons protobuf length\").Base(err)\n\t\t}\n\t\tif _, err := buffer.Write(bytes); err != nil {\n\t\t\treturn errors.New(\"failed to write addons protobuf value\").Base(err)\n\t\t}\n\tdefault:\n\t\tif err := buffer.WriteByte(0); err != nil {\n\t\t\treturn errors.New(\"failed to write addons protobuf length\").Base(err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc DecodeHeaderAddons(buffer *buf.Buffer, reader io.Reader) (*Addons, error) {\n\taddons := new(Addons)\n\tbuffer.Clear()\n\tif _, err := buffer.ReadFullFrom(reader, 1); err != nil {\n\t\treturn nil, errors.New(\"failed to read addons protobuf length\").Base(err)\n\t}\n\n\tif length := int32(buffer.Byte(0)); length != 0 {\n\t\tbuffer.Clear()\n\t\tif _, err := buffer.ReadFullFrom(reader, length); err != nil {\n\t\t\treturn nil, errors.New(\"failed to read addons protobuf value\").Base(err)\n\t\t}\n\n\t\tif err := proto.Unmarshal(buffer.Bytes(), addons); err != nil {\n\t\t\treturn nil, errors.New(\"failed to unmarshal addons protobuf value\").Base(err)\n\t\t}\n\n\t\t// Verification.\n\t\tswitch addons.Flow {\n\t\tdefault:\n\t\t}\n\t}\n\n\treturn addons, nil\n}\n\n// EncodeBodyAddons returns a Writer that auto-encrypt content written by caller.\nfunc EncodeBodyAddons(writer buf.Writer, request *protocol.RequestHeader, requestAddons *Addons, state *proxy.TrafficState, isUplink bool, context context.Context, conn net.Conn, ob *session.Outbound) buf.Writer {\n\tif request.Command == protocol.RequestCommandUDP {\n\t\treturn NewMultiLengthPacketWriter(writer)\n\t}\n\tif requestAddons.Flow == vless.XRV {\n\t\treturn proxy.NewVisionWriter(writer, state, isUplink, context, conn, ob, request.User.Account.(*vless.MemoryAccount).Testseed)\n\t}\n\treturn writer\n}\n\n// DecodeBodyAddons returns a Reader from which caller can fetch decrypted body.\nfunc DecodeBodyAddons(reader io.Reader, request *protocol.RequestHeader, addons *Addons) buf.Reader {\n\tswitch addons.Flow {\n\tdefault:\n\t\tif request.Command == protocol.RequestCommandUDP {\n\t\t\treturn NewLengthPacketReader(reader)\n\t\t}\n\t}\n\treturn buf.NewReader(reader)\n}\n\nfunc NewMultiLengthPacketWriter(writer buf.Writer) *MultiLengthPacketWriter {\n\treturn &MultiLengthPacketWriter{\n\t\tWriter: writer,\n\t}\n}\n\ntype MultiLengthPacketWriter struct {\n\tbuf.Writer\n}\n\nfunc (w *MultiLengthPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\tmb2Write := make(buf.MultiBuffer, 0, len(mb)+1)\n\tfor _, b := range mb {\n\t\tlength := b.Len()\n\t\tif length == 0 || length+2 > buf.Size {\n\t\t\tcontinue\n\t\t}\n\t\teb := buf.New()\n\t\tif err := eb.WriteByte(byte(length >> 8)); err != nil {\n\t\t\teb.Release()\n\t\t\tcontinue\n\t\t}\n\t\tif err := eb.WriteByte(byte(length)); err != nil {\n\t\t\teb.Release()\n\t\t\tcontinue\n\t\t}\n\t\tif _, err := eb.Write(b.Bytes()); err != nil {\n\t\t\teb.Release()\n\t\t\tcontinue\n\t\t}\n\t\tmb2Write = append(mb2Write, eb)\n\t}\n\tif mb2Write.IsEmpty() {\n\t\treturn nil\n\t}\n\treturn w.Writer.WriteMultiBuffer(mb2Write)\n}\n\nfunc NewLengthPacketWriter(writer io.Writer) *LengthPacketWriter {\n\treturn &LengthPacketWriter{\n\t\tWriter: writer,\n\t\tcache:  make([]byte, 0, 65536),\n\t}\n}\n\ntype LengthPacketWriter struct {\n\tio.Writer\n\tcache []byte\n}\n\nfunc (w *LengthPacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tlength := mb.Len() // none of mb is nil\n\t// fmt.Println(\"Write\", length)\n\tif length == 0 {\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\tw.cache = w.cache[:0]\n\t}()\n\tw.cache = append(w.cache, byte(length>>8), byte(length))\n\tfor i, b := range mb {\n\t\tw.cache = append(w.cache, b.Bytes()...)\n\t\tb.Release()\n\t\tmb[i] = nil\n\t}\n\tif _, err := w.Write(w.cache); err != nil {\n\t\treturn errors.New(\"failed to write a packet\").Base(err)\n\t}\n\treturn nil\n}\n\nfunc NewLengthPacketReader(reader io.Reader) *LengthPacketReader {\n\treturn &LengthPacketReader{\n\t\tReader: reader,\n\t\tcache:  make([]byte, 2),\n\t}\n}\n\ntype LengthPacketReader struct {\n\tio.Reader\n\tcache []byte\n}\n\nfunc (r *LengthPacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tif _, err := io.ReadFull(r.Reader, r.cache); err != nil { // maybe EOF\n\t\treturn nil, errors.New(\"failed to read packet length\").Base(err)\n\t}\n\tlength := int32(r.cache[0])<<8 | int32(r.cache[1])\n\t// fmt.Println(\"Read\", length)\n\tmb := make(buf.MultiBuffer, 0, length/buf.Size+1)\n\tfor length > 0 {\n\t\tsize := length\n\t\tif size > buf.Size {\n\t\t\tsize = buf.Size\n\t\t}\n\t\tlength -= size\n\t\tb := buf.New()\n\t\tif _, err := b.ReadFullFrom(r.Reader, size); err != nil {\n\t\t\treturn nil, errors.New(\"failed to read packet payload\").Base(err)\n\t\t}\n\t\tmb = append(mb, b)\n\t}\n\treturn mb, nil\n}\n"
  },
  {
    "path": "proxy/vless/encoding/addons.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vless/encoding/addons.proto\n\npackage encoding\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Addons struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tFlow          string                 `protobuf:\"bytes,1,opt,name=Flow,proto3\" json:\"Flow,omitempty\"`\n\tSeed          []byte                 `protobuf:\"bytes,2,opt,name=Seed,proto3\" json:\"Seed,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Addons) Reset() {\n\t*x = Addons{}\n\tmi := &file_proxy_vless_encoding_addons_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Addons) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Addons) ProtoMessage() {}\n\nfunc (x *Addons) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vless_encoding_addons_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Addons.ProtoReflect.Descriptor instead.\nfunc (*Addons) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vless_encoding_addons_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Addons) GetFlow() string {\n\tif x != nil {\n\t\treturn x.Flow\n\t}\n\treturn \"\"\n}\n\nfunc (x *Addons) GetSeed() []byte {\n\tif x != nil {\n\t\treturn x.Seed\n\t}\n\treturn nil\n}\n\nvar File_proxy_vless_encoding_addons_proto protoreflect.FileDescriptor\n\nconst file_proxy_vless_encoding_addons_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!proxy/vless/encoding/addons.proto\\x12\\x19xray.proxy.vless.encoding\\\"0\\n\" +\n\t\"\\x06Addons\\x12\\x12\\n\" +\n\t\"\\x04Flow\\x18\\x01 \\x01(\\tR\\x04Flow\\x12\\x12\\n\" +\n\t\"\\x04Seed\\x18\\x02 \\x01(\\fR\\x04SeedBm\\n\" +\n\t\"\\x1dcom.xray.proxy.vless.encodingP\\x01Z.github.com/xtls/xray-core/proxy/vless/encoding\\xaa\\x02\\x19Xray.Proxy.Vless.Encodingb\\x06proto3\"\n\nvar (\n\tfile_proxy_vless_encoding_addons_proto_rawDescOnce sync.Once\n\tfile_proxy_vless_encoding_addons_proto_rawDescData []byte\n)\n\nfunc file_proxy_vless_encoding_addons_proto_rawDescGZIP() []byte {\n\tfile_proxy_vless_encoding_addons_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vless_encoding_addons_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vless_encoding_addons_proto_rawDesc), len(file_proxy_vless_encoding_addons_proto_rawDesc)))\n\t})\n\treturn file_proxy_vless_encoding_addons_proto_rawDescData\n}\n\nvar file_proxy_vless_encoding_addons_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_vless_encoding_addons_proto_goTypes = []any{\n\t(*Addons)(nil), // 0: xray.proxy.vless.encoding.Addons\n}\nvar file_proxy_vless_encoding_addons_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vless_encoding_addons_proto_init() }\nfunc file_proxy_vless_encoding_addons_proto_init() {\n\tif File_proxy_vless_encoding_addons_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vless_encoding_addons_proto_rawDesc), len(file_proxy_vless_encoding_addons_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vless_encoding_addons_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vless_encoding_addons_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vless_encoding_addons_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vless_encoding_addons_proto = out.File\n\tfile_proxy_vless_encoding_addons_proto_goTypes = nil\n\tfile_proxy_vless_encoding_addons_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vless/encoding/addons.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vless.encoding;\noption csharp_namespace = \"Xray.Proxy.Vless.Encoding\";\noption go_package = \"github.com/xtls/xray-core/proxy/vless/encoding\";\noption java_package = \"com.xray.proxy.vless.encoding\";\noption java_multiple_files = true;\n\nmessage Addons {\n  string Flow = 1;\n  bytes Seed = 2;\n}\n"
  },
  {
    "path": "proxy/vless/encoding/encoding.go",
    "content": "package encoding\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n)\n\nconst (\n\tVersion = byte(0)\n)\n\nvar addrParser = protocol.NewAddressParser(\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6),\n\tprotocol.PortThenAddress(),\n)\n\n// EncodeRequestHeader writes encoded request header into the given writer.\nfunc EncodeRequestHeader(writer io.Writer, request *protocol.RequestHeader, requestAddons *Addons) error {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tif err := buffer.WriteByte(request.Version); err != nil {\n\t\treturn errors.New(\"failed to write request version\").Base(err)\n\t}\n\n\tif _, err := buffer.Write(request.User.Account.(*vless.MemoryAccount).ID.Bytes()); err != nil {\n\t\treturn errors.New(\"failed to write request user id\").Base(err)\n\t}\n\n\tif err := EncodeHeaderAddons(&buffer, requestAddons); err != nil {\n\t\treturn errors.New(\"failed to encode request header addons\").Base(err)\n\t}\n\n\tif err := buffer.WriteByte(byte(request.Command)); err != nil {\n\t\treturn errors.New(\"failed to write request command\").Base(err)\n\t}\n\n\tif request.Command != protocol.RequestCommandMux && request.Command != protocol.RequestCommandRvs {\n\t\tif err := addrParser.WriteAddressPort(&buffer, request.Address, request.Port); err != nil {\n\t\t\treturn errors.New(\"failed to write request address and port\").Base(err)\n\t\t}\n\t}\n\n\tif _, err := writer.Write(buffer.Bytes()); err != nil {\n\t\treturn errors.New(\"failed to write request header\").Base(err)\n\t}\n\n\treturn nil\n}\n\n// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.\nfunc DecodeRequestHeader(isfb bool, first *buf.Buffer, reader io.Reader, validator vless.Validator) ([]byte, *protocol.RequestHeader, *Addons, bool, error) {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\trequest := new(protocol.RequestHeader)\n\n\tif isfb {\n\t\trequest.Version = first.Byte(0)\n\t} else {\n\t\tif _, err := buffer.ReadFullFrom(reader, 1); err != nil {\n\t\t\treturn nil, nil, nil, false, errors.New(\"failed to read request version\").Base(err)\n\t\t}\n\t\trequest.Version = buffer.Byte(0)\n\t}\n\n\tswitch request.Version {\n\tcase 0:\n\n\t\tvar id [16]byte\n\n\t\tif isfb {\n\t\t\tcopy(id[:], first.BytesRange(1, 17))\n\t\t} else {\n\t\t\tbuffer.Clear()\n\t\t\tif _, err := buffer.ReadFullFrom(reader, 16); err != nil {\n\t\t\t\treturn nil, nil, nil, false, errors.New(\"failed to read request user id\").Base(err)\n\t\t\t}\n\t\t\tcopy(id[:], buffer.Bytes())\n\t\t}\n\n\t\tif request.User = validator.Get(id); request.User == nil {\n\t\t\tu := uuid.UUID(id)\n\t\t\treturn nil, nil, nil, isfb, errors.New(\"invalid request user id: \" + u.String())\n\t\t}\n\n\t\tif isfb {\n\t\t\tfirst.Advance(17)\n\t\t}\n\n\t\trequestAddons, err := DecodeHeaderAddons(&buffer, reader)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, false, errors.New(\"failed to decode request header addons\").Base(err)\n\t\t}\n\n\t\tbuffer.Clear()\n\t\tif _, err := buffer.ReadFullFrom(reader, 1); err != nil {\n\t\t\treturn nil, nil, nil, false, errors.New(\"failed to read request command\").Base(err)\n\t\t}\n\n\t\trequest.Command = protocol.RequestCommand(buffer.Byte(0))\n\t\tswitch request.Command {\n\t\tcase protocol.RequestCommandMux:\n\t\t\trequest.Address = net.DomainAddress(\"v1.mux.cool\")\n\t\tcase protocol.RequestCommandRvs:\n\t\t\trequest.Address = net.DomainAddress(\"v1.rvs.cool\")\n\t\tcase protocol.RequestCommandTCP, protocol.RequestCommandUDP:\n\t\t\tif addr, port, err := addrParser.ReadAddressPort(&buffer, reader); err == nil {\n\t\t\t\trequest.Address = addr\n\t\t\t\trequest.Port = port\n\t\t\t}\n\t\t}\n\t\tif request.Address == nil {\n\t\t\treturn nil, nil, nil, false, errors.New(\"invalid request address\")\n\t\t}\n\t\treturn id[:], request, requestAddons, false, nil\n\tdefault:\n\t\treturn nil, nil, nil, isfb, errors.New(\"invalid request version\")\n\t}\n}\n\n// EncodeResponseHeader writes encoded response header into the given writer.\nfunc EncodeResponseHeader(writer io.Writer, request *protocol.RequestHeader, responseAddons *Addons) error {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tif err := buffer.WriteByte(request.Version); err != nil {\n\t\treturn errors.New(\"failed to write response version\").Base(err)\n\t}\n\n\tif err := EncodeHeaderAddons(&buffer, responseAddons); err != nil {\n\t\treturn errors.New(\"failed to encode response header addons\").Base(err)\n\t}\n\n\tif _, err := writer.Write(buffer.Bytes()); err != nil {\n\t\treturn errors.New(\"failed to write response header\").Base(err)\n\t}\n\n\treturn nil\n}\n\n// DecodeResponseHeader decodes and returns (if successful) a ResponseHeader from an input stream.\nfunc DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*Addons, error) {\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tif _, err := buffer.ReadFullFrom(reader, 1); err != nil {\n\t\treturn nil, errors.New(\"failed to read response version\").Base(err)\n\t}\n\n\tif buffer.Byte(0) != request.Version {\n\t\treturn nil, errors.New(\"unexpected response version. Expecting \", int(request.Version), \" but actually \", int(buffer.Byte(0)))\n\t}\n\n\tresponseAddons, err := DecodeHeaderAddons(&buffer, reader)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to decode response header addons\").Base(err)\n\t}\n\n\treturn responseAddons, nil\n}\n\n// XtlsRead can switch to splice copy\nfunc XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, trafficState *proxy.TrafficState, isUplink bool, ctx context.Context) error {\n\terr := func() error {\n\t\tfor {\n\t\t\tif isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {\n\t\t\t\tvar writerConn net.Conn\n\t\t\t\tvar inTimer *signal.ActivityTimer\n\t\t\t\tif inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {\n\t\t\t\t\twriterConn = inbound.Conn\n\t\t\t\t\tinTimer = inbound.Timer\n\t\t\t\t}\n\t\t\t\treturn proxy.CopyRawConnIfExist(ctx, conn, writerConn, writer, timer, inTimer)\n\t\t\t}\n\t\t\tbuffer, err := reader.ReadMultiBuffer()\n\t\t\tif !buffer.IsEmpty() {\n\t\t\t\ttimer.Update()\n\t\t\t\tif werr := writer.WriteMultiBuffer(buffer); werr != nil {\n\t\t\t\t\treturn werr\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}()\n\tif err != nil && errors.Cause(err) != io.EOF {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/vless/encoding/encoding_test.go",
    "content": "package encoding_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t. \"github.com/xtls/xray-core/proxy/vless/encoding\"\n)\n\nfunc toAccount(a *vless.Account) protocol.Account {\n\taccount, err := a.AsAccount()\n\tcommon.Must(err)\n\treturn account\n}\n\nfunc TestRequestSerialization(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tLevel: 0,\n\t\tEmail: \"test@example.com\",\n\t}\n\tid := uuid.New()\n\taccount := &vless.Account{\n\t\tId: id.String(),\n\t}\n\tuser.Account = toAccount(account)\n\n\texpectedRequest := &protocol.RequestHeader{\n\t\tVersion: Version,\n\t\tUser:    user,\n\t\tCommand: protocol.RequestCommandTCP,\n\t\tAddress: net.DomainAddress(\"www.example.com\"),\n\t\tPort:    net.Port(443),\n\t}\n\texpectedAddons := &Addons{}\n\n\tbuffer := buf.StackNew()\n\tcommon.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons))\n\n\tValidator := new(vless.MemoryValidator)\n\tValidator.Add(user)\n\n\t_, actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)\n\tcommon.Must(err)\n\n\tif r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\taddonsComparer := func(x, y *Addons) bool {\n\t\treturn (x.Flow == y.Flow) && (cmp.Equal(x.Seed, y.Seed))\n\t}\n\tif r := cmp.Diff(actualAddons, expectedAddons, cmp.Comparer(addonsComparer)); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestInvalidRequest(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tLevel: 0,\n\t\tEmail: \"test@example.com\",\n\t}\n\tid := uuid.New()\n\taccount := &vless.Account{\n\t\tId: id.String(),\n\t}\n\tuser.Account = toAccount(account)\n\n\texpectedRequest := &protocol.RequestHeader{\n\t\tVersion: Version,\n\t\tUser:    user,\n\t\tCommand: protocol.RequestCommand(100),\n\t\tAddress: net.DomainAddress(\"www.example.com\"),\n\t\tPort:    net.Port(443),\n\t}\n\texpectedAddons := &Addons{}\n\n\tbuffer := buf.StackNew()\n\tcommon.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons))\n\n\tValidator := new(vless.MemoryValidator)\n\tValidator.Add(user)\n\n\t_, _, _, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestMuxRequest(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tLevel: 0,\n\t\tEmail: \"test@example.com\",\n\t}\n\tid := uuid.New()\n\taccount := &vless.Account{\n\t\tId: id.String(),\n\t}\n\tuser.Account = toAccount(account)\n\n\texpectedRequest := &protocol.RequestHeader{\n\t\tVersion: Version,\n\t\tUser:    user,\n\t\tCommand: protocol.RequestCommandMux,\n\t\tAddress: net.DomainAddress(\"v1.mux.cool\"),\n\t}\n\texpectedAddons := &Addons{}\n\n\tbuffer := buf.StackNew()\n\tcommon.Must(EncodeRequestHeader(&buffer, expectedRequest, expectedAddons))\n\n\tValidator := new(vless.MemoryValidator)\n\tValidator.Add(user)\n\n\t_, actualRequest, actualAddons, _, err := DecodeRequestHeader(false, nil, &buffer, Validator)\n\tcommon.Must(err)\n\n\tif r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\taddonsComparer := func(x, y *Addons) bool {\n\t\treturn (x.Flow == y.Flow) && (cmp.Equal(x.Seed, y.Seed))\n\t}\n\tif r := cmp.Diff(actualAddons, expectedAddons, cmp.Comparer(addonsComparer)); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n"
  },
  {
    "path": "proxy/vless/encryption/client.go",
    "content": "package encryption\n\nimport (\n\t\"crypto/cipher\"\n\t\"crypto/ecdh\"\n\t\"crypto/mlkem\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"lukechampine.com/blake3\"\n)\n\ntype ClientInstance struct {\n\tNfsPKeys      []any\n\tNfsPKeysBytes [][]byte\n\tHash32s       [][32]byte\n\tRelaysLength  int\n\tXorMode       uint32\n\tSeconds       uint32\n\tPaddingLens   [][3]int\n\tPaddingGaps   [][3]int\n\n\tRWLock sync.RWMutex\n\tExpire time.Time\n\tPfsKey []byte\n\tTicket []byte\n}\n\nfunc (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {\n\tif i.NfsPKeys != nil {\n\t\treturn errors.New(\"already initialized\")\n\t}\n\tl := len(nfsPKeysBytes)\n\tif l == 0 {\n\t\treturn errors.New(\"empty nfsPKeysBytes\")\n\t}\n\ti.NfsPKeys = make([]any, l)\n\ti.NfsPKeysBytes = nfsPKeysBytes\n\ti.Hash32s = make([][32]byte, l)\n\tfor j, k := range nfsPKeysBytes {\n\t\tif len(k) == 32 {\n\t\t\tif i.NfsPKeys[j], err = ecdh.X25519().NewPublicKey(k); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ti.RelaysLength += 32 + 32\n\t\t} else {\n\t\t\tif i.NfsPKeys[j], err = mlkem.NewEncapsulationKey768(k); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ti.RelaysLength += 1088 + 32\n\t\t}\n\t\ti.Hash32s[j] = blake3.Sum256(k)\n\t}\n\ti.RelaysLength -= 32\n\ti.XorMode = xorMode\n\ti.Seconds = seconds\n\treturn ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)\n}\n\nfunc (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {\n\tif i.NfsPKeys == nil {\n\t\treturn nil, errors.New(\"uninitialized\")\n\t}\n\tc := NewCommonConn(conn, protocol.HasAESGCMHardwareSupport)\n\n\tivAndRealysLength := 16 + i.RelaysLength\n\tpfsKeyExchangeLength := 18 + 1184 + 32 + 16\n\tpaddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)\n\tclientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)\n\n\tiv := clientHello[:16]\n\trand.Read(iv)\n\trelays := clientHello[16:ivAndRealysLength]\n\tvar nfsKey []byte\n\tvar lastCTR cipher.Stream\n\tfor j, k := range i.NfsPKeys {\n\t\tvar index = 32\n\t\tif k, ok := k.(*ecdh.PublicKey); ok {\n\t\t\tprivateKey, _ := ecdh.X25519().GenerateKey(rand.Reader)\n\t\t\tcopy(relays, privateKey.PublicKey().Bytes())\n\t\t\tvar err error\n\t\t\tnfsKey, err = privateKey.ECDH(k)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif k, ok := k.(*mlkem.EncapsulationKey768); ok {\n\t\t\tvar ciphertext []byte\n\t\t\tnfsKey, ciphertext = k.Encapsulate()\n\t\t\tcopy(relays, ciphertext)\n\t\t\tindex = 1088\n\t\t}\n\t\tif i.XorMode > 0 { // this xor can (others can't) be recovered by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, that's why \"native\" values\n\t\t\tNewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // make X25519 public key / ML-KEM-768 ciphertext distinguishable from random bytes\n\t\t}\n\t\tif lastCTR != nil {\n\t\t\tlastCTR.XORKeyStream(relays, relays[:32]) // make this relay irreplaceable\n\t\t}\n\t\tif j == len(i.NfsPKeys)-1 {\n\t\t\tbreak\n\t\t}\n\t\tlastCTR = NewCTR(nfsKey, iv)\n\t\tlastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])\n\t\trelays = relays[index+32:]\n\t}\n\tnfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)\n\n\tif i.Seconds > 0 {\n\t\ti.RWLock.RLock()\n\t\tif time.Now().Before(i.Expire) {\n\t\t\tc.Client = i\n\t\t\tc.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection\n\t\t\tnfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)\n\t\t\tnfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)\n\t\t\ti.RWLock.RUnlock()\n\t\t\tc.PreWrite = clientHello[:ivAndRealysLength+18+32]\n\t\t\tc.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES)\n\t\t\tif i.XorMode == 2 {\n\t\t\t\tc.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)\n\t\t\t}\n\t\t\treturn c, nil\n\t\t}\n\t\ti.RWLock.RUnlock()\n\t}\n\n\tpfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]\n\tnfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)\n\tmlkem768DKey, _ := mlkem.GenerateKey768()\n\tx25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)\n\tpfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)\n\tnfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)\n\n\tpadding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]\n\tnfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)\n\tnfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)\n\n\tpaddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0]\n\tfor i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control\n\t\tif l > 0 {\n\t\t\tif _, err := conn.Write(clientHello[:l]); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tclientHello = clientHello[l:]\n\t\t}\n\t\tif len(paddingGaps) > i {\n\t\t\ttime.Sleep(paddingGaps[i])\n\t\t}\n\t}\n\n\tencryptedPfsPublicKey := make([]byte, 1088+32+16)\n\tif _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {\n\t\treturn nil, err\n\t}\n\tnfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)\n\tmlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpeerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1088 : 1088+32])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx25519Key, err := x25519SKey.ECDH(peerX25519PKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpfsKey := make([]byte, 32+32) // no more capacity\n\tcopy(pfsKey, mlkem768Key)\n\tcopy(pfsKey[32:], x25519Key)\n\tc.UnitedKey = append(pfsKey, nfsKey...)\n\tc.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)\n\tc.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES)\n\n\tencryptedTicket := make([]byte, 32)\n\tif _, err := io.ReadFull(conn, encryptedTicket); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {\n\t\treturn nil, err\n\t}\n\tseconds := DecodeLength(encryptedTicket)\n\n\tif i.Seconds > 0 && seconds > 0 {\n\t\ti.RWLock.Lock()\n\t\ti.Expire = time.Now().Add(time.Duration(seconds) * time.Second)\n\t\ti.PfsKey = pfsKey\n\t\ti.Ticket = encryptedTicket[:16]\n\t\ti.RWLock.Unlock()\n\t}\n\n\tencryptedLength := make([]byte, 18)\n\tif _, err := io.ReadFull(conn, encryptedLength); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {\n\t\treturn nil, err\n\t}\n\tlength := DecodeLength(encryptedLength[:2])\n\tc.PeerPadding = make([]byte, length) // important: allows server sends padding slowly, eliminating 1-RTT's traffic pattern\n\n\tif i.XorMode == 2 {\n\t\tc.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length)\n\t}\n\treturn c, nil\n}\n"
  },
  {
    "path": "proxy/vless/encryption/common.go",
    "content": "package encryption\n\nimport (\n\t\"bytes\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"golang.org/x/crypto/chacha20poly1305\"\n\t\"lukechampine.com/blake3\"\n)\n\nvar OutBytesPool = sync.Pool{\n\tNew: func() any {\n\t\treturn make([]byte, 5+8192+16)\n\t},\n}\n\ntype CommonConn struct {\n\tnet.Conn\n\tUseAES      bool\n\tClient      *ClientInstance\n\tUnitedKey   []byte\n\tPreWrite    []byte\n\tAEAD        *AEAD\n\tPeerAEAD    *AEAD\n\tPeerPadding []byte\n\trawInput    bytes.Buffer\n\tinput       bytes.Reader\n}\n\nfunc NewCommonConn(conn net.Conn, useAES bool) *CommonConn {\n\treturn &CommonConn{\n\t\tConn:   conn,\n\t\tUseAES: useAES,\n\t}\n}\n\nfunc (c *CommonConn) Write(b []byte) (int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\toutBytes := OutBytesPool.Get().([]byte)\n\tdefer OutBytesPool.Put(outBytes)\n\tfor n := 0; n < len(b); {\n\t\tb := b[n:]\n\t\tif len(b) > 8192 {\n\t\t\tb = b[:8192] // for avoiding another copy() in peer's Read()\n\t\t}\n\t\tn += len(b)\n\t\theaderAndData := outBytes[:5+len(b)+16]\n\t\tEncodeHeader(headerAndData, len(b)+16)\n\t\tmax := false\n\t\tif bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {\n\t\t\tmax = true\n\t\t}\n\t\tc.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5])\n\t\tif max {\n\t\t\tc.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)\n\t\t}\n\t\tif c.PreWrite != nil {\n\t\t\theaderAndData = append(c.PreWrite, headerAndData...)\n\t\t\tc.PreWrite = nil\n\t\t}\n\t\tif _, err := c.Conn.Write(headerAndData); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\treturn len(b), nil\n}\n\nfunc (c *CommonConn) Read(b []byte) (int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\tif c.PeerAEAD == nil { // client's 0-RTT\n\t\tserverRandom := make([]byte, 16)\n\t\tif _, err := io.ReadFull(c.Conn, serverRandom); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tc.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES)\n\t\tif xorConn, ok := c.Conn.(*XorConn); ok {\n\t\t\txorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)\n\t\t}\n\t}\n\tif c.PeerPadding != nil { // client's 1-RTT\n\t\tif _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tc.PeerPadding = nil\n\t}\n\tif c.input.Len() > 0 {\n\t\treturn c.input.Read(b)\n\t}\n\tpeerHeader := [5]byte{}\n\tif _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {\n\t\treturn 0, err\n\t}\n\tl, err := DecodeHeader(peerHeader[:]) // l: 17~16640\n\tif err != nil {\n\t\tif c.Client != nil && strings.Contains(err.Error(), \"invalid header: \") { // client's 0-RTT\n\t\t\tc.Client.RWLock.Lock()\n\t\t\tif bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) {\n\t\t\t\tc.Client.Expire = time.Now() // expired\n\t\t\t}\n\t\t\tc.Client.RWLock.Unlock()\n\t\t\treturn 0, errors.New(\"new handshake needed\")\n\t\t}\n\t\treturn 0, err\n\t}\n\tc.Client = nil\n\tif c.rawInput.Cap() < l {\n\t\tc.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading\n\t}\n\tpeerData := c.rawInput.Bytes()[:l]\n\tif _, err := io.ReadFull(c.Conn, peerData); err != nil {\n\t\treturn 0, err\n\t}\n\tdst := peerData[:l-16]\n\tif len(dst) <= len(b) {\n\t\tdst = b[:len(dst)] // avoids another copy()\n\t}\n\tvar newAEAD *AEAD\n\tif bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {\n\t\tnewAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)\n\t}\n\t_, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])\n\tif newAEAD != nil {\n\t\tc.PeerAEAD = newAEAD\n\t}\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif len(dst) > len(b) {\n\t\tc.input.Reset(dst[copy(b, dst):])\n\t\tdst = b // for len(dst)\n\t}\n\treturn len(dst), nil\n}\n\ntype AEAD struct {\n\tcipher.AEAD\n\tNonce [12]byte\n}\n\nfunc NewAEAD(ctx, key []byte, useAES bool) *AEAD {\n\tk := make([]byte, 32)\n\tblake3.DeriveKey(k, string(ctx), key)\n\tvar aead cipher.AEAD\n\tif useAES {\n\t\tblock, _ := aes.NewCipher(k)\n\t\taead, _ = cipher.NewGCM(block)\n\t} else {\n\t\taead, _ = chacha20poly1305.New(k)\n\t}\n\treturn &AEAD{AEAD: aead}\n}\n\nfunc (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {\n\tif nonce == nil {\n\t\tnonce = IncreaseNonce(a.Nonce[:])\n\t}\n\treturn a.AEAD.Seal(dst, nonce, plaintext, additionalData)\n}\n\nfunc (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {\n\tif nonce == nil {\n\t\tnonce = IncreaseNonce(a.Nonce[:])\n\t}\n\treturn a.AEAD.Open(dst, nonce, ciphertext, additionalData)\n}\n\nfunc IncreaseNonce(nonce []byte) []byte {\n\tfor i := range 12 {\n\t\tnonce[11-i]++\n\t\tif nonce[11-i] != 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nonce\n}\n\nvar MaxNonce = bytes.Repeat([]byte{255}, 12)\n\nfunc EncodeLength(l int) []byte {\n\treturn []byte{byte(l >> 8), byte(l)}\n}\n\nfunc DecodeLength(b []byte) int {\n\treturn int(b[0])<<8 | int(b[1])\n}\n\nfunc EncodeHeader(h []byte, l int) {\n\th[0] = 23\n\th[1] = 3\n\th[2] = 3\n\th[3] = byte(l >> 8)\n\th[4] = byte(l)\n}\n\nfunc DecodeHeader(h []byte) (l int, err error) {\n\tl = int(h[3])<<8 | int(h[4])\n\tif h[0] != 23 || h[1] != 3 || h[2] != 3 {\n\t\tl = 0\n\t}\n\tif l < 17 || l > 16640 { // TLS 1.3 max record: 16384 + 256 (RFC 8446 §5.2)\n\t\terr = errors.New(\"invalid header: \" + fmt.Sprintf(\"%v\", h[:5])) // DO NOT CHANGE: relied by client's Read()\n\t}\n\treturn\n}\n\nfunc ParsePadding(padding string, paddingLens, paddingGaps *[][3]int) (err error) {\n\tif padding == \"\" {\n\t\treturn\n\t}\n\tmaxLen := 0\n\tfor i, s := range strings.Split(padding, \".\") {\n\t\tx := strings.Split(s, \"-\")\n\t\tif len(x) < 3 || x[0] == \"\" || x[1] == \"\" || x[2] == \"\" {\n\t\t\treturn errors.New(\"invalid padding lenth/gap parameter: \" + s)\n\t\t}\n\t\ty := [3]int{}\n\t\tif y[0], err = strconv.Atoi(x[0]); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif y[1], err = strconv.Atoi(x[1]); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif y[2], err = strconv.Atoi(x[2]); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif i == 0 && (y[0] < 100 || y[1] < 18+17 || y[2] < 18+17) {\n\t\t\treturn errors.New(\"first padding length must not be smaller than 35\")\n\t\t}\n\t\tif i%2 == 0 {\n\t\t\t*paddingLens = append(*paddingLens, y)\n\t\t\tmaxLen += max(y[1], y[2])\n\t\t} else {\n\t\t\t*paddingGaps = append(*paddingGaps, y)\n\t\t}\n\t}\n\tif maxLen > 18+65535 {\n\t\treturn errors.New(\"total padding length must not be larger than 65553\")\n\t}\n\treturn\n}\n\nfunc CreatPadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) {\n\tif len(paddingLens) == 0 {\n\t\tpaddingLens = [][3]int{{100, 111, 1111}, {50, 0, 3333}}\n\t\tpaddingGaps = [][3]int{{75, 0, 111}}\n\t}\n\tfor _, y := range paddingLens {\n\t\tl := 0\n\t\tif y[0] >= int(crypto.RandBetween(0, 100)) {\n\t\t\tl = int(crypto.RandBetween(int64(y[1]), int64(y[2])))\n\t\t}\n\t\tlens = append(lens, l)\n\t\tlength += l\n\t}\n\tfor _, y := range paddingGaps {\n\t\tg := 0\n\t\tif y[0] >= int(crypto.RandBetween(0, 100)) {\n\t\t\tg = int(crypto.RandBetween(int64(y[1]), int64(y[2])))\n\t\t}\n\t\tgaps = append(gaps, time.Duration(g)*time.Millisecond)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "proxy/vless/encryption/server.go",
    "content": "package encryption\n\nimport (\n\t\"bytes\"\n\t\"crypto/cipher\"\n\t\"crypto/ecdh\"\n\t\"crypto/mlkem\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"lukechampine.com/blake3\"\n)\n\ntype ServerSession struct {\n\tPfsKey  []byte\n\tNfsKeys sync.Map\n}\n\ntype ServerInstance struct {\n\tNfsSKeys      []any\n\tNfsPKeysBytes [][]byte\n\tHash32s       [][32]byte\n\tRelaysLength  int\n\tXorMode       uint32\n\tSecondsFrom   int64\n\tSecondsTo     int64\n\tPaddingLens   [][3]int\n\tPaddingGaps   [][3]int\n\n\tRWLock   sync.RWMutex\n\tClosed   bool\n\tLasts    map[int64][16]byte\n\tTickets  [][16]byte\n\tSessions map[[16]byte]*ServerSession\n}\n\nfunc (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode uint32, secondsFrom, secondsTo int64, padding string) (err error) {\n\tif i.NfsSKeys != nil {\n\t\treturn errors.New(\"already initialized\")\n\t}\n\tl := len(nfsSKeysBytes)\n\tif l == 0 {\n\t\treturn errors.New(\"empty nfsSKeysBytes\")\n\t}\n\ti.NfsSKeys = make([]any, l)\n\ti.NfsPKeysBytes = make([][]byte, l)\n\ti.Hash32s = make([][32]byte, l)\n\tfor j, k := range nfsSKeysBytes {\n\t\tif len(k) == 32 {\n\t\t\tif i.NfsSKeys[j], err = ecdh.X25519().NewPrivateKey(k); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ti.NfsPKeysBytes[j] = i.NfsSKeys[j].(*ecdh.PrivateKey).PublicKey().Bytes()\n\t\t\ti.RelaysLength += 32 + 32\n\t\t} else {\n\t\t\tif i.NfsSKeys[j], err = mlkem.NewDecapsulationKey768(k); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ti.NfsPKeysBytes[j] = i.NfsSKeys[j].(*mlkem.DecapsulationKey768).EncapsulationKey().Bytes()\n\t\t\ti.RelaysLength += 1088 + 32\n\t\t}\n\t\ti.Hash32s[j] = blake3.Sum256(i.NfsPKeysBytes[j])\n\t}\n\ti.RelaysLength -= 32\n\ti.XorMode = xorMode\n\ti.SecondsFrom = secondsFrom\n\ti.SecondsTo = secondsTo\n\terr = ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)\n\tif err != nil {\n\t\treturn\n\t}\n\tif i.SecondsFrom > 0 || i.SecondsTo > 0 {\n\t\ti.Lasts = make(map[int64][16]byte)\n\t\ti.Tickets = make([][16]byte, 0, 1024)\n\t\ti.Sessions = make(map[[16]byte]*ServerSession)\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\ttime.Sleep(time.Minute)\n\t\t\t\ti.RWLock.Lock()\n\t\t\t\tif i.Closed {\n\t\t\t\t\ti.RWLock.Unlock()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tminute := time.Now().Unix() / 60\n\t\t\t\tlast := i.Lasts[minute]\n\t\t\t\tdelete(i.Lasts, minute)\n\t\t\t\tdelete(i.Lasts, minute-1) // for insurance\n\t\t\t\tif last != [16]byte{} {\n\t\t\t\t\tfor j, ticket := range i.Tickets {\n\t\t\t\t\t\tdelete(i.Sessions, ticket)\n\t\t\t\t\t\tif ticket == last {\n\t\t\t\t\t\t\ti.Tickets = i.Tickets[j+1:]\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti.RWLock.Unlock()\n\t\t\t}\n\t\t}()\n\t}\n\treturn\n}\n\nfunc (i *ServerInstance) Close() (err error) {\n\ti.RWLock.Lock()\n\ti.Closed = true\n\ti.RWLock.Unlock()\n\treturn\n}\n\nfunc (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) {\n\tif i.NfsSKeys == nil {\n\t\treturn nil, errors.New(\"uninitialized\")\n\t}\n\tc := NewCommonConn(conn, true)\n\n\tivAndRelays := make([]byte, 16+i.RelaysLength)\n\tif _, err := io.ReadFull(conn, ivAndRelays); err != nil {\n\t\treturn nil, err\n\t}\n\tif fallback != nil {\n\t\t*fallback = append(*fallback, ivAndRelays...)\n\t}\n\tiv := ivAndRelays[:16]\n\trelays := ivAndRelays[16:]\n\tvar nfsKey []byte\n\tvar lastCTR cipher.Stream\n\tfor j, k := range i.NfsSKeys {\n\t\tif lastCTR != nil {\n\t\t\tlastCTR.XORKeyStream(relays, relays[:32]) // recover this relay\n\t\t}\n\t\tvar index = 32\n\t\tif _, ok := k.(*mlkem.DecapsulationKey768); ok {\n\t\t\tindex = 1088\n\t\t}\n\t\tif i.XorMode > 0 {\n\t\t\tNewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator2, because we have PSK :)\n\t\t}\n\t\tif k, ok := k.(*ecdh.PrivateKey); ok {\n\t\t\tpublicKey, err := ecdh.X25519().NewPublicKey(relays[:index])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif publicKey.Bytes()[31] > 127 { // we just don't want the observer can change even one bit without breaking the connection, though it has nothing to do with security\n\t\t\t\treturn nil, errors.New(\"the highest bit of the last byte of the peer-sent X25519 public key is not 0\")\n\t\t\t}\n\t\t\tnfsKey, err = k.ECDH(publicKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif k, ok := k.(*mlkem.DecapsulationKey768); ok {\n\t\t\tvar err error\n\t\t\tnfsKey, err = k.Decapsulate(relays[:index])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif j == len(i.NfsSKeys)-1 {\n\t\t\tbreak\n\t\t}\n\t\trelays = relays[index:]\n\t\tlastCTR = NewCTR(nfsKey, iv)\n\t\tlastCTR.XORKeyStream(relays, relays[:32])\n\t\tif !bytes.Equal(relays[:32], i.Hash32s[j+1][:]) {\n\t\t\treturn nil, errors.New(\"unexpected hash32: \", fmt.Sprintf(\"%v\", relays[:32]))\n\t\t}\n\t\trelays = relays[32:]\n\t}\n\tnfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)\n\n\tencryptedLength := make([]byte, 18)\n\tif _, err := io.ReadFull(conn, encryptedLength); err != nil {\n\t\treturn nil, err\n\t}\n\tif fallback != nil {\n\t\t*fallback = append(*fallback, encryptedLength...)\n\t}\n\tdecryptedLength := make([]byte, 2)\n\tif _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {\n\t\tc.UseAES = !c.UseAES\n\t\tnfsAEAD = NewAEAD(iv, nfsKey, c.UseAES)\n\t\tif _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif fallback != nil {\n\t\t*fallback = nil\n\t}\n\tlength := DecodeLength(decryptedLength)\n\n\tif length == 32 {\n\t\tif i.SecondsFrom == 0 && i.SecondsTo == 0 {\n\t\t\treturn nil, errors.New(\"0-RTT is not allowed\")\n\t\t}\n\t\tencryptedTicket := make([]byte, 32)\n\t\tif _, err := io.ReadFull(conn, encryptedTicket); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ti.RWLock.RLock()\n\t\ts := i.Sessions[[16]byte(ticket)]\n\t\ti.RWLock.RUnlock()\n\t\tif s == nil {\n\t\t\tnoises := make([]byte, crypto.RandBetween(1279, 2279)) // matches 1-RTT's server hello length for \"random\", though it is not important, just for example\n\t\t\tvar err error\n\t\t\tfor err == nil {\n\t\t\t\trand.Read(noises)\n\t\t\t\t_, err = DecodeHeader(noises)\n\t\t\t}\n\t\t\tconn.Write(noises) // make client do new handshake\n\t\t\treturn nil, errors.New(\"expired ticket\")\n\t\t}\n\t\tif _, loaded := s.NfsKeys.LoadOrStore([32]byte(nfsKey), true); loaded { // prevents bad client also\n\t\t\treturn nil, errors.New(\"replay detected\")\n\t\t}\n\t\tc.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request)\n\t\tc.PreWrite = make([]byte, 16)\n\t\trand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for \"native\" and \"xorpub\")\n\t\tc.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES)\n\t\tc.PeerAEAD = NewAEAD(encryptedTicket, c.UnitedKey, c.UseAES) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)\n\t\tif i.XorMode == 2 {\n\t\t\tc.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client\n\t\t}\n\t\treturn c, nil\n\t}\n\n\tif length < 1184+32+16 { // client may send more public keys in the future's version\n\t\treturn nil, errors.New(\"too short length\")\n\t}\n\tencryptedPfsPublicKey := make([]byte, length)\n\tif _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := nfsAEAD.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {\n\t\treturn nil, err\n\t}\n\tmlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmlkem768Key, encapsulatedPfsKey := mlkem768EKey.Encapsulate()\n\tpeerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1184 : 1184+32])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)\n\tx25519Key, err := x25519SKey.ECDH(peerX25519PKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpfsKey := make([]byte, 32+32) // no more capacity\n\tcopy(pfsKey, mlkem768Key)\n\tcopy(pfsKey[32:], x25519Key)\n\tpfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...)\n\tc.UnitedKey = append(pfsKey, nfsKey...)\n\tc.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)\n\tc.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES)\n\n\tticket := [16]byte{}\n\trand.Read(ticket[:])\n\tvar seconds int64\n\tif i.SecondsTo == 0 {\n\t\tseconds = i.SecondsFrom * crypto.RandBetween(50, 100) / 100\n\t} else {\n\t\tseconds = crypto.RandBetween(i.SecondsFrom, i.SecondsTo)\n\t}\n\tcopy(ticket[:], EncodeLength(int(seconds)))\n\tif seconds > 0 {\n\t\ti.RWLock.Lock()\n\t\ti.Lasts[(time.Now().Unix()+max(i.SecondsFrom, i.SecondsTo))/60+2] = ticket\n\t\ti.Tickets = append(i.Tickets, ticket)\n\t\ti.Sessions[ticket] = &ServerSession{PfsKey: pfsKey}\n\t\ti.RWLock.Unlock()\n\t}\n\n\tpfsKeyExchangeLength := 1088 + 32 + 16\n\tencryptedTicketLength := 32\n\tpaddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)\n\tserverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)\n\tnfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)\n\tc.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket[:], nil)\n\tpadding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]\n\tc.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)\n\tc.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)\n\n\tpaddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0]\n\tfor i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control\n\t\tif l > 0 {\n\t\t\tif _, err := conn.Write(serverHello[:l]); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tserverHello = serverHello[l:]\n\t\t}\n\t\tif len(paddingGaps) > i {\n\t\t\ttime.Sleep(paddingGaps[i])\n\t\t}\n\t}\n\n\t// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern\n\tif _, err := io.ReadFull(conn, encryptedLength); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := nfsAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {\n\t\treturn nil, err\n\t}\n\tencryptedPadding := make([]byte, DecodeLength(encryptedLength[:2]))\n\tif _, err := io.ReadFull(conn, encryptedPadding); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := nfsAEAD.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif i.XorMode == 2 {\n\t\tc.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket[:]), NewCTR(c.UnitedKey, iv), 0, 0)\n\t}\n\treturn c, nil\n}\n"
  },
  {
    "path": "proxy/vless/encryption/xor.go",
    "content": "package encryption\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"net\"\n\n\t\"lukechampine.com/blake3\"\n)\n\nfunc NewCTR(key, iv []byte) cipher.Stream {\n\tk := make([]byte, 32)\n\tblake3.DeriveKey(k, \"VLESS\", key) // avoids using key directly\n\tblock, _ := aes.NewCipher(k)\n\treturn cipher.NewCTR(block, iv)\n\t//chacha20.NewUnauthenticatedCipher()\n}\n\ntype XorConn struct {\n\tnet.Conn\n\tCTR       cipher.Stream\n\tPeerCTR   cipher.Stream\n\tOutSkip   int\n\tOutHeader []byte\n\tInSkip    int\n\tInHeader  []byte\n}\n\nfunc NewXorConn(conn net.Conn, ctr, peerCTR cipher.Stream, outSkip, inSkip int) *XorConn {\n\treturn &XorConn{\n\t\tConn:      conn,\n\t\tCTR:       ctr,\n\t\tPeerCTR:   peerCTR,\n\t\tOutSkip:   outSkip,\n\t\tOutHeader: make([]byte, 0, 5), // important\n\t\tInSkip:    inSkip,\n\t\tInHeader:  make([]byte, 0, 5), // important\n\t}\n}\n\nfunc (c *XorConn) Write(b []byte) (int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\tfor p := b; ; {\n\t\tif len(p) <= c.OutSkip {\n\t\t\tc.OutSkip -= len(p)\n\t\t\tbreak\n\t\t}\n\t\tp = p[c.OutSkip:]\n\t\tc.OutSkip = 0\n\t\tneed := 5 - len(c.OutHeader)\n\t\tif len(p) < need {\n\t\t\tc.OutHeader = append(c.OutHeader, p...)\n\t\t\tc.CTR.XORKeyStream(p, p)\n\t\t\tbreak\n\t\t}\n\t\tc.OutSkip, _ = DecodeHeader(append(c.OutHeader, p[:need]...))\n\t\tc.OutHeader = c.OutHeader[:0]\n\t\tc.CTR.XORKeyStream(p[:need], p[:need])\n\t\tp = p[need:]\n\t}\n\tif _, err := c.Conn.Write(b); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(b), nil\n}\n\nfunc (c *XorConn) Read(b []byte) (int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\tn, err := c.Conn.Read(b)\n\tfor p := b[:n]; ; {\n\t\tif len(p) <= c.InSkip {\n\t\t\tc.InSkip -= len(p)\n\t\t\tbreak\n\t\t}\n\t\tp = p[c.InSkip:]\n\t\tc.InSkip = 0\n\t\tneed := 5 - len(c.InHeader)\n\t\tif len(p) < need {\n\t\t\tc.PeerCTR.XORKeyStream(p, p)\n\t\t\tc.InHeader = append(c.InHeader, p...)\n\t\t\tbreak\n\t\t}\n\t\tc.PeerCTR.XORKeyStream(p[:need], p[:need])\n\t\tc.InSkip, _ = DecodeHeader(append(c.InHeader, p[:need]...))\n\t\tc.InHeader = c.InHeader[:0]\n\t\tp = p[need:]\n\t}\n\treturn n, err\n}\n"
  },
  {
    "path": "proxy/vless/inbound/config.go",
    "content": "package inbound\n"
  },
  {
    "path": "proxy/vless/inbound/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vless/inbound/config.proto\n\npackage inbound\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Fallback struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tAlpn          string                 `protobuf:\"bytes,2,opt,name=alpn,proto3\" json:\"alpn,omitempty\"`\n\tPath          string                 `protobuf:\"bytes,3,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tType          string                 `protobuf:\"bytes,4,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tDest          string                 `protobuf:\"bytes,5,opt,name=dest,proto3\" json:\"dest,omitempty\"`\n\tXver          uint64                 `protobuf:\"varint,6,opt,name=xver,proto3\" json:\"xver,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Fallback) Reset() {\n\t*x = Fallback{}\n\tmi := &file_proxy_vless_inbound_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Fallback) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Fallback) ProtoMessage() {}\n\nfunc (x *Fallback) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vless_inbound_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Fallback.ProtoReflect.Descriptor instead.\nfunc (*Fallback) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Fallback) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetAlpn() string {\n\tif x != nil {\n\t\treturn x.Alpn\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetDest() string {\n\tif x != nil {\n\t\treturn x.Dest\n\t}\n\treturn \"\"\n}\n\nfunc (x *Fallback) GetXver() uint64 {\n\tif x != nil {\n\t\treturn x.Xver\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClients       []*protocol.User       `protobuf:\"bytes,1,rep,name=clients,proto3\" json:\"clients,omitempty\"`\n\tFallbacks     []*Fallback            `protobuf:\"bytes,2,rep,name=fallbacks,proto3\" json:\"fallbacks,omitempty\"`\n\tDecryption    string                 `protobuf:\"bytes,3,opt,name=decryption,proto3\" json:\"decryption,omitempty\"`\n\tXorMode       uint32                 `protobuf:\"varint,4,opt,name=xorMode,proto3\" json:\"xorMode,omitempty\"`\n\tSecondsFrom   int64                  `protobuf:\"varint,5,opt,name=seconds_from,json=secondsFrom,proto3\" json:\"seconds_from,omitempty\"`\n\tSecondsTo     int64                  `protobuf:\"varint,6,opt,name=seconds_to,json=secondsTo,proto3\" json:\"seconds_to,omitempty\"`\n\tPadding       string                 `protobuf:\"bytes,7,opt,name=padding,proto3\" json:\"padding,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_vless_inbound_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vless_inbound_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vless_inbound_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Config) GetClients() []*protocol.User {\n\tif x != nil {\n\t\treturn x.Clients\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetFallbacks() []*Fallback {\n\tif x != nil {\n\t\treturn x.Fallbacks\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetDecryption() string {\n\tif x != nil {\n\t\treturn x.Decryption\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetXorMode() uint32 {\n\tif x != nil {\n\t\treturn x.XorMode\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetSecondsFrom() int64 {\n\tif x != nil {\n\t\treturn x.SecondsFrom\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetSecondsTo() int64 {\n\tif x != nil {\n\t\treturn x.SecondsTo\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetPadding() string {\n\tif x != nil {\n\t\treturn x.Padding\n\t}\n\treturn \"\"\n}\n\nvar File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_vless_inbound_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\" proxy/vless/inbound/config.proto\\x12\\x18xray.proxy.vless.inbound\\x1a\\x1acommon/protocol/user.proto\\\"\\x82\\x01\\n\" +\n\t\"\\bFallback\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x12\\n\" +\n\t\"\\x04alpn\\x18\\x02 \\x01(\\tR\\x04alpn\\x12\\x12\\n\" +\n\t\"\\x04path\\x18\\x03 \\x01(\\tR\\x04path\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x04 \\x01(\\tR\\x04type\\x12\\x12\\n\" +\n\t\"\\x04dest\\x18\\x05 \\x01(\\tR\\x04dest\\x12\\x12\\n\" +\n\t\"\\x04xver\\x18\\x06 \\x01(\\x04R\\x04xver\\\"\\x96\\x02\\n\" +\n\t\"\\x06Config\\x124\\n\" +\n\t\"\\aclients\\x18\\x01 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\aclients\\x12@\\n\" +\n\t\"\\tfallbacks\\x18\\x02 \\x03(\\v2\\\".xray.proxy.vless.inbound.FallbackR\\tfallbacks\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"decryption\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"decryption\\x12\\x18\\n\" +\n\t\"\\axorMode\\x18\\x04 \\x01(\\rR\\axorMode\\x12!\\n\" +\n\t\"\\fseconds_from\\x18\\x05 \\x01(\\x03R\\vsecondsFrom\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"seconds_to\\x18\\x06 \\x01(\\x03R\\tsecondsTo\\x12\\x18\\n\" +\n\t\"\\apadding\\x18\\a \\x01(\\tR\\apaddingBj\\n\" +\n\t\"\\x1ccom.xray.proxy.vless.inboundP\\x01Z-github.com/xtls/xray-core/proxy/vless/inbound\\xaa\\x02\\x18Xray.Proxy.Vless.Inboundb\\x06proto3\"\n\nvar (\n\tfile_proxy_vless_inbound_config_proto_rawDescOnce sync.Once\n\tfile_proxy_vless_inbound_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_vless_inbound_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_vless_inbound_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vless_inbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vless_inbound_config_proto_rawDesc), len(file_proxy_vless_inbound_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_vless_inbound_config_proto_rawDescData\n}\n\nvar file_proxy_vless_inbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_proxy_vless_inbound_config_proto_goTypes = []any{\n\t(*Fallback)(nil),      // 0: xray.proxy.vless.inbound.Fallback\n\t(*Config)(nil),        // 1: xray.proxy.vless.inbound.Config\n\t(*protocol.User)(nil), // 2: xray.common.protocol.User\n}\nvar file_proxy_vless_inbound_config_proto_depIdxs = []int32{\n\t2, // 0: xray.proxy.vless.inbound.Config.clients:type_name -> xray.common.protocol.User\n\t0, // 1: xray.proxy.vless.inbound.Config.fallbacks:type_name -> xray.proxy.vless.inbound.Fallback\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vless_inbound_config_proto_init() }\nfunc file_proxy_vless_inbound_config_proto_init() {\n\tif File_proxy_vless_inbound_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vless_inbound_config_proto_rawDesc), len(file_proxy_vless_inbound_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vless_inbound_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vless_inbound_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vless_inbound_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vless_inbound_config_proto = out.File\n\tfile_proxy_vless_inbound_config_proto_goTypes = nil\n\tfile_proxy_vless_inbound_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vless/inbound/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vless.inbound;\noption csharp_namespace = \"Xray.Proxy.Vless.Inbound\";\noption go_package = \"github.com/xtls/xray-core/proxy/vless/inbound\";\noption java_package = \"com.xray.proxy.vless.inbound\";\noption java_multiple_files = true;\n\nimport \"common/protocol/user.proto\";\n\nmessage Fallback {\n  string name = 1;\n  string alpn = 2;\n  string path = 3;\n  string type = 4;\n  string dest = 5;\n  uint64 xver = 6;\n}\n\nmessage Config {\n  repeated xray.common.protocol.User clients = 1;\n  repeated Fallback fallbacks = 2;\n\n  string decryption = 3;\n  uint32 xorMode = 4;\n  int64 seconds_from = 5;\n  int64 seconds_to = 6;\n  string padding = 7;\n}\n"
  },
  {
    "path": "proxy/vless/inbound/inbound.go",
    "content": "package inbound\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/reverse\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\tfeature_inbound \"github.com/xtls/xray-core/features/inbound\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/features/stats\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t\"github.com/xtls/xray-core/proxy/vless/encoding\"\n\t\"github.com/xtls/xray-core/proxy/vless/encryption\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tvar dc dns.Client\n\t\tif err := core.RequireFeatures(ctx, func(d dns.Client) error {\n\t\t\tdc = d\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tc := config.(*Config)\n\n\t\tvalidator := new(vless.MemoryValidator)\n\t\tfor _, user := range c.Clients {\n\t\t\tu, err := user.ToMemoryUser()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to get VLESS user\").Base(err).AtError()\n\t\t\t}\n\t\t\tif err := validator.Add(u); err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to initiate user\").Base(err).AtError()\n\t\t\t}\n\t\t}\n\n\t\treturn New(ctx, c, dc, validator)\n\t}))\n}\n\n// Handler is an inbound connection handler that handles messages in VLess protocol.\ntype Handler struct {\n\tinboundHandlerManager  feature_inbound.Manager\n\tpolicyManager          policy.Manager\n\tstats                  stats.Manager\n\tvalidator              vless.Validator\n\tdecryption             *encryption.ServerInstance\n\toutboundHandlerManager outbound.Manager\n\tdefaultDispatcher      routing.Dispatcher\n\tctx                    context.Context\n\tfallbacks              map[string]map[string]map[string]*Fallback // or nil\n\t// regexps               map[string]*regexp.Regexp       // or nil\n}\n\n// New creates a new VLess inbound handler.\nfunc New(ctx context.Context, config *Config, dc dns.Client, validator vless.Validator) (*Handler, error) {\n\tv := core.MustFromContext(ctx)\n\thandler := &Handler{\n\t\tinboundHandlerManager:  v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager),\n\t\tpolicyManager:          v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tstats:                  v.GetFeature(stats.ManagerType()).(stats.Manager),\n\t\tvalidator:              validator,\n\t\toutboundHandlerManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),\n\t\tdefaultDispatcher:      v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),\n\t\tctx:                    ctx,\n\t}\n\n\tif config.Decryption != \"\" && config.Decryption != \"none\" {\n\t\ts := strings.Split(config.Decryption, \".\")\n\t\tvar nfsSKeysBytes [][]byte\n\t\tfor _, r := range s {\n\t\t\tb, _ := base64.RawURLEncoding.DecodeString(r)\n\t\t\tnfsSKeysBytes = append(nfsSKeysBytes, b)\n\t\t}\n\t\thandler.decryption = &encryption.ServerInstance{}\n\t\tif err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.SecondsFrom, config.SecondsTo, config.Padding); err != nil {\n\t\t\treturn nil, errors.New(\"failed to use decryption\").Base(err).AtError()\n\t\t}\n\t}\n\n\tif config.Fallbacks != nil {\n\t\thandler.fallbacks = make(map[string]map[string]map[string]*Fallback)\n\t\t// handler.regexps = make(map[string]*regexp.Regexp)\n\t\tfor _, fb := range config.Fallbacks {\n\t\t\tif handler.fallbacks[fb.Name] == nil {\n\t\t\t\thandler.fallbacks[fb.Name] = make(map[string]map[string]*Fallback)\n\t\t\t}\n\t\t\tif handler.fallbacks[fb.Name][fb.Alpn] == nil {\n\t\t\t\thandler.fallbacks[fb.Name][fb.Alpn] = make(map[string]*Fallback)\n\t\t\t}\n\t\t\thandler.fallbacks[fb.Name][fb.Alpn][fb.Path] = fb\n\t\t\t/*\n\t\t\t\tif fb.Path != \"\" {\n\t\t\t\t\tif r, err := regexp.Compile(fb.Path); err != nil {\n\t\t\t\t\t\treturn nil, errors.New(\"invalid path regexp\").Base(err).AtError()\n\t\t\t\t\t} else {\n\t\t\t\t\t\thandler.regexps[fb.Path] = r\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t*/\n\t\t}\n\t\tif handler.fallbacks[\"\"] != nil {\n\t\t\tfor name, apfb := range handler.fallbacks {\n\t\t\t\tif name != \"\" {\n\t\t\t\t\tfor alpn := range handler.fallbacks[\"\"] {\n\t\t\t\t\t\tif apfb[alpn] == nil {\n\t\t\t\t\t\t\tapfb[alpn] = make(map[string]*Fallback)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, apfb := range handler.fallbacks {\n\t\t\tif apfb[\"\"] != nil {\n\t\t\t\tfor alpn, pfb := range apfb {\n\t\t\t\t\tif alpn != \"\" { // && alpn != \"h2\" {\n\t\t\t\t\t\tfor path, fb := range apfb[\"\"] {\n\t\t\t\t\t\t\tif pfb[path] == nil {\n\t\t\t\t\t\t\t\tpfb[path] = fb\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif handler.fallbacks[\"\"] != nil {\n\t\t\tfor name, apfb := range handler.fallbacks {\n\t\t\t\tif name != \"\" {\n\t\t\t\t\tfor alpn, pfb := range handler.fallbacks[\"\"] {\n\t\t\t\t\t\tfor path, fb := range pfb {\n\t\t\t\t\t\t\tif apfb[alpn][path] == nil {\n\t\t\t\t\t\t\t\tapfb[alpn][path] = fb\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn handler, nil\n}\n\nfunc isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool {\n\tif request.Command != protocol.RequestCommandMux {\n\t\treturn false\n\t}\n\tif first.Len() < 7 {\n\t\treturn true\n\t}\n\tfirstBytes := first.Bytes()\n\treturn !(firstBytes[2] == 0 && // ID high\n\t\tfirstBytes[3] == 0 && // ID low\n\t\tfirstBytes[6] == 2) // Network type: UDP\n}\n\nfunc (h *Handler) GetReverse(a *vless.MemoryAccount) (*Reverse, error) {\n\tu := h.validator.Get(a.ID.UUID())\n\tif u == nil {\n\t\treturn nil, errors.New(\"reverse: user \" + a.ID.String() + \" doesn't exist anymore\")\n\t}\n\ta = u.Account.(*vless.MemoryAccount)\n\tif a.Reverse == nil || a.Reverse.Tag == \"\" {\n\t\treturn nil, errors.New(\"reverse: user \" + a.ID.String() + \" is not allowed to create reverse proxy\")\n\t}\n\tr := h.outboundHandlerManager.GetHandler(a.Reverse.Tag)\n\tif r == nil {\n\t\tpicker, _ := reverse.NewStaticMuxPicker()\n\t\tr = &Reverse{tag: a.Reverse.Tag, picker: picker, client: &mux.ClientManager{Picker: picker}}\n\t\tfor len(h.outboundHandlerManager.ListHandlers(h.ctx)) == 0 {\n\t\t\ttime.Sleep(time.Second) // prevents this outbound from becoming the default outbound\n\t\t}\n\t\tif err := h.outboundHandlerManager.AddHandler(h.ctx, r); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif r, ok := r.(*Reverse); ok {\n\t\treturn r, nil\n\t}\n\treturn nil, errors.New(\"reverse: outbound \" + a.Reverse.Tag + \" is not type Reverse\")\n}\n\nfunc (h *Handler) RemoveReverse(u *protocol.MemoryUser) {\n\tif u != nil {\n\t\ta := u.Account.(*vless.MemoryAccount)\n\t\tif a.Reverse != nil && a.Reverse.Tag != \"\" {\n\t\t\th.outboundHandlerManager.RemoveHandler(h.ctx, a.Reverse.Tag)\n\t\t}\n\t}\n}\n\n// Close implements common.Closable.Close().\nfunc (h *Handler) Close() error {\n\tif h.decryption != nil {\n\t\th.decryption.Close()\n\t}\n\tfor _, u := range h.validator.GetAll() {\n\t\th.RemoveReverse(u)\n\t}\n\treturn errors.Combine(common.Close(h.validator))\n}\n\n// AddUser implements proxy.UserManager.AddUser().\nfunc (h *Handler) AddUser(ctx context.Context, u *protocol.MemoryUser) error {\n\treturn h.validator.Add(u)\n}\n\n// RemoveUser implements proxy.UserManager.RemoveUser().\nfunc (h *Handler) RemoveUser(ctx context.Context, e string) error {\n\th.RemoveReverse(h.validator.GetByEmail(e))\n\treturn h.validator.Del(e)\n}\n\n// GetUser implements proxy.UserManager.GetUser().\nfunc (h *Handler) GetUser(ctx context.Context, email string) *protocol.MemoryUser {\n\treturn h.validator.GetByEmail(email)\n}\n\n// GetUsers implements proxy.UserManager.GetUsers().\nfunc (h *Handler) GetUsers(ctx context.Context) []*protocol.MemoryUser {\n\treturn h.validator.GetAll()\n}\n\n// GetUsersCount implements proxy.UserManager.GetUsersCount().\nfunc (h *Handler) GetUsersCount(context.Context) int64 {\n\treturn h.validator.GetCount()\n}\n\n// Network implements proxy.Inbound.Network().\nfunc (*Handler) Network() []net.Network {\n\treturn []net.Network{net.Network_TCP, net.Network_UNIX}\n}\n\n// Process implements proxy.Inbound.Process().\nfunc (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatch routing.Dispatcher) error {\n\tiConn := stat.TryUnwrapStatsConn(connection)\n\n\tif h.decryption != nil {\n\t\tvar err error\n\t\tif connection, err = h.decryption.Handshake(connection, nil); err != nil {\n\t\t\treturn errors.New(\"ML-KEM-768 handshake failed\").Base(err).AtInfo()\n\t\t}\n\t}\n\n\tsessionPolicy := h.policyManager.ForLevel(0)\n\tif err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {\n\t\treturn errors.New(\"unable to set read deadline\").Base(err).AtWarning()\n\t}\n\n\tfirst := buf.FromBytes(make([]byte, buf.Size))\n\tfirst.Clear()\n\tfirstLen, errR := first.ReadFrom(connection)\n\tif errR != nil {\n\t\treturn errR\n\t}\n\terrors.LogInfo(ctx, \"firstLen = \", firstLen)\n\n\treader := &buf.BufferedReader{\n\t\tReader: buf.NewReader(connection),\n\t\tBuffer: buf.MultiBuffer{first},\n\t}\n\n\tvar userSentID []byte // not MemoryAccount.ID\n\tvar request *protocol.RequestHeader\n\tvar requestAddons *encoding.Addons\n\tvar err error\n\n\tnapfb := h.fallbacks\n\tisfb := napfb != nil\n\n\tif isfb && firstLen < 18 {\n\t\terr = errors.New(\"fallback directly\")\n\t} else {\n\t\tuserSentID, request, requestAddons, isfb, err = encoding.DecodeRequestHeader(isfb, first, reader, h.validator)\n\t}\n\n\tif err != nil {\n\t\tif isfb {\n\t\t\tif err := connection.SetReadDeadline(time.Time{}); err != nil {\n\t\t\t\terrors.LogWarningInner(ctx, err, \"unable to set back read deadline\")\n\t\t\t}\n\t\t\terrors.LogInfoInner(ctx, err, \"fallback starts\")\n\n\t\t\tname := \"\"\n\t\t\talpn := \"\"\n\t\t\tif tlsConn, ok := iConn.(*tls.Conn); ok {\n\t\t\t\tcs := tlsConn.ConnectionState()\n\t\t\t\tname = cs.ServerName\n\t\t\t\talpn = cs.NegotiatedProtocol\n\t\t\t\terrors.LogInfo(ctx, \"realName = \"+name)\n\t\t\t\terrors.LogInfo(ctx, \"realAlpn = \"+alpn)\n\t\t\t} else if realityConn, ok := iConn.(*reality.Conn); ok {\n\t\t\t\tcs := realityConn.ConnectionState()\n\t\t\t\tname = cs.ServerName\n\t\t\t\talpn = cs.NegotiatedProtocol\n\t\t\t\terrors.LogInfo(ctx, \"realName = \"+name)\n\t\t\t\terrors.LogInfo(ctx, \"realAlpn = \"+alpn)\n\t\t\t}\n\t\t\tname = strings.ToLower(name)\n\t\t\talpn = strings.ToLower(alpn)\n\n\t\t\tif len(napfb) > 1 || napfb[\"\"] == nil {\n\t\t\t\tif name != \"\" && napfb[name] == nil {\n\t\t\t\t\tmatch := \"\"\n\t\t\t\t\tfor n := range napfb {\n\t\t\t\t\t\tif n != \"\" && strings.Contains(name, n) && len(n) > len(match) {\n\t\t\t\t\t\t\tmatch = n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tname = match\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif napfb[name] == nil {\n\t\t\t\tname = \"\"\n\t\t\t}\n\t\t\tapfb := napfb[name]\n\t\t\tif apfb == nil {\n\t\t\t\treturn errors.New(`failed to find the default \"name\" config`).AtWarning()\n\t\t\t}\n\n\t\t\tif apfb[alpn] == nil {\n\t\t\t\talpn = \"\"\n\t\t\t}\n\t\t\tpfb := apfb[alpn]\n\t\t\tif pfb == nil {\n\t\t\t\treturn errors.New(`failed to find the default \"alpn\" config`).AtWarning()\n\t\t\t}\n\n\t\t\tpath := \"\"\n\t\t\tif len(pfb) > 1 || pfb[\"\"] == nil {\n\t\t\t\t/*\n\t\t\t\t\tif lines := bytes.Split(firstBytes, []byte{'\\r', '\\n'}); len(lines) > 1 {\n\t\t\t\t\t\tif s := bytes.Split(lines[0], []byte{' '}); len(s) == 3 {\n\t\t\t\t\t\t\tif len(s[0]) < 8 && len(s[1]) > 0 && len(s[2]) == 8 {\n\t\t\t\t\t\t\t\terrors.New(\"realPath = \" + string(s[1])).AtInfo().WriteToLog(sid)\n\t\t\t\t\t\t\t\tfor _, fb := range pfb {\n\t\t\t\t\t\t\t\t\tif fb.Path != \"\" && h.regexps[fb.Path].Match(s[1]) {\n\t\t\t\t\t\t\t\t\t\tpath = fb.Path\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t*/\n\t\t\t\tif firstLen >= 18 && first.Byte(4) != '*' { // not h2c\n\t\t\t\t\tfirstBytes := first.Bytes()\n\t\t\t\t\tfor i := 4; i <= 8; i++ { // 5 -> 9\n\t\t\t\t\t\tif firstBytes[i] == '/' && firstBytes[i-1] == ' ' {\n\t\t\t\t\t\t\tsearch := len(firstBytes)\n\t\t\t\t\t\t\tif search > 64 {\n\t\t\t\t\t\t\t\tsearch = 64 // up to about 60\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor j := i + 1; j < search; j++ {\n\t\t\t\t\t\t\t\tk := firstBytes[j]\n\t\t\t\t\t\t\t\tif k == '\\r' || k == '\\n' { // avoid logging \\r or \\n\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif k == '?' || k == ' ' {\n\t\t\t\t\t\t\t\t\tpath = string(firstBytes[i:j])\n\t\t\t\t\t\t\t\t\terrors.LogInfo(ctx, \"realPath = \"+path)\n\t\t\t\t\t\t\t\t\tif pfb[path] == nil {\n\t\t\t\t\t\t\t\t\t\tpath = \"\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfb := pfb[path]\n\t\t\tif fb == nil {\n\t\t\t\treturn errors.New(`failed to find the default \"path\" config`).AtWarning()\n\t\t\t}\n\n\t\t\tctx, cancel := context.WithCancel(ctx)\n\t\t\ttimer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)\n\t\t\tctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)\n\n\t\t\tvar conn net.Conn\n\t\t\tif err := retry.ExponentialBackoff(5, 100).On(func() error {\n\t\t\t\tvar dialer net.Dialer\n\t\t\t\tconn, err = dialer.DialContext(ctx, fb.Type, fb.Dest)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}); err != nil {\n\t\t\t\treturn errors.New(\"failed to dial to \" + fb.Dest).Base(err).AtWarning()\n\t\t\t}\n\t\t\tdefer conn.Close()\n\n\t\t\tserverReader := buf.NewReader(conn)\n\t\t\tserverWriter := buf.NewWriter(conn)\n\n\t\t\tpostRequest := func() error {\n\t\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\t\t\t\tif fb.Xver != 0 {\n\t\t\t\t\tipType := 4\n\t\t\t\t\tremoteAddr, remotePort, err := net.SplitHostPort(connection.RemoteAddr().String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tipType = 0\n\t\t\t\t\t}\n\t\t\t\t\tlocalAddr, localPort, err := net.SplitHostPort(connection.LocalAddr().String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tipType = 0\n\t\t\t\t\t}\n\t\t\t\t\tif ipType == 4 {\n\t\t\t\t\t\tfor i := 0; i < len(remoteAddr); i++ {\n\t\t\t\t\t\t\tif remoteAddr[i] == ':' {\n\t\t\t\t\t\t\t\tipType = 6\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpro := buf.New()\n\t\t\t\t\tdefer pro.Release()\n\t\t\t\t\tswitch fb.Xver {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tif ipType == 0 {\n\t\t\t\t\t\t\tpro.Write([]byte(\"PROXY UNKNOWN\\r\\n\"))\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ipType == 4 {\n\t\t\t\t\t\t\tpro.Write([]byte(\"PROXY TCP4 \" + remoteAddr + \" \" + localAddr + \" \" + remotePort + \" \" + localPort + \"\\r\\n\"))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpro.Write([]byte(\"PROXY TCP6 \" + remoteAddr + \" \" + localAddr + \" \" + remotePort + \" \" + localPort + \"\\r\\n\"))\n\t\t\t\t\t\t}\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tpro.Write([]byte(\"\\x0D\\x0A\\x0D\\x0A\\x00\\x0D\\x0A\\x51\\x55\\x49\\x54\\x0A\")) // signature\n\t\t\t\t\t\tif ipType == 0 {\n\t\t\t\t\t\t\tpro.Write([]byte(\"\\x20\\x00\\x00\\x00\")) // v2 + LOCAL + UNSPEC + UNSPEC + 0 bytes\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ipType == 4 {\n\t\t\t\t\t\t\tpro.Write([]byte(\"\\x21\\x11\\x00\\x0C\")) // v2 + PROXY + AF_INET + STREAM + 12 bytes\n\t\t\t\t\t\t\tpro.Write(net.ParseIP(remoteAddr).To4())\n\t\t\t\t\t\t\tpro.Write(net.ParseIP(localAddr).To4())\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpro.Write([]byte(\"\\x21\\x21\\x00\\x24\")) // v2 + PROXY + AF_INET6 + STREAM + 36 bytes\n\t\t\t\t\t\t\tpro.Write(net.ParseIP(remoteAddr).To16())\n\t\t\t\t\t\t\tpro.Write(net.ParseIP(localAddr).To16())\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp1, _ := strconv.ParseUint(remotePort, 10, 16)\n\t\t\t\t\t\tp2, _ := strconv.ParseUint(localPort, 10, 16)\n\t\t\t\t\t\tpro.Write([]byte{byte(p1 >> 8), byte(p1), byte(p2 >> 8), byte(p2)})\n\t\t\t\t\t}\n\t\t\t\t\tif err := serverWriter.WriteMultiBuffer(buf.MultiBuffer{pro}); err != nil {\n\t\t\t\t\t\treturn errors.New(\"failed to set PROXY protocol v\", fb.Xver).Base(err).AtWarning()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err := buf.Copy(reader, serverWriter, buf.UpdateActivity(timer)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to fallback request payload\").Base(err).AtInfo()\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\twriter := buf.NewWriter(connection)\n\n\t\t\tgetResponse := func() error {\n\t\t\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\t\t\t\tif err := buf.Copy(serverReader, writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to deliver response payload\").Base(err).AtInfo()\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif err := task.Run(ctx, task.OnSuccess(postRequest, task.Close(serverWriter)), task.OnSuccess(getResponse, task.Close(writer))); err != nil {\n\t\t\t\tcommon.Interrupt(serverReader)\n\t\t\t\tcommon.Interrupt(serverWriter)\n\t\t\t\treturn errors.New(\"fallback ends\").Base(err).AtInfo()\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tif errors.Cause(err) != io.EOF {\n\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\tFrom:   connection.RemoteAddr(),\n\t\t\t\tTo:     \"\",\n\t\t\t\tStatus: log.AccessRejected,\n\t\t\t\tReason: err,\n\t\t\t})\n\t\t\terr = errors.New(\"invalid request from \", connection.RemoteAddr()).Base(err).AtInfo()\n\t\t}\n\t\treturn err\n\t}\n\n\tif err := connection.SetReadDeadline(time.Time{}); err != nil {\n\t\terrors.LogWarningInner(ctx, err, \"unable to set back read deadline\")\n\t}\n\terrors.LogInfo(ctx, \"received request for \", request.Destination())\n\n\tinbound := session.InboundFromContext(ctx)\n\tif inbound == nil {\n\t\tpanic(\"no inbound metadata\")\n\t}\n\tinbound.Name = \"vless\"\n\tinbound.User = request.User\n\tinbound.VlessRoute = net.PortFromBytes(userSentID[6:8])\n\n\taccount := request.User.Account.(*vless.MemoryAccount)\n\n\tif account.Reverse != nil && request.Command != protocol.RequestCommandRvs {\n\t\treturn errors.New(\"for safety reasons, user \" + account.ID.String() + \" is not allowed to use forward proxy\")\n\t}\n\n\tresponseAddons := &encoding.Addons{\n\t\t// Flow: requestAddons.Flow,\n\t}\n\n\tvar input *bytes.Reader\n\tvar rawInput *bytes.Buffer\n\tswitch requestAddons.Flow {\n\tcase vless.XRV:\n\t\tif account.Flow == requestAddons.Flow {\n\t\t\tinbound.CanSpliceCopy = 2\n\t\t\tswitch request.Command {\n\t\t\tcase protocol.RequestCommandUDP:\n\t\t\t\treturn errors.New(requestAddons.Flow + \" doesn't support UDP\").AtWarning()\n\t\t\tcase protocol.RequestCommandMux, protocol.RequestCommandRvs:\n\t\t\t\tinbound.CanSpliceCopy = 3\n\t\t\t\tfallthrough // we will break Mux connections that contain TCP requests\n\t\t\tcase protocol.RequestCommandTCP:\n\t\t\t\tvar t reflect.Type\n\t\t\t\tvar p uintptr\n\t\t\t\tif commonConn, ok := connection.(*encryption.CommonConn); ok {\n\t\t\t\t\tif _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {\n\t\t\t\t\t\tinbound.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated\n\t\t\t\t\t}\n\t\t\t\t\tt = reflect.TypeOf(commonConn).Elem()\n\t\t\t\t\tp = uintptr(unsafe.Pointer(commonConn))\n\t\t\t\t} else if tlsConn, ok := iConn.(*tls.Conn); ok {\n\t\t\t\t\tif tlsConn.ConnectionState().Version != gotls.VersionTLS13 {\n\t\t\t\t\t\treturn errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning()\n\t\t\t\t\t}\n\t\t\t\t\tt = reflect.TypeOf(tlsConn.Conn).Elem()\n\t\t\t\t\tp = uintptr(unsafe.Pointer(tlsConn.Conn))\n\t\t\t\t} else if realityConn, ok := iConn.(*reality.Conn); ok {\n\t\t\t\t\tt = reflect.TypeOf(realityConn.Conn).Elem()\n\t\t\t\t\tp = uintptr(unsafe.Pointer(realityConn.Conn))\n\t\t\t\t} else {\n\t\t\t\t\treturn errors.New(\"XTLS only supports TLS and REALITY directly for now.\").AtWarning()\n\t\t\t\t}\n\t\t\t\ti, _ := t.FieldByName(\"input\")\n\t\t\t\tr, _ := t.FieldByName(\"rawInput\")\n\t\t\t\tinput = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))\n\t\t\t\trawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))\n\t\t\t}\n\t\t} else {\n\t\t\treturn errors.New(\"account \" + account.ID.String() + \" is not able to use the flow \" + requestAddons.Flow).AtWarning()\n\t\t}\n\tcase \"\":\n\t\tinbound.CanSpliceCopy = 3\n\t\tif account.Flow == vless.XRV && (request.Command == protocol.RequestCommandTCP || isMuxAndNotXUDP(request, first)) {\n\t\t\treturn errors.New(\"account \" + account.ID.String() + \" is rejected since the client flow is empty. Note that the pure TLS proxy has certain TLS in TLS characters.\").AtWarning()\n\t\t}\n\tdefault:\n\t\treturn errors.New(\"unknown request flow \" + requestAddons.Flow).AtWarning()\n\t}\n\n\tif request.Command != protocol.RequestCommandMux {\n\t\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\tFrom:   connection.RemoteAddr(),\n\t\t\tTo:     request.Destination(),\n\t\t\tStatus: log.AccessAccepted,\n\t\t\tReason: \"\",\n\t\t\tEmail:  request.User.Email,\n\t\t})\n\t} else if account.Flow == vless.XRV {\n\t\tctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP)\n\t}\n\n\ttrafficState := proxy.NewTrafficState(userSentID)\n\tclientReader := encoding.DecodeBodyAddons(reader, request, requestAddons)\n\tif requestAddons.Flow == vless.XRV {\n\t\tclientReader = proxy.NewVisionReader(clientReader, trafficState, true, ctx, connection, input, rawInput, nil)\n\t}\n\n\tbufferWriter := buf.NewBufferedWriter(buf.NewWriter(connection))\n\tif err := encoding.EncodeResponseHeader(bufferWriter, request, responseAddons); err != nil {\n\t\treturn errors.New(\"failed to encode response header\").Base(err).AtWarning()\n\t}\n\tclientWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, false, ctx, connection, nil)\n\tbufferWriter.SetFlushNext()\n\n\tif request.Command == protocol.RequestCommandRvs {\n\t\tr, err := h.GetReverse(account)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn r.NewMux(ctx, dispatcher.WrapLink(ctx, h.policyManager, h.stats, &transport.Link{Reader: clientReader, Writer: clientWriter}))\n\t}\n\n\tif err := dispatch.DispatchLink(ctx, request.Destination(), &transport.Link{\n\t\tReader: clientReader,\n\t\tWriter: clientWriter},\n\t); err != nil {\n\t\treturn errors.New(\"failed to dispatch request\").Base(err)\n\t}\n\treturn nil\n}\n\ntype Reverse struct {\n\ttag    string\n\tpicker *reverse.StaticMuxPicker\n\tclient *mux.ClientManager\n}\n\nfunc (r *Reverse) Tag() string {\n\treturn r.tag\n}\n\nfunc (r *Reverse) NewMux(ctx context.Context, link *transport.Link) error {\n\tmuxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})\n\tif err != nil {\n\t\treturn errors.New(\"failed to create mux client worker\").Base(err).AtWarning()\n\t}\n\tworker, err := reverse.NewPortalWorker(muxClient)\n\tif err != nil {\n\t\treturn errors.New(\"failed to create portal worker\").Base(err).AtWarning()\n\t}\n\tr.picker.AddWorker(worker)\n\tselect {\n\tcase <-ctx.Done():\n\tcase <-muxClient.WaitClosed():\n\t}\n\treturn nil\n}\n\nfunc (r *Reverse) Dispatch(ctx context.Context, link *transport.Link) {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif ob != nil {\n\t\tif ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address {\n\t\t\tlink.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}\n\t\t\tlink.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}\n\t\t}\n\t\tr.client.Dispatch(session.ContextWithIsReverseMux(ctx, true), link)\n\t}\n}\n\nfunc (r *Reverse) Start() error {\n\treturn nil\n}\n\nfunc (r *Reverse) Close() error {\n\treturn nil\n}\n\nfunc (r *Reverse) SenderSettings() *serial.TypedMessage {\n\treturn nil\n}\n\nfunc (r *Reverse) ProxySettings() *serial.TypedMessage {\n\treturn nil\n}\n"
  },
  {
    "path": "proxy/vless/outbound/config.go",
    "content": "package outbound\n"
  },
  {
    "path": "proxy/vless/outbound/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vless/outbound/config.proto\n\npackage outbound\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tVnext         *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=vnext,proto3\" json:\"vnext,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_vless_outbound_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vless_outbound_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vless_outbound_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetVnext() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Vnext\n\t}\n\treturn nil\n}\n\nvar File_proxy_vless_outbound_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_vless_outbound_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!proxy/vless/outbound/config.proto\\x12\\x19xray.proxy.vless.outbound\\x1a!common/protocol/server_spec.proto\\\"D\\n\" +\n\t\"\\x06Config\\x12:\\n\" +\n\t\"\\x05vnext\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\x05vnextBm\\n\" +\n\t\"\\x1dcom.xray.proxy.vless.outboundP\\x01Z.github.com/xtls/xray-core/proxy/vless/outbound\\xaa\\x02\\x19Xray.Proxy.Vless.Outboundb\\x06proto3\"\n\nvar (\n\tfile_proxy_vless_outbound_config_proto_rawDescOnce sync.Once\n\tfile_proxy_vless_outbound_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_vless_outbound_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_vless_outbound_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vless_outbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vless_outbound_config_proto_rawDesc), len(file_proxy_vless_outbound_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_vless_outbound_config_proto_rawDescData\n}\n\nvar file_proxy_vless_outbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_vless_outbound_config_proto_goTypes = []any{\n\t(*Config)(nil),                  // 0: xray.proxy.vless.outbound.Config\n\t(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint\n}\nvar file_proxy_vless_outbound_config_proto_depIdxs = []int32{\n\t1, // 0: xray.proxy.vless.outbound.Config.vnext:type_name -> xray.common.protocol.ServerEndpoint\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vless_outbound_config_proto_init() }\nfunc file_proxy_vless_outbound_config_proto_init() {\n\tif File_proxy_vless_outbound_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vless_outbound_config_proto_rawDesc), len(file_proxy_vless_outbound_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vless_outbound_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vless_outbound_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vless_outbound_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vless_outbound_config_proto = out.File\n\tfile_proxy_vless_outbound_config_proto_goTypes = nil\n\tfile_proxy_vless_outbound_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vless/outbound/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vless.outbound;\noption csharp_namespace = \"Xray.Proxy.Vless.Outbound\";\noption go_package = \"github.com/xtls/xray-core/proxy/vless/outbound\";\noption java_package = \"com.xray.proxy.vless.outbound\";\noption java_multiple_files = true;\n\nimport \"common/protocol/server_spec.proto\";\n\nmessage Config {\n  xray.common.protocol.ServerEndpoint vnext = 1;\n}\n"
  },
  {
    "path": "proxy/vless/outbound/outbound.go",
    "content": "package outbound\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"encoding/base64\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\t\"unsafe\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\tproxyman \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\t\"github.com/xtls/xray-core/app/reverse\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\txctx \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/mux\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/xudp\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t\"github.com/xtls/xray-core/proxy/vless/encoding\"\n\t\"github.com/xtls/xray-core/proxy/vless/encryption\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n\n// Handler is an outbound connection handler for VLess protocol.\ntype Handler struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n\tcone          bool\n\tencryption    *encryption.ClientInstance\n\treverse       *Reverse\n\n\ttestpre  uint32\n\tinitpre  sync.Once\n\tpreConns chan *ConnExpire\n}\n\ntype ConnExpire struct {\n\tConn   stat.Connection\n\tExpire time.Time\n}\n\n// New creates a new VLess outbound handler.\nfunc New(ctx context.Context, config *Config) (*Handler, error) {\n\tif config.Vnext == nil {\n\t\treturn nil, errors.New(`no vnext found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Vnext)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err).AtError()\n\t}\n\n\tv := core.MustFromContext(ctx)\n\thandler := &Handler{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tcone:          ctx.Value(\"cone\").(bool),\n\t}\n\n\ta := handler.server.User.Account.(*vless.MemoryAccount)\n\tif a.Encryption != \"\" && a.Encryption != \"none\" {\n\t\ts := strings.Split(a.Encryption, \".\")\n\t\tvar nfsPKeysBytes [][]byte\n\t\tfor _, r := range s {\n\t\t\tb, _ := base64.RawURLEncoding.DecodeString(r)\n\t\t\tnfsPKeysBytes = append(nfsPKeysBytes, b)\n\t\t}\n\t\thandler.encryption = &encryption.ClientInstance{}\n\t\tif err := handler.encryption.Init(nfsPKeysBytes, a.XorMode, a.Seconds, a.Padding); err != nil {\n\t\t\treturn nil, errors.New(\"failed to use encryption\").Base(err).AtError()\n\t\t}\n\t}\n\n\tif a.Reverse != nil {\n\t\thandler.reverse = &Reverse{\n\t\t\ttag:        a.Reverse.Tag,\n\t\t\tdispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),\n\t\t\tctx: session.ContextWithInbound(ctx, &session.Inbound{\n\t\t\t\tTag:  a.Reverse.Tag,\n\t\t\t\tUser: handler.server.User, // TODO: email\n\t\t\t}),\n\t\t\thandler: handler,\n\t\t}\n\t\thandler.reverse.monitorTask = &task.Periodic{\n\t\t\tExecute:  handler.reverse.monitor,\n\t\t\tInterval: time.Second * 2,\n\t\t}\n\t\tgo func() {\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\thandler.reverse.Start()\n\t\t}()\n\t}\n\n\thandler.testpre = a.Testpre\n\n\treturn handler, nil\n}\n\n// Close implements common.Closable.Close().\nfunc (h *Handler) Close() error {\n\tif h.preConns != nil {\n\t\tclose(h.preConns)\n\t}\n\tif h.reverse != nil {\n\t\treturn h.reverse.Close()\n\t}\n\treturn nil\n}\n\n// Process implements proxy.Outbound.Process().\nfunc (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() && ob.Target.Address.String() != \"v1.rvs.cool\" {\n\t\treturn errors.New(\"target not specified\").AtError()\n\t}\n\tob.Name = \"vless\"\n\n\trec := h.server\n\tvar conn stat.Connection\n\n\tif h.testpre > 0 && h.reverse == nil {\n\t\th.initpre.Do(func() {\n\t\t\th.preConns = make(chan *ConnExpire)\n\t\t\tfor range h.testpre { // TODO: randomize\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer func() { recover() }()\n\t\t\t\t\tctx := xctx.ContextWithID(context.Background(), session.NewID())\n\t\t\t\t\tfor {\n\t\t\t\t\t\tconn, err := dialer.Dial(ctx, rec.Destination)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\terrors.LogWarningInner(ctx, err, \"pre-connect failed\")\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\th.preConns <- &ConnExpire{Conn: conn, Expire: time.Now().Add(time.Minute * 2)} // TODO: customize & randomize\n\t\t\t\t\t\ttime.Sleep(time.Millisecond * 200)                                             // TODO: customize & randomize\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t})\n\t\tfor {\n\t\t\tconnTime := <-h.preConns\n\t\t\tif connTime == nil {\n\t\t\t\treturn errors.New(\"closed handler\").AtWarning()\n\t\t\t}\n\t\t\tif time.Now().Before(connTime.Expire) {\n\t\t\t\tconn = connTime.Conn\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tconnTime.Conn.Close()\n\t\t}\n\t}\n\n\tif conn == nil {\n\t\tif err := retry.ExponentialBackoff(5, 200).On(func() error {\n\t\t\tvar err error\n\t\t\tconn, err = dialer.Dial(ctx, rec.Destination)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn errors.New(\"failed to find an available destination\").Base(err).AtWarning()\n\t\t}\n\t}\n\tdefer conn.Close()\n\n\tob.Conn = conn // for Vision's pre-connect\n\n\tiConn := stat.TryUnwrapStatsConn(conn)\n\ttarget := ob.Target\n\terrors.LogInfo(ctx, \"tunneling request to \", target, \" via \", rec.Destination.NetAddr())\n\n\tif h.encryption != nil {\n\t\tvar err error\n\t\tif conn, err = h.encryption.Handshake(conn); err != nil {\n\t\t\treturn errors.New(\"ML-KEM-768 handshake failed\").Base(err).AtInfo()\n\t\t}\n\t}\n\n\tcommand := protocol.RequestCommandTCP\n\tif target.Network == net.Network_UDP {\n\t\tcommand = protocol.RequestCommandUDP\n\t}\n\tif target.Address.Family().IsDomain() {\n\t\tswitch target.Address.Domain() {\n\t\tcase \"v1.mux.cool\":\n\t\t\tcommand = protocol.RequestCommandMux\n\t\tcase \"v1.rvs.cool\":\n\t\t\tif target.Network != net.Network_Unknown {\n\t\t\t\treturn errors.New(\"nice try baby\").AtError()\n\t\t\t}\n\t\t\tcommand = protocol.RequestCommandRvs\n\t\t}\n\t}\n\n\trequest := &protocol.RequestHeader{\n\t\tVersion: encoding.Version,\n\t\tUser:    rec.User,\n\t\tCommand: command,\n\t\tAddress: target.Address,\n\t\tPort:    target.Port,\n\t}\n\n\taccount := request.User.Account.(*vless.MemoryAccount)\n\n\trequestAddons := &encoding.Addons{\n\t\tFlow: account.Flow,\n\t}\n\n\tvar input *bytes.Reader\n\tvar rawInput *bytes.Buffer\n\tallowUDP443 := false\n\tswitch requestAddons.Flow {\n\tcase vless.XRV + \"-udp443\":\n\t\tallowUDP443 = true\n\t\trequestAddons.Flow = requestAddons.Flow[:16]\n\t\tfallthrough\n\tcase vless.XRV:\n\t\tob.CanSpliceCopy = 2\n\t\tswitch request.Command {\n\t\tcase protocol.RequestCommandUDP:\n\t\t\tif !allowUDP443 && request.Port == 443 {\n\t\t\t\treturn errors.New(\"XTLS rejected UDP/443 traffic\").AtInfo()\n\t\t\t}\n\t\tcase protocol.RequestCommandMux:\n\t\t\tfallthrough // let server break Mux connections that contain TCP requests\n\t\tcase protocol.RequestCommandTCP, protocol.RequestCommandRvs:\n\t\t\tvar t reflect.Type\n\t\t\tvar p uintptr\n\t\t\tif commonConn, ok := conn.(*encryption.CommonConn); ok {\n\t\t\t\tif _, ok := commonConn.Conn.(*encryption.XorConn); ok || !proxy.IsRAWTransportWithoutSecurity(iConn) {\n\t\t\t\t\tob.CanSpliceCopy = 3 // full-random xorConn / non-RAW transport / another securityConn should not be penetrated\n\t\t\t\t}\n\t\t\t\tt = reflect.TypeOf(commonConn).Elem()\n\t\t\t\tp = uintptr(unsafe.Pointer(commonConn))\n\t\t\t} else if tlsConn, ok := iConn.(*tls.Conn); ok {\n\t\t\t\tt = reflect.TypeOf(tlsConn.Conn).Elem()\n\t\t\t\tp = uintptr(unsafe.Pointer(tlsConn.Conn))\n\t\t\t} else if utlsConn, ok := iConn.(*tls.UConn); ok {\n\t\t\t\tt = reflect.TypeOf(utlsConn.Conn).Elem()\n\t\t\t\tp = uintptr(unsafe.Pointer(utlsConn.Conn))\n\t\t\t} else if realityConn, ok := iConn.(*reality.UConn); ok {\n\t\t\t\tt = reflect.TypeOf(realityConn.Conn).Elem()\n\t\t\t\tp = uintptr(unsafe.Pointer(realityConn.Conn))\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"XTLS only supports TLS and REALITY directly for now.\").AtWarning()\n\t\t\t}\n\t\t\ti, _ := t.FieldByName(\"input\")\n\t\t\tr, _ := t.FieldByName(\"rawInput\")\n\t\t\tinput = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))\n\t\t\trawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))\n\t\tdefault:\n\t\t\tpanic(\"unknown VLESS request command\")\n\t\t}\n\tdefault:\n\t\tob.CanSpliceCopy = 3\n\t}\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tsessionPolicy := h.policyManager.ForLevel(request.User.Level)\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tclientReader := link.Reader // .(*pipe.Reader)\n\tclientWriter := link.Writer // .(*pipe.Writer)\n\ttrafficState := proxy.NewTrafficState(account.ID.Bytes())\n\tif request.Command == protocol.RequestCommandUDP && (requestAddons.Flow == vless.XRV || (h.cone && request.Port != 53 && request.Port != 443)) {\n\t\trequest.Command = protocol.RequestCommandMux\n\t\trequest.Address = net.DomainAddress(\"v1.mux.cool\")\n\t\trequest.Port = net.Port(666)\n\t}\n\n\tpostRequest := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\tbufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\t\tif err := encoding.EncodeRequestHeader(bufferWriter, request, requestAddons); err != nil {\n\t\t\treturn errors.New(\"failed to encode request header\").Base(err).AtWarning()\n\t\t}\n\n\t\t// default: serverWriter := bufferWriter\n\t\tserverWriter := encoding.EncodeBodyAddons(bufferWriter, request, requestAddons, trafficState, true, ctx, conn, ob)\n\t\tif request.Command == protocol.RequestCommandMux && request.Port == 666 {\n\t\t\tserverWriter = xudp.NewPacketWriter(serverWriter, target, xudp.GetGlobalID(ctx))\n\t\t}\n\t\ttimeoutReader, ok := clientReader.(buf.TimeoutReader)\n\t\tif ok {\n\t\t\tmultiBuffer, err1 := timeoutReader.ReadMultiBufferTimeout(time.Millisecond * 500)\n\t\t\tif err1 == nil {\n\t\t\t\tif err := serverWriter.WriteMultiBuffer(multiBuffer); err != nil {\n\t\t\t\t\treturn err // ...\n\t\t\t\t}\n\t\t\t} else if err1 != buf.ErrReadTimeout {\n\t\t\t\treturn err1\n\t\t\t} else if requestAddons.Flow == vless.XRV {\n\t\t\t\tmb := make(buf.MultiBuffer, 1)\n\t\t\t\terrors.LogInfo(ctx, \"Insert padding with empty content to camouflage VLESS header \", mb.Len())\n\t\t\t\tif err := serverWriter.WriteMultiBuffer(mb); err != nil {\n\t\t\t\t\treturn err // ...\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\terrors.LogDebug(ctx, \"Reader is not timeout reader, will send out vless header separately from first payload\")\n\t\t}\n\t\t// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer\n\t\tif err := bufferWriter.SetBuffered(false); err != nil {\n\t\t\treturn errors.New(\"failed to write A request payload\").Base(err).AtWarning()\n\t\t}\n\n\t\tif requestAddons.Flow == vless.XRV {\n\t\t\tif tlsConn, ok := iConn.(*tls.Conn); ok {\n\t\t\t\tif tlsConn.ConnectionState().Version != gotls.VersionTLS13 {\n\t\t\t\t\treturn errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, tlsConn.ConnectionState().Version).AtWarning()\n\t\t\t\t}\n\t\t\t} else if utlsConn, ok := iConn.(*tls.UConn); ok {\n\t\t\t\tif utlsConn.ConnectionState().Version != utls.VersionTLS13 {\n\t\t\t\t\treturn errors.New(`failed to use `+requestAddons.Flow+`, found outer tls version `, utlsConn.ConnectionState().Version).AtWarning()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\terr := buf.Copy(clientReader, serverWriter, buf.UpdateActivity(timer))\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to transfer request payload\").Base(err).AtInfo()\n\t\t}\n\n\t\t// Indicates the end of request payload.\n\t\tswitch requestAddons.Flow {\n\t\tdefault:\n\t\t}\n\t\treturn nil\n\t}\n\n\tgetResponse := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\tresponseAddons, err := encoding.DecodeResponseHeader(conn, request)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to decode response header\").Base(err).AtInfo()\n\t\t}\n\n\t\t// default: serverReader := buf.NewReader(conn)\n\t\tserverReader := encoding.DecodeBodyAddons(conn, request, responseAddons)\n\t\tif requestAddons.Flow == vless.XRV {\n\t\t\tserverReader = proxy.NewVisionReader(serverReader, trafficState, false, ctx, conn, input, rawInput, ob)\n\t\t}\n\t\tif request.Command == protocol.RequestCommandMux && request.Port == 666 {\n\t\t\tif requestAddons.Flow == vless.XRV {\n\t\t\t\tserverReader = xudp.NewPacketReader(&buf.BufferedReader{Reader: serverReader})\n\t\t\t} else {\n\t\t\t\tserverReader = xudp.NewPacketReader(conn)\n\t\t\t}\n\t\t}\n\n\t\tif requestAddons.Flow == vless.XRV {\n\t\t\terr = encoding.XtlsRead(serverReader, clientWriter, timer, conn, trafficState, false, ctx)\n\t\t} else {\n\t\t\t// from serverReader.ReadMultiBuffer to clientWriter.WriteMultiBuffer\n\t\t\terr = buf.Copy(serverReader, clientWriter, buf.UpdateActivity(timer))\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to transfer response payload\").Base(err).AtInfo()\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tif err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err).AtInfo()\n\t}\n\n\treturn nil\n}\n\ntype Reverse struct {\n\ttag         string\n\tdispatcher  routing.Dispatcher\n\tctx         context.Context\n\thandler     *Handler\n\tworkers     []*reverse.BridgeWorker\n\tmonitorTask *task.Periodic\n}\n\nfunc (r *Reverse) monitor() error {\n\tvar activeWorkers []*reverse.BridgeWorker\n\tfor _, w := range r.workers {\n\t\tif w.IsActive() {\n\t\t\tactiveWorkers = append(activeWorkers, w)\n\t\t}\n\t}\n\tif len(activeWorkers) != len(r.workers) {\n\t\tr.workers = activeWorkers\n\t}\n\n\tvar numConnections uint32\n\tvar numWorker uint32\n\tfor _, w := range r.workers {\n\t\tif w.IsActive() {\n\t\t\tnumConnections += w.Connections()\n\t\t\tnumWorker++\n\t\t}\n\t}\n\tif numWorker == 0 || numConnections/numWorker > 16 {\n\t\treader1, writer1 := pipe.New(pipe.WithSizeLimit(2 * buf.Size))\n\t\treader2, writer2 := pipe.New(pipe.WithSizeLimit(2 * buf.Size))\n\t\tlink1 := &transport.Link{Reader: reader1, Writer: writer2}\n\t\tlink2 := &transport.Link{Reader: reader2, Writer: writer1}\n\t\tw := &reverse.BridgeWorker{\n\t\t\tTag:        r.tag,\n\t\t\tDispatcher: r.dispatcher,\n\t\t}\n\t\tworker, err := mux.NewServerWorker(session.ContextWithIsReverseMux(r.ctx, true), w, link1)\n\t\tif err != nil {\n\t\t\terrors.LogWarningInner(r.ctx, err, \"failed to create mux server worker\")\n\t\t\treturn nil\n\t\t}\n\t\tw.Worker = worker\n\t\tr.workers = append(r.workers, w)\n\t\tgo func() {\n\t\t\tctx := session.ContextWithOutbounds(r.ctx, []*session.Outbound{{\n\t\t\t\tTarget: net.Destination{Address: net.DomainAddress(\"v1.rvs.cool\")},\n\t\t\t}})\n\t\t\tr.handler.Process(ctx, link2, session.FullHandlerFromContext(ctx).(*proxyman.Handler))\n\t\t\tcommon.Interrupt(reader1)\n\t\t\tcommon.Interrupt(reader2)\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc (r *Reverse) Start() error {\n\treturn r.monitorTask.Start()\n}\n\nfunc (r *Reverse) Close() error {\n\treturn r.monitorTask.Close()\n}\n"
  },
  {
    "path": "proxy/vless/validator.go",
    "content": "package vless\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\ntype Validator interface {\n\tGet(id uuid.UUID) *protocol.MemoryUser\n\tAdd(u *protocol.MemoryUser) error\n\tDel(email string) error\n\tGetByEmail(email string) *protocol.MemoryUser\n\tGetAll() []*protocol.MemoryUser\n\tGetCount() int64\n}\n\nfunc ProcessUUID(id [16]byte) [16]byte {\n\tid[6] = 0\n\tid[7] = 0\n\treturn id\n}\n\n// MemoryValidator stores valid VLESS users.\ntype MemoryValidator struct {\n\t// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.\n\temail sync.Map\n\tusers sync.Map\n}\n\n// Add a VLESS user, Email must be empty or unique.\nfunc (v *MemoryValidator) Add(u *protocol.MemoryUser) error {\n\tif u.Email != \"\" {\n\t\t_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)\n\t\tif loaded {\n\t\t\treturn errors.New(\"User \", u.Email, \" already exists.\")\n\t\t}\n\t}\n\tv.users.Store(ProcessUUID(u.Account.(*MemoryAccount).ID.UUID()), u)\n\treturn nil\n}\n\n// Del a VLESS user with a non-empty Email.\nfunc (v *MemoryValidator) Del(e string) error {\n\tif e == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n\tle := strings.ToLower(e)\n\tu, _ := v.email.Load(le)\n\tif u == nil {\n\t\treturn errors.New(\"User \", e, \" not found.\")\n\t}\n\tv.email.Delete(le)\n\tv.users.Delete(ProcessUUID(u.(*protocol.MemoryUser).Account.(*MemoryAccount).ID.UUID()))\n\treturn nil\n}\n\n// Get a VLESS user with UUID, nil if user doesn't exist.\nfunc (v *MemoryValidator) Get(id uuid.UUID) *protocol.MemoryUser {\n\tu, _ := v.users.Load(ProcessUUID(id))\n\tif u != nil {\n\t\treturn u.(*protocol.MemoryUser)\n\t}\n\treturn nil\n}\n\n// Get a VLESS user with email, nil if user doesn't exist.\nfunc (v *MemoryValidator) GetByEmail(email string) *protocol.MemoryUser {\n\temail = strings.ToLower(email)\n\tu, _ := v.email.Load(email)\n\tif u != nil {\n\t\treturn u.(*protocol.MemoryUser)\n\t}\n\treturn nil\n}\n\n// Get all users\nfunc (v *MemoryValidator) GetAll() []*protocol.MemoryUser {\n\tvar u = make([]*protocol.MemoryUser, 0, 100)\n\tv.email.Range(func(key, value interface{}) bool {\n\t\tu = append(u, value.(*protocol.MemoryUser))\n\t\treturn true\n\t})\n\treturn u\n}\n\n// Get users count\nfunc (v *MemoryValidator) GetCount() int64 {\n\tvar c int64 = 0\n\tv.email.Range(func(key, value interface{}) bool {\n\t\tc++\n\t\treturn true\n\t})\n\treturn c\n}\n"
  },
  {
    "path": "proxy/vless/vless.go",
    "content": "// Package vless contains the implementation of VLess protocol and transportation.\n//\n// VLess contains both inbound and outbound connections. VLess inbound is usually used on servers\n// together with 'freedom' to talk to final destination, while VLess outbound is usually used on\n// clients with 'socks' for proxying.\npackage vless\n\nconst (\n\tNone = \"none\"\n\tXRV  = \"xtls-rprx-vision\"\n)\n"
  },
  {
    "path": "proxy/vmess/account.go",
    "content": "package vmess\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\n// MemoryAccount is an in-memory form of VMess account.\ntype MemoryAccount struct {\n\t// ID is the main ID of the account.\n\tID *protocol.ID\n\t// Security type of the account. Used for client connections.\n\tSecurity protocol.SecurityType\n\n\tAuthenticatedLengthExperiment bool\n\tNoTerminationSignal           bool\n}\n\n// Equals implements protocol.Account.\nfunc (a *MemoryAccount) Equals(account protocol.Account) bool {\n\tvmessAccount, ok := account.(*MemoryAccount)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn a.ID.Equals(vmessAccount.ID)\n}\n\nfunc (a *MemoryAccount) ToProto() proto.Message {\n\tvar test = \"\"\n\tif a.AuthenticatedLengthExperiment {\n\t\ttest = \"AuthenticatedLength|\"\n\t}\n\tif a.NoTerminationSignal {\n\t\ttest = test + \"NoTerminationSignal\"\n\t}\n\treturn &Account{\n\t\tId:               a.ID.String(),\n\t\tTestsEnabled:     test,\n\t\tSecuritySettings: &protocol.SecurityConfig{Type: a.Security},\n\t}\n}\n\n// AsAccount implements protocol.Account.\nfunc (a *Account) AsAccount() (protocol.Account, error) {\n\tid, err := uuid.ParseString(a.Id)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse ID\").Base(err).AtError()\n\t}\n\tprotoID := protocol.NewID(id)\n\tvar AuthenticatedLength, NoTerminationSignal bool\n\tif strings.Contains(a.TestsEnabled, \"AuthenticatedLength\") {\n\t\tAuthenticatedLength = true\n\t}\n\tif strings.Contains(a.TestsEnabled, \"NoTerminationSignal\") {\n\t\tNoTerminationSignal = true\n\t}\n\treturn &MemoryAccount{\n\t\tID:                            protoID,\n\t\tSecurity:                      a.SecuritySettings.GetSecurityType(),\n\t\tAuthenticatedLengthExperiment: AuthenticatedLength,\n\t\tNoTerminationSignal:           NoTerminationSignal,\n\t}, nil\n}\n"
  },
  {
    "path": "proxy/vmess/account.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vmess/account.proto\n\npackage vmess\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Account struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// ID of the account, in the form of a UUID, e.g.,\n\t// \"66ad4540-b58c-4ad2-9926-ea63445a9b57\".\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\t// Security settings. Only applies to client side.\n\tSecuritySettings *protocol.SecurityConfig `protobuf:\"bytes,3,opt,name=security_settings,json=securitySettings,proto3\" json:\"security_settings,omitempty\"`\n\t// Define tests enabled for this account\n\tTestsEnabled  string `protobuf:\"bytes,4,opt,name=tests_enabled,json=testsEnabled,proto3\" json:\"tests_enabled,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Account) Reset() {\n\t*x = Account{}\n\tmi := &file_proxy_vmess_account_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Account) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Account) ProtoMessage() {}\n\nfunc (x *Account) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vmess_account_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Account.ProtoReflect.Descriptor instead.\nfunc (*Account) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vmess_account_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Account) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Account) GetSecuritySettings() *protocol.SecurityConfig {\n\tif x != nil {\n\t\treturn x.SecuritySettings\n\t}\n\treturn nil\n}\n\nfunc (x *Account) GetTestsEnabled() string {\n\tif x != nil {\n\t\treturn x.TestsEnabled\n\t}\n\treturn \"\"\n}\n\nvar File_proxy_vmess_account_proto protoreflect.FileDescriptor\n\nconst file_proxy_vmess_account_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x19proxy/vmess/account.proto\\x12\\x10xray.proxy.vmess\\x1a\\x1dcommon/protocol/headers.proto\\\"\\x91\\x01\\n\" +\n\t\"\\aAccount\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\tR\\x02id\\x12Q\\n\" +\n\t\"\\x11security_settings\\x18\\x03 \\x01(\\v2$.xray.common.protocol.SecurityConfigR\\x10securitySettings\\x12#\\n\" +\n\t\"\\rtests_enabled\\x18\\x04 \\x01(\\tR\\ftestsEnabledBR\\n\" +\n\t\"\\x14com.xray.proxy.vmessP\\x01Z%github.com/xtls/xray-core/proxy/vmess\\xaa\\x02\\x10Xray.Proxy.Vmessb\\x06proto3\"\n\nvar (\n\tfile_proxy_vmess_account_proto_rawDescOnce sync.Once\n\tfile_proxy_vmess_account_proto_rawDescData []byte\n)\n\nfunc file_proxy_vmess_account_proto_rawDescGZIP() []byte {\n\tfile_proxy_vmess_account_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vmess_account_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vmess_account_proto_rawDesc), len(file_proxy_vmess_account_proto_rawDesc)))\n\t})\n\treturn file_proxy_vmess_account_proto_rawDescData\n}\n\nvar file_proxy_vmess_account_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_vmess_account_proto_goTypes = []any{\n\t(*Account)(nil),                 // 0: xray.proxy.vmess.Account\n\t(*protocol.SecurityConfig)(nil), // 1: xray.common.protocol.SecurityConfig\n}\nvar file_proxy_vmess_account_proto_depIdxs = []int32{\n\t1, // 0: xray.proxy.vmess.Account.security_settings:type_name -> xray.common.protocol.SecurityConfig\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vmess_account_proto_init() }\nfunc file_proxy_vmess_account_proto_init() {\n\tif File_proxy_vmess_account_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vmess_account_proto_rawDesc), len(file_proxy_vmess_account_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vmess_account_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vmess_account_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vmess_account_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vmess_account_proto = out.File\n\tfile_proxy_vmess_account_proto_goTypes = nil\n\tfile_proxy_vmess_account_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vmess/account.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vmess;\noption csharp_namespace = \"Xray.Proxy.Vmess\";\noption go_package = \"github.com/xtls/xray-core/proxy/vmess\";\noption java_package = \"com.xray.proxy.vmess\";\noption java_multiple_files = true;\n\nimport \"common/protocol/headers.proto\";\n\nmessage Account {\n  // ID of the account, in the form of a UUID, e.g.,\n  // \"66ad4540-b58c-4ad2-9926-ea63445a9b57\".\n  string id = 1;\n  // Security settings. Only applies to client side.\n  xray.common.protocol.SecurityConfig security_settings = 3;\n  // Define tests enabled for this account\n  string tests_enabled = 4;\n}\n"
  },
  {
    "path": "proxy/vmess/aead/authid.go",
    "content": "package aead\n\nimport (\n\t\"bytes\"\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\trand3 \"crypto/rand\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"hash/crc32\"\n\t\"io\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/antireplay\"\n)\n\nvar (\n\tErrNotFound     = errors.New(\"user do not exist\")\n\tErrNeagtiveTime = errors.New(\"timestamp is negative\")\n\tErrInvalidTime  = errors.New(\"invalid timestamp, perhaps unsynchronized time\")\n\tErrReplay       = errors.New(\"replayed request\")\n)\n\nfunc CreateAuthID(cmdKey []byte, time int64) [16]byte {\n\tbuf := bytes.NewBuffer(nil)\n\tcommon.Must(binary.Write(buf, binary.BigEndian, time))\n\tvar zero uint32\n\tcommon.Must2(io.CopyN(buf, rand3.Reader, 4))\n\tzero = crc32.ChecksumIEEE(buf.Bytes())\n\tcommon.Must(binary.Write(buf, binary.BigEndian, zero))\n\taesBlock := NewCipherFromKey(cmdKey)\n\tif buf.Len() != 16 {\n\t\tpanic(\"Size unexpected\")\n\t}\n\tvar result [16]byte\n\taesBlock.Encrypt(result[:], buf.Bytes())\n\treturn result\n}\n\nfunc NewCipherFromKey(cmdKey []byte) cipher.Block {\n\taesBlock, err := aes.NewCipher(KDF16(cmdKey, KDFSaltConstAuthIDEncryptionKey))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn aesBlock\n}\n\ntype AuthIDDecoder struct {\n\ts cipher.Block\n}\n\nfunc NewAuthIDDecoder(cmdKey []byte) *AuthIDDecoder {\n\treturn &AuthIDDecoder{NewCipherFromKey(cmdKey)}\n}\n\nfunc (aidd *AuthIDDecoder) Decode(data [16]byte) (int64, uint32, int32, []byte) {\n\taidd.s.Decrypt(data[:], data[:])\n\tvar t int64\n\tvar zero uint32\n\tvar rand int32\n\treader := bytes.NewReader(data[:])\n\tcommon.Must(binary.Read(reader, binary.BigEndian, &t))\n\tcommon.Must(binary.Read(reader, binary.BigEndian, &rand))\n\tcommon.Must(binary.Read(reader, binary.BigEndian, &zero))\n\treturn t, zero, rand, data[:]\n}\n\nfunc NewAuthIDDecoderHolder() *AuthIDDecoderHolder {\n\treturn &AuthIDDecoderHolder{make(map[string]*AuthIDDecoderItem), antireplay.NewMapFilter[[16]byte](120)}\n}\n\ntype AuthIDDecoderHolder struct {\n\tdecoders map[string]*AuthIDDecoderItem\n\tfilter   *antireplay.ReplayFilter[[16]byte]\n}\n\ntype AuthIDDecoderItem struct {\n\tdec    *AuthIDDecoder\n\tticket interface{}\n}\n\nfunc NewAuthIDDecoderItem(key [16]byte, ticket interface{}) *AuthIDDecoderItem {\n\treturn &AuthIDDecoderItem{\n\t\tdec:    NewAuthIDDecoder(key[:]),\n\t\tticket: ticket,\n\t}\n}\n\nfunc (a *AuthIDDecoderHolder) AddUser(key [16]byte, ticket interface{}) {\n\ta.decoders[string(key[:])] = NewAuthIDDecoderItem(key, ticket)\n}\n\nfunc (a *AuthIDDecoderHolder) RemoveUser(key [16]byte) {\n\tdelete(a.decoders, string(key[:]))\n}\n\nfunc (a *AuthIDDecoderHolder) Match(authID [16]byte) (interface{}, error) {\n\tfor _, v := range a.decoders {\n\t\tt, z, _, d := v.dec.Decode(authID)\n\t\tif z != crc32.ChecksumIEEE(d[:12]) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif t < 0 {\n\t\t\treturn nil, ErrNeagtiveTime\n\t\t}\n\n\t\tif math.Abs(math.Abs(float64(t))-float64(time.Now().Unix())) > 120 {\n\t\t\treturn nil, ErrInvalidTime\n\t\t}\n\n\t\tif !a.filter.Check(authID) {\n\t\t\treturn nil, ErrReplay\n\t\t}\n\n\t\treturn v.ticket, nil\n\t}\n\treturn nil, ErrNotFound\n}\n"
  },
  {
    "path": "proxy/vmess/aead/authid_test.go",
    "content": "package aead\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCreateAuthID(t *testing.T) {\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tauthid := CreateAuthID(key, time.Now().Unix())\n\n\tfmt.Println(key)\n\tfmt.Println(authid)\n}\n\nfunc TestCreateAuthIDAndDecode(t *testing.T) {\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tauthid := CreateAuthID(key, time.Now().Unix())\n\n\tfmt.Println(key)\n\tfmt.Println(authid)\n\n\tAuthDecoder := NewAuthIDDecoderHolder()\n\tvar keyw [16]byte\n\tcopy(keyw[:], key)\n\tAuthDecoder.AddUser(keyw, \"Demo User\")\n\tres, err := AuthDecoder.Match(authid)\n\tfmt.Println(res)\n\tfmt.Println(err)\n\tassert.Equal(t, \"Demo User\", res)\n\tassert.Nil(t, err)\n}\n\nfunc TestCreateAuthIDAndDecode2(t *testing.T) {\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tauthid := CreateAuthID(key, time.Now().Unix())\n\n\tfmt.Println(key)\n\tfmt.Println(authid)\n\n\tAuthDecoder := NewAuthIDDecoderHolder()\n\tvar keyw [16]byte\n\tcopy(keyw[:], key)\n\tAuthDecoder.AddUser(keyw, \"Demo User\")\n\tres, err := AuthDecoder.Match(authid)\n\tfmt.Println(res)\n\tfmt.Println(err)\n\tassert.Equal(t, \"Demo User\", res)\n\tassert.Nil(t, err)\n\n\tkey2 := KDF16([]byte(\"Demo Key for Auth ID Test2\"), \"Demo Path for Auth ID Test\")\n\tauthid2 := CreateAuthID(key2, time.Now().Unix())\n\n\tres2, err2 := AuthDecoder.Match(authid2)\n\tassert.EqualError(t, err2, \"user do not exist\")\n\tassert.Nil(t, res2)\n}\n\nfunc TestCreateAuthIDAndDecodeMassive(t *testing.T) {\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tauthid := CreateAuthID(key, time.Now().Unix())\n\n\tfmt.Println(key)\n\tfmt.Println(authid)\n\n\tAuthDecoder := NewAuthIDDecoderHolder()\n\tvar keyw [16]byte\n\tcopy(keyw[:], key)\n\tAuthDecoder.AddUser(keyw, \"Demo User\")\n\tres, err := AuthDecoder.Match(authid)\n\tfmt.Println(res)\n\tfmt.Println(err)\n\tassert.Equal(t, \"Demo User\", res)\n\tassert.Nil(t, err)\n\n\tfor i := 0; i <= 10000; i++ {\n\t\tkey2 := KDF16([]byte(\"Demo Key for Auth ID Test2\"), \"Demo Path for Auth ID Test\", strconv.Itoa(i))\n\t\tvar keyw2 [16]byte\n\t\tcopy(keyw2[:], key2)\n\t\tAuthDecoder.AddUser(keyw2, \"Demo User\"+strconv.Itoa(i))\n\t}\n\n\tauthid3 := CreateAuthID(key, time.Now().Unix())\n\n\tres2, err2 := AuthDecoder.Match(authid3)\n\tassert.Equal(t, \"Demo User\", res2)\n\tassert.Nil(t, err2)\n}\n\nfunc TestCreateAuthIDAndDecodeSuperMassive(t *testing.T) {\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tauthid := CreateAuthID(key, time.Now().Unix())\n\n\tfmt.Println(key)\n\tfmt.Println(authid)\n\n\tAuthDecoder := NewAuthIDDecoderHolder()\n\tvar keyw [16]byte\n\tcopy(keyw[:], key)\n\tAuthDecoder.AddUser(keyw, \"Demo User\")\n\tres, err := AuthDecoder.Match(authid)\n\tfmt.Println(res)\n\tfmt.Println(err)\n\tassert.Equal(t, \"Demo User\", res)\n\tassert.Nil(t, err)\n\n\tfor i := 0; i <= 1000000; i++ {\n\t\tkey2 := KDF16([]byte(\"Demo Key for Auth ID Test2\"), \"Demo Path for Auth ID Test\", strconv.Itoa(i))\n\t\tvar keyw2 [16]byte\n\t\tcopy(keyw2[:], key2)\n\t\tAuthDecoder.AddUser(keyw2, \"Demo User\"+strconv.Itoa(i))\n\t}\n\n\tauthid3 := CreateAuthID(key, time.Now().Unix())\n\n\tbefore := time.Now()\n\tres2, err2 := AuthDecoder.Match(authid3)\n\tafter := time.Now()\n\tassert.Equal(t, \"Demo User\", res2)\n\tassert.Nil(t, err2)\n\n\tfmt.Println(after.Sub(before).Seconds())\n}\n"
  },
  {
    "path": "proxy/vmess/aead/consts.go",
    "content": "package aead\n\nconst (\n\tKDFSaltConstAuthIDEncryptionKey             = \"AES Auth ID Encryption\"\n\tKDFSaltConstAEADRespHeaderLenKey            = \"AEAD Resp Header Len Key\"\n\tKDFSaltConstAEADRespHeaderLenIV             = \"AEAD Resp Header Len IV\"\n\tKDFSaltConstAEADRespHeaderPayloadKey        = \"AEAD Resp Header Key\"\n\tKDFSaltConstAEADRespHeaderPayloadIV         = \"AEAD Resp Header IV\"\n\tKDFSaltConstVMessAEADKDF                    = \"VMess AEAD KDF\"\n\tKDFSaltConstVMessHeaderPayloadAEADKey       = \"VMess Header AEAD Key\"\n\tKDFSaltConstVMessHeaderPayloadAEADIV        = \"VMess Header AEAD Nonce\"\n\tKDFSaltConstVMessHeaderPayloadLengthAEADKey = \"VMess Header AEAD Key_Length\"\n\tKDFSaltConstVMessHeaderPayloadLengthAEADIV  = \"VMess Header AEAD Nonce_Length\"\n)\n"
  },
  {
    "path": "proxy/vmess/aead/encrypt.go",
    "content": "package aead\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n)\n\nfunc SealVMessAEADHeader(key [16]byte, data []byte) []byte {\n\tgeneratedAuthID := CreateAuthID(key[:], time.Now().Unix())\n\n\tconnectionNonce := make([]byte, 8)\n\tif _, err := io.ReadFull(rand.Reader, connectionNonce); err != nil {\n\t\tpanic(err.Error())\n\t}\n\n\taeadPayloadLengthSerializeBuffer := bytes.NewBuffer(nil)\n\n\theaderPayloadDataLen := uint16(len(data))\n\n\tcommon.Must(binary.Write(aeadPayloadLengthSerializeBuffer, binary.BigEndian, headerPayloadDataLen))\n\n\taeadPayloadLengthSerializedByte := aeadPayloadLengthSerializeBuffer.Bytes()\n\tvar payloadHeaderLengthAEADEncrypted []byte\n\n\t{\n\t\tpayloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADKey, string(generatedAuthID[:]), string(connectionNonce))\n\n\t\tpayloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]\n\n\t\tpayloadHeaderAEAD := crypto.NewAesGcm(payloadHeaderLengthAEADKey)\n\n\t\tpayloadHeaderLengthAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderLengthAEADNonce, aeadPayloadLengthSerializedByte, generatedAuthID[:])\n\t}\n\n\tvar payloadHeaderAEADEncrypted []byte\n\n\t{\n\t\tpayloadHeaderAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadAEADKey, string(generatedAuthID[:]), string(connectionNonce))\n\n\t\tpayloadHeaderAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadAEADIV, string(generatedAuthID[:]), string(connectionNonce))[:12]\n\n\t\tpayloadHeaderAEAD := crypto.NewAesGcm(payloadHeaderAEADKey)\n\n\t\tpayloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:])\n\t}\n\n\toutputBuffer := bytes.NewBuffer(nil)\n\n\tcommon.Must2(outputBuffer.Write(generatedAuthID[:]))               // 16\n\tcommon.Must2(outputBuffer.Write(payloadHeaderLengthAEADEncrypted)) // 2+16\n\tcommon.Must2(outputBuffer.Write(connectionNonce))                  // 8\n\tcommon.Must2(outputBuffer.Write(payloadHeaderAEADEncrypted))\n\n\treturn outputBuffer.Bytes()\n}\n\nfunc OpenVMessAEADHeader(key [16]byte, authid [16]byte, data io.Reader) ([]byte, bool, int, error) {\n\tvar payloadHeaderLengthAEADEncrypted [18]byte\n\tvar nonce [8]byte\n\n\tvar bytesRead int\n\n\tauthidCheckValueReadBytesCounts, err := io.ReadFull(data, payloadHeaderLengthAEADEncrypted[:])\n\tbytesRead += authidCheckValueReadBytesCounts\n\tif err != nil {\n\t\treturn nil, false, bytesRead, err\n\t}\n\n\tnonceReadBytesCounts, err := io.ReadFull(data, nonce[:])\n\tbytesRead += nonceReadBytesCounts\n\tif err != nil {\n\t\treturn nil, false, bytesRead, err\n\t}\n\n\t// Decrypt Length\n\n\tvar decryptedAEADHeaderLengthPayloadResult []byte\n\n\t{\n\t\tpayloadHeaderLengthAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADKey, string(authid[:]), string(nonce[:]))\n\n\t\tpayloadHeaderLengthAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadLengthAEADIV, string(authid[:]), string(nonce[:]))[:12]\n\n\t\tpayloadHeaderLengthAEAD := crypto.NewAesGcm(payloadHeaderLengthAEADKey)\n\n\t\tdecryptedAEADHeaderLengthPayload, erropenAEAD := payloadHeaderLengthAEAD.Open(nil, payloadHeaderLengthAEADNonce, payloadHeaderLengthAEADEncrypted[:], authid[:])\n\n\t\tif erropenAEAD != nil {\n\t\t\treturn nil, true, bytesRead, erropenAEAD\n\t\t}\n\n\t\tdecryptedAEADHeaderLengthPayloadResult = decryptedAEADHeaderLengthPayload\n\t}\n\n\tvar length uint16\n\n\tcommon.Must(binary.Read(bytes.NewReader(decryptedAEADHeaderLengthPayloadResult), binary.BigEndian, &length))\n\n\tvar decryptedAEADHeaderPayloadR []byte\n\n\tvar payloadHeaderAEADEncryptedReadedBytesCounts int\n\n\t{\n\t\tpayloadHeaderAEADKey := KDF16(key[:], KDFSaltConstVMessHeaderPayloadAEADKey, string(authid[:]), string(nonce[:]))\n\n\t\tpayloadHeaderAEADNonce := KDF(key[:], KDFSaltConstVMessHeaderPayloadAEADIV, string(authid[:]), string(nonce[:]))[:12]\n\n\t\t// 16 == AEAD Tag size\n\t\tpayloadHeaderAEADEncrypted := make([]byte, length+16)\n\n\t\tpayloadHeaderAEADEncryptedReadedBytesCounts, err = io.ReadFull(data, payloadHeaderAEADEncrypted)\n\t\tbytesRead += payloadHeaderAEADEncryptedReadedBytesCounts\n\t\tif err != nil {\n\t\t\treturn nil, false, bytesRead, err\n\t\t}\n\n\t\tpayloadHeaderAEAD := crypto.NewAesGcm(payloadHeaderAEADKey)\n\n\t\tdecryptedAEADHeaderPayload, erropenAEAD := payloadHeaderAEAD.Open(nil, payloadHeaderAEADNonce, payloadHeaderAEADEncrypted, authid[:])\n\n\t\tif erropenAEAD != nil {\n\t\t\treturn nil, true, bytesRead, erropenAEAD\n\t\t}\n\n\t\tdecryptedAEADHeaderPayloadR = decryptedAEADHeaderPayload\n\t}\n\n\treturn decryptedAEADHeaderPayloadR, false, bytesRead, nil\n}\n"
  },
  {
    "path": "proxy/vmess/aead/encrypt_test.go",
    "content": "package aead\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestOpenVMessAEADHeader(t *testing.T) {\n\tTestHeader := []byte(\"Test Header\")\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tvar keyw [16]byte\n\tcopy(keyw[:], key)\n\tsealed := SealVMessAEADHeader(keyw, TestHeader)\n\n\tAEADR := bytes.NewReader(sealed)\n\n\tvar authid [16]byte\n\n\tio.ReadFull(AEADR, authid[:])\n\n\tout, _, _, err := OpenVMessAEADHeader(keyw, authid, AEADR)\n\n\tfmt.Println(string(out))\n\tfmt.Println(err)\n}\n\nfunc TestOpenVMessAEADHeader2(t *testing.T) {\n\tTestHeader := []byte(\"Test Header\")\n\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\tvar keyw [16]byte\n\tcopy(keyw[:], key)\n\tsealed := SealVMessAEADHeader(keyw, TestHeader)\n\n\tAEADR := bytes.NewReader(sealed)\n\n\tvar authid [16]byte\n\n\tio.ReadFull(AEADR, authid[:])\n\n\tout, _, readen, err := OpenVMessAEADHeader(keyw, authid, AEADR)\n\tassert.Equal(t, len(sealed)-16-AEADR.Len(), readen)\n\tassert.Equal(t, string(TestHeader), string(out))\n\tassert.Nil(t, err)\n}\n\nfunc TestOpenVMessAEADHeader4(t *testing.T) {\n\tfor i := 0; i <= 60; i++ {\n\t\tTestHeader := []byte(\"Test Header\")\n\t\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\t\tvar keyw [16]byte\n\t\tcopy(keyw[:], key)\n\t\tsealed := SealVMessAEADHeader(keyw, TestHeader)\n\t\tvar sealedm [16]byte\n\t\tcopy(sealedm[:], sealed)\n\t\tsealed[i] ^= 0xff\n\t\tAEADR := bytes.NewReader(sealed)\n\n\t\tvar authid [16]byte\n\n\t\tio.ReadFull(AEADR, authid[:])\n\n\t\tout, drain, readen, err := OpenVMessAEADHeader(keyw, authid, AEADR)\n\t\tassert.Equal(t, len(sealed)-16-AEADR.Len(), readen)\n\t\tassert.Equal(t, true, drain)\n\t\tassert.NotNil(t, err)\n\t\tif err == nil {\n\t\t\tfmt.Println(\">\")\n\t\t}\n\t\tassert.Nil(t, out)\n\t}\n}\n\nfunc TestOpenVMessAEADHeader4Massive(t *testing.T) {\n\tfor j := 0; j < 1000; j++ {\n\t\tfor i := 0; i <= 60; i++ {\n\t\t\tTestHeader := []byte(\"Test Header\")\n\t\t\tkey := KDF16([]byte(\"Demo Key for Auth ID Test\"), \"Demo Path for Auth ID Test\")\n\t\t\tvar keyw [16]byte\n\t\t\tcopy(keyw[:], key)\n\t\t\tsealed := SealVMessAEADHeader(keyw, TestHeader)\n\t\t\tvar sealedm [16]byte\n\t\t\tcopy(sealedm[:], sealed)\n\t\t\tsealed[i] ^= 0xff\n\t\t\tAEADR := bytes.NewReader(sealed)\n\n\t\t\tvar authid [16]byte\n\n\t\t\tio.ReadFull(AEADR, authid[:])\n\n\t\t\tout, drain, readen, err := OpenVMessAEADHeader(keyw, authid, AEADR)\n\t\t\tassert.Equal(t, len(sealed)-16-AEADR.Len(), readen)\n\t\t\tassert.Equal(t, true, drain)\n\t\t\tassert.NotNil(t, err)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\">\")\n\t\t\t}\n\t\t\tassert.Nil(t, out)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/aead/kdf.go",
    "content": "package aead\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"hash\"\n)\n\ntype hash2 struct {\n\thash.Hash\n}\n\nfunc KDF(key []byte, path ...string) []byte {\n\thmacf := hmac.New(sha256.New, []byte(KDFSaltConstVMessAEADKDF))\n\n\tfor _, v := range path {\n\t\tfirst := true\n\t\thmacf = hmac.New(func() hash.Hash {\n\t\t\tif first {\n\t\t\t\tfirst = false\n\t\t\t\treturn hash2{hmacf}\n\t\t\t}\n\t\t\treturn hmacf\n\t\t}, []byte(v))\n\t}\n\thmacf.Write(key)\n\treturn hmacf.Sum(nil)\n}\n\nfunc KDF16(key []byte, path ...string) []byte {\n\tr := KDF(key, path...)\n\treturn r[:16]\n}\n"
  },
  {
    "path": "proxy/vmess/encoding/auth.go",
    "content": "package encoding\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/binary\"\n\t\"hash/fnv\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"golang.org/x/crypto/sha3\"\n)\n\n// Authenticate authenticates a byte array using Fnv hash.\nfunc Authenticate(b []byte) uint32 {\n\tfnv1hash := fnv.New32a()\n\tcommon.Must2(fnv1hash.Write(b))\n\treturn fnv1hash.Sum32()\n}\n\n// [DEPRECATED 2023-06]\ntype NoOpAuthenticator struct{}\n\nfunc (NoOpAuthenticator) NonceSize() int {\n\treturn 0\n}\n\nfunc (NoOpAuthenticator) Overhead() int {\n\treturn 0\n}\n\n// Seal implements AEAD.Seal().\nfunc (NoOpAuthenticator) Seal(dst, nonce, plaintext, additionalData []byte) []byte {\n\treturn append(dst[:0], plaintext...)\n}\n\n// Open implements AEAD.Open().\nfunc (NoOpAuthenticator) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {\n\treturn append(dst[:0], ciphertext...), nil\n}\n\n// GenerateChacha20Poly1305Key generates a 32-byte key from a given 16-byte array.\nfunc GenerateChacha20Poly1305Key(b []byte) []byte {\n\tkey := make([]byte, 32)\n\tt := md5.Sum(b)\n\tcopy(key, t[:])\n\tt = md5.Sum(key[:16])\n\tcopy(key[16:], t[:])\n\treturn key\n}\n\ntype ShakeSizeParser struct {\n\tshake  sha3.ShakeHash\n\tbuffer [2]byte\n}\n\nfunc NewShakeSizeParser(nonce []byte) *ShakeSizeParser {\n\tshake := sha3.NewShake128()\n\tcommon.Must2(shake.Write(nonce))\n\treturn &ShakeSizeParser{\n\t\tshake: shake,\n\t}\n}\n\nfunc (*ShakeSizeParser) SizeBytes() int32 {\n\treturn 2\n}\n\nfunc (s *ShakeSizeParser) next() uint16 {\n\tcommon.Must2(s.shake.Read(s.buffer[:]))\n\treturn binary.BigEndian.Uint16(s.buffer[:])\n}\n\nfunc (s *ShakeSizeParser) Decode(b []byte) (uint16, error) {\n\tmask := s.next()\n\tsize := binary.BigEndian.Uint16(b)\n\treturn mask ^ size, nil\n}\n\nfunc (s *ShakeSizeParser) Encode(size uint16, b []byte) []byte {\n\tmask := s.next()\n\tbinary.BigEndian.PutUint16(b, mask^size)\n\treturn b[:2]\n}\n\nfunc (s *ShakeSizeParser) NextPaddingLen() uint16 {\n\treturn s.next() % 64\n}\n\nfunc (s *ShakeSizeParser) MaxPaddingLen() uint16 {\n\treturn 64\n}\n\ntype AEADSizeParser struct {\n\tcrypto.AEADChunkSizeParser\n}\n\nfunc NewAEADSizeParser(auth *crypto.AEADAuthenticator) *AEADSizeParser {\n\treturn &AEADSizeParser{crypto.AEADChunkSizeParser{Auth: auth}}\n}\n"
  },
  {
    "path": "proxy/vmess/encoding/client.go",
    "content": "package encoding\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"hash/fnv\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/bitmask\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/drain\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\tvmessaead \"github.com/xtls/xray-core/proxy/vmess/aead\"\n\t\"golang.org/x/crypto/chacha20poly1305\"\n)\n\n// ClientSession stores connection session info for VMess client.\ntype ClientSession struct {\n\trequestBodyKey  [16]byte\n\trequestBodyIV   [16]byte\n\tresponseBodyKey [16]byte\n\tresponseBodyIV  [16]byte\n\tresponseReader  io.Reader\n\tresponseHeader  byte\n\n\treadDrainer drain.Drainer\n}\n\n// NewClientSession creates a new ClientSession.\nfunc NewClientSession(ctx context.Context, behaviorSeed int64) *ClientSession {\n\tsession := &ClientSession{}\n\n\trandomBytes := make([]byte, 33) // 16 + 16 + 1\n\tcommon.Must2(rand.Read(randomBytes))\n\tcopy(session.requestBodyKey[:], randomBytes[:16])\n\tcopy(session.requestBodyIV[:], randomBytes[16:32])\n\tsession.responseHeader = randomBytes[32]\n\n\tBodyKey := sha256.Sum256(session.requestBodyKey[:])\n\tcopy(session.responseBodyKey[:], BodyKey[:16])\n\tBodyIV := sha256.Sum256(session.requestBodyIV[:])\n\tcopy(session.responseBodyIV[:], BodyIV[:16])\n\t{\n\t\tvar err error\n\t\tsession.readDrainer, err = drain.NewBehaviorSeedLimitedDrainer(behaviorSeed, 18, 3266, 64)\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(ctx, err, \"unable to initialize drainer\")\n\t\t\tsession.readDrainer = drain.NewNopDrainer()\n\t\t}\n\t}\n\n\treturn session\n}\n\nfunc (c *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writer io.Writer) error {\n\taccount := header.User.Account.(*vmess.MemoryAccount)\n\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\tcommon.Must(buffer.WriteByte(Version))\n\tcommon.Must2(buffer.Write(c.requestBodyIV[:]))\n\tcommon.Must2(buffer.Write(c.requestBodyKey[:]))\n\tcommon.Must(buffer.WriteByte(c.responseHeader))\n\tcommon.Must(buffer.WriteByte(byte(header.Option)))\n\n\tpaddingLen := dice.Roll(16)\n\tsecurity := byte(paddingLen<<4) | byte(header.Security)\n\tcommon.Must2(buffer.Write([]byte{security, byte(0), byte(header.Command)}))\n\n\tif header.Command != protocol.RequestCommandMux {\n\t\tif err := addrParser.WriteAddressPort(buffer, header.Address, header.Port); err != nil {\n\t\t\treturn errors.New(\"failed to writer address and port\").Base(err)\n\t\t}\n\t}\n\n\tif paddingLen > 0 {\n\t\tcommon.Must2(buffer.ReadFullFrom(rand.Reader, int32(paddingLen)))\n\t}\n\n\t{\n\t\tfnv1a := fnv.New32a()\n\t\tcommon.Must2(fnv1a.Write(buffer.Bytes()))\n\t\thashBytes := buffer.Extend(int32(fnv1a.Size()))\n\t\tfnv1a.Sum(hashBytes[:0])\n\t}\n\n\tvar fixedLengthCmdKey [16]byte\n\tcopy(fixedLengthCmdKey[:], account.ID.CmdKey())\n\tvmessout := vmessaead.SealVMessAEADHeader(fixedLengthCmdKey, buffer.Bytes())\n\tcommon.Must2(io.Copy(writer, bytes.NewReader(vmessout)))\n\n\treturn nil\n}\n\nfunc (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {\n\tvar sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{}\n\tif request.Option.Has(protocol.RequestOptionChunkMasking) {\n\t\tsizeParser = NewShakeSizeParser(c.requestBodyIV[:])\n\t}\n\tvar padding crypto.PaddingLengthGenerator\n\tif request.Option.Has(protocol.RequestOptionGlobalPadding) {\n\t\tvar ok bool\n\t\tpadding, ok = sizeParser.(crypto.PaddingLengthGenerator)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid option: RequestOptionGlobalPadding\")\n\t\t}\n\t}\n\n\tswitch request.Security {\n\tcase protocol.SecurityType_NONE:\n\t\tif request.Option.Has(protocol.RequestOptionChunkStream) {\n\t\t\tif request.Command.TransferType() == protocol.TransferTypeStream {\n\t\t\t\treturn crypto.NewChunkStreamWriter(sizeParser, writer), nil\n\t\t\t}\n\t\t\tauth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    new(NoOpAuthenticator),\n\t\t\t\tNonceGenerator:          crypto.GenerateEmptyBytes(),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\treturn crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil\n\t\t}\n\n\t\treturn buf.NewWriter(writer), nil\n\tcase protocol.SecurityType_AES128_GCM:\n\t\taead := crypto.NewAesGcm(c.requestBodyKey[:])\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(c.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD := crypto.NewAesGcm(AuthenticatedLengthKey)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil\n\tcase protocol.SecurityType_CHACHA20_POLY1305:\n\t\taead, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:]))\n\t\tcommon.Must(err)\n\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(c.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(AuthenticatedLengthKey))\n\t\t\tcommon.Must(err)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil\n\tdefault:\n\t\treturn nil, errors.New(\"invalid option: Security\")\n\t}\n}\n\nfunc (c *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.ResponseHeader, error) {\n\taeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)\n\taeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]\n\n\taeadResponseHeaderLengthEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderLengthEncryptionKey)\n\n\tvar aeadEncryptedResponseHeaderLength [18]byte\n\tvar decryptedResponseHeaderLength int\n\tvar decryptedResponseHeaderLengthBinaryDeserializeBuffer uint16\n\n\tif n, err := io.ReadFull(reader, aeadEncryptedResponseHeaderLength[:]); err != nil {\n\t\tc.readDrainer.AcknowledgeReceive(n)\n\t\treturn nil, drain.WithError(c.readDrainer, reader, errors.New(\"Unable to Read Header Len\").Base(err))\n\t} else { // nolint: golint\n\t\tc.readDrainer.AcknowledgeReceive(n)\n\t}\n\tif decryptedResponseHeaderLengthBinaryBuffer, err := aeadResponseHeaderLengthEncryptionAEAD.Open(nil, aeadResponseHeaderLengthEncryptionIV, aeadEncryptedResponseHeaderLength[:], nil); err != nil {\n\t\treturn nil, drain.WithError(c.readDrainer, reader, errors.New(\"Failed To Decrypt Length\").Base(err))\n\t} else { // nolint: golint\n\t\tcommon.Must(binary.Read(bytes.NewReader(decryptedResponseHeaderLengthBinaryBuffer), binary.BigEndian, &decryptedResponseHeaderLengthBinaryDeserializeBuffer))\n\t\tdecryptedResponseHeaderLength = int(decryptedResponseHeaderLengthBinaryDeserializeBuffer)\n\t}\n\n\taeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(c.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)\n\taeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(c.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]\n\n\taeadResponseHeaderPayloadEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderPayloadEncryptionKey)\n\n\tencryptedResponseHeaderBuffer := make([]byte, decryptedResponseHeaderLength+16)\n\n\tif n, err := io.ReadFull(reader, encryptedResponseHeaderBuffer); err != nil {\n\t\tc.readDrainer.AcknowledgeReceive(n)\n\t\treturn nil, drain.WithError(c.readDrainer, reader, errors.New(\"Unable to Read Header Data\").Base(err))\n\t} else { // nolint: golint\n\t\tc.readDrainer.AcknowledgeReceive(n)\n\t}\n\n\tif decryptedResponseHeaderBuffer, err := aeadResponseHeaderPayloadEncryptionAEAD.Open(nil, aeadResponseHeaderPayloadEncryptionIV, encryptedResponseHeaderBuffer, nil); err != nil {\n\t\treturn nil, drain.WithError(c.readDrainer, reader, errors.New(\"Failed To Decrypt Payload\").Base(err))\n\t} else { // nolint: golint\n\t\tc.responseReader = bytes.NewReader(decryptedResponseHeaderBuffer)\n\t}\n\n\tbuffer := buf.StackNew()\n\tdefer buffer.Release()\n\n\tif _, err := buffer.ReadFullFrom(c.responseReader, 4); err != nil {\n\t\treturn nil, errors.New(\"failed to read response header\").Base(err).AtWarning()\n\t}\n\n\tif buffer.Byte(0) != c.responseHeader {\n\t\treturn nil, errors.New(\"unexpected response header. Expecting \", int(c.responseHeader), \" but actually \", int(buffer.Byte(0)))\n\t}\n\n\theader := &protocol.ResponseHeader{\n\t\tOption: bitmask.Byte(buffer.Byte(1)),\n\t}\n\n\tif buffer.Byte(2) != 0 {\n\t\tcmdID := buffer.Byte(2)\n\t\tdataLen := int32(buffer.Byte(3))\n\n\t\tbuffer.Clear()\n\t\tif _, err := buffer.ReadFullFrom(c.responseReader, dataLen); err != nil {\n\t\t\treturn nil, errors.New(\"failed to read response command\").Base(err)\n\t\t}\n\t\tcommand, err := UnmarshalCommand(cmdID, buffer.Bytes())\n\t\tif err == nil {\n\t\t\theader.Command = command\n\t\t}\n\t}\n\taesStream := crypto.NewAesDecryptionStream(c.responseBodyKey[:], c.responseBodyIV[:])\n\tc.responseReader = crypto.NewCryptionReader(aesStream, reader)\n\treturn header, nil\n}\n\nfunc (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) (buf.Reader, error) {\n\tvar sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{}\n\tif request.Option.Has(protocol.RequestOptionChunkMasking) {\n\t\tsizeParser = NewShakeSizeParser(c.responseBodyIV[:])\n\t}\n\tvar padding crypto.PaddingLengthGenerator\n\tif request.Option.Has(protocol.RequestOptionGlobalPadding) {\n\t\tvar ok bool\n\t\tpadding, ok = sizeParser.(crypto.PaddingLengthGenerator)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid option: RequestOptionGlobalPadding\")\n\t\t}\n\t}\n\n\tswitch request.Security {\n\tcase protocol.SecurityType_NONE:\n\t\tif request.Option.Has(protocol.RequestOptionChunkStream) {\n\t\t\tif request.Command.TransferType() == protocol.TransferTypeStream {\n\t\t\t\treturn crypto.NewChunkStreamReader(sizeParser, reader), nil\n\t\t\t}\n\n\t\t\tauth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    new(NoOpAuthenticator),\n\t\t\t\tNonceGenerator:          crypto.GenerateEmptyBytes(),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\n\t\t\treturn crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil\n\t\t}\n\n\t\treturn buf.NewReader(reader), nil\n\tcase protocol.SecurityType_AES128_GCM:\n\t\taead := crypto.NewAesGcm(c.responseBodyKey[:])\n\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(c.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD := crypto.NewAesGcm(AuthenticatedLengthKey)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil\n\tcase protocol.SecurityType_CHACHA20_POLY1305:\n\t\taead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:]))\n\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(c.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(AuthenticatedLengthKey))\n\t\t\tcommon.Must(err)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil\n\tdefault:\n\t\treturn nil, errors.New(\"invalid option: Security\")\n\t}\n}\n\nfunc GenerateChunkNonce(nonce []byte, size uint32) crypto.BytesGenerator {\n\tc := append([]byte(nil), nonce...)\n\tcount := uint16(0)\n\treturn func() []byte {\n\t\tbinary.BigEndian.PutUint16(c, count)\n\t\tcount++\n\t\treturn c[:size]\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/encoding/commands.go",
    "content": "package encoding\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nvar (\n\tErrCommandTooLarge     = errors.New(\"Command too large.\")\n\tErrCommandTypeMismatch = errors.New(\"Command type mismatch.\")\n\tErrInvalidAuth         = errors.New(\"Invalid auth.\")\n\tErrInsufficientLength  = errors.New(\"Insufficient length.\")\n\tErrUnknownCommand      = errors.New(\"Unknown command.\")\n)\n\nfunc MarshalCommand(command interface{}, writer io.Writer) error {\n\tif command == nil {\n\t\treturn ErrUnknownCommand\n\t}\n\n\tvar cmdID byte\n\tvar factory CommandFactory\n\tswitch command.(type) {\n\tdefault:\n\t\treturn ErrUnknownCommand\n\t}\n\n\tbuffer := buf.New()\n\tdefer buffer.Release()\n\n\terr := factory.Marshal(command, buffer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tauth := Authenticate(buffer.Bytes())\n\tlength := buffer.Len() + 4\n\tif length > 255 {\n\t\treturn ErrCommandTooLarge\n\t}\n\n\tcommon.Must2(writer.Write([]byte{cmdID, byte(length), byte(auth >> 24), byte(auth >> 16), byte(auth >> 8), byte(auth)}))\n\tcommon.Must2(writer.Write(buffer.Bytes()))\n\treturn nil\n}\n\nfunc UnmarshalCommand(cmdID byte, data []byte) (protocol.ResponseCommand, error) {\n\tif len(data) <= 4 {\n\t\treturn nil, ErrInsufficientLength\n\t}\n\texpectedAuth := Authenticate(data[4:])\n\tactualAuth := binary.BigEndian.Uint32(data[:4])\n\tif expectedAuth != actualAuth {\n\t\treturn nil, ErrInvalidAuth\n\t}\n\n\tvar factory CommandFactory\n\tswitch cmdID {\n\tdefault:\n\t\treturn nil, ErrUnknownCommand\n\t}\n\treturn factory.Unmarshal(data[4:])\n}\n\ntype CommandFactory interface {\n\tMarshal(command interface{}, writer io.Writer) error\n\tUnmarshal(data []byte) (interface{}, error)\n}\n"
  },
  {
    "path": "proxy/vmess/encoding/encoding.go",
    "content": "package encoding\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\nconst (\n\tVersion = byte(1)\n)\n\nvar addrParser = protocol.NewAddressParser(\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv4), net.AddressFamilyIPv4),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeDomain), net.AddressFamilyDomain),\n\tprotocol.AddressFamilyByte(byte(protocol.AddressTypeIPv6), net.AddressFamilyIPv6),\n\tprotocol.PortThenAddress(),\n)\n"
  },
  {
    "path": "proxy/vmess/encoding/encoding_test.go",
    "content": "package encoding_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t. \"github.com/xtls/xray-core/proxy/vmess/encoding\"\n)\n\nfunc toAccount(a *vmess.Account) protocol.Account {\n\taccount, err := a.AsAccount()\n\tcommon.Must(err)\n\treturn account\n}\n\nfunc TestRequestSerialization(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tLevel: 0,\n\t\tEmail: \"test@example.com\",\n\t}\n\tid := uuid.New()\n\taccount := &vmess.Account{\n\t\tId: id.String(),\n\t}\n\tuser.Account = toAccount(account)\n\n\texpectedRequest := &protocol.RequestHeader{\n\t\tVersion:  1,\n\t\tUser:     user,\n\t\tCommand:  protocol.RequestCommandTCP,\n\t\tAddress:  net.DomainAddress(\"www.example.com\"),\n\t\tPort:     net.Port(443),\n\t\tSecurity: protocol.SecurityType_AES128_GCM,\n\t}\n\n\tbuffer := buf.New()\n\tclient := NewClientSession(context.TODO(), 0)\n\tcommon.Must(client.EncodeRequestHeader(expectedRequest, buffer))\n\n\tbuffer2 := buf.New()\n\tbuffer2.Write(buffer.Bytes())\n\n\tsessionHistory := NewSessionHistory()\n\tdefer common.Close(sessionHistory)\n\n\tuserValidator := vmess.NewTimedUserValidator()\n\tuserValidator.Add(user)\n\tdefer common.Close(userValidator)\n\n\tserver := NewServerSession(userValidator, sessionHistory)\n\tactualRequest, err := server.DecodeRequestHeader(buffer, false)\n\tcommon.Must(err)\n\n\tif r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\t_, err = server.DecodeRequestHeader(buffer2, false)\n\t// anti replay attack\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestInvalidRequest(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tLevel: 0,\n\t\tEmail: \"test@example.com\",\n\t}\n\tid := uuid.New()\n\taccount := &vmess.Account{\n\t\tId: id.String(),\n\t}\n\tuser.Account = toAccount(account)\n\n\texpectedRequest := &protocol.RequestHeader{\n\t\tVersion:  1,\n\t\tUser:     user,\n\t\tCommand:  protocol.RequestCommand(100),\n\t\tAddress:  net.DomainAddress(\"www.example.com\"),\n\t\tPort:     net.Port(443),\n\t\tSecurity: protocol.SecurityType_AES128_GCM,\n\t}\n\n\tbuffer := buf.New()\n\tclient := NewClientSession(context.TODO(), 0)\n\tcommon.Must(client.EncodeRequestHeader(expectedRequest, buffer))\n\n\tbuffer2 := buf.New()\n\tbuffer2.Write(buffer.Bytes())\n\n\tsessionHistory := NewSessionHistory()\n\tdefer common.Close(sessionHistory)\n\n\tuserValidator := vmess.NewTimedUserValidator()\n\tuserValidator.Add(user)\n\tdefer common.Close(userValidator)\n\n\tserver := NewServerSession(userValidator, sessionHistory)\n\t_, err := server.DecodeRequestHeader(buffer, false)\n\tif err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestMuxRequest(t *testing.T) {\n\tuser := &protocol.MemoryUser{\n\t\tLevel: 0,\n\t\tEmail: \"test@example.com\",\n\t}\n\tid := uuid.New()\n\taccount := &vmess.Account{\n\t\tId: id.String(),\n\t}\n\tuser.Account = toAccount(account)\n\n\texpectedRequest := &protocol.RequestHeader{\n\t\tVersion:  1,\n\t\tUser:     user,\n\t\tCommand:  protocol.RequestCommandMux,\n\t\tSecurity: protocol.SecurityType_AES128_GCM,\n\t\tAddress:  net.DomainAddress(\"v1.mux.cool\"),\n\t}\n\n\tbuffer := buf.New()\n\tclient := NewClientSession(context.TODO(), 0)\n\tcommon.Must(client.EncodeRequestHeader(expectedRequest, buffer))\n\n\tbuffer2 := buf.New()\n\tbuffer2.Write(buffer.Bytes())\n\n\tsessionHistory := NewSessionHistory()\n\tdefer common.Close(sessionHistory)\n\n\tuserValidator := vmess.NewTimedUserValidator()\n\tuserValidator.Add(user)\n\tdefer common.Close(userValidator)\n\n\tserver := NewServerSession(userValidator, sessionHistory)\n\tactualRequest, err := server.DecodeRequestHeader(buffer, false)\n\tcommon.Must(err)\n\n\tif r := cmp.Diff(actualRequest, expectedRequest, cmp.AllowUnexported(protocol.ID{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/encoding/server.go",
    "content": "package encoding\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"hash/fnv\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/bitmask\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/drain\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\tvmessaead \"github.com/xtls/xray-core/proxy/vmess/aead\"\n\t\"golang.org/x/crypto/chacha20poly1305\"\n)\n\ntype sessionID struct {\n\tuser  [16]byte\n\tkey   [16]byte\n\tnonce [16]byte\n}\n\n// SessionHistory keeps track of historical session ids, to prevent replay attacks.\ntype SessionHistory struct {\n\tsync.RWMutex\n\tcache map[sessionID]time.Time\n\ttask  *task.Periodic\n}\n\n// NewSessionHistory creates a new SessionHistory object.\nfunc NewSessionHistory() *SessionHistory {\n\th := &SessionHistory{\n\t\tcache: make(map[sessionID]time.Time, 128),\n\t}\n\th.task = &task.Periodic{\n\t\tInterval: time.Second * 30,\n\t\tExecute:  h.removeExpiredEntries,\n\t}\n\treturn h\n}\n\n// Close implements common.Closable.\nfunc (h *SessionHistory) Close() error {\n\treturn h.task.Close()\n}\n\nfunc (h *SessionHistory) addIfNotExits(session sessionID) bool {\n\th.Lock()\n\n\tif expire, found := h.cache[session]; found && expire.After(time.Now()) {\n\t\th.Unlock()\n\t\treturn false\n\t}\n\n\th.cache[session] = time.Now().Add(time.Minute * 3)\n\th.Unlock()\n\tcommon.Must(h.task.Start())\n\treturn true\n}\n\nfunc (h *SessionHistory) removeExpiredEntries() error {\n\tnow := time.Now()\n\n\th.Lock()\n\tdefer h.Unlock()\n\n\tif len(h.cache) == 0 {\n\t\treturn errors.New(\"nothing to do\")\n\t}\n\n\tfor session, expire := range h.cache {\n\t\tif expire.Before(now) {\n\t\t\tdelete(h.cache, session)\n\t\t}\n\t}\n\n\tif len(h.cache) == 0 {\n\t\th.cache = make(map[sessionID]time.Time, 128)\n\t}\n\n\treturn nil\n}\n\n// ServerSession keeps information for a session in VMess server.\ntype ServerSession struct {\n\tuserValidator   *vmess.TimedUserValidator\n\tsessionHistory  *SessionHistory\n\trequestBodyKey  [16]byte\n\trequestBodyIV   [16]byte\n\tresponseBodyKey [16]byte\n\tresponseBodyIV  [16]byte\n\tresponseWriter  io.Writer\n\tresponseHeader  byte\n}\n\n// NewServerSession creates a new ServerSession, using the given UserValidator.\n// The ServerSession instance doesn't take ownership of the validator.\nfunc NewServerSession(validator *vmess.TimedUserValidator, sessionHistory *SessionHistory) *ServerSession {\n\treturn &ServerSession{\n\t\tuserValidator:  validator,\n\t\tsessionHistory: sessionHistory,\n\t}\n}\n\nfunc parseSecurityType(b byte) protocol.SecurityType {\n\tif _, f := protocol.SecurityType_name[int32(b)]; f {\n\t\tst := protocol.SecurityType(b)\n\t\t// For backward compatibility.\n\t\tif st == protocol.SecurityType_UNKNOWN {\n\t\t\tst = protocol.SecurityType_AUTO\n\t\t}\n\t\treturn st\n\t}\n\treturn protocol.SecurityType_UNKNOWN\n}\n\n// DecodeRequestHeader decodes and returns (if successful) a RequestHeader from an input stream.\nfunc (s *ServerSession) DecodeRequestHeader(reader io.Reader, isDrain bool) (*protocol.RequestHeader, error) {\n\tbuffer := buf.New()\n\n\tdrainer, err := drain.NewBehaviorSeedLimitedDrainer(int64(s.userValidator.GetBehaviorSeed()), 16+38, 3266, 64)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to initialize drainer\").Base(err)\n\t}\n\n\tdrainConnection := func(e error) error {\n\t\t// We read a deterministic generated length of data before closing the connection to offset padding read pattern\n\t\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\t\tif isDrain {\n\t\t\treturn drain.WithError(drainer, reader, e)\n\t\t}\n\t\treturn e\n\t}\n\n\tdefer func() {\n\t\tbuffer.Release()\n\t}()\n\n\tif _, err := buffer.ReadFullFrom(reader, protocol.IDBytesLen); err != nil {\n\t\treturn nil, errors.New(\"failed to read request header\").Base(err)\n\t}\n\n\tvar decryptor io.Reader\n\tvar vmessAccount *vmess.MemoryAccount\n\n\tuser, foundAEAD, errorAEAD := s.userValidator.GetAEAD(buffer.Bytes())\n\n\tvar fixedSizeAuthID [16]byte\n\tcopy(fixedSizeAuthID[:], buffer.Bytes())\n\n\tswitch {\n\tcase foundAEAD:\n\t\tvmessAccount = user.Account.(*vmess.MemoryAccount)\n\t\tvar fixedSizeCmdKey [16]byte\n\t\tcopy(fixedSizeCmdKey[:], vmessAccount.ID.CmdKey())\n\t\taeadData, shouldDrain, bytesRead, errorReason := vmessaead.OpenVMessAEADHeader(fixedSizeCmdKey, fixedSizeAuthID, reader)\n\t\tif errorReason != nil {\n\t\t\tif shouldDrain {\n\t\t\t\tdrainer.AcknowledgeReceive(bytesRead)\n\t\t\t\treturn nil, drainConnection(errors.New(\"AEAD read failed\").Base(errorReason))\n\t\t\t} else {\n\t\t\t\treturn nil, drainConnection(errors.New(\"AEAD read failed, drain skipped\").Base(errorReason))\n\t\t\t}\n\t\t}\n\t\tdecryptor = bytes.NewReader(aeadData)\n\tdefault:\n\t\treturn nil, drainConnection(errors.New(\"invalid user\").Base(errorAEAD))\n\t}\n\n\tdrainer.AcknowledgeReceive(int(buffer.Len()))\n\tbuffer.Clear()\n\tif _, err := buffer.ReadFullFrom(decryptor, 38); err != nil {\n\t\treturn nil, errors.New(\"failed to read request header\").Base(err)\n\t}\n\n\trequest := &protocol.RequestHeader{\n\t\tUser:    user,\n\t\tVersion: buffer.Byte(0),\n\t}\n\n\tcopy(s.requestBodyIV[:], buffer.BytesRange(1, 17))   // 16 bytes\n\tcopy(s.requestBodyKey[:], buffer.BytesRange(17, 33)) // 16 bytes\n\tvar sid sessionID\n\tcopy(sid.user[:], vmessAccount.ID.Bytes())\n\tsid.key = s.requestBodyKey\n\tsid.nonce = s.requestBodyIV\n\tif !s.sessionHistory.addIfNotExits(sid) {\n\t\treturn nil, errors.New(\"duplicated session id, possibly under replay attack, but this is a AEAD request\")\n\t}\n\n\ts.responseHeader = buffer.Byte(33)             // 1 byte\n\trequest.Option = bitmask.Byte(buffer.Byte(34)) // 1 byte\n\tpaddingLen := int(buffer.Byte(35) >> 4)\n\trequest.Security = parseSecurityType(buffer.Byte(35) & 0x0F)\n\t// 1 bytes reserved\n\trequest.Command = protocol.RequestCommand(buffer.Byte(37))\n\n\tswitch request.Command {\n\tcase protocol.RequestCommandMux:\n\t\trequest.Address = net.DomainAddress(\"v1.mux.cool\")\n\t\trequest.Port = 0\n\n\tcase protocol.RequestCommandTCP, protocol.RequestCommandUDP:\n\t\tif addr, port, err := addrParser.ReadAddressPort(buffer, decryptor); err == nil {\n\t\t\trequest.Address = addr\n\t\t\trequest.Port = port\n\t\t}\n\t}\n\n\tif paddingLen > 0 {\n\t\tif _, err := buffer.ReadFullFrom(decryptor, int32(paddingLen)); err != nil {\n\t\t\treturn nil, errors.New(\"failed to read padding\").Base(err)\n\t\t}\n\t}\n\n\tif _, err := buffer.ReadFullFrom(decryptor, 4); err != nil {\n\t\treturn nil, errors.New(\"failed to read checksum\").Base(err)\n\t}\n\n\tfnv1a := fnv.New32a()\n\tcommon.Must2(fnv1a.Write(buffer.BytesTo(-4)))\n\tactualHash := fnv1a.Sum32()\n\texpectedHash := binary.BigEndian.Uint32(buffer.BytesFrom(-4))\n\n\tif actualHash != expectedHash {\n\t\treturn nil, errors.New(\"invalid auth, but this is a AEAD request\")\n\t}\n\n\tif request.Address == nil {\n\t\treturn nil, errors.New(\"invalid remote address\")\n\t}\n\n\tif request.Security == protocol.SecurityType_UNKNOWN || request.Security == protocol.SecurityType_AUTO {\n\t\treturn nil, errors.New(\"unknown security type: \", request.Security)\n\t}\n\n\treturn request, nil\n}\n\n// DecodeRequestBody returns Reader from which caller can fetch decrypted body.\nfunc (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) (buf.Reader, error) {\n\tvar sizeParser crypto.ChunkSizeDecoder = crypto.PlainChunkSizeParser{}\n\tif request.Option.Has(protocol.RequestOptionChunkMasking) {\n\t\tsizeParser = NewShakeSizeParser(s.requestBodyIV[:])\n\t}\n\tvar padding crypto.PaddingLengthGenerator\n\tif request.Option.Has(protocol.RequestOptionGlobalPadding) {\n\t\tvar ok bool\n\t\tpadding, ok = sizeParser.(crypto.PaddingLengthGenerator)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid option: RequestOptionGlobalPadding\")\n\t\t}\n\t}\n\n\tswitch request.Security {\n\tcase protocol.SecurityType_NONE:\n\t\tif request.Option.Has(protocol.RequestOptionChunkStream) {\n\t\t\tif request.Command.TransferType() == protocol.TransferTypeStream {\n\t\t\t\treturn crypto.NewChunkStreamReader(sizeParser, reader), nil\n\t\t\t}\n\n\t\t\tauth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    new(NoOpAuthenticator),\n\t\t\t\tNonceGenerator:          crypto.GenerateEmptyBytes(),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\treturn crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding), nil\n\t\t}\n\t\treturn buf.NewReader(reader), nil\n\n\tcase protocol.SecurityType_AES128_GCM:\n\t\taead := crypto.NewAesGcm(s.requestBodyKey[:])\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(s.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD := crypto.NewAesGcm(AuthenticatedLengthKey)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil\n\n\tcase protocol.SecurityType_CHACHA20_POLY1305:\n\t\taead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:]))\n\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(s.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(AuthenticatedLengthKey))\n\t\t\tcommon.Must(err)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding), nil\n\n\tdefault:\n\t\treturn nil, errors.New(\"invalid option: Security\")\n\t}\n}\n\n// EncodeResponseHeader writes encoded response header into the given writer.\nfunc (s *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, writer io.Writer) {\n\tvar encryptionWriter io.Writer\n\tBodyKey := sha256.Sum256(s.requestBodyKey[:])\n\tcopy(s.responseBodyKey[:], BodyKey[:16])\n\tBodyIV := sha256.Sum256(s.requestBodyIV[:])\n\tcopy(s.responseBodyIV[:], BodyIV[:16])\n\n\taesStream := crypto.NewAesEncryptionStream(s.responseBodyKey[:], s.responseBodyIV[:])\n\tencryptionWriter = crypto.NewCryptionWriter(aesStream, writer)\n\ts.responseWriter = encryptionWriter\n\n\taeadEncryptedHeaderBuffer := bytes.NewBuffer(nil)\n\tencryptionWriter = aeadEncryptedHeaderBuffer\n\n\tcommon.Must2(encryptionWriter.Write([]byte{s.responseHeader, byte(header.Option)}))\n\terr := MarshalCommand(header.Command, encryptionWriter)\n\tif err != nil {\n\t\tcommon.Must2(encryptionWriter.Write([]byte{0x00, 0x00}))\n\t}\n\n\taeadResponseHeaderLengthEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderLenKey)\n\taeadResponseHeaderLengthEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderLenIV)[:12]\n\n\taeadResponseHeaderLengthEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderLengthEncryptionKey)\n\n\taeadResponseHeaderLengthEncryptionBuffer := bytes.NewBuffer(nil)\n\n\tdecryptedResponseHeaderLengthBinaryDeserializeBuffer := uint16(aeadEncryptedHeaderBuffer.Len())\n\n\tcommon.Must(binary.Write(aeadResponseHeaderLengthEncryptionBuffer, binary.BigEndian, decryptedResponseHeaderLengthBinaryDeserializeBuffer))\n\n\tAEADEncryptedLength := aeadResponseHeaderLengthEncryptionAEAD.Seal(nil, aeadResponseHeaderLengthEncryptionIV, aeadResponseHeaderLengthEncryptionBuffer.Bytes(), nil)\n\tcommon.Must2(io.Copy(writer, bytes.NewReader(AEADEncryptedLength)))\n\n\taeadResponseHeaderPayloadEncryptionKey := vmessaead.KDF16(s.responseBodyKey[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadKey)\n\taeadResponseHeaderPayloadEncryptionIV := vmessaead.KDF(s.responseBodyIV[:], vmessaead.KDFSaltConstAEADRespHeaderPayloadIV)[:12]\n\n\taeadResponseHeaderPayloadEncryptionAEAD := crypto.NewAesGcm(aeadResponseHeaderPayloadEncryptionKey)\n\n\taeadEncryptedHeaderPayload := aeadResponseHeaderPayloadEncryptionAEAD.Seal(nil, aeadResponseHeaderPayloadEncryptionIV, aeadEncryptedHeaderBuffer.Bytes(), nil)\n\tcommon.Must2(io.Copy(writer, bytes.NewReader(aeadEncryptedHeaderPayload)))\n}\n\n// EncodeResponseBody returns a Writer that auto-encrypt content written by caller.\nfunc (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) {\n\tvar sizeParser crypto.ChunkSizeEncoder = crypto.PlainChunkSizeParser{}\n\tif request.Option.Has(protocol.RequestOptionChunkMasking) {\n\t\tsizeParser = NewShakeSizeParser(s.responseBodyIV[:])\n\t}\n\tvar padding crypto.PaddingLengthGenerator\n\tif request.Option.Has(protocol.RequestOptionGlobalPadding) {\n\t\tvar ok bool\n\t\tpadding, ok = sizeParser.(crypto.PaddingLengthGenerator)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid option: RequestOptionGlobalPadding\")\n\t\t}\n\t}\n\n\tswitch request.Security {\n\tcase protocol.SecurityType_NONE:\n\t\tif request.Option.Has(protocol.RequestOptionChunkStream) {\n\t\t\tif request.Command.TransferType() == protocol.TransferTypeStream {\n\t\t\t\treturn crypto.NewChunkStreamWriter(sizeParser, writer), nil\n\t\t\t}\n\n\t\t\tauth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    new(NoOpAuthenticator),\n\t\t\t\tNonceGenerator:          crypto.GenerateEmptyBytes(),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\treturn crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding), nil\n\t\t}\n\t\treturn buf.NewWriter(writer), nil\n\n\tcase protocol.SecurityType_AES128_GCM:\n\t\taead := crypto.NewAesGcm(s.responseBodyKey[:])\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(s.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD := crypto.NewAesGcm(AuthenticatedLengthKey)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil\n\n\tcase protocol.SecurityType_CHACHA20_POLY1305:\n\t\taead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:]))\n\n\t\tauth := &crypto.AEADAuthenticator{\n\t\t\tAEAD:                    aead,\n\t\t\tNonceGenerator:          GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())),\n\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t}\n\t\tif request.Option.Has(protocol.RequestOptionAuthenticatedLength) {\n\t\t\tAuthenticatedLengthKey := vmessaead.KDF16(s.requestBodyKey[:], \"auth_len\")\n\t\t\tAuthenticatedLengthKeyAEAD, err := chacha20poly1305.New(GenerateChacha20Poly1305Key(AuthenticatedLengthKey))\n\t\t\tcommon.Must(err)\n\n\t\t\tlengthAuth := &crypto.AEADAuthenticator{\n\t\t\t\tAEAD:                    AuthenticatedLengthKeyAEAD,\n\t\t\t\tNonceGenerator:          GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())),\n\t\t\t\tAdditionalDataGenerator: crypto.GenerateEmptyBytes(),\n\t\t\t}\n\t\t\tsizeParser = NewAEADSizeParser(lengthAuth)\n\t\t}\n\t\treturn crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding), nil\n\n\tdefault:\n\t\treturn nil, errors.New(\"invalid option: Security\")\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/inbound/config.go",
    "content": "package inbound\n\n// GetDefaultValue returns default settings of DefaultConfig.\nfunc (c *Config) GetDefaultValue() *DefaultConfig {\n\tif c.GetDefault() == nil {\n\t\treturn &DefaultConfig{}\n\t}\n\treturn c.Default\n}\n"
  },
  {
    "path": "proxy/vmess/inbound/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vmess/inbound/config.proto\n\npackage inbound\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DetourConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tTo            string                 `protobuf:\"bytes,1,opt,name=to,proto3\" json:\"to,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DetourConfig) Reset() {\n\t*x = DetourConfig{}\n\tmi := &file_proxy_vmess_inbound_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DetourConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DetourConfig) ProtoMessage() {}\n\nfunc (x *DetourConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vmess_inbound_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DetourConfig.ProtoReflect.Descriptor instead.\nfunc (*DetourConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *DetourConfig) GetTo() string {\n\tif x != nil {\n\t\treturn x.To\n\t}\n\treturn \"\"\n}\n\ntype DefaultConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tLevel         uint32                 `protobuf:\"varint,2,opt,name=level,proto3\" json:\"level,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DefaultConfig) Reset() {\n\t*x = DefaultConfig{}\n\tmi := &file_proxy_vmess_inbound_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DefaultConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DefaultConfig) ProtoMessage() {}\n\nfunc (x *DefaultConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vmess_inbound_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DefaultConfig.ProtoReflect.Descriptor instead.\nfunc (*DefaultConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *DefaultConfig) GetLevel() uint32 {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUser          []*protocol.User       `protobuf:\"bytes,1,rep,name=user,proto3\" json:\"user,omitempty\"`\n\tDefault       *DefaultConfig         `protobuf:\"bytes,2,opt,name=default,proto3\" json:\"default,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_vmess_inbound_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vmess_inbound_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vmess_inbound_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Config) GetUser() []*protocol.User {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetDefault() *DefaultConfig {\n\tif x != nil {\n\t\treturn x.Default\n\t}\n\treturn nil\n}\n\nvar File_proxy_vmess_inbound_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_vmess_inbound_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\" proxy/vmess/inbound/config.proto\\x12\\x18xray.proxy.vmess.inbound\\x1a\\x1acommon/protocol/user.proto\\\"\\x1e\\n\" +\n\t\"\\fDetourConfig\\x12\\x0e\\n\" +\n\t\"\\x02to\\x18\\x01 \\x01(\\tR\\x02to\\\"%\\n\" +\n\t\"\\rDefaultConfig\\x12\\x14\\n\" +\n\t\"\\x05level\\x18\\x02 \\x01(\\rR\\x05level\\\"{\\n\" +\n\t\"\\x06Config\\x12.\\n\" +\n\t\"\\x04user\\x18\\x01 \\x03(\\v2\\x1a.xray.common.protocol.UserR\\x04user\\x12A\\n\" +\n\t\"\\adefault\\x18\\x02 \\x01(\\v2'.xray.proxy.vmess.inbound.DefaultConfigR\\adefaultBj\\n\" +\n\t\"\\x1ccom.xray.proxy.vmess.inboundP\\x01Z-github.com/xtls/xray-core/proxy/vmess/inbound\\xaa\\x02\\x18Xray.Proxy.Vmess.Inboundb\\x06proto3\"\n\nvar (\n\tfile_proxy_vmess_inbound_config_proto_rawDescOnce sync.Once\n\tfile_proxy_vmess_inbound_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_vmess_inbound_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_vmess_inbound_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vmess_inbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vmess_inbound_config_proto_rawDesc), len(file_proxy_vmess_inbound_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_vmess_inbound_config_proto_rawDescData\n}\n\nvar file_proxy_vmess_inbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_proxy_vmess_inbound_config_proto_goTypes = []any{\n\t(*DetourConfig)(nil),  // 0: xray.proxy.vmess.inbound.DetourConfig\n\t(*DefaultConfig)(nil), // 1: xray.proxy.vmess.inbound.DefaultConfig\n\t(*Config)(nil),        // 2: xray.proxy.vmess.inbound.Config\n\t(*protocol.User)(nil), // 3: xray.common.protocol.User\n}\nvar file_proxy_vmess_inbound_config_proto_depIdxs = []int32{\n\t3, // 0: xray.proxy.vmess.inbound.Config.user:type_name -> xray.common.protocol.User\n\t1, // 1: xray.proxy.vmess.inbound.Config.default:type_name -> xray.proxy.vmess.inbound.DefaultConfig\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vmess_inbound_config_proto_init() }\nfunc file_proxy_vmess_inbound_config_proto_init() {\n\tif File_proxy_vmess_inbound_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vmess_inbound_config_proto_rawDesc), len(file_proxy_vmess_inbound_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vmess_inbound_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vmess_inbound_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vmess_inbound_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vmess_inbound_config_proto = out.File\n\tfile_proxy_vmess_inbound_config_proto_goTypes = nil\n\tfile_proxy_vmess_inbound_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vmess/inbound/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vmess.inbound;\noption csharp_namespace = \"Xray.Proxy.Vmess.Inbound\";\noption go_package = \"github.com/xtls/xray-core/proxy/vmess/inbound\";\noption java_package = \"com.xray.proxy.vmess.inbound\";\noption java_multiple_files = true;\n\nimport \"common/protocol/user.proto\";\n\nmessage DetourConfig {\n  string to = 1;\n}\n\nmessage DefaultConfig {\n  uint32 level = 2;\n}\n\nmessage Config {\n  repeated xray.common.protocol.User user = 1;\n  DefaultConfig default = 2;\n}\n"
  },
  {
    "path": "proxy/vmess/inbound/inbound.go",
    "content": "package inbound\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/core\"\n\tfeature_inbound \"github.com/xtls/xray-core/features/inbound\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/encoding\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\ntype userByEmail struct {\n\tsync.Mutex\n\tcache        map[string]*protocol.MemoryUser\n\tdefaultLevel uint32\n}\n\nfunc newUserByEmail(config *DefaultConfig) *userByEmail {\n\treturn &userByEmail{\n\t\tcache:        make(map[string]*protocol.MemoryUser),\n\t\tdefaultLevel: config.Level,\n\t}\n}\n\nfunc (v *userByEmail) addNoLock(u *protocol.MemoryUser) bool {\n\temail := strings.ToLower(u.Email)\n\t_, found := v.cache[email]\n\tif found {\n\t\treturn false\n\t}\n\tv.cache[email] = u\n\treturn true\n}\n\nfunc (v *userByEmail) Add(u *protocol.MemoryUser) bool {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\treturn v.addNoLock(u)\n}\n\nfunc (v *userByEmail) GetOrGenerate(email string) (*protocol.MemoryUser, bool) {\n\temail = strings.ToLower(email)\n\n\tv.Lock()\n\tdefer v.Unlock()\n\n\tuser, found := v.cache[email]\n\tif !found {\n\t\tid := uuid.New()\n\t\trawAccount := &vmess.Account{\n\t\t\tId: id.String(),\n\t\t}\n\t\taccount, err := rawAccount.AsAccount()\n\t\tcommon.Must(err)\n\t\tuser = &protocol.MemoryUser{\n\t\t\tLevel:   v.defaultLevel,\n\t\t\tEmail:   email,\n\t\t\tAccount: account,\n\t\t}\n\t\tv.cache[email] = user\n\t}\n\treturn user, found\n}\n\nfunc (v *userByEmail) Get(email string) *protocol.MemoryUser {\n\temail = strings.ToLower(email)\n\tv.Lock()\n\tdefer v.Unlock()\n\treturn v.cache[email]\n}\n\nfunc (v *userByEmail) Remove(email string) bool {\n\temail = strings.ToLower(email)\n\n\tv.Lock()\n\tdefer v.Unlock()\n\n\tif _, found := v.cache[email]; !found {\n\t\treturn false\n\t}\n\tdelete(v.cache, email)\n\treturn true\n}\n\n// Handler is an inbound connection handler that handles messages in VMess protocol.\ntype Handler struct {\n\tpolicyManager         policy.Manager\n\tinboundHandlerManager feature_inbound.Manager\n\tclients               *vmess.TimedUserValidator\n\tusersByEmail          *userByEmail\n\tsessionHistory        *encoding.SessionHistory\n}\n\n// New creates a new VMess inbound handler.\nfunc New(ctx context.Context, config *Config) (*Handler, error) {\n\tv := core.MustFromContext(ctx)\n\thandler := &Handler{\n\t\tpolicyManager:         v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tinboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager),\n\t\tclients:               vmess.NewTimedUserValidator(),\n\t\tusersByEmail:          newUserByEmail(config.GetDefaultValue()),\n\t\tsessionHistory:        encoding.NewSessionHistory(),\n\t}\n\n\tfor _, user := range config.User {\n\t\tmUser, err := user.ToMemoryUser()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get VMess user\").Base(err)\n\t\t}\n\n\t\tif err := handler.AddUser(ctx, mUser); err != nil {\n\t\t\treturn nil, errors.New(\"failed to initiate user\").Base(err)\n\t\t}\n\t}\n\n\treturn handler, nil\n}\n\n// Close implements common.Closable.\nfunc (h *Handler) Close() error {\n\treturn errors.Combine(\n\t\th.sessionHistory.Close(),\n\t\tcommon.Close(h.usersByEmail))\n}\n\n// Network implements proxy.Inbound.Network().\nfunc (*Handler) Network() []net.Network {\n\treturn []net.Network{net.Network_TCP, net.Network_UNIX}\n}\n\nfunc (h *Handler) GetOrGenerateUser(email string) *protocol.MemoryUser {\n\tuser, existing := h.usersByEmail.GetOrGenerate(email)\n\tif !existing {\n\t\th.clients.Add(user)\n\t}\n\treturn user\n}\n\nfunc (h *Handler) GetUser(ctx context.Context, email string) *protocol.MemoryUser {\n\treturn h.usersByEmail.Get(email)\n}\n\nfunc (h *Handler) GetUsers(ctx context.Context) []*protocol.MemoryUser {\n\treturn h.clients.GetUsers()\n}\n\nfunc (h *Handler) GetUsersCount(context.Context) int64 {\n\treturn h.clients.GetCount()\n}\n\nfunc (h *Handler) AddUser(ctx context.Context, user *protocol.MemoryUser) error {\n\tif len(user.Email) > 0 && !h.usersByEmail.Add(user) {\n\t\treturn errors.New(\"User \", user.Email, \" already exists.\")\n\t}\n\treturn h.clients.Add(user)\n}\n\nfunc (h *Handler) RemoveUser(ctx context.Context, email string) error {\n\tif email == \"\" {\n\t\treturn errors.New(\"Email must not be empty.\")\n\t}\n\tif !h.usersByEmail.Remove(email) {\n\t\treturn errors.New(\"User \", email, \" not found.\")\n\t}\n\th.clients.Remove(email)\n\treturn nil\n}\n\nfunc transferResponse(timer signal.ActivityUpdater, session *encoding.ServerSession, request *protocol.RequestHeader, response *protocol.ResponseHeader, input buf.Reader, output *buf.BufferedWriter) error {\n\tsession.EncodeResponseHeader(response, output)\n\n\tbodyWriter, err := session.EncodeResponseBody(request, output)\n\tif err != nil {\n\t\treturn errors.New(\"failed to start decoding response\").Base(err)\n\t}\n\t{\n\t\t// Optimize for small response packet\n\t\tdata, err := input.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := bodyWriter.WriteMultiBuffer(data); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := output.SetBuffered(false); err != nil {\n\t\treturn err\n\t}\n\n\tif err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil {\n\t\treturn err\n\t}\n\n\taccount := request.User.Account.(*vmess.MemoryAccount)\n\n\tif request.Option.Has(protocol.RequestOptionChunkStream) && !account.NoTerminationSignal {\n\t\tif err := bodyWriter.WriteMultiBuffer(buf.MultiBuffer{}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Process implements proxy.Inbound.Process().\nfunc (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error {\n\tsessionPolicy := h.policyManager.ForLevel(0)\n\tif err := connection.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {\n\t\treturn errors.New(\"unable to set read deadline\").Base(err).AtWarning()\n\t}\n\n\tiConn := stat.TryUnwrapStatsConn(connection)\n\t_, isDrain := iConn.(*net.TCPConn)\n\tif !isDrain {\n\t\t_, isDrain = iConn.(*net.UnixConn)\n\t}\n\n\treader := &buf.BufferedReader{Reader: buf.NewReader(connection)}\n\tsvrSession := encoding.NewServerSession(h.clients, h.sessionHistory)\n\trequest, err := svrSession.DecodeRequestHeader(reader, isDrain)\n\tif err != nil {\n\t\tif errors.Cause(err) != io.EOF {\n\t\t\tlog.Record(&log.AccessMessage{\n\t\t\t\tFrom:   connection.RemoteAddr(),\n\t\t\t\tTo:     \"\",\n\t\t\t\tStatus: log.AccessRejected,\n\t\t\t\tReason: err,\n\t\t\t})\n\t\t\terr = errors.New(\"invalid request from \", connection.RemoteAddr()).Base(err).AtInfo()\n\t\t}\n\t\treturn err\n\t}\n\n\tif request.Command != protocol.RequestCommandMux {\n\t\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\t\tFrom:   connection.RemoteAddr(),\n\t\t\tTo:     request.Destination(),\n\t\t\tStatus: log.AccessAccepted,\n\t\t\tReason: \"\",\n\t\t\tEmail:  request.User.Email,\n\t\t})\n\t}\n\n\terrors.LogInfo(ctx, \"received request for \", request.Destination())\n\n\tif err := connection.SetReadDeadline(time.Time{}); err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"unable to set back read deadline\")\n\t}\n\n\tinbound := session.InboundFromContext(ctx)\n\tinbound.Name = \"vmess\"\n\tinbound.CanSpliceCopy = 3\n\tinbound.User = request.User\n\n\tsessionPolicy = h.policyManager.ForLevel(request.User.Level)\n\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)\n\tlink, err := dispatcher.Dispatch(ctx, request.Destination())\n\tif err != nil {\n\t\treturn errors.New(\"failed to dispatch request to \", request.Destination()).Base(err)\n\t}\n\n\trequestDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\tbodyReader, err := svrSession.DecodeRequestBody(request, reader)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to start decoding\").Base(err)\n\t\t}\n\t\tif err := buf.Copy(bodyReader, link.Writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to transfer request\").Base(err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\twriter := buf.NewBufferedWriter(buf.NewWriter(connection))\n\t\tdefer writer.Flush()\n\n\t\tresponse := &protocol.ResponseHeader{\n\t\t\tCommand: h.generateCommand(ctx, request),\n\t\t}\n\t\treturn transferResponse(timer, svrSession, request, response, link.Reader, writer)\n\t}\n\n\trequestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestDonePost, responseDone); err != nil {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\n// Stub command generator\nfunc (h *Handler) generateCommand(ctx context.Context, request *protocol.RequestHeader) protocol.ResponseCommand {\n\treturn nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "proxy/vmess/outbound/command.go",
    "content": "package outbound\n\nimport (\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\n// As a stub command consumer.\nfunc (h *Handler) handleCommand(dest net.Destination, cmd protocol.ResponseCommand) {\n\tswitch cmd.(type) {\n\tdefault:\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/outbound/config.go",
    "content": "package outbound\n"
  },
  {
    "path": "proxy/vmess/outbound/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/vmess/outbound/config.proto\n\npackage outbound\n\nimport (\n\tprotocol \"github.com/xtls/xray-core/common/protocol\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tReceiver      *protocol.ServerEndpoint `protobuf:\"bytes,1,opt,name=Receiver,proto3\" json:\"Receiver,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_proxy_vmess_outbound_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_vmess_outbound_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_proxy_vmess_outbound_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetReceiver() *protocol.ServerEndpoint {\n\tif x != nil {\n\t\treturn x.Receiver\n\t}\n\treturn nil\n}\n\nvar File_proxy_vmess_outbound_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_vmess_outbound_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!proxy/vmess/outbound/config.proto\\x12\\x19xray.proxy.vmess.outbound\\x1a!common/protocol/server_spec.proto\\\"J\\n\" +\n\t\"\\x06Config\\x12@\\n\" +\n\t\"\\bReceiver\\x18\\x01 \\x01(\\v2$.xray.common.protocol.ServerEndpointR\\bReceiverBm\\n\" +\n\t\"\\x1dcom.xray.proxy.vmess.outboundP\\x01Z.github.com/xtls/xray-core/proxy/vmess/outbound\\xaa\\x02\\x19Xray.Proxy.Vmess.Outboundb\\x06proto3\"\n\nvar (\n\tfile_proxy_vmess_outbound_config_proto_rawDescOnce sync.Once\n\tfile_proxy_vmess_outbound_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_vmess_outbound_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_vmess_outbound_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_vmess_outbound_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_vmess_outbound_config_proto_rawDesc), len(file_proxy_vmess_outbound_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_vmess_outbound_config_proto_rawDescData\n}\n\nvar file_proxy_vmess_outbound_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_proxy_vmess_outbound_config_proto_goTypes = []any{\n\t(*Config)(nil),                  // 0: xray.proxy.vmess.outbound.Config\n\t(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint\n}\nvar file_proxy_vmess_outbound_config_proto_depIdxs = []int32{\n\t1, // 0: xray.proxy.vmess.outbound.Config.Receiver:type_name -> xray.common.protocol.ServerEndpoint\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_vmess_outbound_config_proto_init() }\nfunc file_proxy_vmess_outbound_config_proto_init() {\n\tif File_proxy_vmess_outbound_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_vmess_outbound_config_proto_rawDesc), len(file_proxy_vmess_outbound_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_vmess_outbound_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_vmess_outbound_config_proto_depIdxs,\n\t\tMessageInfos:      file_proxy_vmess_outbound_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_vmess_outbound_config_proto = out.File\n\tfile_proxy_vmess_outbound_config_proto_goTypes = nil\n\tfile_proxy_vmess_outbound_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/vmess/outbound/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.vmess.outbound;\noption csharp_namespace = \"Xray.Proxy.Vmess.Outbound\";\noption go_package = \"github.com/xtls/xray-core/proxy/vmess/outbound\";\noption java_package = \"com.xray.proxy.vmess.outbound\";\noption java_multiple_files = true;\n\nimport \"common/protocol/server_spec.proto\";\n\nmessage Config {\n  xray.common.protocol.ServerEndpoint Receiver = 1;\n}\n"
  },
  {
    "path": "proxy/vmess/outbound/outbound.go",
    "content": "package outbound\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"hash/crc64\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/common/xudp\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/encoding\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// Handler is an outbound connection handler for VMess protocol.\ntype Handler struct {\n\tserver        *protocol.ServerSpec\n\tpolicyManager policy.Manager\n\tcone          bool\n}\n\n// New creates a new VMess outbound handler.\nfunc New(ctx context.Context, config *Config) (*Handler, error) {\n\tif config.Receiver == nil {\n\t\treturn nil, errors.New(`no vnext found`)\n\t}\n\tserver, err := protocol.NewServerSpecFromPB(config.Receiver)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get server spec\").Base(err)\n\t}\n\n\tv := core.MustFromContext(ctx)\n\thandler := &Handler{\n\t\tserver:        server,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tcone:          ctx.Value(\"cone\").(bool),\n\t}\n\n\treturn handler, nil\n}\n\n// Process implements proxy.Outbound.Process().\nfunc (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified\").AtError()\n\t}\n\tob.Name = \"vmess\"\n\tob.CanSpliceCopy = 3\n\n\trec := h.server\n\tvar conn stat.Connection\n\n\terr := retry.ExponentialBackoff(5, 200).On(func() error {\n\t\trawConn, err := dialer.Dial(ctx, rec.Destination)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconn = rawConn\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to find an available destination\").Base(err).AtWarning()\n\t}\n\tdefer conn.Close()\n\n\ttarget := ob.Target\n\terrors.LogInfo(ctx, \"tunneling request to \", target, \" via \", rec.Destination.NetAddr())\n\n\tcommand := protocol.RequestCommandTCP\n\tif target.Network == net.Network_UDP {\n\t\tcommand = protocol.RequestCommandUDP\n\t}\n\tif target.Address.Family().IsDomain() && target.Address.Domain() == \"v1.mux.cool\" {\n\t\tcommand = protocol.RequestCommandMux\n\t}\n\n\tuser := rec.User\n\trequest := &protocol.RequestHeader{\n\t\tVersion: encoding.Version,\n\t\tUser:    user,\n\t\tCommand: command,\n\t\tAddress: target.Address,\n\t\tPort:    target.Port,\n\t\tOption:  protocol.RequestOptionChunkStream,\n\t}\n\n\taccount := request.User.Account.(*vmess.MemoryAccount)\n\trequest.Security = account.Security\n\n\tif request.Security == protocol.SecurityType_AES128_GCM || request.Security == protocol.SecurityType_NONE || request.Security == protocol.SecurityType_CHACHA20_POLY1305 {\n\t\trequest.Option.Set(protocol.RequestOptionChunkMasking)\n\t}\n\n\tif shouldEnablePadding(request.Security) && request.Option.Has(protocol.RequestOptionChunkMasking) {\n\t\trequest.Option.Set(protocol.RequestOptionGlobalPadding)\n\t}\n\n\tif request.Security == protocol.SecurityType_ZERO {\n\t\trequest.Security = protocol.SecurityType_NONE\n\t\trequest.Option.Clear(protocol.RequestOptionChunkStream)\n\t\trequest.Option.Clear(protocol.RequestOptionChunkMasking)\n\t}\n\n\tif account.AuthenticatedLengthExperiment {\n\t\trequest.Option.Set(protocol.RequestOptionAuthenticatedLength)\n\t}\n\n\tinput := link.Reader\n\toutput := link.Writer\n\n\thashkdf := hmac.New(sha256.New, []byte(\"VMessBF\"))\n\thashkdf.Write(account.ID.Bytes())\n\n\tbehaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO))\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tsession := encoding.NewClientSession(ctx, int64(behaviorSeed))\n\tsessionPolicy := h.policyManager.ForLevel(request.User.Level)\n\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, sessionPolicy.Timeouts.ConnectionIdle)\n\n\tif request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 {\n\t\trequest.Command = protocol.RequestCommandMux\n\t\trequest.Address = net.DomainAddress(\"v1.mux.cool\")\n\t\trequest.Port = net.Port(666)\n\t}\n\n\trequestDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)\n\n\t\twriter := buf.NewBufferedWriter(buf.NewWriter(conn))\n\t\tif err := session.EncodeRequestHeader(request, writer); err != nil {\n\t\t\treturn errors.New(\"failed to encode request\").Base(err).AtWarning()\n\t\t}\n\n\t\tbodyWriter, err := session.EncodeRequestBody(request, writer)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to start encoding\").Base(err)\n\t\t}\n\t\tbodyWriter2 := bodyWriter\n\t\tif request.Command == protocol.RequestCommandMux && request.Port == 666 {\n\t\t\tbodyWriter = xudp.NewPacketWriter(bodyWriter, target, xudp.GetGlobalID(ctx))\n\t\t}\n\t\tif err := buf.CopyOnceTimeout(input, bodyWriter, time.Millisecond*100); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {\n\t\t\treturn errors.New(\"failed to write first payload\").Base(err)\n\t\t}\n\n\t\tif err := writer.SetBuffered(false); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := buf.Copy(input, bodyWriter, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif request.Option.Has(protocol.RequestOptionChunkStream) && !account.NoTerminationSignal {\n\t\t\tif err := bodyWriter2.WriteMultiBuffer(buf.MultiBuffer{}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tdefer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)\n\n\t\treader := &buf.BufferedReader{Reader: buf.NewReader(conn)}\n\t\theader, err := session.DecodeResponseHeader(reader)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to read header\").Base(err)\n\t\t}\n\t\th.handleCommand(rec.Destination, header.Command)\n\n\t\tbodyReader, err := session.DecodeResponseBody(request, reader)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to start encoding response\").Base(err)\n\t\t}\n\t\tif request.Command == protocol.RequestCommandMux && request.Port == 666 {\n\t\t\tbodyReader = xudp.NewPacketReader(&buf.BufferedReader{Reader: bodyReader})\n\t\t}\n\n\t\treturn buf.Copy(bodyReader, output, buf.UpdateActivity(timer))\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tresponseDonePost := task.OnSuccess(responseDone, task.Close(output))\n\tif err := task.Run(ctx, requestDone, responseDonePost); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\nvar (\n\tenablePadding = false\n)\n\nfunc shouldEnablePadding(s protocol.SecurityType) bool {\n\treturn enablePadding || s == protocol.SecurityType_AES128_GCM || s == protocol.SecurityType_CHACHA20_POLY1305 || s == protocol.SecurityType_AUTO\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn New(ctx, config.(*Config))\n\t}))\n\n\tconst defaultFlagValue = \"NOT_DEFINED_AT_ALL\"\n\n\tpaddingValue := platform.NewEnvFlag(platform.UseVmessPadding).GetValue(func() string { return defaultFlagValue })\n\tif paddingValue != defaultFlagValue {\n\t\tenablePadding = true\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/validator.go",
    "content": "package vmess\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"hash/crc64\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/proxy/vmess/aead\"\n)\n\n// TimedUserValidator is a user Validator based on time.\ntype TimedUserValidator struct {\n\tsync.RWMutex\n\tusers []*protocol.MemoryUser\n\n\tbehaviorSeed  uint64\n\tbehaviorFused bool\n\n\taeadDecoderHolder *aead.AuthIDDecoderHolder\n}\n\n// NewTimedUserValidator creates a new TimedUserValidator.\nfunc NewTimedUserValidator() *TimedUserValidator {\n\ttuv := &TimedUserValidator{\n\t\tusers:             make([]*protocol.MemoryUser, 0, 16),\n\t\taeadDecoderHolder: aead.NewAuthIDDecoderHolder(),\n\t}\n\treturn tuv\n}\n\nfunc (v *TimedUserValidator) Add(u *protocol.MemoryUser) error {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\tv.users = append(v.users, u)\n\n\taccount, ok := u.Account.(*MemoryAccount)\n\tif !ok {\n\t\treturn errors.New(\"account type is incorrect\")\n\t}\n\tif !v.behaviorFused {\n\t\thashkdf := hmac.New(sha256.New, []byte(\"VMESSBSKDF\"))\n\t\thashkdf.Write(account.ID.Bytes())\n\t\tv.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))\n\t}\n\n\tvar cmdkeyfl [16]byte\n\tcopy(cmdkeyfl[:], account.ID.CmdKey())\n\tv.aeadDecoderHolder.AddUser(cmdkeyfl, u)\n\n\treturn nil\n}\n\nfunc (v *TimedUserValidator) GetUsers() []*protocol.MemoryUser {\n\tv.Lock()\n\tdefer v.Unlock()\n\tdst := make([]*protocol.MemoryUser, len(v.users))\n\tcopy(dst, v.users)\n\treturn dst\n}\n\nfunc (v *TimedUserValidator) GetCount() int64 {\n\tv.Lock()\n\tdefer v.Unlock()\n\treturn int64(len(v.users))\n}\n\nfunc (v *TimedUserValidator) GetAEAD(userHash []byte) (*protocol.MemoryUser, bool, error) {\n\tv.RLock()\n\tdefer v.RUnlock()\n\n\tvar userHashFL [16]byte\n\tcopy(userHashFL[:], userHash)\n\n\tuserd, err := v.aeadDecoderHolder.Match(userHashFL)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\treturn userd.(*protocol.MemoryUser), true, nil\n}\n\nfunc (v *TimedUserValidator) Remove(email string) bool {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\temail = strings.ToLower(email)\n\tidx := -1\n\tfor i, u := range v.users {\n\t\tif strings.EqualFold(u.Email, email) {\n\t\t\tidx = i\n\t\t\tvar cmdkeyfl [16]byte\n\t\t\tcopy(cmdkeyfl[:], u.Account.(*MemoryAccount).ID.CmdKey())\n\t\t\tv.aeadDecoderHolder.RemoveUser(cmdkeyfl)\n\t\t\tbreak\n\t\t}\n\t}\n\tif idx == -1 {\n\t\treturn false\n\t}\n\tulen := len(v.users)\n\n\tv.users[idx] = v.users[ulen-1]\n\tv.users[ulen-1] = nil\n\tv.users = v.users[:ulen-1]\n\n\treturn true\n}\n\nfunc (v *TimedUserValidator) GetBehaviorSeed() uint64 {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\tv.behaviorFused = true\n\tif v.behaviorSeed == 0 {\n\t\tv.behaviorSeed = dice.RollUint64()\n\t}\n\treturn v.behaviorSeed\n}\n\nvar ErrNotFound = errors.New(\"Not Found\")\n\nvar ErrTainted = errors.New(\"ErrTainted\")\n"
  },
  {
    "path": "proxy/vmess/validator_test.go",
    "content": "package vmess_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t. \"github.com/xtls/xray-core/proxy/vmess\"\n)\n\nfunc toAccount(a *Account) protocol.Account {\n\taccount, err := a.AsAccount()\n\tcommon.Must(err)\n\treturn account\n}\n\nfunc BenchmarkUserValidator(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tv := NewTimedUserValidator()\n\n\t\tfor j := 0; j < 1500; j++ {\n\t\t\tid := uuid.New()\n\t\t\tv.Add(&protocol.MemoryUser{\n\t\t\t\tEmail: \"test\",\n\t\t\t\tAccount: toAccount(&Account{\n\t\t\t\t\tId: id.String(),\n\t\t\t\t}),\n\t\t\t})\n\t\t}\n\n\t\tcommon.Close(v)\n\t}\n}\n"
  },
  {
    "path": "proxy/vmess/vmess.go",
    "content": "// Package vmess contains the implementation of VMess protocol and transportation.\n//\n// VMess contains both inbound and outbound connections. VMess inbound is usually used on servers\n// together with 'freedom' to talk to final destination, while VMess outbound is usually used on\n// clients with 'socks' for proxying.\npackage vmess\n"
  },
  {
    "path": "proxy/wireguard/bind.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"sync\"\n\n\t\"golang.zx2c4.com/wireguard/conn\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\ntype netReadInfo struct {\n\t// status\n\twaiter sync.WaitGroup\n\t// param\n\tbuff []byte\n\t// result\n\tbytes    int\n\tendpoint conn.Endpoint\n\terr      error\n}\n\n// reduce duplicated code\ntype netBind struct {\n\tdns       dns.Client\n\tdnsOption dns.IPOption\n\n\tworkers   int\n\treadQueue chan *netReadInfo\n}\n\n// SetMark implements conn.Bind\nfunc (bind *netBind) SetMark(mark uint32) error {\n\treturn nil\n}\n\n// ParseEndpoint implements conn.Bind\nfunc (n *netBind) ParseEndpoint(s string) (conn.Endpoint, error) {\n\tipStr, port, err := net.SplitHostPort(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tportNum, err := strconv.Atoi(port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taddr := net.ParseAddress(ipStr)\n\tif addr.Family() == net.AddressFamilyDomain {\n\t\tips, _, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t} else if len(ips) == 0 {\n\t\t\treturn nil, dns.ErrEmptyResponse\n\t\t}\n\t\taddr = net.IPAddress(ips[0])\n\t}\n\n\tdst := net.Destination{\n\t\tAddress: addr,\n\t\tPort:    net.Port(portNum),\n\t\tNetwork: net.Network_UDP,\n\t}\n\n\treturn &netEndpoint{\n\t\tdst: dst,\n\t}, nil\n}\n\n// BatchSize implements conn.Bind\nfunc (bind *netBind) BatchSize() int {\n\treturn 1\n}\n\n// Open implements conn.Bind\nfunc (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {\n\tbind.readQueue = make(chan *netReadInfo)\n\n\tfun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tn = 0\n\t\t\t\terr = errors.New(\"channel closed\")\n\t\t\t}\n\t\t}()\n\n\t\tr := &netReadInfo{\n\t\t\tbuff: bufs[0],\n\t\t}\n\t\tr.waiter.Add(1)\n\t\tbind.readQueue <- r\n\t\tr.waiter.Wait() // wait read goroutine done, or we will miss the result\n\t\tsizes[0], eps[0] = r.bytes, r.endpoint\n\t\treturn 1, r.err\n\t}\n\tworkers := bind.workers\n\tif workers <= 0 {\n\t\tworkers = 1\n\t}\n\tarr := make([]conn.ReceiveFunc, workers)\n\tfor i := 0; i < workers; i++ {\n\t\tarr[i] = fun\n\t}\n\n\treturn arr, uint16(uport), nil\n}\n\n// Close implements conn.Bind\nfunc (bind *netBind) Close() error {\n\tif bind.readQueue != nil {\n\t\tclose(bind.readQueue)\n\t}\n\treturn nil\n}\n\ntype netBindClient struct {\n\tnetBind\n\n\tctx      context.Context\n\tdialer   internet.Dialer\n\treserved []byte\n}\n\nfunc (bind *netBindClient) connectTo(endpoint *netEndpoint) error {\n\tc, err := bind.dialer.Dial(bind.ctx, endpoint.dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tendpoint.conn = c\n\n\tgo func(readQueue <-chan *netReadInfo, endpoint *netEndpoint) {\n\t\tfor {\n\t\t\tv, ok := <-readQueue\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ti, err := c.Read(v.buff)\n\n\t\t\tif i > 3 {\n\t\t\t\tv.buff[1] = 0\n\t\t\t\tv.buff[2] = 0\n\t\t\t\tv.buff[3] = 0\n\t\t\t}\n\n\t\t\tv.bytes = i\n\t\t\tv.endpoint = endpoint\n\t\t\tv.err = err\n\t\t\tv.waiter.Done()\n\t\t\tif err != nil {\n\t\t\t\tendpoint.conn = nil\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}(bind.readQueue, endpoint)\n\n\treturn nil\n}\n\nfunc (bind *netBindClient) Send(buff [][]byte, endpoint conn.Endpoint) error {\n\tvar err error\n\n\tnend, ok := endpoint.(*netEndpoint)\n\tif !ok {\n\t\treturn conn.ErrWrongEndpointType\n\t}\n\n\tif nend.conn == nil {\n\t\terr = bind.connectTo(nend)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, buff := range buff {\n\t\tif len(buff) > 3 && len(bind.reserved) == 3 {\n\t\t\tcopy(buff[1:], bind.reserved)\n\t\t}\n\t\tif _, err = nend.conn.Write(buff); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\ntype netBindServer struct {\n\tnetBind\n}\n\nfunc (bind *netBindServer) Send(buff [][]byte, endpoint conn.Endpoint) error {\n\tvar err error\n\n\tnend, ok := endpoint.(*netEndpoint)\n\tif !ok {\n\t\treturn conn.ErrWrongEndpointType\n\t}\n\n\tif nend.conn == nil {\n\t\treturn errors.New(\"connection not open yet\")\n\t}\n\n\tfor _, buff := range buff {\n\t\tif _, err = nend.conn.Write(buff); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn err\n}\n\ntype netEndpoint struct {\n\tdst  net.Destination\n\tconn net.Conn\n}\n\nfunc (netEndpoint) ClearSrc() {}\n\nfunc (e netEndpoint) DstIP() netip.Addr {\n\treturn netip.Addr{}\n}\n\nfunc (e netEndpoint) SrcIP() netip.Addr {\n\treturn netip.Addr{}\n}\n\nfunc (e netEndpoint) DstToBytes() []byte {\n\tvar dat []byte\n\tif e.dst.Address.Family().IsIPv4() {\n\t\tdat = e.dst.Address.IP().To4()[:]\n\t} else {\n\t\tdat = e.dst.Address.IP().To16()[:]\n\t}\n\tdat = append(dat, byte(e.dst.Port), byte(e.dst.Port>>8))\n\treturn dat\n}\n\nfunc (e netEndpoint) DstToString() string {\n\treturn e.dst.NetAddr()\n}\n\nfunc (e netEndpoint) SrcToString() string {\n\treturn \"\"\n}\n\nfunc toNetIpAddr(addr net.Address) netip.Addr {\n\tif addr.Family().IsIPv4() {\n\t\tip := addr.IP()\n\t\treturn netip.AddrFrom4([4]byte{ip[0], ip[1], ip[2], ip[3]})\n\t} else {\n\t\tip := addr.IP()\n\t\tarr := [16]byte{}\n\t\tfor i := 0; i < 16; i++ {\n\t\t\tarr[i] = ip[i]\n\t\t}\n\t\treturn netip.AddrFrom16(arr)\n\t}\n}\n"
  },
  {
    "path": "proxy/wireguard/client.go",
    "content": "/*\n\nSome of codes are copied from https://github.com/octeep/wireproxy, license below.\n\nCopyright (c) 2022 Wind T.F. Wong <octeep@pm.me>\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n*/\n\npackage wireguard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// Handler is an outbound connection that silently swallow the entire payload.\ntype Handler struct {\n\tconf          *DeviceConfig\n\tnet           Tunnel\n\tbind          *netBindClient\n\tpolicyManager policy.Manager\n\tdns           dns.Client\n\t// cached configuration\n\tendpoints        []netip.Addr\n\thasIPv4, hasIPv6 bool\n\twgLock           sync.Mutex\n}\n\n// New creates a new wireguard handler.\nfunc New(ctx context.Context, conf *DeviceConfig) (*Handler, error) {\n\tv := core.MustFromContext(ctx)\n\n\tendpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\td := v.GetFeature(dns.ClientType()).(dns.Client)\n\treturn &Handler{\n\t\tconf:          conf,\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t\tdns:           d,\n\t\tendpoints:     endpoints,\n\t\thasIPv4:       hasIPv4,\n\t\thasIPv6:       hasIPv6,\n\t}, nil\n}\n\nfunc (h *Handler) Close() (err error) {\n\tgo func() {\n\t\th.wgLock.Lock()\n\t\tdefer h.wgLock.Unlock()\n\n\t\tif h.net != nil {\n\t\t\t_ = h.net.Close()\n\t\t\th.net = nil\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer) (err error) {\n\th.wgLock.Lock()\n\tdefer h.wgLock.Unlock()\n\n\tif h.bind != nil && h.bind.dialer == dialer && h.net != nil {\n\t\treturn nil\n\t}\n\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: log.Severity_Info,\n\t\tContent:  \"switching dialer\",\n\t})\n\n\tif h.net != nil {\n\t\t_ = h.net.Close()\n\t\th.net = nil\n\t}\n\tif h.bind != nil {\n\t\t_ = h.bind.Close()\n\t\th.bind = nil\n\t}\n\n\t// bind := conn.NewStdNetBind() // TODO: conn.Bind wrapper for dialer\n\th.bind = &netBindClient{\n\t\tnetBind: netBind{\n\t\t\tdns: h.dns,\n\t\t\tdnsOption: dns.IPOption{\n\t\t\t\tIPv4Enable: h.hasIPv4,\n\t\t\t\tIPv6Enable: h.hasIPv6,\n\t\t\t},\n\t\t\tworkers: int(h.conf.NumWorkers),\n\t\t},\n\t\tctx:      ctx,\n\t\tdialer:   dialer,\n\t\treserved: h.conf.Reserved,\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\th.bind.Close()\n\t\t\th.bind = nil\n\t\t}\n\t}()\n\n\th.net, err = h.makeVirtualTun()\n\tif err != nil {\n\t\treturn errors.New(\"failed to create virtual tun interface\").Base(err)\n\t}\n\treturn nil\n}\n\n// Process implements OutboundHandler.Dispatch().\nfunc (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif !ob.Target.IsValid() {\n\t\treturn errors.New(\"target not specified\")\n\t}\n\tob.Name = \"wireguard\"\n\tob.CanSpliceCopy = 3\n\n\tif err := h.processWireGuard(ctx, dialer); err != nil {\n\t\treturn err\n\t}\n\n\t// Destination of the inner request.\n\tdestination := ob.Target\n\tcommand := protocol.RequestCommandTCP\n\tif destination.Network == net.Network_UDP {\n\t\tcommand = protocol.RequestCommandUDP\n\t}\n\n\t// resolve dns\n\taddr := destination.Address\n\tif addr.Family().IsDomain() {\n\t\tips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{\n\t\t\tIPv4Enable: h.hasIPv4 && h.conf.preferIP4(),\n\t\t\tIPv6Enable: h.hasIPv6 && h.conf.preferIP6(),\n\t\t})\n\t\t{ // Resolve fallback\n\t\t\tif (len(ips) == 0 || err != nil) && h.conf.hasFallback() {\n\t\t\t\tips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{\n\t\t\t\t\tIPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),\n\t\t\t\t\tIPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to lookup DNS\").Base(err)\n\t\t} else if len(ips) == 0 {\n\t\t\treturn dns.ErrEmptyResponse\n\t\t}\n\t\taddr = net.IPAddress(ips[dice.Roll(len(ips))])\n\t}\n\n\tvar newCtx context.Context\n\tvar newCancel context.CancelFunc\n\tif session.TimeoutOnlyFromContext(ctx) {\n\t\tnewCtx, newCancel = context.WithCancel(context.Background())\n\t}\n\n\tp := h.policyManager.ForLevel(0)\n\n\tctx, cancel := context.WithCancel(ctx)\n\ttimer := signal.CancelAfterInactivity(ctx, func() {\n\t\tcancel()\n\t\tif newCancel != nil {\n\t\t\tnewCancel()\n\t\t}\n\t}, p.Timeouts.ConnectionIdle)\n\taddrPort := netip.AddrPortFrom(toNetIpAddr(addr), destination.Port.Value())\n\n\tvar requestFunc func() error\n\tvar responseFunc func() error\n\n\tif command == protocol.RequestCommandTCP {\n\t\tconn, err := h.net.DialContextTCPAddrPort(ctx, addrPort)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create TCP connection\").Base(err)\n\t\t}\n\t\tdefer conn.Close()\n\n\t\trequestFunc = func() error {\n\t\t\tdefer timer.SetTimeout(p.Timeouts.DownlinkOnly)\n\t\t\treturn buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))\n\t\t}\n\t\tresponseFunc = func() error {\n\t\t\tdefer timer.SetTimeout(p.Timeouts.UplinkOnly)\n\t\t\treturn buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))\n\t\t}\n\t} else if command == protocol.RequestCommandUDP {\n\t\tconn, err := h.net.DialUDPAddrPort(netip.AddrPort{}, addrPort)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create UDP connection\").Base(err)\n\t\t}\n\t\tdefer conn.Close()\n\n\t\trequestFunc = func() error {\n\t\t\tdefer timer.SetTimeout(p.Timeouts.DownlinkOnly)\n\t\t\treturn buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))\n\t\t}\n\t\tresponseFunc = func() error {\n\t\t\tdefer timer.SetTimeout(p.Timeouts.UplinkOnly)\n\t\t\treturn buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))\n\t\t}\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tresponseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestFunc, responseDonePost); err != nil {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\n// creates a tun interface on netstack given a configuration\nfunc (h *Handler) makeVirtualTun() (Tunnel, error) {\n\tt, err := h.conf.createTun()(h.endpoints, int(h.conf.Mtu), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\th.bind.dnsOption.IPv4Enable = h.hasIPv4\n\th.bind.dnsOption.IPv6Enable = h.hasIPv6\n\n\tif err = t.BuildDevice(h.createIPCRequest(), h.bind); err != nil {\n\t\t_ = t.Close()\n\t\treturn nil, err\n\t}\n\treturn t, nil\n}\n\n// serialize the config into an IPC request\nfunc (h *Handler) createIPCRequest() string {\n\tvar request strings.Builder\n\n\trequest.WriteString(fmt.Sprintf(\"private_key=%s\\n\", h.conf.SecretKey))\n\n\tif !h.conf.IsClient {\n\t\t// placeholder, we'll handle actual port listening on Xray\n\t\trequest.WriteString(\"listen_port=1337\\n\")\n\t}\n\n\tfor _, peer := range h.conf.Peers {\n\t\tif peer.PublicKey != \"\" {\n\t\t\trequest.WriteString(fmt.Sprintf(\"public_key=%s\\n\", peer.PublicKey))\n\t\t}\n\n\t\tif peer.PreSharedKey != \"\" {\n\t\t\trequest.WriteString(fmt.Sprintf(\"preshared_key=%s\\n\", peer.PreSharedKey))\n\t\t}\n\n\t\taddress, port, err := net.SplitHostPort(peer.Endpoint)\n\t\tif err != nil {\n\t\t\terrors.LogError(h.bind.ctx, \"failed to split endpoint \", peer.Endpoint, \" into address and port\")\n\t\t}\n\t\taddr := net.ParseAddress(address)\n\t\tif addr.Family().IsDomain() {\n\t\t\tdialerIp := h.bind.dialer.DestIpAddress()\n\t\t\tif dialerIp != nil {\n\t\t\t\taddr = net.ParseAddress(dialerIp.String())\n\t\t\t\terrors.LogInfo(h.bind.ctx, \"createIPCRequest use dialer dest ip: \", addr)\n\t\t\t} else {\n\t\t\t\tips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{\n\t\t\t\t\tIPv4Enable: h.conf.preferIP4(),\n\t\t\t\t\tIPv6Enable: h.conf.preferIP6(),\n\t\t\t\t})\n\t\t\t\t{ // Resolve fallback\n\t\t\t\t\tif (len(ips) == 0 || err != nil) && h.conf.hasFallback() {\n\t\t\t\t\t\tips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{\n\t\t\t\t\t\t\tIPv4Enable: h.conf.fallbackIP4(),\n\t\t\t\t\t\t\tIPv6Enable: h.conf.fallbackIP6(),\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogInfoInner(h.bind.ctx, err, \"createIPCRequest failed to lookup DNS\")\n\t\t\t\t} else if len(ips) == 0 {\n\t\t\t\t\terrors.LogInfo(h.bind.ctx, \"createIPCRequest empty lookup DNS\")\n\t\t\t\t} else {\n\t\t\t\t\taddr = net.IPAddress(ips[dice.Roll(len(ips))])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif peer.Endpoint != \"\" {\n\t\t\trequest.WriteString(fmt.Sprintf(\"endpoint=%s:%s\\n\", addr, port))\n\t\t}\n\n\t\tfor _, ip := range peer.AllowedIps {\n\t\t\trequest.WriteString(fmt.Sprintf(\"allowed_ip=%s\\n\", ip))\n\t\t}\n\n\t\tif peer.KeepAlive != 0 {\n\t\t\trequest.WriteString(fmt.Sprintf(\"persistent_keepalive_interval=%d\\n\", peer.KeepAlive))\n\t\t}\n\t}\n\n\treturn request.String()[:request.Len()]\n}\n"
  },
  {
    "path": "proxy/wireguard/config.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nfunc (c *DeviceConfig) preferIP4() bool {\n\treturn c.DomainStrategy == DeviceConfig_FORCE_IP ||\n\t\tc.DomainStrategy == DeviceConfig_FORCE_IP4 ||\n\t\tc.DomainStrategy == DeviceConfig_FORCE_IP46\n}\n\nfunc (c *DeviceConfig) preferIP6() bool {\n\treturn c.DomainStrategy == DeviceConfig_FORCE_IP ||\n\t\tc.DomainStrategy == DeviceConfig_FORCE_IP6 ||\n\t\tc.DomainStrategy == DeviceConfig_FORCE_IP64\n}\n\nfunc (c *DeviceConfig) hasFallback() bool {\n\treturn c.DomainStrategy == DeviceConfig_FORCE_IP46 || c.DomainStrategy == DeviceConfig_FORCE_IP64\n}\n\nfunc (c *DeviceConfig) fallbackIP4() bool {\n\treturn c.DomainStrategy == DeviceConfig_FORCE_IP64\n}\n\nfunc (c *DeviceConfig) fallbackIP6() bool {\n\treturn c.DomainStrategy == DeviceConfig_FORCE_IP46\n}\n\nfunc (c *DeviceConfig) createTun() tunCreator {\n\tif !c.IsClient {\n\t\t// See tun_linux.go createKernelTun()\n\t\terrors.LogWarning(context.Background(), \"Using gVisor TUN. WG inbound doesn't support kernel TUN yet.\")\n\t\treturn createGVisorTun\n\t}\n\tif c.NoKernelTun {\n\t\terrors.LogWarning(context.Background(), \"Using gVisor TUN. NoKernelTun is set to true.\")\n\t\treturn createGVisorTun\n\t}\n\tkernelTunSupported, err := KernelTunSupported()\n\tif err != nil {\n\t\terrors.LogWarning(context.Background(), \"Using gVisor TUN. Failed to check kernel TUN support:\", err)\n\t\treturn createGVisorTun\n\t}\n\tif !kernelTunSupported {\n\t\terrors.LogWarning(context.Background(), \"Using gVisor TUN. Kernel TUN is not supported on your OS, or your permission is insufficient.\")\n\t\treturn createGVisorTun\n\t}\n\terrors.LogWarning(context.Background(), \"Using kernel TUN.\")\n\treturn createKernelTun\n}\n"
  },
  {
    "path": "proxy/wireguard/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: proxy/wireguard/config.proto\n\npackage wireguard\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DeviceConfig_DomainStrategy int32\n\nconst (\n\tDeviceConfig_FORCE_IP   DeviceConfig_DomainStrategy = 0\n\tDeviceConfig_FORCE_IP4  DeviceConfig_DomainStrategy = 1\n\tDeviceConfig_FORCE_IP6  DeviceConfig_DomainStrategy = 2\n\tDeviceConfig_FORCE_IP46 DeviceConfig_DomainStrategy = 3\n\tDeviceConfig_FORCE_IP64 DeviceConfig_DomainStrategy = 4\n)\n\n// Enum value maps for DeviceConfig_DomainStrategy.\nvar (\n\tDeviceConfig_DomainStrategy_name = map[int32]string{\n\t\t0: \"FORCE_IP\",\n\t\t1: \"FORCE_IP4\",\n\t\t2: \"FORCE_IP6\",\n\t\t3: \"FORCE_IP46\",\n\t\t4: \"FORCE_IP64\",\n\t}\n\tDeviceConfig_DomainStrategy_value = map[string]int32{\n\t\t\"FORCE_IP\":   0,\n\t\t\"FORCE_IP4\":  1,\n\t\t\"FORCE_IP6\":  2,\n\t\t\"FORCE_IP46\": 3,\n\t\t\"FORCE_IP64\": 4,\n\t}\n)\n\nfunc (x DeviceConfig_DomainStrategy) Enum() *DeviceConfig_DomainStrategy {\n\tp := new(DeviceConfig_DomainStrategy)\n\t*p = x\n\treturn p\n}\n\nfunc (x DeviceConfig_DomainStrategy) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DeviceConfig_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_proxy_wireguard_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DeviceConfig_DomainStrategy) Type() protoreflect.EnumType {\n\treturn &file_proxy_wireguard_config_proto_enumTypes[0]\n}\n\nfunc (x DeviceConfig_DomainStrategy) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DeviceConfig_DomainStrategy.Descriptor instead.\nfunc (DeviceConfig_DomainStrategy) EnumDescriptor() ([]byte, []int) {\n\treturn file_proxy_wireguard_config_proto_rawDescGZIP(), []int{1, 0}\n}\n\ntype PeerConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPublicKey     string                 `protobuf:\"bytes,1,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\"`\n\tPreSharedKey  string                 `protobuf:\"bytes,2,opt,name=pre_shared_key,json=preSharedKey,proto3\" json:\"pre_shared_key,omitempty\"`\n\tEndpoint      string                 `protobuf:\"bytes,3,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\tKeepAlive     uint32                 `protobuf:\"varint,4,opt,name=keep_alive,json=keepAlive,proto3\" json:\"keep_alive,omitempty\"`\n\tAllowedIps    []string               `protobuf:\"bytes,5,rep,name=allowed_ips,json=allowedIps,proto3\" json:\"allowed_ips,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PeerConfig) Reset() {\n\t*x = PeerConfig{}\n\tmi := &file_proxy_wireguard_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PeerConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PeerConfig) ProtoMessage() {}\n\nfunc (x *PeerConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_wireguard_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PeerConfig.ProtoReflect.Descriptor instead.\nfunc (*PeerConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_wireguard_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *PeerConfig) GetPublicKey() string {\n\tif x != nil {\n\t\treturn x.PublicKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *PeerConfig) GetPreSharedKey() string {\n\tif x != nil {\n\t\treturn x.PreSharedKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *PeerConfig) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *PeerConfig) GetKeepAlive() uint32 {\n\tif x != nil {\n\t\treturn x.KeepAlive\n\t}\n\treturn 0\n}\n\nfunc (x *PeerConfig) GetAllowedIps() []string {\n\tif x != nil {\n\t\treturn x.AllowedIps\n\t}\n\treturn nil\n}\n\ntype DeviceConfig struct {\n\tstate          protoimpl.MessageState      `protogen:\"open.v1\"`\n\tSecretKey      string                      `protobuf:\"bytes,1,opt,name=secret_key,json=secretKey,proto3\" json:\"secret_key,omitempty\"`\n\tEndpoint       []string                    `protobuf:\"bytes,2,rep,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\tPeers          []*PeerConfig               `protobuf:\"bytes,3,rep,name=peers,proto3\" json:\"peers,omitempty\"`\n\tMtu            int32                       `protobuf:\"varint,4,opt,name=mtu,proto3\" json:\"mtu,omitempty\"`\n\tNumWorkers     int32                       `protobuf:\"varint,5,opt,name=num_workers,json=numWorkers,proto3\" json:\"num_workers,omitempty\"`\n\tReserved       []byte                      `protobuf:\"bytes,6,opt,name=reserved,proto3\" json:\"reserved,omitempty\"`\n\tDomainStrategy DeviceConfig_DomainStrategy `protobuf:\"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy\" json:\"domain_strategy,omitempty\"`\n\tIsClient       bool                        `protobuf:\"varint,8,opt,name=is_client,json=isClient,proto3\" json:\"is_client,omitempty\"`\n\tNoKernelTun    bool                        `protobuf:\"varint,9,opt,name=no_kernel_tun,json=noKernelTun,proto3\" json:\"no_kernel_tun,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *DeviceConfig) Reset() {\n\t*x = DeviceConfig{}\n\tmi := &file_proxy_wireguard_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeviceConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeviceConfig) ProtoMessage() {}\n\nfunc (x *DeviceConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_proxy_wireguard_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeviceConfig.ProtoReflect.Descriptor instead.\nfunc (*DeviceConfig) Descriptor() ([]byte, []int) {\n\treturn file_proxy_wireguard_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *DeviceConfig) GetSecretKey() string {\n\tif x != nil {\n\t\treturn x.SecretKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *DeviceConfig) GetEndpoint() []string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn nil\n}\n\nfunc (x *DeviceConfig) GetPeers() []*PeerConfig {\n\tif x != nil {\n\t\treturn x.Peers\n\t}\n\treturn nil\n}\n\nfunc (x *DeviceConfig) GetMtu() int32 {\n\tif x != nil {\n\t\treturn x.Mtu\n\t}\n\treturn 0\n}\n\nfunc (x *DeviceConfig) GetNumWorkers() int32 {\n\tif x != nil {\n\t\treturn x.NumWorkers\n\t}\n\treturn 0\n}\n\nfunc (x *DeviceConfig) GetReserved() []byte {\n\tif x != nil {\n\t\treturn x.Reserved\n\t}\n\treturn nil\n}\n\nfunc (x *DeviceConfig) GetDomainStrategy() DeviceConfig_DomainStrategy {\n\tif x != nil {\n\t\treturn x.DomainStrategy\n\t}\n\treturn DeviceConfig_FORCE_IP\n}\n\nfunc (x *DeviceConfig) GetIsClient() bool {\n\tif x != nil {\n\t\treturn x.IsClient\n\t}\n\treturn false\n}\n\nfunc (x *DeviceConfig) GetNoKernelTun() bool {\n\tif x != nil {\n\t\treturn x.NoKernelTun\n\t}\n\treturn false\n}\n\nvar File_proxy_wireguard_config_proto protoreflect.FileDescriptor\n\nconst file_proxy_wireguard_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1cproxy/wireguard/config.proto\\x12\\x14xray.proxy.wireguard\\\"\\xad\\x01\\n\" +\n\t\"\\n\" +\n\t\"PeerConfig\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"public_key\\x18\\x01 \\x01(\\tR\\tpublicKey\\x12$\\n\" +\n\t\"\\x0epre_shared_key\\x18\\x02 \\x01(\\tR\\fpreSharedKey\\x12\\x1a\\n\" +\n\t\"\\bendpoint\\x18\\x03 \\x01(\\tR\\bendpoint\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"keep_alive\\x18\\x04 \\x01(\\rR\\tkeepAlive\\x12\\x1f\\n\" +\n\t\"\\vallowed_ips\\x18\\x05 \\x03(\\tR\\n\" +\n\t\"allowedIps\\\"\\xcb\\x03\\n\" +\n\t\"\\fDeviceConfig\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"secret_key\\x18\\x01 \\x01(\\tR\\tsecretKey\\x12\\x1a\\n\" +\n\t\"\\bendpoint\\x18\\x02 \\x03(\\tR\\bendpoint\\x126\\n\" +\n\t\"\\x05peers\\x18\\x03 \\x03(\\v2 .xray.proxy.wireguard.PeerConfigR\\x05peers\\x12\\x10\\n\" +\n\t\"\\x03mtu\\x18\\x04 \\x01(\\x05R\\x03mtu\\x12\\x1f\\n\" +\n\t\"\\vnum_workers\\x18\\x05 \\x01(\\x05R\\n\" +\n\t\"numWorkers\\x12\\x1a\\n\" +\n\t\"\\breserved\\x18\\x06 \\x01(\\fR\\breserved\\x12Z\\n\" +\n\t\"\\x0fdomain_strategy\\x18\\a \\x01(\\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\\x0edomainStrategy\\x12\\x1b\\n\" +\n\t\"\\tis_client\\x18\\b \\x01(\\bR\\bisClient\\x12\\\"\\n\" +\n\t\"\\rno_kernel_tun\\x18\\t \\x01(\\bR\\vnoKernelTun\\\"\\\\\\n\" +\n\t\"\\x0eDomainStrategy\\x12\\f\\n\" +\n\t\"\\bFORCE_IP\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tFORCE_IP4\\x10\\x01\\x12\\r\\n\" +\n\t\"\\tFORCE_IP6\\x10\\x02\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"FORCE_IP46\\x10\\x03\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"FORCE_IP64\\x10\\x04B^\\n\" +\n\t\"\\x18com.xray.proxy.wireguardP\\x01Z)github.com/xtls/xray-core/proxy/wireguard\\xaa\\x02\\x14Xray.Proxy.WireGuardb\\x06proto3\"\n\nvar (\n\tfile_proxy_wireguard_config_proto_rawDescOnce sync.Once\n\tfile_proxy_wireguard_config_proto_rawDescData []byte\n)\n\nfunc file_proxy_wireguard_config_proto_rawDescGZIP() []byte {\n\tfile_proxy_wireguard_config_proto_rawDescOnce.Do(func() {\n\t\tfile_proxy_wireguard_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)))\n\t})\n\treturn file_proxy_wireguard_config_proto_rawDescData\n}\n\nvar file_proxy_wireguard_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_proxy_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_proxy_wireguard_config_proto_goTypes = []any{\n\t(DeviceConfig_DomainStrategy)(0), // 0: xray.proxy.wireguard.DeviceConfig.DomainStrategy\n\t(*PeerConfig)(nil),               // 1: xray.proxy.wireguard.PeerConfig\n\t(*DeviceConfig)(nil),             // 2: xray.proxy.wireguard.DeviceConfig\n}\nvar file_proxy_wireguard_config_proto_depIdxs = []int32{\n\t1, // 0: xray.proxy.wireguard.DeviceConfig.peers:type_name -> xray.proxy.wireguard.PeerConfig\n\t0, // 1: xray.proxy.wireguard.DeviceConfig.domain_strategy:type_name -> xray.proxy.wireguard.DeviceConfig.DomainStrategy\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_proxy_wireguard_config_proto_init() }\nfunc file_proxy_wireguard_config_proto_init() {\n\tif File_proxy_wireguard_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_wireguard_config_proto_rawDesc), len(file_proxy_wireguard_config_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_proxy_wireguard_config_proto_goTypes,\n\t\tDependencyIndexes: file_proxy_wireguard_config_proto_depIdxs,\n\t\tEnumInfos:         file_proxy_wireguard_config_proto_enumTypes,\n\t\tMessageInfos:      file_proxy_wireguard_config_proto_msgTypes,\n\t}.Build()\n\tFile_proxy_wireguard_config_proto = out.File\n\tfile_proxy_wireguard_config_proto_goTypes = nil\n\tfile_proxy_wireguard_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "proxy/wireguard/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.proxy.wireguard;\noption csharp_namespace = \"Xray.Proxy.WireGuard\";\noption go_package = \"github.com/xtls/xray-core/proxy/wireguard\";\noption java_package = \"com.xray.proxy.wireguard\";\noption java_multiple_files = true;\n\nmessage PeerConfig {\n  string public_key = 1;\n  string pre_shared_key = 2;\n  string endpoint = 3;\n  uint32 keep_alive = 4;\n  repeated string allowed_ips = 5;\n}\n\nmessage DeviceConfig {\n  enum DomainStrategy {\n    FORCE_IP = 0;\n    FORCE_IP4 = 1;\n    FORCE_IP6 = 2;\n    FORCE_IP46 = 3;\n    FORCE_IP64 = 4;\n  }\n  string secret_key = 1;\n  repeated string endpoint = 2;\n  repeated PeerConfig peers = 3;\n  int32 mtu = 4;\n  int32 num_workers = 5;\n  bytes reserved = 6;\n  DomainStrategy domain_strategy = 7;\n  bool is_client = 8;\n  bool no_kernel_tun = 9;\n}"
  },
  {
    "path": "proxy/wireguard/gvisortun/tun.go",
    "content": "/* SPDX-License-Identifier: MIT\n *\n * Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.\n */\n\npackage gvisortun\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"golang.zx2c4.com/wireguard/tun\"\n\t\"gvisor.dev/gvisor/pkg/buffer\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/header\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/channel\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv4\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv6\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/icmp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/tcp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/udp\"\n)\n\ntype netTun struct {\n\tep             *channel.Endpoint\n\tstack          *stack.Stack\n\tevents         chan tun.Event\n\tincomingPacket chan *buffer.View\n\tmtu            int\n\thasV4, hasV6   bool\n\tcloseOnce      sync.Once\n}\n\ntype Net netTun\n\nfunc CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (tun.Device, *Net, *stack.Stack, error) {\n\topts := stack.Options{\n\t\tNetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},\n\t\tTransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},\n\t\tHandleLocal:        !promiscuousMode,\n\t}\n\tdev := &netTun{\n\t\tep:             channel.New(1024, uint32(mtu), \"\"),\n\t\tstack:          stack.New(opts),\n\t\tevents:         make(chan tun.Event, 1),\n\t\tincomingPacket: make(chan *buffer.View),\n\t\tmtu:            mtu,\n\t}\n\tdev.ep.AddNotify(dev)\n\ttcpipErr := dev.stack.CreateNIC(1, dev.ep)\n\tif tcpipErr != nil {\n\t\treturn nil, nil, dev.stack, fmt.Errorf(\"CreateNIC: %v\", tcpipErr)\n\t}\n\tfor _, ip := range localAddresses {\n\t\tvar protoNumber tcpip.NetworkProtocolNumber\n\t\tif ip.Is4() {\n\t\t\tprotoNumber = ipv4.ProtocolNumber\n\t\t} else if ip.Is6() {\n\t\t\tprotoNumber = ipv6.ProtocolNumber\n\t\t}\n\t\tprotoAddr := tcpip.ProtocolAddress{\n\t\t\tProtocol:          protoNumber,\n\t\t\tAddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),\n\t\t}\n\t\ttcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})\n\t\tif tcpipErr != nil {\n\t\t\treturn nil, nil, dev.stack, fmt.Errorf(\"AddProtocolAddress(%v): %v\", ip, tcpipErr)\n\t\t}\n\t\tif ip.Is4() {\n\t\t\tdev.hasV4 = true\n\t\t} else if ip.Is6() {\n\t\t\tdev.hasV6 = true\n\t\t}\n\t}\n\tif dev.hasV4 {\n\t\tdev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})\n\t}\n\tif dev.hasV6 {\n\t\tdev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})\n\t}\n\tif promiscuousMode {\n\t\t// enable promiscuous mode to handle all packets processed by netstack\n\t\tdev.stack.SetPromiscuousMode(1, true)\n\t\tdev.stack.SetSpoofing(1, true)\n\t}\n\n\topt := tcpip.CongestionControlOption(\"cubic\")\n\tif err := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {\n\t\treturn nil, nil, dev.stack, fmt.Errorf(\"SetTransportProtocolOption(%d, &%T(%s)): %s\", tcp.ProtocolNumber, opt, opt, err)\n\t}\n\n\tdev.events <- tun.EventUp\n\treturn dev, (*Net)(dev), dev.stack, nil\n}\n\n// BatchSize implements tun.Device\nfunc (tun *netTun) BatchSize() int {\n\treturn 1\n}\n\n// Name implements tun.Device\nfunc (tun *netTun) Name() (string, error) {\n\treturn \"go\", nil\n}\n\n// File implements tun.Device\nfunc (tun *netTun) File() *os.File {\n\treturn nil\n}\n\n// Events implements tun.Device\nfunc (tun *netTun) Events() <-chan tun.Event {\n\treturn tun.events\n}\n\n// Read implements tun.Device\n\nfunc (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {\n\tview, ok := <-tun.incomingPacket\n\tif !ok {\n\t\treturn 0, os.ErrClosed\n\t}\n\n\tn, err := view.Read(buf[0][offset:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tsizes[0] = n\n\treturn 1, nil\n}\n\n// Write implements tun.Device\nfunc (tun *netTun) Write(buf [][]byte, offset int) (int, error) {\n\tfor _, buf := range buf {\n\t\tpacket := buf[offset:]\n\t\tif len(packet) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tpkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})\n\t\tswitch packet[0] >> 4 {\n\t\tcase 4:\n\t\t\ttun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)\n\t\tcase 6:\n\t\t\ttun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)\n\t\tdefault:\n\t\t\treturn 0, syscall.EAFNOSUPPORT\n\t\t}\n\t}\n\treturn len(buf), nil\n}\n\n// WriteNotify implements channel.Notification\nfunc (tun *netTun) WriteNotify() {\n\tpkt := tun.ep.Read()\n\tif pkt == nil {\n\t\treturn\n\t}\n\n\tview := pkt.ToView()\n\tpkt.DecRef()\n\n\ttun.incomingPacket <- view\n}\n\n// Flush  implements tun.Device\nfunc (tun *netTun) Flush() error {\n\treturn nil\n}\n\n// Close implements tun.Device\nfunc (tun *netTun) Close() error {\n\ttun.closeOnce.Do(func() {\n\t\ttun.stack.RemoveNIC(1)\n\n\t\tclose(tun.events)\n\n\t\ttun.ep.Close()\n\n\t\tclose(tun.incomingPacket)\n\t})\n\treturn nil\n}\n\n// MTU  implements tun.Device\nfunc (tun *netTun) MTU() (int, error) {\n\treturn tun.mtu, nil\n}\n\nfunc convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {\n\tvar protoNumber tcpip.NetworkProtocolNumber\n\tif endpoint.Addr().Is4() {\n\t\tprotoNumber = ipv4.ProtocolNumber\n\t} else {\n\t\tprotoNumber = ipv6.ProtocolNumber\n\t}\n\treturn tcpip.FullAddress{\n\t\tNIC:  1,\n\t\tAddr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()),\n\t\tPort: endpoint.Port(),\n\t}, protoNumber\n}\n\nfunc (net *Net) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (*gonet.TCPConn, error) {\n\tfa, pn := convertToFullAddr(addr)\n\treturn gonet.DialContextTCP(ctx, net.stack, fa, pn)\n}\n\nfunc (net *Net) DialUDPAddrPort(laddr, raddr netip.AddrPort) (*gonet.UDPConn, error) {\n\tvar lfa, rfa *tcpip.FullAddress\n\tvar pn tcpip.NetworkProtocolNumber\n\tif laddr.IsValid() || laddr.Port() > 0 {\n\t\tvar addr tcpip.FullAddress\n\t\taddr, pn = convertToFullAddr(laddr)\n\t\tlfa = &addr\n\t}\n\tif raddr.IsValid() || raddr.Port() > 0 {\n\t\tvar addr tcpip.FullAddress\n\t\taddr, pn = convertToFullAddr(raddr)\n\t\trfa = &addr\n\t}\n\treturn gonet.DialUDP(net.stack, lfa, rfa, pn)\n}\n"
  },
  {
    "path": "proxy/wireguard/server.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\tc \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/policy\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nvar nullDestination = net.TCPDestination(net.AnyIP, 0)\n\ntype Server struct {\n\tbindServer *netBindServer\n\n\tinfo          routingInfo\n\tpolicyManager policy.Manager\n}\n\ntype routingInfo struct {\n\tctx         context.Context\n\tdispatcher  routing.Dispatcher\n\tinboundTag  *session.Inbound\n\tcontentTag  *session.Content\n}\n\nfunc NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {\n\tv := core.MustFromContext(ctx)\n\n\tendpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tserver := &Server{\n\t\tbindServer: &netBindServer{\n\t\t\tnetBind: netBind{\n\t\t\t\tdns: v.GetFeature(dns.ClientType()).(dns.Client),\n\t\t\t\tdnsOption: dns.IPOption{\n\t\t\t\t\tIPv4Enable: hasIPv4,\n\t\t\t\t\tIPv6Enable: hasIPv6,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tpolicyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),\n\t}\n\n\ttun, err := conf.createTun()(endpoints, int(conf.Mtu), server.forwardConnection)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = tun.BuildDevice(createIPCRequest(conf), server.bindServer); err != nil {\n\t\t_ = tun.Close()\n\t\treturn nil, err\n\t}\n\n\treturn server, nil\n}\n\n// Network implements proxy.Inbound.\nfunc (*Server) Network() []net.Network {\n\treturn []net.Network{net.Network_UDP}\n}\n\n// Process implements proxy.Inbound.\nfunc (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {\n\ts.info = routingInfo{\n\t\tctx:        ctx,\n\t\tdispatcher: dispatcher,\n\t\tinboundTag: session.InboundFromContext(ctx),\n\t\tcontentTag: session.ContentFromContext(ctx),\n\t}\n\n\tep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnep := ep.(*netEndpoint)\n\tnep.conn = conn\n\n\treader := buf.NewPacketReader(conn)\n\tfor {\n\t\tmpayload, err := reader.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, payload := range mpayload {\n\t\t\tv, ok := <-s.bindServer.readQueue\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ti, err := payload.Read(v.buff)\n\n\t\t\tv.bytes = i\n\t\t\tv.endpoint = nep\n\t\t\tv.err = err\n\t\t\tv.waiter.Done()\n\t\t\tif err != nil && goerrors.Is(err, io.EOF) {\n\t\t\t\tnep.conn = nil\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {\n\tif s.info.dispatcher == nil {\n\t\terrors.LogError(s.info.ctx, \"unexpected: dispatcher == nil\")\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\tctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))\n\tsid := session.NewID()\n\tctx = c.ContextWithID(ctx, sid)\n\tinbound := session.Inbound{} // since promiscuousModeHandler mixed-up context, we shallow copy inbound (tag) and content (configs)\n\tif s.info.inboundTag != nil {\n\t\tinbound = *s.info.inboundTag\n\t}\n\tinbound.Name = \"wireguard\"\n\tinbound.CanSpliceCopy = 3\n\n\t// overwrite the source to use the tun address for each sub context.\n\t// Since gvisor.ForwarderRequest doesn't provide any info to associate the sub-context with the Parent context\n\t// Currently we have no way to link to the original source address\n\tinbound.Source = net.DestinationFromAddr(conn.RemoteAddr())\n\tctx = session.ContextWithInbound(ctx, &inbound)\n\tif s.info.contentTag != nil {\n\t\tctx = session.ContextWithContent(ctx, s.info.contentTag)\n\t}\n\tctx = session.SubContextFromMuxInbound(ctx)\n\n\tplcy := s.policyManager.ForLevel(0)\n\ttimer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)\n\n\tctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{\n\t\tFrom:   nullDestination,\n\t\tTo:     dest,\n\t\tStatus: log.AccessAccepted,\n\t\tReason: \"\",\n\t})\n\n\tlink, err := s.info.dispatcher.Dispatch(ctx, dest)\n\tif err != nil {\n\t\terrors.LogErrorInner(ctx, err, \"dispatch connection\")\n\t}\n\tdefer cancel()\n\n\trequestDone := func() error {\n\t\tdefer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)\n\t\tif err := buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to transport all TCP request\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tresponseDone := func() error {\n\t\tdefer timer.SetTimeout(plcy.Timeouts.UplinkOnly)\n\t\tif err := buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)); err != nil {\n\t\t\treturn errors.New(\"failed to transport all TCP response\").Base(err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\trequestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestDonePost, responseDone); err != nil {\n\t\tcommon.Interrupt(link.Reader)\n\t\tcommon.Interrupt(link.Writer)\n\t\terrors.LogDebugInner(ctx, err, \"connection ends\")\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "proxy/wireguard/server_test.go",
    "content": "package wireguard_test\n\nimport (\n\t\"context\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"runtime/debug\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/wireguard\"\n)\n\n// TestWireGuardServerInitializationError verifies that an error during TUN initialization\n// (triggered by an empty SecretKey) in the WireGuard server does not cause a panic and returns an error instead.\nfunc TestWireGuardServerInitializationError(t *testing.T) {\n\t// Create a minimal core instance with default features\n\tconfig := &core.Config{}\n\tinstance, err := core.New(config)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create core instance: %v\", err)\n\t}\n\t// Set the Xray instance in the context\n\tctx := context.WithValue(context.Background(), core.XrayKey(1), instance)\n\n\t// Define the server configuration with an empty SecretKey to trigger error\n\tconf := &wireguard.DeviceConfig{\n\t\tIsClient:  false,\n\t\tEndpoint:  []string{\"10.0.0.1/32\"},\n\t\tMtu:       1420,\n\t\tSecretKey: \"\", // Empty SecretKey to trigger error\n\t\tPeers: []*wireguard.PeerConfig{\n\t\t\t{\n\t\t\t\tPublicKey:  \"some_public_key\",\n\t\t\t\tAllowedIps: []string{\"10.0.0.2/32\"},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Use defer to catch any panic and fail the test explicitly\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Errorf(\"TUN initialization panicked: %v\", r)\n\t\t\tdebug.PrintStack()\n\t\t}\n\t}()\n\n\t// Attempt to initialize the WireGuard server\n\t_, err = wireguard.NewServer(ctx, conf)\n\n\t// Check that an error is returned\n\tassert.ErrorContains(t, err, \"failed to set private_key: hex string does not fit the slice\")\n}\n"
  },
  {
    "path": "proxy/wireguard/tun.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/proxy/wireguard/gvisortun\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/tcp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/udp\"\n\t\"gvisor.dev/gvisor/pkg/waiter\"\n\n\t\"golang.zx2c4.com/wireguard/conn\"\n\t\"golang.zx2c4.com/wireguard/device\"\n\t\"golang.zx2c4.com/wireguard/tun\"\n)\n\ntype tunCreator func(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error)\n\ntype promiscuousModeHandler func(dest net.Destination, conn net.Conn)\n\ntype Tunnel interface {\n\tBuildDevice(ipc string, bind conn.Bind) error\n\tDialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error)\n\tDialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error)\n\tClose() error\n}\n\ntype tunnel struct {\n\ttun    tun.Device\n\tdevice *device.Device\n\trw     sync.Mutex\n}\n\nfunc (t *tunnel) BuildDevice(ipc string, bind conn.Bind) (err error) {\n\tt.rw.Lock()\n\tdefer t.rw.Unlock()\n\n\tif t.device != nil {\n\t\treturn errors.New(\"device is already initialized\")\n\t}\n\n\tlogger := &device.Logger{\n\t\tVerbosef: func(format string, args ...any) {\n\t\t\tlog.Record(&log.GeneralMessage{\n\t\t\t\tSeverity: log.Severity_Debug,\n\t\t\t\tContent:  fmt.Sprintf(format, args...),\n\t\t\t})\n\t\t},\n\t\tErrorf: func(format string, args ...any) {\n\t\t\tlog.Record(&log.GeneralMessage{\n\t\t\t\tSeverity: log.Severity_Error,\n\t\t\t\tContent:  fmt.Sprintf(format, args...),\n\t\t\t})\n\t\t},\n\t}\n\n\tt.device = device.NewDevice(t.tun, bind, logger)\n\tif err = t.device.IpcSet(ipc); err != nil {\n\t\treturn err\n\t}\n\tif err = t.device.Up(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (t *tunnel) Close() (err error) {\n\tt.rw.Lock()\n\tdefer t.rw.Unlock()\n\n\tif t.device == nil {\n\t\treturn nil\n\t}\n\n\tt.device.Close()\n\tt.device = nil\n\terr = t.tun.Close()\n\tt.tun = nil\n\treturn nil\n}\n\nfunc CalculateInterfaceName(name string) (tunName string) {\n\tif runtime.GOOS == \"darwin\" {\n\t\ttunName = \"utun\"\n\t} else if name != \"\" {\n\t\ttunName = name\n\t} else {\n\t\ttunName = \"tun\"\n\t}\n\tinterfaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn\n\t}\n\tvar tunIndex int\n\tfor _, netInterface := range interfaces {\n\t\tif strings.HasPrefix(netInterface.Name, tunName) {\n\t\t\tindex, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)\n\t\t\tif parseErr == nil {\n\t\t\t\ttunIndex = int(index) + 1\n\t\t\t}\n\t\t}\n\t}\n\ttunName = fmt.Sprintf(\"%s%d\", tunName, tunIndex)\n\treturn\n}\n\nvar _ Tunnel = (*gvisorNet)(nil)\n\ntype gvisorNet struct {\n\ttunnel\n\tnet *gvisortun.Net\n}\n\nfunc (g *gvisorNet) Close() error {\n\treturn g.tunnel.Close()\n}\n\nfunc (g *gvisorNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (\n\tnet.Conn, error,\n) {\n\treturn g.net.DialContextTCPAddrPort(ctx, addr)\n}\n\nfunc (g *gvisorNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {\n\treturn g.net.DialUDPAddrPort(laddr, raddr)\n}\n\nfunc createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) {\n\tout := &gvisorNet{}\n\ttun, n, stack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif handler != nil {\n\t\t// handler is only used for promiscuous mode\n\t\t// capture all packets and send to handler\n\n\t\ttcpForwarder := tcp.NewForwarder(stack, 0, 65535, func(r *tcp.ForwarderRequest) {\n\t\t\tgo func(r *tcp.ForwarderRequest) {\n\t\t\t\tvar (\n\t\t\t\t\twq waiter.Queue\n\t\t\t\t\tid = r.ID()\n\t\t\t\t)\n\n\t\t\t\t// Perform a TCP three-way handshake.\n\t\t\t\tep, err := r.CreateEndpoint(&wq)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogError(context.Background(), err.String())\n\t\t\t\t\tr.Complete(true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tr.Complete(false)\n\t\t\t\tdefer ep.Close()\n\n\t\t\t\t// enable tcp keep-alive to prevent hanging connections\n\t\t\t\tep.SocketOptions().SetKeepAlive(true)\n\n\t\t\t\t// local address is actually destination\n\t\t\t\thandler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))\n\t\t\t}(r)\n\t\t})\n\t\tstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)\n\n\t\tudpForwarder := udp.NewForwarder(stack, func(r *udp.ForwarderRequest) bool {\n\t\t\tgo func(r *udp.ForwarderRequest) {\n\t\t\t\tvar (\n\t\t\t\t\twq waiter.Queue\n\t\t\t\t\tid = r.ID()\n\t\t\t\t)\n\n\t\t\t\tep, err := r.CreateEndpoint(&wq)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogError(context.Background(), err.String())\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer ep.Close()\n\n\t\t\t\t// prevents hanging connections and ensure timely release\n\t\t\t\tep.SocketOptions().SetLinger(tcpip.LingerOption{\n\t\t\t\t\tEnabled: true,\n\t\t\t\t\tTimeout: 15 * time.Second,\n\t\t\t\t})\n\n\t\t\t\thandler(net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewUDPConn(&wq, ep))\n\t\t\t}(r)\n\n\t\t\treturn true\n\t\t})\n\t\tstack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)\n\t}\n\n\tout.tun, out.net = tun, n\n\treturn out, nil\n}\n"
  },
  {
    "path": "proxy/wireguard/tun_default.go",
    "content": "//go:build !linux || android\n\npackage wireguard\n\nimport (\n\t\"errors\"\n\t\"net/netip\"\n)\n\nfunc createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (t Tunnel, err error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\nfunc KernelTunSupported() (bool, error) {\n\treturn false, nil\n}\n"
  },
  {
    "path": "proxy/wireguard/tun_linux.go",
    "content": "//go:build linux && !android\n\npackage wireguard\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"sync\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/sagernet/sing/common/control\"\n\t\"github.com/vishvananda/netlink\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\twgtun \"golang.zx2c4.com/wireguard/tun\"\n)\n\ntype deviceNet struct {\n\ttunnel\n\tdialer net.Dialer\n\n\thandle    *netlink.Handle\n\tlinkAddrs []netlink.Addr\n\troutes    []*netlink.Route\n\trules     []*netlink.Rule\n}\n\nvar (\n\ttableIndex int = 10230\n\tmu         sync.Mutex\n)\n\nfunc allocateIPv6TableIndex() int {\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\tif tableIndex > 10230 {\n\t\terrors.LogInfo(context.Background(), \"allocate new ipv6 table index: \", tableIndex)\n\t}\n\tcurrentIndex := tableIndex\n\ttableIndex++\n\treturn currentIndex\n}\n\nfunc newDeviceNet(interfaceName string) *deviceNet {\n\tvar dialer net.Dialer\n\tbindControl := control.BindToInterface(control.NewDefaultInterfaceFinder(), interfaceName, -1)\n\tdialer.Control = control.Append(dialer.Control, bindControl)\n\treturn &deviceNet{dialer: dialer}\n}\n\nfunc (d *deviceNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (\n\tnet.Conn, error,\n) {\n\treturn d.dialer.DialContext(ctx, \"tcp\", addr.String())\n}\n\nfunc (d *deviceNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {\n\tdialer := d.dialer\n\tdialer.LocalAddr = &net.UDPAddr{IP: laddr.Addr().AsSlice(), Port: int(laddr.Port())}\n\treturn dialer.DialContext(context.Background(), \"udp\", raddr.String())\n}\n\nfunc (d *deviceNet) Close() (err error) {\n\tvar errs []error\n\tfor _, rule := range d.rules {\n\t\tif err = d.handle.RuleDel(rule); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"failed to delete rule: %w\", err))\n\t\t}\n\t}\n\tfor _, route := range d.routes {\n\t\tif err = d.handle.RouteDel(route); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"failed to delete route: %w\", err))\n\t\t}\n\t}\n\tif err = d.tunnel.Close(); err != nil {\n\t\terrs = append(errs, fmt.Errorf(\"failed to close tunnel: %w\", err))\n\t}\n\tif d.handle != nil {\n\t\td.handle.Close()\n\t\td.handle = nil\n\t}\n\tif len(errs) == 0 {\n\t\treturn nil\n\t}\n\treturn goerrors.Join(errs...)\n}\n\nfunc createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (t Tunnel, err error) {\n\tif handler != nil {\n\t\treturn nil, errors.New(\"TODO: support promiscuous mode\")\n\t}\n\n\tvar v4, v6 *netip.Addr\n\tfor _, prefixes := range localAddresses {\n\t\tif v4 == nil && prefixes.Is4() {\n\t\t\tx := prefixes\n\t\t\tv4 = &x\n\t\t}\n\t\tif v6 == nil && prefixes.Is6() {\n\t\t\tx := prefixes\n\t\t\tv6 = &x\n\t\t}\n\t}\n\n\twriteSysctlZero := func(path string) error {\n\t\t_, err := os.Stat(path)\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn os.WriteFile(path, []byte(\"0\"), 0o644)\n\t}\n\n\t// system configs.\n\tif v4 != nil {\n\t\tif err = writeSysctlZero(\"/proc/sys/net/ipv4/conf/all/rp_filter\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to disable ipv4 rp_filter for all: %w\", err)\n\t\t}\n\t}\n\tif v6 != nil {\n\t\tif err = writeSysctlZero(\"/proc/sys/net/ipv6/conf/all/disable_ipv6\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to enable ipv6: %w\", err)\n\t\t}\n\t\tif err = writeSysctlZero(\"/proc/sys/net/ipv6/conf/all/rp_filter\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to disable ipv6 rp_filter for all: %w\", err)\n\t\t}\n\t}\n\n\tn := CalculateInterfaceName(\"wg\")\n\twgt, err := wgtun.CreateTUN(n, mtu)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = wgt.Close()\n\t\t}\n\t}()\n\n\t// disable linux rp_filter for tunnel device to avoid packet drop.\n\t// the operation require root privilege on container require '--privileged' flag.\n\tif v4 != nil {\n\t\tif err = writeSysctlZero(\"/proc/sys/net/ipv4/conf/\" + n + \"/rp_filter\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to disable ipv4 rp_filter for tunnel: %w\", err)\n\t\t}\n\t}\n\tif v6 != nil {\n\t\tif err = writeSysctlZero(\"/proc/sys/net/ipv6/conf/\" + n + \"/rp_filter\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to disable ipv6 rp_filter for tunnel: %w\", err)\n\t\t}\n\t}\n\n\tipv6TableIndex := allocateIPv6TableIndex()\n\tif v6 != nil {\n\t\tr := &netlink.Route{Table: ipv6TableIndex}\n\t\tfor {\n\t\t\trouteList, fErr := netlink.RouteListFiltered(netlink.FAMILY_V6, r, netlink.RT_FILTER_TABLE)\n\t\t\tif len(routeList) == 0 || fErr != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tipv6TableIndex--\n\t\t\tif ipv6TableIndex < 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to find available ipv6 table index\")\n\t\t\t}\n\t\t}\n\t}\n\n\tout := newDeviceNet(n)\n\tout.handle, err = netlink.NewHandle()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = out.Close()\n\t\t}\n\t}()\n\n\tl, err := netlink.LinkByName(n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif v4 != nil {\n\t\taddr := netlink.Addr{\n\t\t\tIPNet: &net.IPNet{\n\t\t\t\tIP:   v4.AsSlice(),\n\t\t\t\tMask: net.CIDRMask(v4.BitLen(), v4.BitLen()),\n\t\t\t},\n\t\t}\n\t\tout.linkAddrs = append(out.linkAddrs, addr)\n\t}\n\tif v6 != nil {\n\t\taddr := netlink.Addr{\n\t\t\tIPNet: &net.IPNet{\n\t\t\t\tIP:   v6.AsSlice(),\n\t\t\t\tMask: net.CIDRMask(v6.BitLen(), v6.BitLen()),\n\t\t\t},\n\t\t}\n\t\tout.linkAddrs = append(out.linkAddrs, addr)\n\n\t\trt := &netlink.Route{\n\t\t\tLinkIndex: l.Attrs().Index,\n\t\t\tDst: &net.IPNet{\n\t\t\t\tIP:   net.IPv6zero,\n\t\t\t\tMask: net.CIDRMask(0, 128),\n\t\t\t},\n\t\t\tTable: ipv6TableIndex,\n\t\t}\n\t\tout.routes = append(out.routes, rt)\n\n\t\tr := netlink.NewRule()\n\t\tr.Table, r.Family, r.Src = ipv6TableIndex, unix.AF_INET6, addr.IPNet\n\t\tout.rules = append(out.rules, r)\n\t\tr = netlink.NewRule()\n\t\tr.Table, r.Family, r.OifName = ipv6TableIndex, unix.AF_INET6, n\n\t\tout.rules = append(out.rules, r)\n\t}\n\n\tfor _, addr := range out.linkAddrs {\n\t\tif err = out.handle.AddrAdd(l, &addr); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to add address %s to %s: %w\", addr, n, err)\n\t\t}\n\t}\n\tif err = out.handle.LinkSetMTU(l, mtu); err != nil {\n\t\treturn nil, err\n\t}\n\tif err = out.handle.LinkSetUp(l); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, route := range out.routes {\n\t\tif err = out.handle.RouteAdd(route); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to add route %s: %w\", route, err)\n\t\t}\n\t}\n\tfor _, rule := range out.rules {\n\t\tif err = out.handle.RuleAdd(rule); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to add rule %s: %w\", rule, err)\n\t\t}\n\t}\n\tout.tun = wgt\n\treturn out, nil\n}\n\nfunc KernelTunSupported() (bool, error) {\n\tvar hdr unix.CapUserHeader\n\thdr.Version = unix.LINUX_CAPABILITY_VERSION_3\n\thdr.Pid = 0 // 0 means current process\n\n\tvar data unix.CapUserData\n\tif err := unix.Capget(&hdr, &data); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to get capabilities: %v\", err)\n\t}\n\n\treturn (data.Effective & (1 << unix.CAP_NET_ADMIN)) != 0, nil\n}\n"
  },
  {
    "path": "proxy/wireguard/wireguard.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"golang.zx2c4.com/wireguard/device\"\n)\n\nvar wgLogger = &device.Logger{\n\tVerbosef: func(format string, args ...any) {\n\t\tlog.Record(&log.GeneralMessage{\n\t\t\tSeverity: log.Severity_Debug,\n\t\t\tContent:  fmt.Sprintf(format, args...),\n\t\t})\n\t},\n\tErrorf: func(format string, args ...any) {\n\t\tlog.Record(&log.GeneralMessage{\n\t\t\tSeverity: log.Severity_Error,\n\t\t\tContent:  fmt.Sprintf(format, args...),\n\t\t})\n\t},\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\tdeviceConfig := config.(*DeviceConfig)\n\t\tif deviceConfig.IsClient {\n\t\t\treturn New(ctx, deviceConfig)\n\t\t} else {\n\t\t\treturn NewServer(ctx, deviceConfig)\n\t\t}\n\t}))\n}\n\n// convert endpoint string to netip.Addr\nfunc parseEndpoints(conf *DeviceConfig) ([]netip.Addr, bool, bool, error) {\n\tvar hasIPv4, hasIPv6 bool\n\n\tendpoints := make([]netip.Addr, len(conf.Endpoint))\n\tfor i, str := range conf.Endpoint {\n\t\tvar addr netip.Addr\n\t\tif strings.Contains(str, \"/\") {\n\t\t\tprefix, err := netip.ParsePrefix(str)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, false, err\n\t\t\t}\n\t\t\taddr = prefix.Addr()\n\t\t\tif prefix.Bits() != addr.BitLen() {\n\t\t\t\treturn nil, false, false, errors.New(\"interface address subnet should be /32 for IPv4 and /128 for IPv6\")\n\t\t\t}\n\t\t} else {\n\t\t\tvar err error\n\t\t\taddr, err = netip.ParseAddr(str)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, false, err\n\t\t\t}\n\t\t}\n\t\tendpoints[i] = addr\n\n\t\tif addr.Is4() {\n\t\t\thasIPv4 = true\n\t\t} else if addr.Is6() {\n\t\t\thasIPv6 = true\n\t\t}\n\t}\n\n\treturn endpoints, hasIPv4, hasIPv6, nil\n}\n\n// serialize the config into an IPC request\nfunc createIPCRequest(conf *DeviceConfig) string {\n\tvar request strings.Builder\n\n\trequest.WriteString(fmt.Sprintf(\"private_key=%s\\n\", conf.SecretKey))\n\n\tif !conf.IsClient {\n\t\t// placeholder, we'll handle actual port listening on Xray\n\t\trequest.WriteString(\"listen_port=1337\\n\")\n\t}\n\n\tfor _, peer := range conf.Peers {\n\t\tif peer.PublicKey != \"\" {\n\t\t\trequest.WriteString(fmt.Sprintf(\"public_key=%s\\n\", peer.PublicKey))\n\t\t}\n\n\t\tif peer.PreSharedKey != \"\" {\n\t\t\trequest.WriteString(fmt.Sprintf(\"preshared_key=%s\\n\", peer.PreSharedKey))\n\t\t}\n\n\t\tif peer.Endpoint != \"\" {\n\t\t\trequest.WriteString(fmt.Sprintf(\"endpoint=%s\\n\", peer.Endpoint))\n\t\t}\n\n\t\tfor _, ip := range peer.AllowedIps {\n\t\t\trequest.WriteString(fmt.Sprintf(\"allowed_ip=%s\\n\", ip))\n\t\t}\n\n\t\tif peer.KeepAlive != 0 {\n\t\t\trequest.WriteString(fmt.Sprintf(\"persistent_keepalive_interval=%d\\n\", peer.KeepAlive))\n\t\t}\n\t}\n\n\treturn request.String()[:request.Len()]\n}\n"
  },
  {
    "path": "testing/coverage/coverall",
    "content": "#!/bin/bash\n\nFAIL=0\n\nXRAY_OUT=${PWD}/out/xray\nexport XRAY_COV=${XRAY_OUT}/cov\nCOVERAGE_FILE=${XRAY_COV}/coverage.txt\n\nfunction test_package {\n  DIR=\".$1\"\n  DEP=$(go list -f '{{ join .Deps \"\\n\" }}' $DIR | grep xray | tr '\\n' ',')\n  DEP=${DEP}$DIR\n  RND_NAME=$(openssl rand -hex 16)\n  COV_PROFILE=${XRAY_COV}/${RND_NAME}.out\n  go test -tags \"json coverage\" -coverprofile=${COV_PROFILE} -coverpkg=$DEP $DIR || FAIL=1\n}\n\nrm -rf ${XRAY_OUT}\nmkdir -p ${XRAY_COV}\ntouch ${COVERAGE_FILE}\n\nTEST_FILES=(./*_test.go)\nif [ -f ${TEST_FILES[0]} ]; then\n  test_package \"\"\nfi\n\nfor DIR in $(find * -type d ! -path \"*.git*\" ! -path \"*vendor*\" ! -path \"*external*\"); do\n  TEST_FILES=($DIR/*_test.go)\n  if [ -f ${TEST_FILES[0]} ]; then\n    test_package \"/$DIR\"\n  fi\ndone\n\nfor OUT_FILE in $(find ${XRAY_COV} -name \"*.out\"); do\n  echo \"Merging file ${OUT_FILE}\"\n  cat ${OUT_FILE} | grep -v \"mode: set\" >> ${COVERAGE_FILE}\ndone\n\nCOV_SORTED=${XRAY_COV}/coverallsorted.out\ncat ${COVERAGE_FILE} | sort -t: -k1 | grep -vw \"testing\" | grep -v \".pb.go\" | grep -vw \"vendor\" | grep -vw \"external\" > ${COV_SORTED}\necho \"mode: set\" | cat - ${COV_SORTED} > ${COVERAGE_FILE}\n\nif [ \"$FAIL\" -eq 0 ]; then\n  echo \"Uploading coverage datea to codecov.\"\n  #bash <(curl -s https://codecov.io/bash) -f ${COVERAGE_FILE} -v || echo \"Codecov did not collect coverage reports.\"\nfi\n\nexit $FAIL\n"
  },
  {
    "path": "testing/coverage/coverall2",
    "content": "#!/bin/bash\n\nCOVERAGE_FILE=${PWD}/coverage.txt\nCOV_SORTED=${PWD}/coverallsorted.out\n\ntouch \"$COVERAGE_FILE\"\n\nfunction test_package {\n  DIR=\".$1\"\n  DEP=$(go list -f '{{ join .Deps \"\\n\" }}' \"$DIR\" | grep xray | tr '\\n' ',')\n  DEP=${DEP}$DIR\n  RND_NAME=$(openssl rand -hex 16)\n  COV_PROFILE=${RND_NAME}.out\n  go test -coverprofile=\"$COV_PROFILE\" -coverpkg=\"$DEP\" \"$DIR\" || return\n}\n\nTEST_FILES=(./*_test.go)\nif [ -f \"${TEST_FILES[0]}\" ]; then\n  test_package \"\"\nfi\n\n# shellcheck disable=SC2044\nfor DIR in $(find ./* -type d ! -path \"*.git*\" ! -path \"*vendor*\" ! -path \"*external*\"); do\n  TEST_FILES=(\"$DIR\"/*_test.go)\n  if [ -f \"${TEST_FILES[0]}\" ]; then\n    test_package \"/$DIR\"\n  fi\ndone\n\n# merge out\nwhile IFS= read -r -d '' OUT_FILE\ndo\n  echo \"Merging file ${OUT_FILE}\"\n  < \"${OUT_FILE}\" grep -v \"mode: set\" >> \"$COVERAGE_FILE\"\ndone <   <(find ./* -name \"*.out\" -print0)\n\n< \"$COVERAGE_FILE\" sort -t: -k1 | grep -vw \"testing\" | grep -v \".pb.go\" | grep -vw \"vendor\" | grep -vw \"external\" > \"$COV_SORTED\"\necho \"mode: set\" | cat - \"${COV_SORTED}\" > \"${COVERAGE_FILE}\"\n\nbash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload'"
  },
  {
    "path": "testing/mocks/dns.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/xtls/xray-core/features/dns (interfaces: Client)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tnet \"net\"\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tdns \"github.com/xtls/xray-core/features/dns\"\n)\n\n// DNSClient is a mock of Client interface\ntype DNSClient struct {\n\tctrl     *gomock.Controller\n\trecorder *DNSClientMockRecorder\n}\n\n// DNSClientMockRecorder is the mock recorder for DNSClient\ntype DNSClientMockRecorder struct {\n\tmock *DNSClient\n}\n\n// NewDNSClient creates a new mock instance\nfunc NewDNSClient(ctrl *gomock.Controller) *DNSClient {\n\tmock := &DNSClient{ctrl: ctrl}\n\tmock.recorder = &DNSClientMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *DNSClient) EXPECT() *DNSClientMockRecorder {\n\treturn m.recorder\n}\n\n// Close mocks base method\nfunc (m *DNSClient) Close() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Close\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Close indicates an expected call of Close\nfunc (mr *DNSClientMockRecorder) Close() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Close\", reflect.TypeOf((*DNSClient)(nil).Close))\n}\n\n// LookupIP mocks base method\nfunc (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, uint32, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"LookupIP\", arg0, arg1)\n\tret0, _ := ret[0].([]net.IP)\n\tret1, _ := ret[1].(uint32)\n\tret2, _ := ret[2].(error)\n\treturn ret0, ret1, ret2\n}\n\n// LookupIP indicates an expected call of LookupIP\nfunc (mr *DNSClientMockRecorder) LookupIP(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"LookupIP\", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0, arg1)\n}\n\n// Start mocks base method\nfunc (m *DNSClient) Start() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Start\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Start indicates an expected call of Start\nfunc (mr *DNSClientMockRecorder) Start() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Start\", reflect.TypeOf((*DNSClient)(nil).Start))\n}\n\n// Type mocks base method\nfunc (m *DNSClient) Type() interface{} {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Type\")\n\tret0, _ := ret[0].(interface{})\n\treturn ret0\n}\n\n// Type indicates an expected call of Type\nfunc (mr *DNSClientMockRecorder) Type() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Type\", reflect.TypeOf((*DNSClient)(nil).Type))\n}\n"
  },
  {
    "path": "testing/mocks/io.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: io (interfaces: Reader,Writer)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n)\n\n// Reader is a mock of Reader interface\ntype Reader struct {\n\tctrl     *gomock.Controller\n\trecorder *ReaderMockRecorder\n}\n\n// ReaderMockRecorder is the mock recorder for Reader\ntype ReaderMockRecorder struct {\n\tmock *Reader\n}\n\n// NewReader creates a new mock instance\nfunc NewReader(ctrl *gomock.Controller) *Reader {\n\tmock := &Reader{ctrl: ctrl}\n\tmock.recorder = &ReaderMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *Reader) EXPECT() *ReaderMockRecorder {\n\treturn m.recorder\n}\n\n// Read mocks base method\nfunc (m *Reader) Read(arg0 []byte) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Read\", arg0)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Read indicates an expected call of Read\nfunc (mr *ReaderMockRecorder) Read(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Read\", reflect.TypeOf((*Reader)(nil).Read), arg0)\n}\n\n// Writer is a mock of Writer interface\ntype Writer struct {\n\tctrl     *gomock.Controller\n\trecorder *WriterMockRecorder\n}\n\n// WriterMockRecorder is the mock recorder for Writer\ntype WriterMockRecorder struct {\n\tmock *Writer\n}\n\n// NewWriter creates a new mock instance\nfunc NewWriter(ctrl *gomock.Controller) *Writer {\n\tmock := &Writer{ctrl: ctrl}\n\tmock.recorder = &WriterMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *Writer) EXPECT() *WriterMockRecorder {\n\treturn m.recorder\n}\n\n// Write mocks base method\nfunc (m *Writer) Write(arg0 []byte) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Write\", arg0)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Write indicates an expected call of Write\nfunc (mr *WriterMockRecorder) Write(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Write\", reflect.TypeOf((*Writer)(nil).Write), arg0)\n}\n"
  },
  {
    "path": "testing/mocks/log.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/xtls/xray-core/common/log (interfaces: Handler)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tlog \"github.com/xtls/xray-core/common/log\"\n)\n\n// LogHandler is a mock of Handler interface\ntype LogHandler struct {\n\tctrl     *gomock.Controller\n\trecorder *LogHandlerMockRecorder\n}\n\n// LogHandlerMockRecorder is the mock recorder for LogHandler\ntype LogHandlerMockRecorder struct {\n\tmock *LogHandler\n}\n\n// NewLogHandler creates a new mock instance\nfunc NewLogHandler(ctrl *gomock.Controller) *LogHandler {\n\tmock := &LogHandler{ctrl: ctrl}\n\tmock.recorder = &LogHandlerMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *LogHandler) EXPECT() *LogHandlerMockRecorder {\n\treturn m.recorder\n}\n\n// Handle mocks base method\nfunc (m *LogHandler) Handle(arg0 log.Message) {\n\tm.ctrl.T.Helper()\n\tm.ctrl.Call(m, \"Handle\", arg0)\n}\n\n// Handle indicates an expected call of Handle\nfunc (mr *LogHandlerMockRecorder) Handle(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Handle\", reflect.TypeOf((*LogHandler)(nil).Handle), arg0)\n}\n"
  },
  {
    "path": "testing/mocks/mux.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/xtls/xray-core/common/mux (interfaces: ClientWorkerFactory)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tmux \"github.com/xtls/xray-core/common/mux\"\n)\n\n// MuxClientWorkerFactory is a mock of ClientWorkerFactory interface\ntype MuxClientWorkerFactory struct {\n\tctrl     *gomock.Controller\n\trecorder *MuxClientWorkerFactoryMockRecorder\n}\n\n// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory\ntype MuxClientWorkerFactoryMockRecorder struct {\n\tmock *MuxClientWorkerFactory\n}\n\n// NewMuxClientWorkerFactory creates a new mock instance\nfunc NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory {\n\tmock := &MuxClientWorkerFactory{ctrl: ctrl}\n\tmock.recorder = &MuxClientWorkerFactoryMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *MuxClientWorkerFactory) EXPECT() *MuxClientWorkerFactoryMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks base method\nfunc (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Create\")\n\tret0, _ := ret[0].(*mux.ClientWorker)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Create indicates an expected call of Create\nfunc (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Create\", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create))\n}\n"
  },
  {
    "path": "testing/mocks/outbound.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/xtls/xray-core/features/outbound (interfaces: Manager,HandlerSelector)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\toutbound \"github.com/xtls/xray-core/features/outbound\"\n)\n\n// OutboundManager is a mock of Manager interface\ntype OutboundManager struct {\n\tctrl     *gomock.Controller\n\trecorder *OutboundManagerMockRecorder\n}\n\n// OutboundManagerMockRecorder is the mock recorder for OutboundManager\ntype OutboundManagerMockRecorder struct {\n\tmock *OutboundManager\n}\n\n// NewOutboundManager creates a new mock instance\nfunc NewOutboundManager(ctrl *gomock.Controller) *OutboundManager {\n\tmock := &OutboundManager{ctrl: ctrl}\n\tmock.recorder = &OutboundManagerMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *OutboundManager) EXPECT() *OutboundManagerMockRecorder {\n\treturn m.recorder\n}\n\n// AddHandler mocks base method\nfunc (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"AddHandler\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// AddHandler indicates an expected call of AddHandler\nfunc (mr *OutboundManagerMockRecorder) AddHandler(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"AddHandler\", reflect.TypeOf((*OutboundManager)(nil).AddHandler), arg0, arg1)\n}\n\n// Close mocks base method\nfunc (m *OutboundManager) Close() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Close\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Close indicates an expected call of Close\nfunc (mr *OutboundManagerMockRecorder) Close() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Close\", reflect.TypeOf((*OutboundManager)(nil).Close))\n}\n\n// GetDefaultHandler mocks base method\nfunc (m *OutboundManager) GetDefaultHandler() outbound.Handler {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetDefaultHandler\")\n\tret0, _ := ret[0].(outbound.Handler)\n\treturn ret0\n}\n\n// GetDefaultHandler indicates an expected call of GetDefaultHandler\nfunc (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetDefaultHandler\", reflect.TypeOf((*OutboundManager)(nil).GetDefaultHandler))\n}\n\n// GetHandler mocks base method\nfunc (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetHandler\", arg0)\n\tret0, _ := ret[0].(outbound.Handler)\n\treturn ret0\n}\n\n// GetHandler indicates an expected call of GetHandler\nfunc (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"GetHandler\", reflect.TypeOf((*OutboundManager)(nil).GetHandler), arg0)\n}\n\n// ListHandlers mocks base method\nfunc (m *OutboundManager) ListHandlers(arg0 context.Context) []outbound.Handler {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"ListHandlers\", arg0)\n\tret0, _ := ret[0].([]outbound.Handler)\n\treturn ret0\n}\n\n// ListHandlers indicates an expected call of ListHandlers\nfunc (mr *OutboundManagerMockRecorder) ListHandlers(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"ListHandlers\", reflect.TypeOf((*OutboundManager)(nil).ListHandlers), arg0)\n}\n\n// RemoveHandler mocks base method\nfunc (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RemoveHandler\", arg0, arg1)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// RemoveHandler indicates an expected call of RemoveHandler\nfunc (mr *OutboundManagerMockRecorder) RemoveHandler(arg0, arg1 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"RemoveHandler\", reflect.TypeOf((*OutboundManager)(nil).RemoveHandler), arg0, arg1)\n}\n\n// Start mocks base method\nfunc (m *OutboundManager) Start() error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Start\")\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Start indicates an expected call of Start\nfunc (mr *OutboundManagerMockRecorder) Start() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Start\", reflect.TypeOf((*OutboundManager)(nil).Start))\n}\n\n// Type mocks base method\nfunc (m *OutboundManager) Type() interface{} {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Type\")\n\tret0, _ := ret[0].(interface{})\n\treturn ret0\n}\n\n// Type indicates an expected call of Type\nfunc (mr *OutboundManagerMockRecorder) Type() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Type\", reflect.TypeOf((*OutboundManager)(nil).Type))\n}\n\n// OutboundHandlerSelector is a mock of HandlerSelector interface\ntype OutboundHandlerSelector struct {\n\tctrl     *gomock.Controller\n\trecorder *OutboundHandlerSelectorMockRecorder\n}\n\n// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector\ntype OutboundHandlerSelectorMockRecorder struct {\n\tmock *OutboundHandlerSelector\n}\n\n// NewOutboundHandlerSelector creates a new mock instance\nfunc NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector {\n\tmock := &OutboundHandlerSelector{ctrl: ctrl}\n\tmock.recorder = &OutboundHandlerSelectorMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *OutboundHandlerSelector) EXPECT() *OutboundHandlerSelectorMockRecorder {\n\treturn m.recorder\n}\n\n// Select mocks base method\nfunc (m *OutboundHandlerSelector) Select(arg0 []string) []string {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Select\", arg0)\n\tret0, _ := ret[0].([]string)\n\treturn ret0\n}\n\n// Select indicates an expected call of Select\nfunc (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Select\", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0)\n}\n"
  },
  {
    "path": "testing/mocks/proxy.go",
    "content": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/xtls/xray-core/proxy (interfaces: Inbound,Outbound)\n\n// Package mocks is a generated GoMock package.\npackage mocks\n\nimport (\n\tcontext \"context\"\n\treflect \"reflect\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\tnet \"github.com/xtls/xray-core/common/net\"\n\trouting \"github.com/xtls/xray-core/features/routing\"\n\ttransport \"github.com/xtls/xray-core/transport\"\n\tinternet \"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// ProxyInbound is a mock of Inbound interface\ntype ProxyInbound struct {\n\tctrl     *gomock.Controller\n\trecorder *ProxyInboundMockRecorder\n}\n\n// ProxyInboundMockRecorder is the mock recorder for ProxyInbound\ntype ProxyInboundMockRecorder struct {\n\tmock *ProxyInbound\n}\n\n// NewProxyInbound creates a new mock instance\nfunc NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound {\n\tmock := &ProxyInbound{ctrl: ctrl}\n\tmock.recorder = &ProxyInboundMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *ProxyInbound) EXPECT() *ProxyInboundMockRecorder {\n\treturn m.recorder\n}\n\n// Network mocks base method\nfunc (m *ProxyInbound) Network() []net.Network {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Network\")\n\tret0, _ := ret[0].([]net.Network)\n\treturn ret0\n}\n\n// Network indicates an expected call of Network\nfunc (mr *ProxyInboundMockRecorder) Network() *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Network\", reflect.TypeOf((*ProxyInbound)(nil).Network))\n}\n\n// Process mocks base method\nfunc (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 stat.Connection, arg3 routing.Dispatcher) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Process\", arg0, arg1, arg2, arg3)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Process indicates an expected call of Process\nfunc (mr *ProxyInboundMockRecorder) Process(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Process\", reflect.TypeOf((*ProxyInbound)(nil).Process), arg0, arg1, arg2, arg3)\n}\n\n// ProxyOutbound is a mock of Outbound interface\ntype ProxyOutbound struct {\n\tctrl     *gomock.Controller\n\trecorder *ProxyOutboundMockRecorder\n}\n\n// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound\ntype ProxyOutboundMockRecorder struct {\n\tmock *ProxyOutbound\n}\n\n// NewProxyOutbound creates a new mock instance\nfunc NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound {\n\tmock := &ProxyOutbound{ctrl: ctrl}\n\tmock.recorder = &ProxyOutboundMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *ProxyOutbound) EXPECT() *ProxyOutboundMockRecorder {\n\treturn m.recorder\n}\n\n// Process mocks base method\nfunc (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2 internet.Dialer) error {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Process\", arg0, arg1, arg2)\n\tret0, _ := ret[0].(error)\n\treturn ret0\n}\n\n// Process indicates an expected call of Process\nfunc (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call {\n\tmr.mock.ctrl.T.Helper()\n\treturn mr.mock.ctrl.RecordCallWithMethodType(mr.mock, \"Process\", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2)\n}\n"
  },
  {
    "path": "testing/scenarios/command_test.go",
    "content": "package scenarios\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/xtls/xray-core/app/commander\"\n\t\"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/proxyman/command\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/app/stats\"\n\tstatscmd \"github.com/xtls/xray-core/app/stats/command\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/protobuf/testing/protocmp\"\n)\n\nfunc TestCommanderListenConfigurationItem(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tclientPort := tcp.PickPort()\n\tcmdPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&commander.Config{\n\t\t\t\tTag:    \"api\",\n\t\t\t\tListen: fmt.Sprintf(\"127.0.0.1:%d\", cmdPort),\n\t\t\t\tService: []*serial.TypedMessage{\n\t\t\t\t\tserial.ToTypedMessage(&command.Config{}),\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"d\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:           \"default-outbound\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcmdConn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", cmdPort), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())\n\tcommon.Must(err)\n\tdefer cmdConn.Close()\n\n\thsClient := command.NewHandlerServiceClient(cmdConn)\n\tresp, err := hsClient.RemoveInbound(context.Background(), &command.RemoveInboundRequest{\n\t\tTag: \"d\",\n\t})\n\tcommon.Must(err)\n\tif resp == nil {\n\t\tt.Error(\"unexpected nil response\")\n\t}\n\n\t{\n\t\t_, err := net.DialTCP(\"tcp\", nil, &net.TCPAddr{\n\t\t\tIP:   []byte{127, 0, 0, 1},\n\t\t\tPort: int(clientPort),\n\t\t})\n\t\tif err == nil {\n\t\t\tt.Error(\"unexpected nil error\")\n\t\t}\n\t}\n}\n\nfunc TestCommanderRemoveHandler(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tclientPort := tcp.PickPort()\n\tcmdPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&commander.Config{\n\t\t\t\tTag: \"api\",\n\t\t\t\tService: []*serial.TypedMessage{\n\t\t\t\t\tserial.ToTypedMessage(&command.Config{}),\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"api\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"api\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"d\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"api\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(cmdPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:           \"default-outbound\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcmdConn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", cmdPort), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())\n\tcommon.Must(err)\n\tdefer cmdConn.Close()\n\n\thsClient := command.NewHandlerServiceClient(cmdConn)\n\tresp, err := hsClient.RemoveInbound(context.Background(), &command.RemoveInboundRequest{\n\t\tTag: \"d\",\n\t})\n\tcommon.Must(err)\n\tif resp == nil {\n\t\tt.Error(\"unexpected nil response\")\n\t}\n\n\t{\n\t\t_, err := net.DialTCP(\"tcp\", nil, &net.TCPAddr{\n\t\t\tIP:   []byte{127, 0, 0, 1},\n\t\t\tPort: int(clientPort),\n\t\t})\n\t\tif err == nil {\n\t\t\tt.Error(\"unexpected nil error\")\n\t\t}\n\t}\n}\n\nfunc TestCommanderListHandlers(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tclientPort := tcp.PickPort()\n\tcmdPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&commander.Config{\n\t\t\t\tTag: \"api\",\n\t\t\t\tService: []*serial.TypedMessage{\n\t\t\t\t\tserial.ToTypedMessage(&command.Config{}),\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"api\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"api\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"d\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"api\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(cmdPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:            \"default-outbound\",\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{}),\n\t\t\t\tProxySettings:  serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcmdConn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", cmdPort), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())\n\tcommon.Must(err)\n\tdefer cmdConn.Close()\n\n\thsClient := command.NewHandlerServiceClient(cmdConn)\n\tinboundResp, err := hsClient.ListInbounds(context.Background(), &command.ListInboundsRequest{})\n\tcommon.Must(err)\n\tif inboundResp == nil {\n\t\tt.Error(\"unexpected nil response\")\n\t}\n\n\tif diff := cmp.Diff(\n\t\tinboundResp.Inbounds,\n\t\tclientConfig.Inbound,\n\t\tprotocmp.Transform(),\n\t\tcmpopts.SortSlices(func(a, b *core.InboundHandlerConfig) bool {\n\t\t\treturn a.Tag < b.Tag\n\t\t})); diff != \"\" {\n\t\tt.Fatalf(\"inbound response doesn't match config (-want +got):\\n%s\", diff)\n\t}\n\n\toutboundResp, err := hsClient.ListOutbounds(context.Background(), &command.ListOutboundsRequest{})\n\tcommon.Must(err)\n\tif outboundResp == nil {\n\t\tt.Error(\"unexpected nil response\")\n\t}\n\n\tif diff := cmp.Diff(\n\t\toutboundResp.Outbounds,\n\t\tclientConfig.Outbound,\n\t\tprotocmp.Transform(),\n\t\tcmpopts.SortSlices(func(a, b *core.InboundHandlerConfig) bool {\n\t\t\treturn a.Tag < b.Tag\n\t\t})); diff != \"\" {\n\t\tt.Fatalf(\"outbound response doesn't match config (-want +got):\\n%s\", diff)\n\t}\n}\n\nfunc TestCommanderAddRemoveUser(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tu1 := protocol.NewID(uuid.New())\n\tu2 := protocol.NewID(uuid.New())\n\n\tcmdPort := tcp.PickPort()\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&commander.Config{\n\t\t\t\tTag: \"api\",\n\t\t\t\tService: []*serial.TypedMessage{\n\t\t\t\t\tserial.ToTypedMessage(&command.Config{}),\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"api\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"api\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"v\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: u1.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"api\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(cmdPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"d\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: u2.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != io.EOF &&\n\t\t/*We might wish to drain the connection*/\n\t\t(err != nil && !strings.HasSuffix(err.Error(), \"i/o timeout\")) {\n\t\tt.Fatal(\"expected error: \", err)\n\t}\n\n\tcmdConn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", cmdPort), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())\n\tcommon.Must(err)\n\tdefer cmdConn.Close()\n\n\thsClient := command.NewHandlerServiceClient(cmdConn)\n\tresp, err := hsClient.AlterInbound(context.Background(), &command.AlterInboundRequest{\n\t\tTag: \"v\",\n\t\tOperation: serial.ToTypedMessage(\n\t\t\t&command.AddUserOperation{\n\t\t\t\tUser: &protocol.User{\n\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\tId: u2.String(),\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t}),\n\t})\n\tcommon.Must(err)\n\tif resp == nil {\n\t\tt.Fatal(\"nil response\")\n\t}\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresp, err = hsClient.AlterInbound(context.Background(), &command.AlterInboundRequest{\n\t\tTag:       \"v\",\n\t\tOperation: serial.ToTypedMessage(&command.RemoveUserOperation{Email: \"test@example.com\"}),\n\t})\n\tcommon.Must(err)\n\tif resp == nil {\n\t\tt.Fatal(\"nil response\")\n\t}\n}\n\nfunc TestCommanderStats(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tcmdPort := tcp.PickPort()\n\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&stats.Config{}),\n\t\t\tserial.ToTypedMessage(&commander.Config{\n\t\t\t\tTag: \"api\",\n\t\t\t\tService: []*serial.TypedMessage{\n\t\t\t\t\tserial.ToTypedMessage(&statscmd.Config{}),\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"api\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"api\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t1: {\n\t\t\t\t\t\tStats: &policy.Policy_Stats{\n\t\t\t\t\t\t\tUserUplink:   true,\n\t\t\t\t\t\t\tUserDownlink: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSystem: &policy.SystemPolicy{\n\t\t\t\t\tStats: &policy.SystemPolicy_Stats{\n\t\t\t\t\t\tInboundUplink: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"vmess\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tLevel: 1,\n\t\t\t\t\t\t\tEmail: \"test\",\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"api\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(cmdPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to create all servers\", err)\n\t}\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 10240*1024, time.Second*20)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcmdConn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", cmdPort), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())\n\tcommon.Must(err)\n\tdefer cmdConn.Close()\n\n\tconst name = \"user>>>test>>>traffic>>>uplink\"\n\tsClient := statscmd.NewStatsServiceClient(cmdConn)\n\n\tsresp, err := sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{\n\t\tName:   name,\n\t\tReset_: true,\n\t})\n\tcommon.Must(err)\n\tif r := cmp.Diff(sresp.Stat, &statscmd.Stat{\n\t\tName:  name,\n\t\tValue: 10240 * 1024,\n\t}, cmpopts.IgnoreUnexported(statscmd.Stat{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\tsresp, err = sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{\n\t\tName: name,\n\t})\n\tcommon.Must(err)\n\tif r := cmp.Diff(sresp.Stat, &statscmd.Stat{\n\t\tName:  name,\n\t\tValue: 0,\n\t}, cmpopts.IgnoreUnexported(statscmd.Stat{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\tsresp, err = sClient.GetStats(context.Background(), &statscmd.GetStatsRequest{\n\t\tName:   \"inbound>>>vmess>>>traffic>>>uplink\",\n\t\tReset_: true,\n\t})\n\tcommon.Must(err)\n\tif sresp.Stat.Value <= 10240*1024 {\n\t\tt.Error(\"value < 10240*1024: \", sresp.Stat.Value)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/common.go",
    "content": "package scenarios\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sync\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/retry\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/units\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc xor(b []byte) []byte {\n\tr := make([]byte, len(b))\n\tfor i, v := range b {\n\t\tr[i] = v ^ 'c'\n\t}\n\treturn r\n}\n\nfunc readFrom(conn net.Conn, timeout time.Duration, length int) []byte {\n\tb := make([]byte, length)\n\tdeadline := time.Now().Add(timeout)\n\tconn.SetReadDeadline(deadline)\n\tn, err := io.ReadFull(conn, b[:length])\n\tif err != nil {\n\t\tfmt.Println(\"Unexpected error from readFrom:\", err)\n\t}\n\treturn b[:n]\n}\n\nfunc readFrom2(conn net.Conn, timeout time.Duration, length int) ([]byte, error) {\n\tb := make([]byte, length)\n\tdeadline := time.Now().Add(timeout)\n\tconn.SetReadDeadline(deadline)\n\tn, err := io.ReadFull(conn, b[:length])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b[:n], nil\n}\n\nfunc InitializeServerConfigs(configs ...*core.Config) ([]*exec.Cmd, error) {\n\tservers := make([]*exec.Cmd, 0, 10)\n\n\tfor _, config := range configs {\n\t\tserver, err := InitializeServerConfig(config)\n\t\tif err != nil {\n\t\t\tCloseAllServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\t\tservers = append(servers, server)\n\t}\n\n\ttime.Sleep(time.Second * 2)\n\n\treturn servers, nil\n}\n\nfunc InitializeServerConfig(config *core.Config) (*exec.Cmd, error) {\n\terr := BuildXray()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfig = withDefaultApps(config)\n\tconfigBytes, err := proto.Marshal(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproc := RunXrayProtobuf(configBytes)\n\n\tif err := proc.Start(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn proc, nil\n}\n\nvar (\n\ttestBinaryPath    string\n\ttestBinaryCleanFn func()\n\ttestBinaryPathGen sync.Once\n)\n\nfunc genTestBinaryPath() {\n\ttestBinaryPathGen.Do(func() {\n\t\tvar tempDir string\n\t\tcommon.Must(retry.Timed(5, 100).On(func() error {\n\t\t\tdir, err := os.MkdirTemp(\"\", \"xray\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ttempDir = dir\n\t\t\ttestBinaryCleanFn = func() { os.RemoveAll(dir) }\n\t\t\treturn nil\n\t\t}))\n\t\tfile := filepath.Join(tempDir, \"xray.test\")\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tfile += \".exe\"\n\t\t}\n\t\ttestBinaryPath = file\n\t\tfmt.Printf(\"Generated binary path: %s\\n\", file)\n\t})\n}\n\nfunc GetSourcePath() string {\n\treturn filepath.Join(\"github.com\", \"xtls\", \"xray-core\", \"main\")\n}\n\nfunc CloseAllServers(servers []*exec.Cmd) {\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: log.Severity_Info,\n\t\tContent:  \"Closing all servers.\",\n\t})\n\tfor _, server := range servers {\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tserver.Process.Kill()\n\t\t} else {\n\t\t\tserver.Process.Signal(syscall.SIGTERM)\n\t\t}\n\t}\n\tfor _, server := range servers {\n\t\tserver.Process.Wait()\n\t}\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: log.Severity_Info,\n\t\tContent:  \"All server closed.\",\n\t})\n}\n\nfunc CloseServer(server *exec.Cmd) {\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: log.Severity_Info,\n\t\tContent:  \"Closing server.\",\n\t})\n\tif runtime.GOOS == \"windows\" {\n\t\tserver.Process.Kill()\n\t} else {\n\t\tserver.Process.Signal(syscall.SIGTERM)\n\t}\n\tserver.Process.Wait()\n\tlog.Record(&log.GeneralMessage{\n\t\tSeverity: log.Severity_Info,\n\t\tContent:  \"Server closed.\",\n\t})\n}\n\nfunc withDefaultApps(config *core.Config) *core.Config {\n\tconfig.App = append(config.App, serial.ToTypedMessage(&dispatcher.Config{}))\n\tconfig.App = append(config.App, serial.ToTypedMessage(&proxyman.InboundConfig{}))\n\tconfig.App = append(config.App, serial.ToTypedMessage(&proxyman.OutboundConfig{}))\n\treturn config\n}\n\nfunc testTCPConn(port net.Port, payloadSize int, timeout time.Duration) func() error {\n\treturn func() error {\n\t\tconn, err := net.DialTCP(\"tcp\", nil, &net.TCPAddr{\n\t\t\tIP:   []byte{127, 0, 0, 1},\n\t\t\tPort: int(port),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer conn.Close()\n\n\t\treturn testTCPConn2(conn, payloadSize, timeout)()\n\t}\n}\n\nfunc testUDPConn(port net.Port, payloadSize int, timeout time.Duration) func() error {\n\treturn func() error {\n\t\tconn, err := net.DialUDP(\"udp\", nil, &net.UDPAddr{\n\t\t\tIP:   []byte{127, 0, 0, 1},\n\t\t\tPort: int(port),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer conn.Close()\n\n\t\treturn testTCPConn2(conn, payloadSize, timeout)()\n\t}\n}\n\nfunc testTCPConn2(conn net.Conn, payloadSize int, timeout time.Duration) func() error {\n\treturn func() (err1 error) {\n\t\tstart := time.Now()\n\t\tdefer func() {\n\t\t\tvar m runtime.MemStats\n\t\t\truntime.ReadMemStats(&m)\n\t\t\t// For info on each, see: https://golang.org/pkg/runtime/#MemStats\n\t\t\tfmt.Println(\"testConn finishes:\", time.Since(start).Milliseconds(), \"ms\\t\",\n\t\t\t\terr1, \"\\tAlloc =\", units.ByteSize(m.Alloc).String(),\n\t\t\t\t\"\\tTotalAlloc =\", units.ByteSize(m.TotalAlloc).String(),\n\t\t\t\t\"\\tSys =\", units.ByteSize(m.Sys).String(),\n\t\t\t\t\"\\tNumGC =\", m.NumGC)\n\t\t}()\n\t\tsingleWrite := func(length int) error {\n\t\t\tpayload := make([]byte, length)\n\t\t\tcommon.Must2(rand.Read(payload))\n\n\t\t\tnBytes, err := conn.Write(payload)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif nBytes != len(payload) {\n\t\t\t\treturn errors.New(\"expect \", len(payload), \" written, but actually \", nBytes)\n\t\t\t}\n\n\t\t\tresponse, err := readFrom2(conn, timeout, length)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_ = response\n\n\t\t\tif r := bytes.Compare(response, xor(payload)); r != 0 {\n\t\t\t\treturn errors.New(r)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t\tfor payloadSize > 0 {\n\t\t\tsizeToWrite := 1024\n\t\t\tif payloadSize < 1024 {\n\t\t\t\tsizeToWrite = payloadSize\n\t\t\t}\n\t\t\tif err := singleWrite(sizeToWrite); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpayloadSize -= sizeToWrite\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc WaitConnAvailableWithTest(t *testing.T, testFunc func() error) bool {\n\tfor i := 1; ; i++ {\n\t\tif i > 10 {\n\t\t\tt.Log(\"All attempts failed to test tcp conn\")\n\t\t\treturn false\n\t\t}\n\t\ttime.Sleep(time.Millisecond * 10)\n\t\tif err := testFunc(); err != nil {\n\t\t\tt.Log(\"err \", err)\n\t\t} else {\n\t\t\tt.Log(\"success with\", i, \"attempts\")\n\t\t\tbreak\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "testing/scenarios/common_coverage.go",
    "content": "//go:build coverage\n// +build coverage\n\npackage scenarios\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"os/exec\"\n\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\nfunc BuildXray() error {\n\tgenTestBinaryPath()\n\tif _, err := os.Stat(testBinaryPath); err == nil {\n\t\treturn nil\n\t}\n\n\tcmd := exec.Command(\"go\", \"test\", \"-tags\", \"coverage coveragemain\", \"-coverpkg\", \"github.com/xtls/xray-core/...\", \"-c\", \"-o\", testBinaryPath, GetSourcePath())\n\treturn cmd.Run()\n}\n\nfunc RunXrayProtobuf(config []byte) *exec.Cmd {\n\tgenTestBinaryPath()\n\n\tcovDir := os.Getenv(\"XRAY_COV\")\n\tos.MkdirAll(covDir, os.ModeDir)\n\trandomID := uuid.New()\n\tprofile := randomID.String() + \".out\"\n\tproc := exec.Command(testBinaryPath, \"-config=stdin:\", \"-format=pb\", \"-test.run\", \"TestRunMainForCoverage\", \"-test.coverprofile\", profile, \"-test.outputdir\", covDir)\n\tproc.Stdin = bytes.NewBuffer(config)\n\tproc.Stderr = os.Stderr\n\tproc.Stdout = os.Stdout\n\n\treturn proc\n}\n"
  },
  {
    "path": "testing/scenarios/common_regular.go",
    "content": "//go:build !coverage\n// +build !coverage\n\npackage scenarios\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n)\n\nfunc BuildXray() error {\n\tgenTestBinaryPath()\n\tif _, err := os.Stat(testBinaryPath); err == nil {\n\t\treturn nil\n\t}\n\n\tfmt.Printf(\"Building Xray into path (%s)\\n\", testBinaryPath)\n\tcmd := exec.Command(\"go\", \"build\", \"-o=\"+testBinaryPath, GetSourcePath())\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\treturn cmd.Run()\n}\n\nfunc RunXrayProtobuf(config []byte) *exec.Cmd {\n\tgenTestBinaryPath()\n\tproc := exec.Command(testBinaryPath, \"-config=stdin:\", \"-format=pb\")\n\tproc.Stdin = bytes.NewBuffer(config)\n\tproc.Stderr = os.Stderr\n\tproc.Stdout = os.Stdout\n\n\treturn proc\n}\n"
  },
  {
    "path": "testing/scenarios/dns_test.go",
    "content": "package scenarios\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/dns\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/socks\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\txproxy \"golang.org/x/net/proxy\"\n)\n\nfunc TestResolveIP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dns.Config{\n\t\t\t\tStaticHosts: []*dns.Config_HostMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   dns.DomainMatchingType_Full,\n\t\t\t\t\t\tDomain: \"google.com\",\n\t\t\t\t\t\tIp:     [][]byte{dest.Address.IP()},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tDomainStrategy: router.Config_IpIfNonMatch,\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tGeoip: []*router.GeoIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCidr: []*router.CIDR{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tIp:     []byte{127, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\tPrefix: 8,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"direct\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\tAuthType: socks.AuthType_NO_AUTH,\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t},\n\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tUdpEnabled: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&blackhole.Config{}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"direct\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{\n\t\t\t\t\tDomainStrategy: internet.DomainStrategy_USE_IP,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\tnoAuthDialer, err := xproxy.SOCKS5(\"tcp\", net.TCPDestination(net.LocalHostIP, serverPort).NetAddr(), nil, xproxy.Direct)\n\t\tcommon.Must(err)\n\t\tconn, err := noAuthDialer.Dial(\"tcp\", fmt.Sprintf(\"google.com:%d\", dest.Port))\n\t\tcommon.Must(err)\n\t\tdefer conn.Close()\n\n\t\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/dokodemo_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestDokodemoTCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\tserver, err := InitializeServerConfig(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseServer(server)\n\n\tclientPortRange := uint32(5)\n\tretry := 1\n\tclientPort := uint32(tcp.PickPort())\n\tfor {\n\t\tclientConfig := &core.Config{\n\t\t\tApp: []*serial.TypedMessage{\n\t\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t\t}),\n\t\t\t},\n\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{{From: clientPort, To: clientPort + clientPortRange}}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tserver, _ := InitializeServerConfig(clientConfig)\n\t\tif server != nil && WaitConnAvailableWithTest(t, testTCPConn(net.Port(clientPort), 1024, time.Second*2)) {\n\t\t\tdefer CloseServer(server)\n\t\t\tbreak\n\t\t}\n\t\tretry++\n\t\tif retry > 5 {\n\t\t\tt.Fatal(\"All attempts failed to start client\")\n\t\t}\n\t\tclientPort = uint32(tcp.PickPort())\n\t}\n\n\tfor port := clientPort; port <= clientPort+clientPortRange; port++ {\n\t\tif err := testTCPConn(net.Port(port), 1024, time.Second*2)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n\nfunc TestDokodemoUDP(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\tserver, err := InitializeServerConfig(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseServer(server)\n\n\tclientPortRange := uint32(3)\n\tretry := 1\n\tclientPort := uint32(udp.PickPort())\n\tfor {\n\t\tclientConfig := &core.Config{\n\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{{From: clientPort, To: clientPort + clientPortRange}}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tserver, _ := InitializeServerConfig(clientConfig)\n\t\tif server != nil && WaitConnAvailableWithTest(t, testUDPConn(net.Port(clientPort), 1024, time.Second*2)) {\n\t\t\tdefer CloseServer(server)\n\t\t\tbreak\n\t\t}\n\t\tretry++\n\t\tif retry > 5 {\n\t\t\tt.Fatal(\"All attempts failed to start client\")\n\t\t}\n\t\tclientPort = uint32(udp.PickPort())\n\t}\n\n\tvar errg errgroup.Group\n\tfor port := clientPort; port <= clientPort+clientPortRange; port++ {\n\t\terrg.Go(testUDPConn(net.Port(port), 1024, time.Second*5))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/feature_test.go",
    "content": "package scenarios\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/inbound\"\n\t_ \"github.com/xtls/xray-core/app/proxyman/outbound\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\tv2http \"github.com/xtls/xray-core/proxy/http\"\n\t\"github.com/xtls/xray-core/proxy/socks\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\txproxy \"golang.org/x/net/proxy\"\n)\n\nfunc TestPassiveConnection(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t\tSendFirst:    []byte(\"send first\"),\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tconn, err := net.DialTCP(\"tcp\", nil, &net.TCPAddr{\n\t\tIP:   []byte{127, 0, 0, 1},\n\t\tPort: int(serverPort),\n\t})\n\tcommon.Must(err)\n\n\t{\n\t\tresponse := make([]byte, 1024)\n\t\tnBytes, err := conn.Read(response)\n\t\tcommon.Must(err)\n\t\tif string(response[:nBytes]) != \"send first\" {\n\t\t\tt.Error(\"unexpected first response: \", string(response[:nBytes]))\n\t\t}\n\t}\n\n\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestProxy(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverUserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: serverUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tproxyUserID := protocol.NewID(uuid.New())\n\tproxyPort := tcp.PickPort()\n\tproxyConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(proxyPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: proxyUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: serverUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tProxySettings: &internet.ProxyConfig{\n\t\t\t\t\t\tTag: \"proxy\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"proxy\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(proxyPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: proxyUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, proxyConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestProxyOverKCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverUserID := protocol.NewID(uuid.New())\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: serverUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tproxyUserID := protocol.NewID(uuid.New())\n\tproxyPort := tcp.PickPort()\n\tproxyConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(proxyPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: proxyUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: serverUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tProxySettings: &internet.ProxyConfig{\n\t\t\t\t\t\tTag: \"proxy\",\n\t\t\t\t\t},\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"proxy\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(proxyPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: proxyUserID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, proxyConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestBlackhole(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\ttcpServer2 := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest2, err := tcpServer2.Start()\n\tcommon.Must(err)\n\tdefer tcpServer2.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverPort2 := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort2)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest2.Address),\n\t\t\t\t\tPort:     uint32(dest2.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:           \"direct\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag:           \"blocked\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&blackhole.Config{}),\n\t\t\t},\n\t\t},\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"blocked\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPortList: &net.PortList{\n\t\t\t\t\t\t\tRange: []*net.PortRange{net.SinglePortRange(dest2.Port)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(serverPort2, 1024, time.Second*5)(); err == nil {\n\t\tt.Error(\"nil error\")\n\t}\n}\n\nfunc TestForward(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\tAuthType: socks.AuthType_NO_AUTH,\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t},\n\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tUdpEnabled: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{\n\t\t\t\t\tDestinationOverride: &freedom.DestinationOverride{\n\t\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\t\tPort:    uint32(dest.Port),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\tnoAuthDialer, err := xproxy.SOCKS5(\"tcp\", net.TCPDestination(net.LocalHostIP, serverPort).NetAddr(), nil, xproxy.Direct)\n\t\tcommon.Must(err)\n\t\tconn, err := noAuthDialer.Dial(\"tcp\", \"google.com:80\")\n\t\tcommon.Must(err)\n\t\tdefer conn.Close()\n\n\t\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n\nfunc TestUDPConnection(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tclientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testUDPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\ttime.Sleep(20 * time.Second)\n\n\tif err := testUDPConn(clientPort, 1024, time.Second*5)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestDomainSniffing(t *testing.T) {\n\tsniffingPort := tcp.PickPort()\n\thttpPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"snif\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(sniffingPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tSniffingSettings: &proxyman.SniffingConfig{\n\t\t\t\t\t\tEnabled:             true,\n\t\t\t\t\t\tDestinationOverride: []string{\"tls\"},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:     443,\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"http\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(httpPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"redir\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{\n\t\t\t\t\tDestinationOverride: &freedom.DestinationOverride{\n\t\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\t\tPort:    uint32(sniffingPort),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag:           \"direct\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"direct\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tInboundTag: []string{\"snif\"},\n\t\t\t\t\t}, {\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"redir\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tInboundTag: []string{\"http\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(\"http://127.0.0.1:\" + httpPort.String())\n\t\t\t},\n\t\t}\n\n\t\tclient := &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\n\t\tresp, err := client.Get(\"https://www.github.com/\")\n\t\tcommon.Must(err)\n\t\tif resp.StatusCode != 200 {\n\t\t\tt.Error(\"unexpected status code: \", resp.StatusCode)\n\t\t}\n\t\tcommon.Must(resp.Write(io.Discard))\n\t}\n}\n\nfunc TestDialXray(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tclient, err := core.New(clientConfig)\n\tcommon.Must(err)\n\n\tconn, err := core.Dial(context.Background(), client, dest)\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/http_test.go",
    "content": "package scenarios\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\tv2http \"github.com/xtls/xray-core/proxy/http\"\n\tv2httptest \"github.com/xtls/xray-core/testing/servers/http\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n)\n\nfunc TestHttpConformance(t *testing.T) {\n\thttpServerPort := tcp.PickPort()\n\thttpServer := &v2httptest.Server{\n\t\tPort:        httpServerPort,\n\t\tPathHandler: make(map[string]http.HandlerFunc),\n\t}\n\t_, err := httpServer.Start()\n\tcommon.Must(err)\n\tdefer httpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(\"http://127.0.0.1:\" + serverPort.String())\n\t\t\t},\n\t\t}\n\n\t\tclient := &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\n\t\tresp, err := client.Get(\"http://127.0.0.1:\" + httpServerPort.String())\n\t\tcommon.Must(err)\n\t\tif resp.StatusCode != 200 {\n\t\t\tt.Fatal(\"status: \", resp.StatusCode)\n\t\t}\n\n\t\tcontent, err := io.ReadAll(resp.Body)\n\t\tcommon.Must(err)\n\t\tif string(content) != \"Home\" {\n\t\t\tt.Fatal(\"body: \", string(content))\n\t\t}\n\t}\n}\n\nfunc TestHttpError(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: func(msg []byte) []byte {\n\t\t\treturn []byte{}\n\t\t},\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\ttime.AfterFunc(time.Second*2, func() {\n\t\ttcpServer.ShouldClose = true\n\t})\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(\"http://127.0.0.1:\" + serverPort.String())\n\t\t\t},\n\t\t}\n\n\t\tclient := &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\n\t\tresp, err := client.Get(\"http://127.0.0.1:\" + dest.Port.String())\n\t\tif resp != nil && resp.StatusCode != 503 || err != nil && !strings.Contains(err.Error(), \"malformed HTTP status code\") {\n\t\t\tt.Error(\"should not receive http response\", err)\n\t\t}\n\t}\n}\n\nfunc TestHTTPConnectMethod(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(\"http://127.0.0.1:\" + serverPort.String())\n\t\t\t},\n\t\t}\n\n\t\tclient := &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\n\t\tpayload := make([]byte, 1024*64)\n\t\tcommon.Must2(rand.Read(payload))\n\n\t\tctx := context.Background()\n\t\treq, err := http.NewRequestWithContext(ctx, \"Connect\", \"http://\"+dest.NetAddr()+\"/\", bytes.NewReader(payload))\n\t\treq.Header.Set(\"X-a\", \"b\")\n\t\treq.Header.Set(\"X-b\", \"d\")\n\t\tcommon.Must(err)\n\n\t\tresp, err := client.Do(req)\n\t\tcommon.Must(err)\n\t\tif resp.StatusCode != 200 {\n\t\t\tt.Fatal(\"status: \", resp.StatusCode)\n\t\t}\n\n\t\tcontent := make([]byte, len(payload))\n\t\tcommon.Must2(io.ReadFull(resp.Body, content))\n\t\tif r := cmp.Diff(content, xor(payload)); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n}\n\nfunc TestHttpPost(t *testing.T) {\n\thttpServerPort := tcp.PickPort()\n\thttpServer := &v2httptest.Server{\n\t\tPort: httpServerPort,\n\t\tPathHandler: map[string]http.HandlerFunc{\n\t\t\t\"/testpost\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tpayload, err := buf.ReadAllToBytes(r.Body)\n\t\t\t\tr.Body.Close()\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tw.WriteHeader(500)\n\t\t\t\t\tw.Write([]byte(\"Unable to read all payload\"))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tpayload = xor(payload)\n\t\t\t\tw.Write(payload)\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := httpServer.Start()\n\tcommon.Must(err)\n\tdefer httpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(\"http://127.0.0.1:\" + serverPort.String())\n\t\t\t},\n\t\t}\n\n\t\tclient := &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\n\t\tpayload := make([]byte, 1024*64)\n\t\tcommon.Must2(rand.Read(payload))\n\n\t\tresp, err := client.Post(\"http://127.0.0.1:\"+httpServerPort.String()+\"/testpost\", \"application/x-www-form-urlencoded\", bytes.NewReader(payload))\n\t\tcommon.Must(err)\n\t\tif resp.StatusCode != 200 {\n\t\t\tt.Fatal(\"status: \", resp.StatusCode)\n\t\t}\n\n\t\tcontent, err := io.ReadAll(resp.Body)\n\t\tcommon.Must(err)\n\t\tif r := cmp.Diff(content, xor(payload)); r != \"\" {\n\t\t\tt.Fatal(r)\n\t\t}\n\t}\n}\n\nfunc setProxyBasicAuth(req *http.Request, user, pass string) {\n\treq.SetBasicAuth(user, pass)\n\treq.Header.Set(\"Proxy-Authorization\", req.Header.Get(\"Authorization\"))\n\treq.Header.Del(\"Authorization\")\n}\n\nfunc TestHttpBasicAuth(t *testing.T) {\n\thttpServerPort := tcp.PickPort()\n\thttpServer := &v2httptest.Server{\n\t\tPort:        httpServerPort,\n\t\tPathHandler: make(map[string]http.HandlerFunc),\n\t}\n\t_, err := httpServer.Start()\n\tcommon.Must(err)\n\tdefer httpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"a\": \"b\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(req *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(\"http://127.0.0.1:\" + serverPort.String())\n\t\t\t},\n\t\t}\n\n\t\tclient := &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\n\t\t{\n\t\t\tresp, err := client.Get(\"http://127.0.0.1:\" + httpServerPort.String())\n\t\t\tcommon.Must(err)\n\t\t\tif resp.StatusCode != 407 {\n\t\t\t\tt.Fatal(\"status: \", resp.StatusCode)\n\t\t\t}\n\t\t}\n\n\t\t{\n\t\t\tctx := context.Background()\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"http://127.0.0.1:\"+httpServerPort.String(), nil)\n\t\t\tcommon.Must(err)\n\n\t\t\tsetProxyBasicAuth(req, \"a\", \"c\")\n\t\t\tresp, err := client.Do(req)\n\t\t\tcommon.Must(err)\n\t\t\tif resp.StatusCode != 407 {\n\t\t\t\tt.Fatal(\"status: \", resp.StatusCode)\n\t\t\t}\n\t\t}\n\n\t\t{\n\t\t\tctx := context.Background()\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"http://127.0.0.1:\"+httpServerPort.String(), nil)\n\t\t\tcommon.Must(err)\n\n\t\t\tsetProxyBasicAuth(req, \"a\", \"b\")\n\t\t\tresp, err := client.Do(req)\n\t\t\tcommon.Must(err)\n\t\t\tif resp.StatusCode != 200 {\n\t\t\t\tt.Fatal(\"status: \", resp.StatusCode)\n\t\t\t}\n\n\t\t\tcontent, err := io.ReadAll(resp.Body)\n\t\t\tcommon.Must(err)\n\t\t\tif string(content) != \"Home\" {\n\t\t\t\tt.Fatal(\"body: \", string(content))\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/main_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n)\n\nfunc TestMain(m *testing.M) {\n\tgenTestBinaryPath()\n\tdefer testBinaryCleanFn()\n\n\tm.Run()\n}\n"
  },
  {
    "path": "testing/scenarios/metrics_test.go",
    "content": "package scenarios\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/app/metrics\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n)\n\nconst expectedMessage = \"goroutine profile: total\"\n\nfunc TestMetrics(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tmetricsPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&metrics.Config{\n\t\t\t\tTag: \"metrics_out\",\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"metrics_in\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"metrics_out\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"metrics_in\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(metricsPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:           \"default-outbound\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tresp, err := http.Get(fmt.Sprintf(\"http://127.0.0.1:%d/debug/pprof/goroutine?debug=1\", metricsPort))\n\tcommon.Must(err)\n\tif resp == nil {\n\t\tt.Error(\"unexpected pprof nil response\")\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\tt.Error(\"unexpected pprof status code\")\n\t}\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(body)[0:len(expectedMessage)] != expectedMessage {\n\t\tt.Error(\"unexpected response body from pprof handler\")\n\t}\n\n\tresp2, err2 := http.Get(fmt.Sprintf(\"http://127.0.0.1:%d/debug/vars\", metricsPort))\n\tcommon.Must(err2)\n\tif resp2 == nil {\n\t\tt.Error(\"unexpected expvars nil response\")\n\t}\n\tif resp2.StatusCode != http.StatusOK {\n\t\tt.Error(\"unexpected expvars status code\")\n\t}\n\tbody2, err2 := io.ReadAll(resp2.Body)\n\tif err2 != nil {\n\t\tt.Fatal(err2)\n\t}\n\tvar json2 map[string]interface{}\n\tif json.Unmarshal(body2, &json2) != nil {\n\t\tt.Error(\"unexpected response body from expvars handler\")\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/policy_test.go",
    "content": "package scenarios\n\nimport (\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc startQuickClosingTCPServer() (net.Listener, error) {\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tb := make([]byte, 1024)\n\t\t\tconn.Read(b)\n\t\t\tconn.Close()\n\t\t}\n\t}()\n\treturn listener, nil\n}\n\nfunc TestVMessClosing(t *testing.T) {\n\ttcpServer, err := startQuickClosingTCPServer()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tdest := net.DestinationFromAddr(tcpServer.Addr())\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*2)(); err != io.EOF {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestZeroBuffer(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tBuffer: &policy.Policy_Buffer{\n\t\t\t\t\t\t\tConnection: 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/reverse_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/policy\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/reverse\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestReverseProxy(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\texternalPort := tcp.PickPort()\n\treversePort := tcp.PickPort()\n\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&reverse.Config{\n\t\t\t\tPortalConfig: []*reverse.PortalConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\tTag:    \"portal\",\n\t\t\t\t\t\tDomain: \"test.example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tDomain: []*router.Domain{\n\t\t\t\t\t\t\t{Type: router.Domain_Full, Value: \"test.example.com\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"portal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"external\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"portal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"external\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(externalPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(reversePort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&blackhole.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&reverse.Config{\n\t\t\t\tBridgeConfig: []*reverse.BridgeConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\tTag:    \"bridge\",\n\t\t\t\t\t\tDomain: \"test.example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tDomain: []*router.Domain{\n\t\t\t\t\t\t\t{Type: router.Domain_Full, Value: \"test.example.com\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"reverse\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"bridge\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"freedom\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:           \"freedom\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"reverse\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(reversePort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 32 {\n\t\terrg.Go(testTCPConn(externalPort, 10240*1024, time.Second*40))\n\t}\n\n\tif err := errg.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestReverseProxyLongRunning(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\texternalPort := tcp.PickPort()\n\treversePort := tcp.PickPort()\n\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Warning,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&reverse.Config{\n\t\t\t\tPortalConfig: []*reverse.PortalConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\tTag:    \"portal\",\n\t\t\t\t\t\tDomain: \"test.example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tDomain: []*router.Domain{\n\t\t\t\t\t\t\t{Type: router.Domain_Full, Value: \"test.example.com\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"portal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"external\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"portal\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag: \"external\",\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(externalPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(reversePort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&blackhole.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Warning,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&policy.Config{\n\t\t\t\tLevel: map[uint32]*policy.Policy{\n\t\t\t\t\t0: {\n\t\t\t\t\t\tTimeout: &policy.Policy_Timeout{\n\t\t\t\t\t\t\tUplinkOnly:   &policy.Second{Value: 0},\n\t\t\t\t\t\t\tDownlinkOnly: &policy.Second{Value: 0},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&reverse.Config{\n\t\t\t\tBridgeConfig: []*reverse.BridgeConfig{\n\t\t\t\t\t{\n\t\t\t\t\t\tTag:    \"bridge\",\n\t\t\t\t\t\tDomain: \"test.example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tDomain: []*router.Domain{\n\t\t\t\t\t\t\t{Type: router.Domain_Full, Value: \"test.example.com\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"reverse\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tInboundTag: []string{\"bridge\"},\n\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\tTag: \"freedom\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tTag:           \"freedom\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tTag: \"reverse\",\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(reversePort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\n\tdefer CloseAllServers(servers)\n\n\tfor range 4096 {\n\t\tif err := testTCPConn(externalPort, 1024, time.Second*20)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/shadowsocks_2022_test.go",
    "content": "package scenarios\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing-shadowsocks/shadowaead_2022\"\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks_2022\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestShadowsocks2022Tcp(t *testing.T) {\n\tfor _, method := range shadowaead_2022.List {\n\t\tpassword := make([]byte, 32)\n\t\trand.Read(password)\n\t\tt.Run(method, func(t *testing.T) {\n\t\t\ttestShadowsocks2022Tcp(t, method, base64.StdEncoding.EncodeToString(password))\n\t\t})\n\t}\n}\n\nfunc TestShadowsocks2022UdpAES128(t *testing.T) {\n\tpassword := make([]byte, 32)\n\trand.Read(password)\n\ttestShadowsocks2022Udp(t, shadowaead_2022.List[0], base64.StdEncoding.EncodeToString(password))\n}\n\nfunc TestShadowsocks2022UdpAES256(t *testing.T) {\n\tpassword := make([]byte, 32)\n\trand.Read(password)\n\ttestShadowsocks2022Udp(t, shadowaead_2022.List[1], base64.StdEncoding.EncodeToString(password))\n}\n\nfunc TestShadowsocks2022UdpChacha(t *testing.T) {\n\tpassword := make([]byte, 32)\n\trand.Read(password)\n\ttestShadowsocks2022Udp(t, shadowaead_2022.List[2], base64.StdEncoding.EncodeToString(password))\n}\n\nfunc testShadowsocks2022Tcp(t *testing.T, method string, password string) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{\n\t\t\t\t\tMethod:  method,\n\t\t\t\t\tKey:     password,\n\t\t\t\t\tNetwork: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks_2022.ClientConfig{\n\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\tMethod:  method,\n\t\t\t\t\tKey:     password,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc testShadowsocks2022Udp(t *testing.T, method string, password string) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tudpDest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{\n\t\t\t\t\tMethod:  method,\n\t\t\t\t\tKey:     password,\n\t\t\t\t\tNetwork: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tudpClientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(udpClientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(udpDest.Address),\n\t\t\t\t\tPort:     uint32(udpDest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks_2022.ClientConfig{\n\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\tMethod:  method,\n\t\t\t\t\tKey:     password,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testUDPConn(udpClientPort, 1024, time.Second*5))\n\t}\n\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/shadowsocks_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/shadowsocks\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\taccount := serial.ToTypedMessage(&shadowsocks.Account{\n\t\tPassword:   \"shadowsocks-password\",\n\t\tCipherType: shadowsocks.CipherType_CHACHA20_POLY1305,\n\t})\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{\n\t\t\t\t\tUsers: []*protocol.User{{\n\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\tLevel:   1,\n\t\t\t\t\t}},\n\t\t\t\t\tNetwork: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{\n\t\t\t\t\tServer:    &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestShadowsocksAES256GCMTCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\taccount := serial.ToTypedMessage(&shadowsocks.Account{\n\t\tPassword:   \"shadowsocks-password\",\n\t\tCipherType: shadowsocks.CipherType_AES_256_GCM,\n\t})\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{\n\t\t\t\t\tUsers: []*protocol.User{{\n\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\tLevel:   1,\n\t\t\t\t\t}},\n\t\t\t\t\tNetwork: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestShadowsocksAES128GCMUDP(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\taccount := serial.ToTypedMessage(&shadowsocks.Account{\n\t\tPassword:   \"shadowsocks-password\",\n\t\tCipherType: shadowsocks.CipherType_AES_128_GCM,\n\t})\n\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{\n\t\t\t\t\tUsers: []*protocol.User{{\n\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\tLevel:   1,\n\t\t\t\t\t}},\n\t\t\t\t\tNetwork: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testUDPConn(clientPort, 1024, time.Second*5))\n\t}\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestShadowsocksAES128GCMUDPMux(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\taccount := serial.ToTypedMessage(&shadowsocks.Account{\n\t\tPassword:   \"shadowsocks-password\",\n\t\tCipherType: shadowsocks.CipherType_AES_128_GCM,\n\t})\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{\n\t\t\t\t\tUsers: []*protocol.User{{\n\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\tLevel:   1,\n\t\t\t\t\t}},\n\t\t\t\t\tNetwork: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tMultiplexSettings: &proxyman.MultiplexingConfig{\n\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\tConcurrency: 8,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testUDPConn(clientPort, 1024, time.Second*5))\n\t}\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestShadowsocksNone(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\n\tdefer tcpServer.Close()\n\n\taccount := serial.ToTypedMessage(&shadowsocks.Account{\n\t\tPassword:   \"shadowsocks-password\",\n\t\tCipherType: shadowsocks.CipherType_NONE,\n\t})\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{\n\t\t\t\t\tUsers: []*protocol.User{{\n\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\tLevel:   1,\n\t\t\t\t\t}},\n\t\t\t\t\tNetwork: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: account,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\n\tdefer CloseAllServers(servers)\n\n\tvar errGroup errgroup.Group\n\tfor range 3 {\n\t\terrGroup.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\n\tif err := errGroup.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/socks_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/app/router\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/blackhole\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/http\"\n\t\"github.com/xtls/xray-core/proxy/socks\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\txproxy \"golang.org/x/net/proxy\"\n\tsocks4 \"h12.io/socks\"\n)\n\nfunc TestSocksBridgeTCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\tAuthType: socks.AuthType_PASSWORD,\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t},\n\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tUdpEnabled: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&socks.Account{\n\t\t\t\t\t\t\t\tUsername: \"Test Account\",\n\t\t\t\t\t\t\t\tPassword: \"Test Password\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSocksWithHttpRequest(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\tAuthType: socks.AuthType_PASSWORD,\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t},\n\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tUdpEnabled: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&http.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&http.Account{\n\t\t\t\t\t\t\t\tUsername: \"Test Account\",\n\t\t\t\t\t\t\t\tPassword: \"Test Password\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSocksBridageUDP(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tretry := 1\n\tserverPort := tcp.PickPort()\n\tfor {\n\t\tserverConfig := &core.Config{\n\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\t\tAuthType: socks.AuthType_PASSWORD,\n\t\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tUdpEnabled: true,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort + 1)}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tserver, _ := InitializeServerConfig(serverConfig)\n\t\tif server != nil && WaitConnAvailableWithTest(t, testUDPConn(serverPort+1, 1024, time.Second*2)) {\n\t\t\tdefer CloseServer(server)\n\t\t\tbreak\n\t\t}\n\t\tretry++\n\t\tif retry > 5 {\n\t\t\tt.Fatal(\"All attempts failed to start server\")\n\t\t}\n\t\tserverPort = tcp.PickPort()\n\t}\n\n\tclientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&socks.Account{\n\t\t\t\t\t\t\t\tUsername: \"Test Account\",\n\t\t\t\t\t\t\t\tPassword: \"Test Password\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tserver, err := InitializeServerConfig(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseServer(server)\n\n\tif !WaitConnAvailableWithTest(t, testUDPConn(clientPort, 1024, time.Second*2)) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestSocksBridageUDPWithRouting(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tretry := 1\n\tserverPort := tcp.PickPort()\n\tfor {\n\t\tserverConfig := &core.Config{\n\t\t\tApp: []*serial.TypedMessage{\n\t\t\t\tserial.ToTypedMessage(&router.Config{\n\t\t\t\t\tRule: []*router.RoutingRule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tTargetTag: &router.RoutingRule_Tag{\n\t\t\t\t\t\t\t\tTag: \"out\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tInboundTag: []string{\"socks\", \"dokodemo\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tTag: \"socks\",\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\t\tAuthType:   socks.AuthType_NO_AUTH,\n\t\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tUdpEnabled: true,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTag: \"dokodemo\",\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort + 1)}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&blackhole.Config{}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTag:           \"out\",\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tserver, _ := InitializeServerConfig(serverConfig)\n\t\tif server != nil && WaitConnAvailableWithTest(t, testUDPConn(serverPort+1, 1024, time.Second*2)) {\n\t\t\tdefer CloseServer(server)\n\t\t\tbreak\n\t\t}\n\t\tretry++\n\t\tif retry > 5 {\n\t\t\tt.Fatal(\"All attempts failed to start server\")\n\t\t}\n\t\tserverPort = tcp.PickPort()\n\t}\n\n\tclientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ClientConfig{\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tserver, err := InitializeServerConfig(clientConfig)\n\tcommon.Must(err)\n\tdefer CloseServer(server)\n\n\tif !WaitConnAvailableWithTest(t, testUDPConn(clientPort, 1024, time.Second*2)) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestSocksConformanceMod(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tauthPort := tcp.PickPort()\n\tnoAuthPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(authPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\tAuthType: socks.AuthType_PASSWORD,\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t},\n\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tUdpEnabled: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(noAuthPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&socks.ServerConfig{\n\t\t\t\t\tAuthType: socks.AuthType_NO_AUTH,\n\t\t\t\t\tAccounts: map[string]string{\n\t\t\t\t\t\t\"Test Account\": \"Test Password\",\n\t\t\t\t\t},\n\t\t\t\t\tAddress:    net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tUdpEnabled: false,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t{\n\t\tnoAuthDialer, err := xproxy.SOCKS5(\"tcp\", net.TCPDestination(net.LocalHostIP, noAuthPort).NetAddr(), nil, xproxy.Direct)\n\t\tcommon.Must(err)\n\t\tconn, err := noAuthDialer.Dial(\"tcp\", dest.NetAddr())\n\t\tcommon.Must(err)\n\t\tdefer conn.Close()\n\n\t\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\t{\n\t\tauthDialer, err := xproxy.SOCKS5(\"tcp\", net.TCPDestination(net.LocalHostIP, authPort).NetAddr(), &xproxy.Auth{User: \"Test Account\", Password: \"Test Password\"}, xproxy.Direct)\n\t\tcommon.Must(err)\n\t\tconn, err := authDialer.Dial(\"tcp\", dest.NetAddr())\n\t\tcommon.Must(err)\n\t\tdefer conn.Close()\n\n\t\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\t{\n\t\tdialer := socks4.Dial(\"socks4://\" + net.TCPDestination(net.LocalHostIP, noAuthPort).NetAddr())\n\t\tconn, err := dialer(\"tcp\", dest.NetAddr())\n\t\tcommon.Must(err)\n\t\tdefer conn.Close()\n\n\t\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\t{\n\t\tdialer := socks4.Dial(\"socks4://\" + net.TCPDestination(net.LocalHostIP, noAuthPort).NetAddr())\n\t\tconn, err := dialer(\"tcp\", net.TCPDestination(net.LocalHostIP, tcpServer.Port).NetAddr())\n\t\tcommon.Must(err)\n\t\tdefer conn.Close()\n\n\t\tif err := testTCPConn2(conn, 1024, time.Second*5)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/tls_test.go",
    "content": "package scenarios\n\nimport (\n\t\"crypto/x509\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/grpc\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/internet/websocket\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestSimpleTLSConnection(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestAutoIssuingCertificate(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\t// Not supported on Windows yet.\n\t\treturn\n\t}\n\n\tif runtime.GOARCH == \"arm64\" {\n\t\treturn\n\t}\n\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tcaCert, err := cert.Generate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment|x509.KeyUsageCertSign))\n\tcommon.Must(err)\n\tcertPEM, keyPEM := caCert.ToPEM()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{{\n\t\t\t\t\t\t\t\t\tCertificate: certPEM,\n\t\t\t\t\t\t\t\t\tKey:         keyPEM,\n\t\t\t\t\t\t\t\t\tUsage:       tls.Certificate_AUTHORITY_ISSUE,\n\t\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tServerName: \"example.com\",\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{{\n\t\t\t\t\t\t\t\t\tCertificate: certPEM,\n\t\t\t\t\t\t\t\t\tUsage:       tls.Certificate_AUTHORITY_VERIFY,\n\t\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tfor range 3 {\n\t\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n\nfunc TestTLSOverKCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestTLSOverWebSocket(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"websocket\",\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"websocket\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"websocket\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&websocket.Config{}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestGRPC(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&grpc.Config{ServiceName: \"🍉\"}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&grpc.Config{ServiceName: \"🍉\"}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestGRPCMultiMode(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&grpc.Config{ServiceName: \"🍉\"}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"grpc\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&grpc.Config{ServiceName: \"🍉\", MultiMode: true}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*10240, time.Second*40))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSimpleTLSConnectionPinned(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\tcertificateDer, _ := cert.MustGenerate(nil)\n\tcertificate := tls.ParseCertificate(certificateDer)\n\tcertHash := tls.GenerateCertHash(certificateDer.Certificate)\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{certificate},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{certHash},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\tcertificateDer, _ := cert.MustGenerate(nil)\n\tcertificate := tls.ParseCertificate(certificateDer)\n\tcertHash := tls.GenerateCertHash(certificateDer.Certificate)\n\tcertHash[1] += 1\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{certificate},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{certHash},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err == nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestUTLSConnectionPinned(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\tcertificateDer, _ := cert.MustGenerate(nil)\n\tcertificate := tls.ParseCertificate(certificateDer)\n\tcertHash := tls.GenerateCertHash(certificateDer.Certificate)\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{certificate},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tFingerprint:          \"random\",\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{certHash},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestUTLSConnectionPinnedWrongCert(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\tcertificateDer, _ := cert.MustGenerate(nil)\n\tcertificate := tls.ParseCertificate(certificateDer)\n\tcertHash := tls.GenerateCertHash(certificateDer.Certificate)\n\tcertHash[1] += 1\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{certificate},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tFingerprint:          \"random\",\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{certHash},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*20)(); err == nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/transport_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/headers/http\"\n\ttcptransport \"github.com/xtls/xray-core/transport/internet/tcp\"\n)\n\nfunc TestHTTPConnectionHeader(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&tcptransport.Config{\n\t\t\t\t\t\t\t\t\tHeaderSettings: serial.ToTypedMessage(&http.Config{}),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&tcptransport.Config{\n\t\t\t\t\t\t\t\t\tHeaderSettings: serial.ToTypedMessage(&http.Config{}),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tif err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/vless_test.go",
    "content": "package scenarios\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\t\"github.com/xtls/xray-core/proxy/vless/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vless/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\ttranstcp \"github.com/xtls/xray-core/transport/internet/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestVless(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVlessTls(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&transtcp.Config{}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVlessXtlsVision(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tCertificate: []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId:   userID.String(),\n\t\t\t\t\t\t\t\tFlow: vless.XRV,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId:   userID.String(),\n\t\t\t\t\t\t\t\tFlow: vless.XRV,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&transtcp.Config{}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&tls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&tls.Config{\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVlessXtlsVisionReality(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tprivateKey, _ := base64.RawURLEncoding.DecodeString(\"aGSYystUbf59_9_6LKRxD27rmSW_-2_nyd9YG_Gwbks\")\n\tpublicKey, _ := base64.RawURLEncoding.DecodeString(\"E59WjnvZcQMu7tR7_BgyhycuEdBS-CtKxfImRCdAvFM\")\n\tshortIds := make([][]byte, 1)\n\tshortIds[0] = make([]byte, 8)\n\thex.Decode(shortIds[0], []byte(\"0123456789abcdef\"))\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&reality.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&reality.Config{\n\t\t\t\t\t\t\t\tShow:        true,\n\t\t\t\t\t\t\t\tDest:        \"www.google.com:443\", // use google for now, may fail in some region\n\t\t\t\t\t\t\t\tServerNames: []string{\"www.google.com\"},\n\t\t\t\t\t\t\t\tPrivateKey:  privateKey,\n\t\t\t\t\t\t\t\tShortIds:    shortIds,\n\t\t\t\t\t\t\t\tType:        \"tcp\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId:   userID.String(),\n\t\t\t\t\t\t\t\tFlow: vless.XRV,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId:   userID.String(),\n\t\t\t\t\t\t\t\tFlow: vless.XRV,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&transtcp.Config{}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&reality.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&reality.Config{\n\t\t\t\t\t\t\t\tShow:        true,\n\t\t\t\t\t\t\t\tFingerprint: \"chrome\",\n\t\t\t\t\t\t\t\tServerName:  \"www.google.com\",\n\t\t\t\t\t\t\t\tPublicKey:   publicKey,\n\t\t\t\t\t\t\t\tShortId:     shortIds[0],\n\t\t\t\t\t\t\t\tSpiderX:     \"/\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\n// This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*)\n// Beacuse figerprint support may be broken after utls/reality update\n// Known broken fingerprint: android, 360\nfunc TestVlessRealityFingerprints(t *testing.T) {\n\tTestFingerprint := func(fingerprint string) error {\n\t\ttcpServer := tcp.Server{\n\t\t\tMsgProcessor: xor,\n\t\t}\n\t\tdest, err := tcpServer.Start()\n\t\tcommon.Must(err)\n\t\tdefer tcpServer.Close()\n\n\t\tuserID := protocol.NewID(uuid.New())\n\t\tserverPort := tcp.PickPort()\n\t\tprivateKey, _ := base64.RawURLEncoding.DecodeString(\"aGSYystUbf59_9_6LKRxD27rmSW_-2_nyd9YG_Gwbks\")\n\t\tpublicKey, _ := base64.RawURLEncoding.DecodeString(\"E59WjnvZcQMu7tR7_BgyhycuEdBS-CtKxfImRCdAvFM\")\n\t\tshortIds := make([][]byte, 1)\n\t\tshortIds[0] = make([]byte, 8)\n\t\thex.Decode(shortIds[0], []byte(\"0123456789abcdef\"))\n\t\tserverConfig := &core.Config{\n\t\t\tApp: []*serial.TypedMessage{\n\t\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\t\tErrorLogType: log.LogType_None,\n\t\t\t\t}),\n\t\t\t},\n\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\tSecurityType: serial.GetMessageType(&reality.Config{}),\n\t\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\t\tserial.ToTypedMessage(&reality.Config{\n\t\t\t\t\t\t\t\t\tShow:        false,\n\t\t\t\t\t\t\t\t\tDest:        \"www.google.com:443\", // use google for now, may fail in some region\n\t\t\t\t\t\t\t\t\tServerNames: []string{\"www.google.com\"},\n\t\t\t\t\t\t\t\t\tPrivateKey:  privateKey,\n\t\t\t\t\t\t\t\t\tShortIds:    shortIds,\n\t\t\t\t\t\t\t\t\tType:        \"tcp\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tclientPort := tcp.PickPort()\n\t\tclientConfig := &core.Config{\n\t\t\tApp: []*serial.TypedMessage{\n\t\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\t\tErrorLogType: log.LogType_None,\n\t\t\t\t}),\n\t\t\t},\n\t\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t}),\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t\t{\n\t\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&transtcp.Config{}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSecurityType: serial.GetMessageType(&reality.Config{}),\n\t\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\t\tserial.ToTypedMessage(&reality.Config{\n\t\t\t\t\t\t\t\t\tShow:        false,\n\t\t\t\t\t\t\t\t\tFingerprint: fingerprint,\n\t\t\t\t\t\t\t\t\tServerName:  \"www.google.com\",\n\t\t\t\t\t\t\t\t\tPublicKey:   publicKey,\n\t\t\t\t\t\t\t\t\tShortId:     shortIds[0],\n\t\t\t\t\t\t\t\t\tSpiderX:     \"/\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\t\tcommon.Must(err)\n\t\tdefer CloseAllServers(servers)\n\n\t\terr = testTCPConn(clientPort, 1024*1024, time.Second*15)()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\tfingerPrints := []string{\"chrome\", \"firefox\", \"safari\", \"ios\", \"edge\", \"qq\"}\n\twg := sync.WaitGroup{}\n\twg.Add(len(fingerPrints))\n\tfor _, fp := range fingerPrints {\n\t\tgo func() {\n\t\t\terr := TestFingerprint(fp)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Fingerprint %s test failed: %v\", fp, err)\n\t\t\t} else {\n\t\t\t\tt.Logf(\"Fingerprint %s test passed\", fp)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n}\n"
  },
  {
    "path": "testing/scenarios/vmess_test.go",
    "content": "package scenarios\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/vmess\"\n\t\"github.com/xtls/xray-core/proxy/vmess/inbound\"\n\t\"github.com/xtls/xray-core/proxy/vmess/outbound\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/kcp\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestVMessGCM(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId:               userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to initialize all servers: \", err.Error())\n\t}\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))\n\t}\n\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessGCMReadv(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId:               userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tconst envName = \"XRAY_BUF_READV\"\n\tcommon.Must(os.Setenv(envName, \"enable\"))\n\tdefer os.Unsetenv(envName)\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to initialize all servers: \", err.Error())\n\t}\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessGCMUDP(t *testing.T) {\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId:               userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testUDPConn(clientPort, 1024, time.Second*5))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessChacha20(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId:               userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_CHACHA20_POLY1305,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*20))\n\t}\n\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessNone(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_NONE,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessKCP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024, time.Minute*2))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessKCPLarge(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&kcp.Config{\n\t\t\t\t\t\t\t\t\tReadBuffer: &kcp.ReadBuffer{\n\t\t\t\t\t\t\t\t\t\tSize: 512 * 1024,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tWriteBuffer: &kcp.WriteBuffer{\n\t\t\t\t\t\t\t\t\t\tSize: 512 * 1024,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tUplinkCapacity: &kcp.UplinkCapacity{\n\t\t\t\t\t\t\t\t\t\tValue: 20,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tDownlinkCapacity: &kcp.DownlinkCapacity{\n\t\t\t\t\t\t\t\t\t\tValue: 20,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"mkcp\",\n\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&kcp.Config{\n\t\t\t\t\t\t\t\t\tReadBuffer: &kcp.ReadBuffer{\n\t\t\t\t\t\t\t\t\t\tSize: 512 * 1024,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tWriteBuffer: &kcp.WriteBuffer{\n\t\t\t\t\t\t\t\t\t\tSize: 512 * 1024,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tUplinkCapacity: &kcp.UplinkCapacity{\n\t\t\t\t\t\t\t\t\t\tValue: 20,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tDownlinkCapacity: &kcp.DownlinkCapacity{\n\t\t\t\t\t\t\t\t\t\tValue: 20,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 513*1024, time.Minute*5))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tdefer func() {\n\t\t<-time.After(5 * time.Second)\n\t\tCloseAllServers(servers)\n\t}()\n}\n\nfunc TestVMessGCMMux(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tMultiplexSettings: &proxyman.MultiplexingConfig{\n\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\tConcurrency: 4,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tfor range \"abcd\" {\n\t\tvar errg errgroup.Group\n\t\tfor range 3 {\n\t\t\terrg.Go(testTCPConn(clientPort, 10240, time.Second*20))\n\t\t}\n\t\tif err := errg.Wait(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n}\n\nfunc TestVMessGCMMuxUDP(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tudpServer := udp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tudpDest, err := udpServer.Start()\n\tcommon.Must(err)\n\tdefer udpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientUDPPort := udp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientUDPPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(udpDest.Address),\n\t\t\t\t\tPort:     uint32(udpDest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_UDP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tMultiplexSettings: &proxyman.MultiplexingConfig{\n\t\t\t\t\t\tEnabled:     true,\n\t\t\t\t\t\tConcurrency: 4,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\n\tfor range \"ab\" {\n\t\tvar errg errgroup.Group\n\t\tfor range 3 {\n\t\t\terrg.Go(testTCPConn(clientPort, 1024, time.Second*10))\n\t\t\terrg.Go(testUDPConn(clientUDPPort, 1024, time.Second*10))\n\t\t}\n\t\tif err := errg.Wait(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tdefer func() {\n\t\t<-time.After(5 * time.Second)\n\t\tCloseAllServers(servers)\n\t}()\n}\n\nfunc TestVMessZero(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_ZERO,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 1024*1024, time.Second*30))\n\t}\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessGCMLengthAuth(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tTestsEnabled: \"AuthenticatedLength\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to initialize all servers: \", err.Error())\n\t}\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))\n\t}\n\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestVMessGCMLengthAuthPlusNoTerminationSignal(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tserverPort := tcp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&inbound.Config{\n\t\t\t\t\tUser: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId:           userID.String(),\n\t\t\t\t\t\t\t\tTestsEnabled: \"AuthenticatedLength|NoTerminationSignal\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&outbound.Config{\n\t\t\t\t\tReceiver: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(serverPort),\n\t\t\t\t\t\tUser:    &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vmess.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t\tSecuritySettings: &protocol.SecurityConfig{\n\t\t\t\t\t\t\t\t\tType: protocol.SecurityType_AES128_GCM,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tTestsEnabled: \"AuthenticatedLength|NoTerminationSignal\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to initialize all servers: \", err.Error())\n\t}\n\tdefer CloseAllServers(servers)\n\n\tvar errg errgroup.Group\n\tfor range 3 {\n\t\terrg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))\n\t}\n\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "testing/scenarios/wireguard_test.go",
    "content": "package scenarios\n\nimport (\n\t\"testing\"\n\t//\"time\"\n\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\t\"github.com/xtls/xray-core/common\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/infra/conf\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\t\"github.com/xtls/xray-core/proxy/wireguard\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t//\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestWireguard(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: xor,\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tserverPrivate, _ := conf.ParseWireGuardKey(\"EGs4lTSJPmgELx6YiJAmPR2meWi6bY+e9rTdCipSj10=\")\n\tserverPublic, _ := conf.ParseWireGuardKey(\"osAMIyil18HeZXGGBDC9KpZoM+L2iGyXWVSYivuM9B0=\")\n\tclientPrivate, _ := conf.ParseWireGuardKey(\"CPQSpgxgdQRZa5SUbT3HLv+mmDVHLW5YR/rQlzum/2I=\")\n\tclientPublic, _ := conf.ParseWireGuardKey(\"MmLJ5iHFVVBp7VsB0hxfpQ0wEzAbT2KQnpQpj0+RtBw=\")\n\n\tserverPort := udp.PickPort()\n\tserverConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{\n\t\t\t\t\tIsClient:    false,\n\t\t\t\t\tNoKernelTun: false,\n\t\t\t\t\tEndpoint:    []string{\"10.0.0.1\"},\n\t\t\t\t\tMtu:         1420,\n\t\t\t\t\tSecretKey:   serverPrivate,\n\t\t\t\t\tPeers: []*wireguard.PeerConfig{{\n\t\t\t\t\t\tPublicKey:  serverPublic,\n\t\t\t\t\t\tAllowedIps: []string{\"0.0.0.0/0\", \"::0/0\"},\n\t\t\t\t\t}},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&freedom.Config{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tclientPort := tcp.PickPort()\n\tclientConfig := &core.Config{\n\t\tApp: []*serial.TypedMessage{\n\t\t\tserial.ToTypedMessage(&log.Config{\n\t\t\t\tErrorLogLevel: clog.Severity_Debug,\n\t\t\t\tErrorLogType:  log.LogType_Console,\n\t\t\t}),\n\t\t},\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   net.NewIPOrDomain(net.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  net.NewIPOrDomain(dest.Address),\n\t\t\t\t\tPort:     uint32(dest.Port),\n\t\t\t\t\tNetworks: []net.Network{net.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&wireguard.DeviceConfig{\n\t\t\t\t\tIsClient:    true,\n\t\t\t\t\tNoKernelTun: false,\n\t\t\t\t\tEndpoint:    []string{\"10.0.0.2\"},\n\t\t\t\t\tMtu:         1420,\n\t\t\t\t\tSecretKey:   clientPrivate,\n\t\t\t\t\tPeers: []*wireguard.PeerConfig{{\n\t\t\t\t\t\tEndpoint:   \"127.0.0.1:\" + serverPort.String(),\n\t\t\t\t\t\tPublicKey:  clientPublic,\n\t\t\t\t\t\tAllowedIps: []string{\"0.0.0.0/0\", \"::0/0\"},\n\t\t\t\t\t}},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\tservers, err := InitializeServerConfigs(serverConfig, clientConfig)\n\tcommon.Must(err)\n\tdefer CloseAllServers(servers)\n\n\t// FIXME: for some reason wg server does not receive\n\n\t// var errg errgroup.Group\n\t// for i := 0; i < 1; i++ {\n\t// \terrg.Go(testTCPConn(clientPort, 1024, time.Second*2))\n\t// }\n\t// if err := errg.Wait(); err != nil {\n\t// \tt.Error(err)\n\t// }\n}\n"
  },
  {
    "path": "testing/servers/http/http.go",
    "content": "package tcp\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\ntype Server struct {\n\tPort        net.Port\n\tPathHandler map[string]http.HandlerFunc\n\tserver      *http.Server\n}\n\nfunc (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) {\n\tif req.URL.Path == \"/\" {\n\t\tresp.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\tresp.WriteHeader(http.StatusOK)\n\t\tresp.Write([]byte(\"Home\"))\n\t\treturn\n\t}\n\n\thandler, found := s.PathHandler[req.URL.Path]\n\tif found {\n\t\thandler(resp, req)\n\t}\n}\n\nfunc (s *Server) Start() (net.Destination, error) {\n\ts.server = &http.Server{\n\t\tAddr:    \"127.0.0.1:\" + s.Port.String(),\n\t\tHandler: s,\n\t}\n\tgo s.server.ListenAndServe()\n\treturn net.TCPDestination(net.LocalHostIP, s.Port), nil\n}\n\nfunc (s *Server) Close() error {\n\treturn s.server.Close()\n}\n"
  },
  {
    "path": "testing/servers/tcp/port.go",
    "content": "package tcp\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// PickPort returns an unused TCP port in the system. The port returned is highly likely to be unused, but not guaranteed.\nfunc PickPort() net.Port {\n\tlistener, err := net.Listen(\"tcp4\", \"127.0.0.1:0\")\n\tcommon.Must(err)\n\tdefer listener.Close()\n\n\taddr := listener.Addr().(*net.TCPAddr)\n\treturn net.Port(addr.Port)\n}\n"
  },
  {
    "path": "testing/servers/tcp/tcp.go",
    "content": "package tcp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/task\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\ntype Server struct {\n\tPort         net.Port\n\tMsgProcessor func(msg []byte) []byte\n\tShouldClose  bool\n\tSendFirst    []byte\n\tListen       net.Address\n\tlistener     net.Listener\n}\n\nfunc (server *Server) Start() (net.Destination, error) {\n\treturn server.StartContext(context.Background(), nil)\n}\n\nfunc (server *Server) StartContext(ctx context.Context, sockopt *internet.SocketConfig) (net.Destination, error) {\n\tlistenerAddr := server.Listen\n\tif listenerAddr == nil {\n\t\tlistenerAddr = net.LocalHostIP\n\t}\n\tlistener, err := internet.ListenSystem(ctx, &net.TCPAddr{\n\t\tIP:   listenerAddr.IP(),\n\t\tPort: int(server.Port),\n\t}, sockopt)\n\tif err != nil {\n\t\treturn net.Destination{}, err\n\t}\n\n\tlocalAddr := listener.Addr().(*net.TCPAddr)\n\tserver.Port = net.Port(localAddr.Port)\n\tserver.listener = listener\n\tgo server.acceptConnections(listener.(*net.TCPListener))\n\n\treturn net.TCPDestination(net.IPAddress(localAddr.IP), net.Port(localAddr.Port)), nil\n}\n\nfunc (server *Server) acceptConnections(listener *net.TCPListener) {\n\tfor {\n\t\tconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Failed accept TCP connection: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\n\t\tgo server.handleConnection(conn)\n\t}\n}\n\nfunc (server *Server) handleConnection(conn net.Conn) {\n\tif len(server.SendFirst) > 0 {\n\t\tconn.Write(server.SendFirst)\n\t}\n\n\tpReader, pWriter := pipe.New(pipe.WithoutSizeLimit())\n\terr := task.Run(context.Background(), func() error {\n\t\tdefer pWriter.Close()\n\n\t\tfor {\n\t\t\tb := buf.New()\n\t\t\tif _, err := b.ReadFrom(conn); err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcopy(b.Bytes(), server.MsgProcessor(b.Bytes()))\n\t\t\tif err := pWriter.WriteMultiBuffer(buf.MultiBuffer{b}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}, func() error {\n\t\tdefer pReader.Interrupt()\n\n\t\tw := buf.NewWriter(conn)\n\t\tfor {\n\t\t\tmb, err := pReader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := w.WriteMultiBuffer(mb); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t})\n\tif err != nil {\n\t\tfmt.Println(\"failed to transfer data: \", err.Error())\n\t}\n\n\tconn.Close()\n}\n\nfunc (server *Server) Close() error {\n\treturn server.listener.Close()\n}\n"
  },
  {
    "path": "testing/servers/udp/port.go",
    "content": "package udp\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\n// PickPort returns an unused UDP port in the system. The port returned is highly likely to be unused, but not guaranteed.\nfunc PickPort() net.Port {\n\tconn, err := net.ListenUDP(\"udp4\", &net.UDPAddr{\n\t\tIP:   net.LocalHostIP.IP(),\n\t\tPort: 0,\n\t})\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\taddr := conn.LocalAddr().(*net.UDPAddr)\n\treturn net.Port(addr.Port)\n}\n"
  },
  {
    "path": "testing/servers/udp/udp.go",
    "content": "package udp\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\ntype Server struct {\n\tPort         net.Port\n\tMsgProcessor func(msg []byte) []byte\n\taccepting    bool\n\tconn         *net.UDPConn\n}\n\nfunc (server *Server) Start() (net.Destination, error) {\n\tconn, err := net.ListenUDP(\"udp\", &net.UDPAddr{\n\t\tIP:   []byte{127, 0, 0, 1},\n\t\tPort: int(server.Port),\n\t\tZone: \"\",\n\t})\n\tif err != nil {\n\t\treturn net.Destination{}, err\n\t}\n\tserver.Port = net.Port(conn.LocalAddr().(*net.UDPAddr).Port)\n\tfmt.Println(\"UDP server started on port \", server.Port)\n\n\tserver.conn = conn\n\tgo server.handleConnection(conn)\n\tlocalAddr := conn.LocalAddr().(*net.UDPAddr)\n\treturn net.UDPDestination(net.IPAddress(localAddr.IP), net.Port(localAddr.Port)), nil\n}\n\nfunc (server *Server) handleConnection(conn *net.UDPConn) {\n\tserver.accepting = true\n\tfor server.accepting {\n\t\tbuffer := make([]byte, 2*1024)\n\t\tnBytes, addr, err := conn.ReadFromUDP(buffer)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Failed to read from UDP: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tresponse := server.MsgProcessor(buffer[:nBytes])\n\t\tif _, err := conn.WriteToUDP(response, addr); err != nil {\n\t\t\tfmt.Println(\"Failed to write to UDP: \", err.Error())\n\t\t}\n\t}\n}\n\nfunc (server *Server) Close() error {\n\tserver.accepting = false\n\treturn server.conn.Close()\n}\n"
  },
  {
    "path": "transport/internet/browser_dialer/dialer.go",
    "content": "package browser_dialer\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t_ \"embed\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/platform\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n)\n\n//go:embed dialer.html\nvar webpage []byte\n\ntype task struct {\n\tMethod string `json:\"method\"`\n\tURL    string `json:\"url\"`\n\tExtra  any    `json:\"extra,omitempty\"`\n\tStreamResponse bool `json:\"streamResponse\"`\n}\n\nvar conns chan *websocket.Conn\n\nvar upgrader = &websocket.Upgrader{\n\tReadBufferSize:   0,\n\tWriteBufferSize:  0,\n\tHandshakeTimeout: time.Second * 4,\n\tCheckOrigin: func(r *http.Request) bool {\n\t\treturn true\n\t},\n}\n\nfunc init() {\n\taddr := platform.NewEnvFlag(platform.BrowserDialerAddress).GetValue(func() string { return \"\" })\n\tif addr != \"\" {\n\t\ttoken := uuid.New()\n\t\tcsrfToken := token.String()\n\t\twebpage = bytes.ReplaceAll(webpage, []byte(\"csrfToken\"), []byte(csrfToken))\n\t\tconns = make(chan *websocket.Conn, 256)\n\t\tgo http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif r.URL.Path == \"/websocket\" {\n\t\t\t\tif r.URL.Query().Get(\"token\") == csrfToken {\n\t\t\t\t\tif conn, err := upgrader.Upgrade(w, r, nil); err == nil {\n\t\t\t\t\t\tconns <- conn\n\t\t\t\t\t} else {\n\t\t\t\t\t\terrors.LogError(context.Background(), \"Browser dialer http upgrade unexpected error\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\");\n\t\t\t\tw.Write(webpage)\n\t\t\t}\n\t\t}))\n\t}\n}\n\nfunc HasBrowserDialer() bool {\n\treturn conns != nil\n}\n\ntype webSocketExtra struct {\n\tProtocol string `json:\"protocol,omitempty\"`\n}\n\nfunc DialWS(uri string, ed []byte) (*websocket.Conn, error) {\n\ttask := task{\n\t\tMethod: \"WS\",\n\t\tURL:    uri,\n\t\tStreamResponse: true,\n\t}\n\n\tif ed != nil {\n\t\ttask.Extra = webSocketExtra{\n\t\t\tProtocol: base64.RawURLEncoding.EncodeToString(ed),\n\t\t}\n\t}\n\n\treturn dialTask(task)\n}\n\ntype httpExtra struct {\n\tReferrer string            `json:\"referrer,omitempty\"`\n\tHeaders  map[string]string `json:\"headers,omitempty\"`\n\tCookies  map[string]string `json:\"cookies,omitempty\"`\n}\n\nfunc httpExtraFromHeadersAndCookies(headers http.Header, cookies []*http.Cookie) *httpExtra {\n\tif len(headers) == 0 {\n\t\treturn nil\n\t}\n\n\textra := httpExtra{}\n\tif referrer := headers.Get(\"Referer\"); referrer != \"\" {\n\t\textra.Referrer = referrer\n\t\theaders.Del(\"Referer\")\n\t}\n\n\tif len(headers) > 0 {\n\t\textra.Headers = make(map[string]string)\n\t\tfor header := range headers {\n\t\t\textra.Headers[header] = headers.Get(header)\n\t\t}\n\t}\n\n\tif len(cookies) > 0 {\n\t\textra.Cookies = make(map[string]string)\n\t\tfor _, cookie := range cookies {\n\t\t\textra.Cookies[cookie.Name] = cookie.Value\n\t\t}\n\t}\n\n\treturn &extra\n}\n\nfunc DialGet(uri string, headers http.Header, cookies []*http.Cookie) (*websocket.Conn, error) {\n\ttask := task{\n\t\tMethod: \"GET\",\n\t\tURL:    uri,\n\t\tExtra:  httpExtraFromHeadersAndCookies(headers, cookies),\n\t\tStreamResponse: true,\n\t}\n\n\treturn dialTask(task)\n}\n\nfunc DialPacket(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {\n\treturn dialWithBody(method, uri, headers, cookies, payload)\n}\n\nfunc dialWithBody(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {\n\ttask := task{\n\t\tMethod: method,\n\t\tURL:    uri,\n\t\tExtra:  httpExtraFromHeadersAndCookies(headers, cookies),\n\t\tStreamResponse: false,\n\t}\n\n\tconn, err := dialTask(task)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = conn.WriteMessage(websocket.BinaryMessage, payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = CheckOK(conn)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn.Close()\n\treturn nil\n}\n\nfunc dialTask(task task) (*websocket.Conn, error) {\n\tdata, err := json.Marshal(task)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar conn *websocket.Conn\n\tfor {\n\t\tconn = <-conns\n\t\tif conn.WriteMessage(websocket.TextMessage, data) != nil {\n\t\t\tconn.Close()\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\terr = CheckOK(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn conn, nil\n}\n\nfunc CheckOK(conn *websocket.Conn) error {\n\tif _, p, err := conn.ReadMessage(); err != nil {\n\t\tconn.Close()\n\t\treturn err\n\t} else if s := string(p); s != \"ok\" {\n\t\tconn.Close()\n\t\treturn errors.New(s)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/browser_dialer/dialer.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>Browser Dialer</title>\n\t<link rel=\"icon\" href=\"data:\">\n</head>\n<body>\n\t<script>\n\t\t\"use strict\";\n\t\t// Enable a much more aggressive JIT for performance gains\n\n\t\t// Copyright (c) 2021 XRAY. Mozilla Public License 2.0.\n\t\tlet url = \"ws://\" + window.location.host + \"/websocket?token=csrfToken\";\n\t\tlet clientIdleCount = 0;\n\t\tlet upstreamGetCount = 0;\n\t\tlet upstreamWsCount = 0;\n\t\tlet upstreamPostCount = 0;\n\n\t\tfunction prepareRequestInit(extra) {\n\t\t\tconst requestInit = {};\n\t\t\tif (extra.referrer) {\n\t\t\t\t// note: we have to strip the protocol and host part.\n\t\t\t\t// Browsers disallow that, and will reset the value to current page if attempted.\n\t\t\t\tconst referrer = URL.parse(extra.referrer);\n\t\t\t\trequestInit.referrer = referrer.pathname + referrer.search + referrer.hash;\n\t\t\t\trequestInit.referrerPolicy = \"unsafe-url\";\n\t\t\t}\n\n\t\t\tif (extra.headers) {\n\t\t\t\trequestInit.headers = extra.headers;\n\t\t\t}\n\n\t\t\tif (extra.cookies) {\n\t\t\t\trequestInit.credentials = 'include';\n\t\t\t}\n\n\t\t\treturn requestInit;\n\t\t}\n\n\t\tfunction setCookiesFromTask(task) {\n\t\t\tif (!task.extra.cookies) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst url = new URL(task.url);\n\n\t\t\tfor (const [name, value] of Object.entries(task.extra.cookies)) {\n\t\t\t\tdocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=' + url.pathname;\n\t\t\t}\n\t\t}\n\n\t\tfunction clearCookiesFromTask(task) {\n\t\t\tif (!task.extra.cookies) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst url = new URL(task.url);\n\n\t\t\tfor (const [name, value] of Object.entries(task.extra.cookies)) {\n\t\t\t\tdocument.cookie = encodeURIComponent(name) + '=; path=' + url.pathname + '; Max-Age=0';\n\t\t\t}\n\t\t}\n\n\t\tlet check = function () {\n\t\t\tif (clientIdleCount > 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tclientIdleCount += 1;\n\t\t\tconsole.log(\"Prepare\", url);\n\t\t\tlet ws = new WebSocket(url);\n\t\t\t// arraybuffer is significantly faster in chrome than default\n\t\t\t// blob, tested with chrome 123\n\t\t\tws.binaryType = \"arraybuffer\";\n\t\t\t// note: this event listener is later overwritten after the\n\t\t\t// handshake has completed. do not attempt to modernize it without\n\t\t\t// double-checking that this continues to work\n\t\t\tws.onmessage = function (event) {\n\t\t\t\tclientIdleCount -= 1;\n\t\t\t\tlet task = JSON.parse(event.data);\n\t\t\t\tif (task.method == \"WS\") {\n\t\t\t\t\tupstreamWsCount += 1;\n\t\t\t\t\tconsole.log(\"Dial WS\", task.url, task.extra.protocol);\n\t\t\t\t\tconst wss = new WebSocket(task.url, task.extra.protocol);\n\t\t\t\t\twss.binaryType = \"arraybuffer\";\n\t\t\t\t\tlet opened = false;\n\t\t\t\t\tws.onmessage = function (event) {\n\t\t\t\t\t\twss.send(event.data)\n\t\t\t\t\t};\n\t\t\t\t\twss.onopen = function (event) {\n\t\t\t\t\t\topened = true;\n\t\t\t\t\t\tws.send(\"ok\")\n\t\t\t\t\t};\n\t\t\t\t\twss.onmessage = function (event) {\n\t\t\t\t\t\tws.send(event.data)\n\t\t\t\t\t};\n\t\t\t\t\twss.onclose = function (event) {\n\t\t\t\t\t\tupstreamWsCount -= 1;\n\t\t\t\t\t\tconsole.log(\"Dial WS DONE, remaining: \", upstreamWsCount);\n\t\t\t\t\t\tws.close()\n\t\t\t\t\t};\n\t\t\t\t\twss.onerror = function (event) {\n\t\t\t\t\t\t!opened && ws.send(\"fail\")\n\t\t\t\t\t\twss.close()\n\t\t\t\t\t};\n\t\t\t\t\tws.onclose = function (event) {\n\t\t\t\t\t\twss.close()\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\telse if (task.method == \"GET\" && task.streamResponse) {\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\tconst requestInit = prepareRequestInit(task.extra);\n\n\t\t\t\t\t\tconsole.log(\"Dial GET\", task.url);\n\t\t\t\t\t\tws.send(\"ok\");\n\t\t\t\t\t\tconst controller = new AbortController();\n\n\t\t\t\t\t\t/*\n\t\t\t\t\t\tAborting a streaming response in JavaScript\n\t\t\t\t\t\trequires two levers to be pulled:\n\n\t\t\t\t\t\tFirst, the streaming read itself has to be cancelled using\n\t\t\t\t\t\treader.cancel(), only then controller.abort() will actually work.\n\n\t\t\t\t\t\tIf controller.abort() alone is called while a\n\t\t\t\t\t\treader.read() is ongoing, it will block until the server closes the\n\t\t\t\t\t\tresponse, the page is refreshed or the network connection is lost.\n\t\t\t\t\t\t*/\n\n\t\t\t\t\t\tlet reader = null;\n\t\t\t\t\t\tws.onclose = (event) => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\treader && reader.cancel();\n\t\t\t\t\t\t\t} catch(e) {}\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tcontroller.abort();\n\t\t\t\t\t\t\t} catch(e) {}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tupstreamGetCount += 1;\n\n\t\t\t\t\t\t\trequestInit.signal = controller.signal;\n\t\t\t\t\t\t\tsetCookiesFromTask(task);\n\t\t\t\t\t\t\tconst response = await fetch(task.url, requestInit);\n\t\t\t\t\t\t\tclearCookiesFromTask(task);\n\n\t\t\t\t\t\t\tconst body = await response.body;\n\t\t\t\t\t\t\treader = body.getReader();\n\n\t\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\t\t\t\tif (value) ws.send(value);  // don't send back \"undefined\" string when received nothing\n\t\t\t\t\t\t\t\tif (done) break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tupstreamGetCount -= 1;\n\t\t\t\t\t\t\tconsole.log(\"Dial GET DONE, remaining: \", upstreamGetCount);\n\t\t\t\t\t\t\tws.close();\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t}\n\t\t\t\telse if (!task.streamResponse) {\n\t\t\t\t\tupstreamPostCount += 1;\n\n\t\t\t\t\tconst requestInit = prepareRequestInit(task.extra);\n\t\t\t\t\trequestInit.method = task.method;\n\n\t\t\t\t\tconsole.log(\"Dial\", task.method, task.url);\n\t\t\t\t\tws.send(\"ok\");\n\t\t\t\t\tws.onmessage = async (event) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tif (event.data.byteLength > 0) {\n\t\t\t\t\t\t\t\trequestInit.body = event.data;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsetCookiesFromTask(task);\n\t\t\t\t\t\t\tconst response = await fetch(task.url, requestInit);\n\t\t\t\t\t\t\tclearCookiesFromTask(task);\n\t\t\t\t\t\t\tif (response.ok) {\n\t\t\t\t\t\t\t\tws.send(\"ok\");\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.error(\"bad status code\");\n\t\t\t\t\t\t\t\tws.send(\"fail\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tupstreamPostCount -= 1;\n\t\t\t\t\t\t\tconsole.log(\"Dial\", task.method, \"packet DONE, remaining: \", upstreamPostCount);\n\t\t\t\t\t\t\tws.close();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tconsole.error(`Incorrect task method=${task.method} streamResponse=${task.streamResponse}.`);\n\t\t\t\t\tws.close();\n\t\t\t\t}\n\n\t\t\t\tcheck();\n\t\t\t};\n\t\t\tws.onerror = function (event) {\n\t\t\t\tws.close();\n\t\t\t};\n\t\t};\n\t\tlet checkTask = setInterval(check, 1000);\n\t</script>\n</body>\n</html>\n"
  },
  {
    "path": "transport/internet/config.go",
    "content": "package internet\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\ntype ConfigCreator func() interface{}\n\nvar (\n\tglobalTransportConfigCreatorCache = make(map[string]ConfigCreator)\n)\n\nvar strategy = [][]byte{\n\t//              name        strategy,   prefer, fallback\n\t{0, 0, 0}, //   AsIs        none,       /,      /\n\t{1, 0, 0}, //   UseIP       use,        both,   none\n\t{1, 4, 0}, //   UseIPv4     use,        4,      none\n\t{1, 6, 0}, //   UseIPv6     use,        6,      none\n\t{1, 4, 6}, //   UseIPv4v6   use,        4,      6\n\t{1, 6, 4}, //   UseIPv6v4   use,        6,      4\n\t{2, 0, 0}, //   ForceIP     force,      both,   none\n\t{2, 4, 0}, //   ForceIPv4   force,      4,      none\n\t{2, 6, 0}, //   ForceIPv6   force,      6,      none\n\t{2, 4, 6}, //   ForceIPv4v6 force,      4,      6\n\t{2, 6, 4}, //   ForceIPv6v4 force,      6,      4\n}\n\nconst unknownProtocol = \"unknown\"\n\nfunc RegisterProtocolConfigCreator(name string, creator ConfigCreator) error {\n\tif _, found := globalTransportConfigCreatorCache[name]; found {\n\t\treturn errors.New(\"protocol \", name, \" is already registered\").AtError()\n\t}\n\tglobalTransportConfigCreatorCache[name] = creator\n\treturn nil\n}\n\n// Note: Each new transport needs to add init() func in transport/internet/xxx/config.go\n// Otherwise, it will cause #3244\nfunc CreateTransportConfig(name string) (interface{}, error) {\n\tcreator, ok := globalTransportConfigCreatorCache[name]\n\tif !ok {\n\t\treturn nil, errors.New(\"unknown transport protocol: \", name)\n\t}\n\treturn creator(), nil\n}\n\nfunc (c *TransportConfig) GetTypedSettings() (interface{}, error) {\n\treturn c.Settings.GetInstance()\n}\n\nfunc (c *TransportConfig) GetUnifiedProtocolName() string {\n\treturn c.ProtocolName\n}\n\nfunc (c *StreamConfig) GetEffectiveProtocol() string {\n\tif c == nil || len(c.ProtocolName) == 0 {\n\t\treturn \"tcp\"\n\t}\n\n\treturn c.ProtocolName\n}\n\nfunc (c *StreamConfig) GetEffectiveTransportSettings() (interface{}, error) {\n\tprotocol := c.GetEffectiveProtocol()\n\treturn c.GetTransportSettingsFor(protocol)\n}\n\nfunc (c *StreamConfig) GetTransportSettingsFor(protocol string) (interface{}, error) {\n\tif c != nil {\n\t\tfor _, settings := range c.TransportSettings {\n\t\t\tif settings.GetUnifiedProtocolName() == protocol {\n\t\t\t\treturn settings.GetTypedSettings()\n\t\t\t}\n\t\t}\n\t}\n\n\treturn CreateTransportConfig(protocol)\n}\n\nfunc (c *StreamConfig) GetEffectiveSecuritySettings() (interface{}, error) {\n\tfor _, settings := range c.SecuritySettings {\n\t\tif settings.Type == c.SecurityType {\n\t\t\treturn settings.GetInstance()\n\t\t}\n\t}\n\treturn serial.GetInstance(c.SecurityType)\n}\n\nfunc (c *StreamConfig) HasSecuritySettings() bool {\n\treturn len(c.SecuritySettings) > 0\n}\n\nfunc (c *ProxyConfig) HasTag() bool {\n\treturn c != nil && len(c.Tag) > 0\n}\n\nfunc (m SocketConfig_TProxyMode) IsEnabled() bool {\n\treturn m != SocketConfig_Off\n}\n\nfunc (s DomainStrategy) HasStrategy() bool {\n\treturn strategy[s][0] != 0\n}\n\nfunc (s DomainStrategy) ForceIP() bool {\n\treturn strategy[s][0] == 2\n}\n\nfunc (s DomainStrategy) PreferIP4() bool {\n\treturn strategy[s][1] == 4 || strategy[s][1] == 0\n}\n\nfunc (s DomainStrategy) PreferIP6() bool {\n\treturn strategy[s][1] == 6 || strategy[s][1] == 0\n}\n\nfunc (s DomainStrategy) HasFallback() bool {\n\treturn strategy[s][2] != 0\n}\n\nfunc (s DomainStrategy) FallbackIP4() bool {\n\treturn strategy[s][2] == 4\n}\n\nfunc (s DomainStrategy) FallbackIP6() bool {\n\treturn strategy[s][2] == 6\n}\n\nfunc (s DomainStrategy) GetDynamicStrategy(addrFamily net.AddressFamily) DomainStrategy {\n\tif addrFamily.IsDomain() {\n\t\treturn s\n\t}\n\tswitch s {\n\tcase DomainStrategy_USE_IP:\n\t\tif addrFamily.IsIPv4() {\n\t\t\treturn DomainStrategy_USE_IP46\n\t\t} else if addrFamily.IsIPv6() {\n\t\t\treturn DomainStrategy_USE_IP64\n\t\t}\n\tcase DomainStrategy_FORCE_IP:\n\t\tif addrFamily.IsIPv4() {\n\t\t\treturn DomainStrategy_FORCE_IP46\n\t\t} else if addrFamily.IsIPv6() {\n\t\t\treturn DomainStrategy_FORCE_IP64\n\t\t}\n\tdefault:\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "transport/internet/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/config.proto\n\npackage internet\n\nimport (\n\tnet \"github.com/xtls/xray-core/common/net\"\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DomainStrategy int32\n\nconst (\n\tDomainStrategy_AS_IS      DomainStrategy = 0\n\tDomainStrategy_USE_IP     DomainStrategy = 1\n\tDomainStrategy_USE_IP4    DomainStrategy = 2\n\tDomainStrategy_USE_IP6    DomainStrategy = 3\n\tDomainStrategy_USE_IP46   DomainStrategy = 4\n\tDomainStrategy_USE_IP64   DomainStrategy = 5\n\tDomainStrategy_FORCE_IP   DomainStrategy = 6\n\tDomainStrategy_FORCE_IP4  DomainStrategy = 7\n\tDomainStrategy_FORCE_IP6  DomainStrategy = 8\n\tDomainStrategy_FORCE_IP46 DomainStrategy = 9\n\tDomainStrategy_FORCE_IP64 DomainStrategy = 10\n)\n\n// Enum value maps for DomainStrategy.\nvar (\n\tDomainStrategy_name = map[int32]string{\n\t\t0:  \"AS_IS\",\n\t\t1:  \"USE_IP\",\n\t\t2:  \"USE_IP4\",\n\t\t3:  \"USE_IP6\",\n\t\t4:  \"USE_IP46\",\n\t\t5:  \"USE_IP64\",\n\t\t6:  \"FORCE_IP\",\n\t\t7:  \"FORCE_IP4\",\n\t\t8:  \"FORCE_IP6\",\n\t\t9:  \"FORCE_IP46\",\n\t\t10: \"FORCE_IP64\",\n\t}\n\tDomainStrategy_value = map[string]int32{\n\t\t\"AS_IS\":      0,\n\t\t\"USE_IP\":     1,\n\t\t\"USE_IP4\":    2,\n\t\t\"USE_IP6\":    3,\n\t\t\"USE_IP46\":   4,\n\t\t\"USE_IP64\":   5,\n\t\t\"FORCE_IP\":   6,\n\t\t\"FORCE_IP4\":  7,\n\t\t\"FORCE_IP6\":  8,\n\t\t\"FORCE_IP46\": 9,\n\t\t\"FORCE_IP64\": 10,\n\t}\n)\n\nfunc (x DomainStrategy) Enum() *DomainStrategy {\n\tp := new(DomainStrategy)\n\t*p = x\n\treturn p\n}\n\nfunc (x DomainStrategy) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DomainStrategy) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_transport_internet_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DomainStrategy) Type() protoreflect.EnumType {\n\treturn &file_transport_internet_config_proto_enumTypes[0]\n}\n\nfunc (x DomainStrategy) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DomainStrategy.Descriptor instead.\nfunc (DomainStrategy) EnumDescriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype AddressPortStrategy int32\n\nconst (\n\tAddressPortStrategy_None              AddressPortStrategy = 0\n\tAddressPortStrategy_SrvPortOnly       AddressPortStrategy = 1\n\tAddressPortStrategy_SrvAddressOnly    AddressPortStrategy = 2\n\tAddressPortStrategy_SrvPortAndAddress AddressPortStrategy = 3\n\tAddressPortStrategy_TxtPortOnly       AddressPortStrategy = 4\n\tAddressPortStrategy_TxtAddressOnly    AddressPortStrategy = 5\n\tAddressPortStrategy_TxtPortAndAddress AddressPortStrategy = 6\n)\n\n// Enum value maps for AddressPortStrategy.\nvar (\n\tAddressPortStrategy_name = map[int32]string{\n\t\t0: \"None\",\n\t\t1: \"SrvPortOnly\",\n\t\t2: \"SrvAddressOnly\",\n\t\t3: \"SrvPortAndAddress\",\n\t\t4: \"TxtPortOnly\",\n\t\t5: \"TxtAddressOnly\",\n\t\t6: \"TxtPortAndAddress\",\n\t}\n\tAddressPortStrategy_value = map[string]int32{\n\t\t\"None\":              0,\n\t\t\"SrvPortOnly\":       1,\n\t\t\"SrvAddressOnly\":    2,\n\t\t\"SrvPortAndAddress\": 3,\n\t\t\"TxtPortOnly\":       4,\n\t\t\"TxtAddressOnly\":    5,\n\t\t\"TxtPortAndAddress\": 6,\n\t}\n)\n\nfunc (x AddressPortStrategy) Enum() *AddressPortStrategy {\n\tp := new(AddressPortStrategy)\n\t*p = x\n\treturn p\n}\n\nfunc (x AddressPortStrategy) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (AddressPortStrategy) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_transport_internet_config_proto_enumTypes[1].Descriptor()\n}\n\nfunc (AddressPortStrategy) Type() protoreflect.EnumType {\n\treturn &file_transport_internet_config_proto_enumTypes[1]\n}\n\nfunc (x AddressPortStrategy) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use AddressPortStrategy.Descriptor instead.\nfunc (AddressPortStrategy) EnumDescriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{1}\n}\n\ntype SocketConfig_TProxyMode int32\n\nconst (\n\t// TProxy is off.\n\tSocketConfig_Off SocketConfig_TProxyMode = 0\n\t// TProxy mode.\n\tSocketConfig_TProxy SocketConfig_TProxyMode = 1\n\t// Redirect mode.\n\tSocketConfig_Redirect SocketConfig_TProxyMode = 2\n)\n\n// Enum value maps for SocketConfig_TProxyMode.\nvar (\n\tSocketConfig_TProxyMode_name = map[int32]string{\n\t\t0: \"Off\",\n\t\t1: \"TProxy\",\n\t\t2: \"Redirect\",\n\t}\n\tSocketConfig_TProxyMode_value = map[string]int32{\n\t\t\"Off\":      0,\n\t\t\"TProxy\":   1,\n\t\t\"Redirect\": 2,\n\t}\n)\n\nfunc (x SocketConfig_TProxyMode) Enum() *SocketConfig_TProxyMode {\n\tp := new(SocketConfig_TProxyMode)\n\t*p = x\n\treturn p\n}\n\nfunc (x SocketConfig_TProxyMode) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (SocketConfig_TProxyMode) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_transport_internet_config_proto_enumTypes[2].Descriptor()\n}\n\nfunc (SocketConfig_TProxyMode) Type() protoreflect.EnumType {\n\treturn &file_transport_internet_config_proto_enumTypes[2]\n}\n\nfunc (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.\nfunc (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0}\n}\n\ntype TransportConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Transport protocol name.\n\tProtocolName string `protobuf:\"bytes,3,opt,name=protocol_name,json=protocolName,proto3\" json:\"protocol_name,omitempty\"`\n\t// Specific transport protocol settings.\n\tSettings      *serial.TypedMessage `protobuf:\"bytes,2,opt,name=settings,proto3\" json:\"settings,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TransportConfig) Reset() {\n\t*x = TransportConfig{}\n\tmi := &file_transport_internet_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TransportConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TransportConfig) ProtoMessage() {}\n\nfunc (x *TransportConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TransportConfig.ProtoReflect.Descriptor instead.\nfunc (*TransportConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *TransportConfig) GetProtocolName() string {\n\tif x != nil {\n\t\treturn x.ProtocolName\n\t}\n\treturn \"\"\n}\n\nfunc (x *TransportConfig) GetSettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Settings\n\t}\n\treturn nil\n}\n\ntype StreamConfig struct {\n\tstate   protoimpl.MessageState `protogen:\"open.v1\"`\n\tAddress *net.IPOrDomain        `protobuf:\"bytes,8,opt,name=address,proto3\" json:\"address,omitempty\"`\n\tPort    uint32                 `protobuf:\"varint,9,opt,name=port,proto3\" json:\"port,omitempty\"`\n\t// Effective network.\n\tProtocolName      string             `protobuf:\"bytes,5,opt,name=protocol_name,json=protocolName,proto3\" json:\"protocol_name,omitempty\"`\n\tTransportSettings []*TransportConfig `protobuf:\"bytes,2,rep,name=transport_settings,json=transportSettings,proto3\" json:\"transport_settings,omitempty\"`\n\t// Type of security. Must be a message name of the settings proto.\n\tSecurityType string `protobuf:\"bytes,3,opt,name=security_type,json=securityType,proto3\" json:\"security_type,omitempty\"`\n\t// Transport security settings. They can be either TLS or REALITY.\n\tSecuritySettings []*serial.TypedMessage `protobuf:\"bytes,4,rep,name=security_settings,json=securitySettings,proto3\" json:\"security_settings,omitempty\"`\n\tUdpmasks         []*serial.TypedMessage `protobuf:\"bytes,10,rep,name=udpmasks,proto3\" json:\"udpmasks,omitempty\"`\n\tTcpmasks         []*serial.TypedMessage `protobuf:\"bytes,11,rep,name=tcpmasks,proto3\" json:\"tcpmasks,omitempty\"`\n\tQuicParams       *QuicParams            `protobuf:\"bytes,12,opt,name=quic_params,json=quicParams,proto3\" json:\"quic_params,omitempty\"`\n\tSocketSettings   *SocketConfig          `protobuf:\"bytes,6,opt,name=socket_settings,json=socketSettings,proto3\" json:\"socket_settings,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *StreamConfig) Reset() {\n\t*x = StreamConfig{}\n\tmi := &file_transport_internet_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StreamConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StreamConfig) ProtoMessage() {}\n\nfunc (x *StreamConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use StreamConfig.ProtoReflect.Descriptor instead.\nfunc (*StreamConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *StreamConfig) GetAddress() *net.IPOrDomain {\n\tif x != nil {\n\t\treturn x.Address\n\t}\n\treturn nil\n}\n\nfunc (x *StreamConfig) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nfunc (x *StreamConfig) GetProtocolName() string {\n\tif x != nil {\n\t\treturn x.ProtocolName\n\t}\n\treturn \"\"\n}\n\nfunc (x *StreamConfig) GetTransportSettings() []*TransportConfig {\n\tif x != nil {\n\t\treturn x.TransportSettings\n\t}\n\treturn nil\n}\n\nfunc (x *StreamConfig) GetSecurityType() string {\n\tif x != nil {\n\t\treturn x.SecurityType\n\t}\n\treturn \"\"\n}\n\nfunc (x *StreamConfig) GetSecuritySettings() []*serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.SecuritySettings\n\t}\n\treturn nil\n}\n\nfunc (x *StreamConfig) GetUdpmasks() []*serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Udpmasks\n\t}\n\treturn nil\n}\n\nfunc (x *StreamConfig) GetTcpmasks() []*serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.Tcpmasks\n\t}\n\treturn nil\n}\n\nfunc (x *StreamConfig) GetQuicParams() *QuicParams {\n\tif x != nil {\n\t\treturn x.QuicParams\n\t}\n\treturn nil\n}\n\nfunc (x *StreamConfig) GetSocketSettings() *SocketConfig {\n\tif x != nil {\n\t\treturn x.SocketSettings\n\t}\n\treturn nil\n}\n\ntype UdpHop struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPorts         []uint32               `protobuf:\"varint,1,rep,packed,name=ports,proto3\" json:\"ports,omitempty\"`\n\tIntervalMin   int64                  `protobuf:\"varint,2,opt,name=interval_min,json=intervalMin,proto3\" json:\"interval_min,omitempty\"`\n\tIntervalMax   int64                  `protobuf:\"varint,3,opt,name=interval_max,json=intervalMax,proto3\" json:\"interval_max,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UdpHop) Reset() {\n\t*x = UdpHop{}\n\tmi := &file_transport_internet_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UdpHop) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UdpHop) ProtoMessage() {}\n\nfunc (x *UdpHop) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UdpHop.ProtoReflect.Descriptor instead.\nfunc (*UdpHop) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *UdpHop) GetPorts() []uint32 {\n\tif x != nil {\n\t\treturn x.Ports\n\t}\n\treturn nil\n}\n\nfunc (x *UdpHop) GetIntervalMin() int64 {\n\tif x != nil {\n\t\treturn x.IntervalMin\n\t}\n\treturn 0\n}\n\nfunc (x *UdpHop) GetIntervalMax() int64 {\n\tif x != nil {\n\t\treturn x.IntervalMax\n\t}\n\treturn 0\n}\n\ntype QuicParams struct {\n\tstate                   protoimpl.MessageState `protogen:\"open.v1\"`\n\tCongestion              string                 `protobuf:\"bytes,1,opt,name=congestion,proto3\" json:\"congestion,omitempty\"`\n\tBrutalUp                uint64                 `protobuf:\"varint,2,opt,name=brutal_up,json=brutalUp,proto3\" json:\"brutal_up,omitempty\"`\n\tBrutalDown              uint64                 `protobuf:\"varint,3,opt,name=brutal_down,json=brutalDown,proto3\" json:\"brutal_down,omitempty\"`\n\tUdpHop                  *UdpHop                `protobuf:\"bytes,4,opt,name=udp_hop,json=udpHop,proto3\" json:\"udp_hop,omitempty\"`\n\tInitStreamReceiveWindow uint64                 `protobuf:\"varint,5,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3\" json:\"init_stream_receive_window,omitempty\"`\n\tMaxStreamReceiveWindow  uint64                 `protobuf:\"varint,6,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3\" json:\"max_stream_receive_window,omitempty\"`\n\tInitConnReceiveWindow   uint64                 `protobuf:\"varint,7,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3\" json:\"init_conn_receive_window,omitempty\"`\n\tMaxConnReceiveWindow    uint64                 `protobuf:\"varint,8,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3\" json:\"max_conn_receive_window,omitempty\"`\n\tMaxIdleTimeout          int64                  `protobuf:\"varint,9,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3\" json:\"max_idle_timeout,omitempty\"`\n\tKeepAlivePeriod         int64                  `protobuf:\"varint,10,opt,name=keep_alive_period,json=keepAlivePeriod,proto3\" json:\"keep_alive_period,omitempty\"`\n\tDisablePathMtuDiscovery bool                   `protobuf:\"varint,11,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3\" json:\"disable_path_mtu_discovery,omitempty\"`\n\tMaxIncomingStreams      int64                  `protobuf:\"varint,12,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3\" json:\"max_incoming_streams,omitempty\"`\n\tunknownFields           protoimpl.UnknownFields\n\tsizeCache               protoimpl.SizeCache\n}\n\nfunc (x *QuicParams) Reset() {\n\t*x = QuicParams{}\n\tmi := &file_transport_internet_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *QuicParams) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QuicParams) ProtoMessage() {}\n\nfunc (x *QuicParams) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.\nfunc (*QuicParams) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *QuicParams) GetCongestion() string {\n\tif x != nil {\n\t\treturn x.Congestion\n\t}\n\treturn \"\"\n}\n\nfunc (x *QuicParams) GetBrutalUp() uint64 {\n\tif x != nil {\n\t\treturn x.BrutalUp\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetBrutalDown() uint64 {\n\tif x != nil {\n\t\treturn x.BrutalDown\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetUdpHop() *UdpHop {\n\tif x != nil {\n\t\treturn x.UdpHop\n\t}\n\treturn nil\n}\n\nfunc (x *QuicParams) GetInitStreamReceiveWindow() uint64 {\n\tif x != nil {\n\t\treturn x.InitStreamReceiveWindow\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetMaxStreamReceiveWindow() uint64 {\n\tif x != nil {\n\t\treturn x.MaxStreamReceiveWindow\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetInitConnReceiveWindow() uint64 {\n\tif x != nil {\n\t\treturn x.InitConnReceiveWindow\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetMaxConnReceiveWindow() uint64 {\n\tif x != nil {\n\t\treturn x.MaxConnReceiveWindow\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetMaxIdleTimeout() int64 {\n\tif x != nil {\n\t\treturn x.MaxIdleTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetKeepAlivePeriod() int64 {\n\tif x != nil {\n\t\treturn x.KeepAlivePeriod\n\t}\n\treturn 0\n}\n\nfunc (x *QuicParams) GetDisablePathMtuDiscovery() bool {\n\tif x != nil {\n\t\treturn x.DisablePathMtuDiscovery\n\t}\n\treturn false\n}\n\nfunc (x *QuicParams) GetMaxIncomingStreams() int64 {\n\tif x != nil {\n\t\treturn x.MaxIncomingStreams\n\t}\n\treturn 0\n}\n\ntype ProxyConfig struct {\n\tstate               protoimpl.MessageState `protogen:\"open.v1\"`\n\tTag                 string                 `protobuf:\"bytes,1,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n\tTransportLayerProxy bool                   `protobuf:\"varint,2,opt,name=transportLayerProxy,proto3\" json:\"transportLayerProxy,omitempty\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *ProxyConfig) Reset() {\n\t*x = ProxyConfig{}\n\tmi := &file_transport_internet_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ProxyConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ProxyConfig) ProtoMessage() {}\n\nfunc (x *ProxyConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.\nfunc (*ProxyConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *ProxyConfig) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\nfunc (x *ProxyConfig) GetTransportLayerProxy() bool {\n\tif x != nil {\n\t\treturn x.TransportLayerProxy\n\t}\n\treturn false\n}\n\ntype CustomSockopt struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSystem        string                 `protobuf:\"bytes,1,opt,name=system,proto3\" json:\"system,omitempty\"`\n\tNetwork       string                 `protobuf:\"bytes,2,opt,name=network,proto3\" json:\"network,omitempty\"`\n\tLevel         string                 `protobuf:\"bytes,3,opt,name=level,proto3\" json:\"level,omitempty\"`\n\tOpt           string                 `protobuf:\"bytes,4,opt,name=opt,proto3\" json:\"opt,omitempty\"`\n\tValue         string                 `protobuf:\"bytes,5,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tType          string                 `protobuf:\"bytes,6,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CustomSockopt) Reset() {\n\t*x = CustomSockopt{}\n\tmi := &file_transport_internet_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CustomSockopt) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CustomSockopt) ProtoMessage() {}\n\nfunc (x *CustomSockopt) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.\nfunc (*CustomSockopt) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *CustomSockopt) GetSystem() string {\n\tif x != nil {\n\t\treturn x.System\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomSockopt) GetNetwork() string {\n\tif x != nil {\n\t\treturn x.Network\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomSockopt) GetLevel() string {\n\tif x != nil {\n\t\treturn x.Level\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomSockopt) GetOpt() string {\n\tif x != nil {\n\t\treturn x.Opt\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomSockopt) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomSockopt) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\n// SocketConfig is options to be applied on network sockets.\ntype SocketConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Mark of the connection. If non-zero, the value will be set to SO_MARK.\n\tMark int32 `protobuf:\"varint,1,opt,name=mark,proto3\" json:\"mark,omitempty\"`\n\t// TFO is the state of TFO settings.\n\tTfo int32 `protobuf:\"varint,2,opt,name=tfo,proto3\" json:\"tfo,omitempty\"`\n\t// TProxy is for enabling TProxy socket option.\n\tTproxy SocketConfig_TProxyMode `protobuf:\"varint,3,opt,name=tproxy,proto3,enum=xray.transport.internet.SocketConfig_TProxyMode\" json:\"tproxy,omitempty\"`\n\t// ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket\n\t// option. This option is for UDP only.\n\tReceiveOriginalDestAddress bool                 `protobuf:\"varint,4,opt,name=receive_original_dest_address,json=receiveOriginalDestAddress,proto3\" json:\"receive_original_dest_address,omitempty\"`\n\tBindAddress                []byte               `protobuf:\"bytes,5,opt,name=bind_address,json=bindAddress,proto3\" json:\"bind_address,omitempty\"`\n\tBindPort                   uint32               `protobuf:\"varint,6,opt,name=bind_port,json=bindPort,proto3\" json:\"bind_port,omitempty\"`\n\tAcceptProxyProtocol        bool                 `protobuf:\"varint,7,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3\" json:\"accept_proxy_protocol,omitempty\"`\n\tDomainStrategy             DomainStrategy       `protobuf:\"varint,8,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy\" json:\"domain_strategy,omitempty\"`\n\tDialerProxy                string               `protobuf:\"bytes,9,opt,name=dialer_proxy,json=dialerProxy,proto3\" json:\"dialer_proxy,omitempty\"`\n\tTcpKeepAliveInterval       int32                `protobuf:\"varint,10,opt,name=tcp_keep_alive_interval,json=tcpKeepAliveInterval,proto3\" json:\"tcp_keep_alive_interval,omitempty\"`\n\tTcpKeepAliveIdle           int32                `protobuf:\"varint,11,opt,name=tcp_keep_alive_idle,json=tcpKeepAliveIdle,proto3\" json:\"tcp_keep_alive_idle,omitempty\"`\n\tTcpCongestion              string               `protobuf:\"bytes,12,opt,name=tcp_congestion,json=tcpCongestion,proto3\" json:\"tcp_congestion,omitempty\"`\n\tInterface                  string               `protobuf:\"bytes,13,opt,name=interface,proto3\" json:\"interface,omitempty\"`\n\tV6Only                     bool                 `protobuf:\"varint,14,opt,name=v6only,proto3\" json:\"v6only,omitempty\"`\n\tTcpWindowClamp             int32                `protobuf:\"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3\" json:\"tcp_window_clamp,omitempty\"`\n\tTcpUserTimeout             int32                `protobuf:\"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3\" json:\"tcp_user_timeout,omitempty\"`\n\tTcpMaxSeg                  int32                `protobuf:\"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3\" json:\"tcp_max_seg,omitempty\"`\n\tPenetrate                  bool                 `protobuf:\"varint,18,opt,name=penetrate,proto3\" json:\"penetrate,omitempty\"`\n\tTcpMptcp                   bool                 `protobuf:\"varint,19,opt,name=tcp_mptcp,json=tcpMptcp,proto3\" json:\"tcp_mptcp,omitempty\"`\n\tCustomSockopt              []*CustomSockopt     `protobuf:\"bytes,20,rep,name=customSockopt,proto3\" json:\"customSockopt,omitempty\"`\n\tAddressPortStrategy        AddressPortStrategy  `protobuf:\"varint,21,opt,name=address_port_strategy,json=addressPortStrategy,proto3,enum=xray.transport.internet.AddressPortStrategy\" json:\"address_port_strategy,omitempty\"`\n\tHappyEyeballs              *HappyEyeballsConfig `protobuf:\"bytes,22,opt,name=happy_eyeballs,json=happyEyeballs,proto3\" json:\"happy_eyeballs,omitempty\"`\n\tTrustedXForwardedFor       []string             `protobuf:\"bytes,23,rep,name=trusted_x_forwarded_for,json=trustedXForwardedFor,proto3\" json:\"trusted_x_forwarded_for,omitempty\"`\n\tunknownFields              protoimpl.UnknownFields\n\tsizeCache                  protoimpl.SizeCache\n}\n\nfunc (x *SocketConfig) Reset() {\n\t*x = SocketConfig{}\n\tmi := &file_transport_internet_config_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SocketConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SocketConfig) ProtoMessage() {}\n\nfunc (x *SocketConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.\nfunc (*SocketConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *SocketConfig) GetMark() int32 {\n\tif x != nil {\n\t\treturn x.Mark\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetTfo() int32 {\n\tif x != nil {\n\t\treturn x.Tfo\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetTproxy() SocketConfig_TProxyMode {\n\tif x != nil {\n\t\treturn x.Tproxy\n\t}\n\treturn SocketConfig_Off\n}\n\nfunc (x *SocketConfig) GetReceiveOriginalDestAddress() bool {\n\tif x != nil {\n\t\treturn x.ReceiveOriginalDestAddress\n\t}\n\treturn false\n}\n\nfunc (x *SocketConfig) GetBindAddress() []byte {\n\tif x != nil {\n\t\treturn x.BindAddress\n\t}\n\treturn nil\n}\n\nfunc (x *SocketConfig) GetBindPort() uint32 {\n\tif x != nil {\n\t\treturn x.BindPort\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetAcceptProxyProtocol() bool {\n\tif x != nil {\n\t\treturn x.AcceptProxyProtocol\n\t}\n\treturn false\n}\n\nfunc (x *SocketConfig) GetDomainStrategy() DomainStrategy {\n\tif x != nil {\n\t\treturn x.DomainStrategy\n\t}\n\treturn DomainStrategy_AS_IS\n}\n\nfunc (x *SocketConfig) GetDialerProxy() string {\n\tif x != nil {\n\t\treturn x.DialerProxy\n\t}\n\treturn \"\"\n}\n\nfunc (x *SocketConfig) GetTcpKeepAliveInterval() int32 {\n\tif x != nil {\n\t\treturn x.TcpKeepAliveInterval\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetTcpKeepAliveIdle() int32 {\n\tif x != nil {\n\t\treturn x.TcpKeepAliveIdle\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetTcpCongestion() string {\n\tif x != nil {\n\t\treturn x.TcpCongestion\n\t}\n\treturn \"\"\n}\n\nfunc (x *SocketConfig) GetInterface() string {\n\tif x != nil {\n\t\treturn x.Interface\n\t}\n\treturn \"\"\n}\n\nfunc (x *SocketConfig) GetV6Only() bool {\n\tif x != nil {\n\t\treturn x.V6Only\n\t}\n\treturn false\n}\n\nfunc (x *SocketConfig) GetTcpWindowClamp() int32 {\n\tif x != nil {\n\t\treturn x.TcpWindowClamp\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetTcpUserTimeout() int32 {\n\tif x != nil {\n\t\treturn x.TcpUserTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetTcpMaxSeg() int32 {\n\tif x != nil {\n\t\treturn x.TcpMaxSeg\n\t}\n\treturn 0\n}\n\nfunc (x *SocketConfig) GetPenetrate() bool {\n\tif x != nil {\n\t\treturn x.Penetrate\n\t}\n\treturn false\n}\n\nfunc (x *SocketConfig) GetTcpMptcp() bool {\n\tif x != nil {\n\t\treturn x.TcpMptcp\n\t}\n\treturn false\n}\n\nfunc (x *SocketConfig) GetCustomSockopt() []*CustomSockopt {\n\tif x != nil {\n\t\treturn x.CustomSockopt\n\t}\n\treturn nil\n}\n\nfunc (x *SocketConfig) GetAddressPortStrategy() AddressPortStrategy {\n\tif x != nil {\n\t\treturn x.AddressPortStrategy\n\t}\n\treturn AddressPortStrategy_None\n}\n\nfunc (x *SocketConfig) GetHappyEyeballs() *HappyEyeballsConfig {\n\tif x != nil {\n\t\treturn x.HappyEyeballs\n\t}\n\treturn nil\n}\n\nfunc (x *SocketConfig) GetTrustedXForwardedFor() []string {\n\tif x != nil {\n\t\treturn x.TrustedXForwardedFor\n\t}\n\treturn nil\n}\n\ntype HappyEyeballsConfig struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tPrioritizeIpv6   bool                   `protobuf:\"varint,1,opt,name=prioritize_ipv6,json=prioritizeIpv6,proto3\" json:\"prioritize_ipv6,omitempty\"`\n\tInterleave       uint32                 `protobuf:\"varint,2,opt,name=interleave,proto3\" json:\"interleave,omitempty\"`\n\tTryDelayMs       uint64                 `protobuf:\"varint,3,opt,name=try_delayMs,json=tryDelayMs,proto3\" json:\"try_delayMs,omitempty\"`\n\tMaxConcurrentTry uint32                 `protobuf:\"varint,4,opt,name=max_concurrent_try,json=maxConcurrentTry,proto3\" json:\"max_concurrent_try,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *HappyEyeballsConfig) Reset() {\n\t*x = HappyEyeballsConfig{}\n\tmi := &file_transport_internet_config_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HappyEyeballsConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HappyEyeballsConfig) ProtoMessage() {}\n\nfunc (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_config_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.\nfunc (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_config_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {\n\tif x != nil {\n\t\treturn x.PrioritizeIpv6\n\t}\n\treturn false\n}\n\nfunc (x *HappyEyeballsConfig) GetInterleave() uint32 {\n\tif x != nil {\n\t\treturn x.Interleave\n\t}\n\treturn 0\n}\n\nfunc (x *HappyEyeballsConfig) GetTryDelayMs() uint64 {\n\tif x != nil {\n\t\treturn x.TryDelayMs\n\t}\n\treturn 0\n}\n\nfunc (x *HappyEyeballsConfig) GetMaxConcurrentTry() uint32 {\n\tif x != nil {\n\t\treturn x.MaxConcurrentTry\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x1ftransport/internet/config.proto\\x12\\x17xray.transport.internet\\x1a!common/serial/typed_message.proto\\x1a\\x18common/net/address.proto\\\"t\\n\" +\n\t\"\\x0fTransportConfig\\x12#\\n\" +\n\t\"\\rprotocol_name\\x18\\x03 \\x01(\\tR\\fprotocolName\\x12<\\n\" +\n\t\"\\bsettings\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\bsettings\\\"\\xdd\\x04\\n\" +\n\t\"\\fStreamConfig\\x125\\n\" +\n\t\"\\aaddress\\x18\\b \\x01(\\v2\\x1b.xray.common.net.IPOrDomainR\\aaddress\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\t \\x01(\\rR\\x04port\\x12#\\n\" +\n\t\"\\rprotocol_name\\x18\\x05 \\x01(\\tR\\fprotocolName\\x12W\\n\" +\n\t\"\\x12transport_settings\\x18\\x02 \\x03(\\v2(.xray.transport.internet.TransportConfigR\\x11transportSettings\\x12#\\n\" +\n\t\"\\rsecurity_type\\x18\\x03 \\x01(\\tR\\fsecurityType\\x12M\\n\" +\n\t\"\\x11security_settings\\x18\\x04 \\x03(\\v2 .xray.common.serial.TypedMessageR\\x10securitySettings\\x12<\\n\" +\n\t\"\\budpmasks\\x18\\n\" +\n\t\" \\x03(\\v2 .xray.common.serial.TypedMessageR\\budpmasks\\x12<\\n\" +\n\t\"\\btcpmasks\\x18\\v \\x03(\\v2 .xray.common.serial.TypedMessageR\\btcpmasks\\x12D\\n\" +\n\t\"\\vquic_params\\x18\\f \\x01(\\v2#.xray.transport.internet.QuicParamsR\\n\" +\n\t\"quicParams\\x12N\\n\" +\n\t\"\\x0fsocket_settings\\x18\\x06 \\x01(\\v2%.xray.transport.internet.SocketConfigR\\x0esocketSettings\\\"d\\n\" +\n\t\"\\x06UdpHop\\x12\\x14\\n\" +\n\t\"\\x05ports\\x18\\x01 \\x03(\\rR\\x05ports\\x12!\\n\" +\n\t\"\\finterval_min\\x18\\x02 \\x01(\\x03R\\vintervalMin\\x12!\\n\" +\n\t\"\\finterval_max\\x18\\x03 \\x01(\\x03R\\vintervalMax\\\"\\xd1\\x04\\n\" +\n\t\"\\n\" +\n\t\"QuicParams\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"congestion\\x18\\x01 \\x01(\\tR\\n\" +\n\t\"congestion\\x12\\x1b\\n\" +\n\t\"\\tbrutal_up\\x18\\x02 \\x01(\\x04R\\bbrutalUp\\x12\\x1f\\n\" +\n\t\"\\vbrutal_down\\x18\\x03 \\x01(\\x04R\\n\" +\n\t\"brutalDown\\x128\\n\" +\n\t\"\\audp_hop\\x18\\x04 \\x01(\\v2\\x1f.xray.transport.internet.UdpHopR\\x06udpHop\\x12;\\n\" +\n\t\"\\x1ainit_stream_receive_window\\x18\\x05 \\x01(\\x04R\\x17initStreamReceiveWindow\\x129\\n\" +\n\t\"\\x19max_stream_receive_window\\x18\\x06 \\x01(\\x04R\\x16maxStreamReceiveWindow\\x127\\n\" +\n\t\"\\x18init_conn_receive_window\\x18\\a \\x01(\\x04R\\x15initConnReceiveWindow\\x125\\n\" +\n\t\"\\x17max_conn_receive_window\\x18\\b \\x01(\\x04R\\x14maxConnReceiveWindow\\x12(\\n\" +\n\t\"\\x10max_idle_timeout\\x18\\t \\x01(\\x03R\\x0emaxIdleTimeout\\x12*\\n\" +\n\t\"\\x11keep_alive_period\\x18\\n\" +\n\t\" \\x01(\\x03R\\x0fkeepAlivePeriod\\x12;\\n\" +\n\t\"\\x1adisable_path_mtu_discovery\\x18\\v \\x01(\\bR\\x17disablePathMtuDiscovery\\x120\\n\" +\n\t\"\\x14max_incoming_streams\\x18\\f \\x01(\\x03R\\x12maxIncomingStreams\\\"Q\\n\" +\n\t\"\\vProxyConfig\\x12\\x10\\n\" +\n\t\"\\x03tag\\x18\\x01 \\x01(\\tR\\x03tag\\x120\\n\" +\n\t\"\\x13transportLayerProxy\\x18\\x02 \\x01(\\bR\\x13transportLayerProxy\\\"\\x93\\x01\\n\" +\n\t\"\\rCustomSockopt\\x12\\x16\\n\" +\n\t\"\\x06system\\x18\\x01 \\x01(\\tR\\x06system\\x12\\x18\\n\" +\n\t\"\\anetwork\\x18\\x02 \\x01(\\tR\\anetwork\\x12\\x14\\n\" +\n\t\"\\x05level\\x18\\x03 \\x01(\\tR\\x05level\\x12\\x10\\n\" +\n\t\"\\x03opt\\x18\\x04 \\x01(\\tR\\x03opt\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x05 \\x01(\\tR\\x05value\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x06 \\x01(\\tR\\x04type\\\"\\x89\\t\\n\" +\n\t\"\\fSocketConfig\\x12\\x12\\n\" +\n\t\"\\x04mark\\x18\\x01 \\x01(\\x05R\\x04mark\\x12\\x10\\n\" +\n\t\"\\x03tfo\\x18\\x02 \\x01(\\x05R\\x03tfo\\x12H\\n\" +\n\t\"\\x06tproxy\\x18\\x03 \\x01(\\x0e20.xray.transport.internet.SocketConfig.TProxyModeR\\x06tproxy\\x12A\\n\" +\n\t\"\\x1dreceive_original_dest_address\\x18\\x04 \\x01(\\bR\\x1areceiveOriginalDestAddress\\x12!\\n\" +\n\t\"\\fbind_address\\x18\\x05 \\x01(\\fR\\vbindAddress\\x12\\x1b\\n\" +\n\t\"\\tbind_port\\x18\\x06 \\x01(\\rR\\bbindPort\\x122\\n\" +\n\t\"\\x15accept_proxy_protocol\\x18\\a \\x01(\\bR\\x13acceptProxyProtocol\\x12P\\n\" +\n\t\"\\x0fdomain_strategy\\x18\\b \\x01(\\x0e2'.xray.transport.internet.DomainStrategyR\\x0edomainStrategy\\x12!\\n\" +\n\t\"\\fdialer_proxy\\x18\\t \\x01(\\tR\\vdialerProxy\\x125\\n\" +\n\t\"\\x17tcp_keep_alive_interval\\x18\\n\" +\n\t\" \\x01(\\x05R\\x14tcpKeepAliveInterval\\x12-\\n\" +\n\t\"\\x13tcp_keep_alive_idle\\x18\\v \\x01(\\x05R\\x10tcpKeepAliveIdle\\x12%\\n\" +\n\t\"\\x0etcp_congestion\\x18\\f \\x01(\\tR\\rtcpCongestion\\x12\\x1c\\n\" +\n\t\"\\tinterface\\x18\\r \\x01(\\tR\\tinterface\\x12\\x16\\n\" +\n\t\"\\x06v6only\\x18\\x0e \\x01(\\bR\\x06v6only\\x12(\\n\" +\n\t\"\\x10tcp_window_clamp\\x18\\x0f \\x01(\\x05R\\x0etcpWindowClamp\\x12(\\n\" +\n\t\"\\x10tcp_user_timeout\\x18\\x10 \\x01(\\x05R\\x0etcpUserTimeout\\x12\\x1e\\n\" +\n\t\"\\vtcp_max_seg\\x18\\x11 \\x01(\\x05R\\ttcpMaxSeg\\x12\\x1c\\n\" +\n\t\"\\tpenetrate\\x18\\x12 \\x01(\\bR\\tpenetrate\\x12\\x1b\\n\" +\n\t\"\\ttcp_mptcp\\x18\\x13 \\x01(\\bR\\btcpMptcp\\x12L\\n\" +\n\t\"\\rcustomSockopt\\x18\\x14 \\x03(\\v2&.xray.transport.internet.CustomSockoptR\\rcustomSockopt\\x12`\\n\" +\n\t\"\\x15address_port_strategy\\x18\\x15 \\x01(\\x0e2,.xray.transport.internet.AddressPortStrategyR\\x13addressPortStrategy\\x12S\\n\" +\n\t\"\\x0ehappy_eyeballs\\x18\\x16 \\x01(\\v2,.xray.transport.internet.HappyEyeballsConfigR\\rhappyEyeballs\\x125\\n\" +\n\t\"\\x17trusted_x_forwarded_for\\x18\\x17 \\x03(\\tR\\x14trustedXForwardedFor\\\"/\\n\" +\n\t\"\\n\" +\n\t\"TProxyMode\\x12\\a\\n\" +\n\t\"\\x03Off\\x10\\x00\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06TProxy\\x10\\x01\\x12\\f\\n\" +\n\t\"\\bRedirect\\x10\\x02\\\"\\xad\\x01\\n\" +\n\t\"\\x13HappyEyeballsConfig\\x12'\\n\" +\n\t\"\\x0fprioritize_ipv6\\x18\\x01 \\x01(\\bR\\x0eprioritizeIpv6\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"interleave\\x18\\x02 \\x01(\\rR\\n\" +\n\t\"interleave\\x12\\x1f\\n\" +\n\t\"\\vtry_delayMs\\x18\\x03 \\x01(\\x04R\\n\" +\n\t\"tryDelayMs\\x12,\\n\" +\n\t\"\\x12max_concurrent_try\\x18\\x04 \\x01(\\rR\\x10maxConcurrentTry*\\xa9\\x01\\n\" +\n\t\"\\x0eDomainStrategy\\x12\\t\\n\" +\n\t\"\\x05AS_IS\\x10\\x00\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06USE_IP\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aUSE_IP4\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aUSE_IP6\\x10\\x03\\x12\\f\\n\" +\n\t\"\\bUSE_IP46\\x10\\x04\\x12\\f\\n\" +\n\t\"\\bUSE_IP64\\x10\\x05\\x12\\f\\n\" +\n\t\"\\bFORCE_IP\\x10\\x06\\x12\\r\\n\" +\n\t\"\\tFORCE_IP4\\x10\\a\\x12\\r\\n\" +\n\t\"\\tFORCE_IP6\\x10\\b\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"FORCE_IP46\\x10\\t\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"FORCE_IP64\\x10\\n\" +\n\t\"*\\x97\\x01\\n\" +\n\t\"\\x13AddressPortStrategy\\x12\\b\\n\" +\n\t\"\\x04None\\x10\\x00\\x12\\x0f\\n\" +\n\t\"\\vSrvPortOnly\\x10\\x01\\x12\\x12\\n\" +\n\t\"\\x0eSrvAddressOnly\\x10\\x02\\x12\\x15\\n\" +\n\t\"\\x11SrvPortAndAddress\\x10\\x03\\x12\\x0f\\n\" +\n\t\"\\vTxtPortOnly\\x10\\x04\\x12\\x12\\n\" +\n\t\"\\x0eTxtAddressOnly\\x10\\x05\\x12\\x15\\n\" +\n\t\"\\x11TxtPortAndAddress\\x10\\x06Bg\\n\" +\n\t\"\\x1bcom.xray.transport.internetP\\x01Z,github.com/xtls/xray-core/transport/internet\\xaa\\x02\\x17Xray.Transport.Internetb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_config_proto_rawDescData\n}\n\nvar file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)\nvar file_transport_internet_config_proto_goTypes = []any{\n\t(DomainStrategy)(0),          // 0: xray.transport.internet.DomainStrategy\n\t(AddressPortStrategy)(0),     // 1: xray.transport.internet.AddressPortStrategy\n\t(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode\n\t(*TransportConfig)(nil),      // 3: xray.transport.internet.TransportConfig\n\t(*StreamConfig)(nil),         // 4: xray.transport.internet.StreamConfig\n\t(*UdpHop)(nil),               // 5: xray.transport.internet.UdpHop\n\t(*QuicParams)(nil),           // 6: xray.transport.internet.QuicParams\n\t(*ProxyConfig)(nil),          // 7: xray.transport.internet.ProxyConfig\n\t(*CustomSockopt)(nil),        // 8: xray.transport.internet.CustomSockopt\n\t(*SocketConfig)(nil),         // 9: xray.transport.internet.SocketConfig\n\t(*HappyEyeballsConfig)(nil),  // 10: xray.transport.internet.HappyEyeballsConfig\n\t(*serial.TypedMessage)(nil),  // 11: xray.common.serial.TypedMessage\n\t(*net.IPOrDomain)(nil),       // 12: xray.common.net.IPOrDomain\n}\nvar file_transport_internet_config_proto_depIdxs = []int32{\n\t11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage\n\t12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain\n\t3,  // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig\n\t11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage\n\t11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage\n\t11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage\n\t6,  // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams\n\t9,  // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig\n\t5,  // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop\n\t2,  // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode\n\t0,  // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy\n\t8,  // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt\n\t1,  // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy\n\t10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig\n\t14, // [14:14] is the sub-list for method output_type\n\t14, // [14:14] is the sub-list for method input_type\n\t14, // [14:14] is the sub-list for extension type_name\n\t14, // [14:14] is the sub-list for extension extendee\n\t0,  // [0:14] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_config_proto_init() }\nfunc file_transport_internet_config_proto_init() {\n\tif File_transport_internet_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   8,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_config_proto_depIdxs,\n\t\tEnumInfos:         file_transport_internet_config_proto_enumTypes,\n\t\tMessageInfos:      file_transport_internet_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_config_proto = out.File\n\tfile_transport_internet_config_proto_goTypes = nil\n\tfile_transport_internet_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet;\noption csharp_namespace = \"Xray.Transport.Internet\";\noption go_package = \"github.com/xtls/xray-core/transport/internet\";\noption java_package = \"com.xray.transport.internet\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\nimport \"common/net/address.proto\";\n\nenum DomainStrategy {\n  AS_IS = 0;\n  USE_IP = 1;\n  USE_IP4 = 2;\n  USE_IP6 = 3;\n  USE_IP46 = 4;\n  USE_IP64 = 5;\n  FORCE_IP = 6;\n  FORCE_IP4 = 7;\n  FORCE_IP6 = 8;\n  FORCE_IP46 = 9;\n  FORCE_IP64 = 10;\n}\n\nenum AddressPortStrategy {\n  None = 0;\n  SrvPortOnly = 1;\n  SrvAddressOnly = 2;\n  SrvPortAndAddress = 3;\n  TxtPortOnly = 4;\n  TxtAddressOnly = 5;\n  TxtPortAndAddress = 6;\n}\n\nmessage TransportConfig {\n  // Transport protocol name.\n  string protocol_name = 3;\n\n  // Specific transport protocol settings.\n  xray.common.serial.TypedMessage settings = 2;\n}\n\nmessage StreamConfig {\n  xray.common.net.IPOrDomain address = 8;\n  uint32 port = 9;\n\n  // Effective network.\n  string protocol_name = 5;\n\n  repeated TransportConfig transport_settings = 2;\n\n  // Type of security. Must be a message name of the settings proto.\n  string security_type = 3;\n\n  // Transport security settings. They can be either TLS or REALITY.\n  repeated xray.common.serial.TypedMessage security_settings = 4;\n\n  repeated xray.common.serial.TypedMessage udpmasks = 10;\n  repeated xray.common.serial.TypedMessage tcpmasks = 11;\n\n  QuicParams quic_params = 12;\n\n  SocketConfig socket_settings = 6;\n}\n\nmessage UdpHop {\n  repeated uint32 ports = 1;\n  int64 interval_min = 2;\n  int64 interval_max = 3;\n}\n\nmessage QuicParams {\n  string congestion = 1;\n  uint64 brutal_up = 2;\n  uint64 brutal_down = 3;\n  UdpHop udp_hop = 4;\n  uint64 init_stream_receive_window = 5;\n  uint64 max_stream_receive_window = 6;\n  uint64 init_conn_receive_window = 7;\n  uint64 max_conn_receive_window = 8;\n  int64 max_idle_timeout = 9;\n  int64 keep_alive_period = 10;\n  bool disable_path_mtu_discovery = 11;\n  int64 max_incoming_streams = 12;\n}\n\nmessage ProxyConfig {\n  string tag = 1;\n  bool transportLayerProxy = 2;\n}\n\nmessage CustomSockopt {\n  string system = 1;\n  string network = 2;\n  string level = 3;\n  string opt = 4;\n  string value = 5;\n  string type = 6;\n}\n\n// SocketConfig is options to be applied on network sockets.\nmessage SocketConfig {\n  // Mark of the connection. If non-zero, the value will be set to SO_MARK.\n  int32 mark = 1;\n\n  // TFO is the state of TFO settings.\n  int32 tfo = 2;\n\n  enum TProxyMode {\n    // TProxy is off.\n    Off = 0;\n    // TProxy mode.\n    TProxy = 1;\n    // Redirect mode.\n    Redirect = 2;\n  }\n\n  // TProxy is for enabling TProxy socket option.\n  TProxyMode tproxy = 3;\n\n  // ReceiveOriginalDestAddress is for enabling IP_RECVORIGDSTADDR socket\n  // option. This option is for UDP only.\n  bool receive_original_dest_address = 4;\n\n  bytes bind_address = 5;\n\n  uint32 bind_port = 6;\n\n  bool accept_proxy_protocol = 7;\n\n  DomainStrategy domain_strategy = 8;\n\n  string dialer_proxy = 9;\n\n  int32 tcp_keep_alive_interval = 10;\n\n  int32 tcp_keep_alive_idle = 11;\n  \n  string tcp_congestion = 12;\n  \n  string interface = 13;\n\n  bool v6only = 14;\n\n  int32 tcp_window_clamp = 15;\n\n  int32 tcp_user_timeout = 16;\n\n  int32 tcp_max_seg = 17;\n\n  bool penetrate = 18;\n\n  bool tcp_mptcp = 19;\n\n  repeated CustomSockopt customSockopt = 20;\n\n  AddressPortStrategy address_port_strategy = 21;\n\n  HappyEyeballsConfig happy_eyeballs = 22;\n\n  repeated string trusted_x_forwarded_for = 23;\n}\n\nmessage HappyEyeballsConfig {\n  bool prioritize_ipv6 = 1;\n  uint32 interleave = 2;\n  uint64 try_delayMs = 3;\n  uint32 max_concurrent_try = 4;\n}\n"
  },
  {
    "path": "transport/internet/dialer.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n\t\"github.com/xtls/xray-core/transport\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\n// Dialer is the interface for dialing outbound connections.\ntype Dialer interface {\n\t// Dial dials a system connection to the given destination.\n\tDial(ctx context.Context, destination net.Destination) (stat.Connection, error)\n\n\t// DestIpAddress returns the ip of proxy server. It is useful in case of Android client, which prepare an IP before proxy connection is established\n\tDestIpAddress() net.IP\n\n\t// SetOutboundGateway set outbound gateway\n\tSetOutboundGateway(ctx context.Context, ob *session.Outbound)\n}\n\n// dialFunc is an interface to dial network connection to a specific destination.\ntype dialFunc func(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error)\n\nvar transportDialerCache = make(map[string]dialFunc)\n\n// RegisterTransportDialer registers a Dialer with given name.\nfunc RegisterTransportDialer(protocol string, dialer dialFunc) error {\n\tif _, found := transportDialerCache[protocol]; found {\n\t\treturn errors.New(protocol, \" dialer already registered\").AtError()\n\t}\n\ttransportDialerCache[protocol] = dialer\n\treturn nil\n}\n\n// Dial dials a internet connection towards the given destination.\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *MemoryStreamConfig) (stat.Connection, error) {\n\tif dest.Network == net.Network_TCP {\n\t\tif streamSettings == nil {\n\t\t\ts, err := ToMemoryStreamConfig(nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"failed to create default stream settings\").Base(err)\n\t\t\t}\n\t\t\tstreamSettings = s\n\t\t}\n\n\t\tprotocol := streamSettings.ProtocolName\n\t\tdialer := transportDialerCache[protocol]\n\t\tif dialer == nil {\n\t\t\treturn nil, errors.New(protocol, \" dialer not registered\").AtError()\n\t\t}\n\t\treturn dialer(ctx, dest, streamSettings)\n\t}\n\n\tif dest.Network == net.Network_UDP {\n\t\tudpDialer := transportDialerCache[\"udp\"]\n\t\tif udpDialer == nil {\n\t\t\treturn nil, errors.New(\"UDP dialer not registered\").AtError()\n\t\t}\n\t\treturn udpDialer(ctx, dest, streamSettings)\n\t}\n\n\treturn nil, errors.New(\"unknown network \", dest.Network)\n}\n\n// DestIpAddress returns the ip of proxy server. It is useful in case of Android client, which prepare an IP before proxy connection is established\nfunc DestIpAddress() net.IP {\n\treturn effectiveSystemDialer.DestIpAddress()\n}\n\nvar (\n\tdnsClient dns.Client\n\tobm       outbound.Manager\n)\n\nfunc LookupForIP(domain string, strategy DomainStrategy, localAddr net.Address) ([]net.IP, error) {\n\tif dnsClient == nil {\n\t\treturn nil, errors.New(\"DNS client not initialized\").AtError()\n\t}\n\n\tips, _, err := dnsClient.LookupIP(domain, dns.IPOption{\n\t\tIPv4Enable: (localAddr == nil && strategy.PreferIP4()) || (localAddr != nil && localAddr.Family().IsIPv4() && (strategy.PreferIP4() || strategy.FallbackIP4())),\n\t\tIPv6Enable: (localAddr == nil && strategy.PreferIP6()) || (localAddr != nil && localAddr.Family().IsIPv6() && (strategy.PreferIP6() || strategy.FallbackIP6())),\n\t})\n\t{ // Resolve fallback\n\t\tif (len(ips) == 0 || err != nil) && strategy.HasFallback() && localAddr == nil {\n\t\t\tips, _, err = dnsClient.LookupIP(domain, dns.IPOption{\n\t\t\t\tIPv4Enable: strategy.FallbackIP4(),\n\t\t\t\tIPv6Enable: strategy.FallbackIP6(),\n\t\t\t})\n\t\t}\n\t}\n\n\tif err == nil && len(ips) == 0 {\n\t\treturn nil, dns.ErrEmptyResponse\n\t}\n\treturn ips, err\n}\n\nfunc redirect(ctx context.Context, dst net.Destination, obt string, h outbound.Handler) net.Conn {\n\terrors.LogInfo(ctx, \"redirecting request \"+dst.String()+\" to \"+obt)\n\toutbounds := session.OutboundsFromContext(ctx)\n\tctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{\n\t\tTarget:  dst,\n\t\tGateway: nil,\n\t\tTag:     obt,\n\t})) // add another outbound in session ctx\n\n\tur, uw := pipe.New(pipe.OptionsFromContext(ctx)...)\n\tdr, dw := pipe.New(pipe.OptionsFromContext(ctx)...)\n\n\tgo h.Dispatch(context.WithoutCancel(ctx), &transport.Link{Reader: ur, Writer: dw})\n\tvar readerOpt cnc.ConnectionOption\n\tif dst.Network == net.Network_TCP {\n\t\treaderOpt = cnc.ConnectionOutputMulti(dr)\n\t} else {\n\t\treaderOpt = cnc.ConnectionOutputMultiUDP(dr)\n\t}\n\tnc := cnc.NewConnection(\n\t\tcnc.ConnectionInputMulti(uw),\n\t\treaderOpt,\n\t\tcnc.ConnectionOnClose(common.ChainedClosable{uw, dw}),\n\t)\n\treturn nc\n\n}\n\nfunc checkAddressPortStrategy(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (*net.Destination, error) {\n\tif sockopt.AddressPortStrategy == AddressPortStrategy_None {\n\t\treturn nil, nil\n\t}\n\tnewDest := dest\n\tvar OverridePort, OverrideAddress bool\n\tvar OverrideBy string\n\tswitch sockopt.AddressPortStrategy {\n\tcase AddressPortStrategy_SrvPortOnly:\n\t\tOverridePort = true\n\t\tOverrideAddress = false\n\t\tOverrideBy = \"srv\"\n\tcase AddressPortStrategy_SrvAddressOnly:\n\t\tOverridePort = false\n\t\tOverrideAddress = true\n\t\tOverrideBy = \"srv\"\n\tcase AddressPortStrategy_SrvPortAndAddress:\n\t\tOverridePort = true\n\t\tOverrideAddress = true\n\t\tOverrideBy = \"srv\"\n\tcase AddressPortStrategy_TxtPortOnly:\n\t\tOverridePort = true\n\t\tOverrideAddress = false\n\t\tOverrideBy = \"txt\"\n\tcase AddressPortStrategy_TxtAddressOnly:\n\t\tOverridePort = false\n\t\tOverrideAddress = true\n\t\tOverrideBy = \"txt\"\n\tcase AddressPortStrategy_TxtPortAndAddress:\n\t\tOverridePort = true\n\t\tOverrideAddress = true\n\t\tOverrideBy = \"txt\"\n\tdefault:\n\t\treturn nil, errors.New(\"unknown AddressPortStrategy\")\n\t}\n\n\tif !dest.Address.Family().IsDomain() {\n\t\treturn nil, nil\n\t}\n\n\tif OverrideBy == \"srv\" {\n\t\terrors.LogDebug(ctx, \"query SRV record for \"+dest.Address.String())\n\t\tparts := strings.SplitN(dest.Address.String(), \".\", 3)\n\t\tif len(parts) != 3 {\n\t\t\treturn nil, errors.New(\"invalid address format\", dest.Address.String())\n\t\t}\n\t\t_, srvRecords, err := net.DefaultResolver.LookupSRV(context.Background(), parts[0][1:], parts[1][1:], parts[2])\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to lookup SRV record\").Base(err)\n\t\t}\n\t\terrors.LogDebug(ctx, \"SRV record: \"+fmt.Sprintf(\"addr=%s, port=%d, priority=%d, weight=%d\", srvRecords[0].Target, srvRecords[0].Port, srvRecords[0].Priority, srvRecords[0].Weight))\n\t\tif OverridePort {\n\t\t\tnewDest.Port = net.Port(srvRecords[0].Port)\n\t\t}\n\t\tif OverrideAddress {\n\t\t\tnewDest.Address = net.ParseAddress(srvRecords[0].Target)\n\t\t}\n\t\treturn &newDest, nil\n\t}\n\tif OverrideBy == \"txt\" {\n\t\terrors.LogDebug(ctx, \"query TXT record for \"+dest.Address.String())\n\t\ttxtRecords, err := net.DefaultResolver.LookupTXT(ctx, dest.Address.String())\n\t\tif err != nil {\n\t\t\terrors.LogError(ctx, \"failed to lookup SRV record: \"+err.Error())\n\t\t\treturn nil, errors.New(\"failed to lookup SRV record\").Base(err)\n\t\t}\n\t\tfor _, txtRecord := range txtRecords {\n\t\t\terrors.LogDebug(ctx, \"TXT record: \"+txtRecord)\n\t\t\taddr_s, port_s, _ := net.SplitHostPort(string(txtRecord))\n\t\t\taddr := net.ParseAddress(addr_s)\n\t\t\tport, err := net.PortFromString(port_s)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif OverridePort {\n\t\t\t\tnewDest.Port = port\n\t\t\t}\n\t\t\tif OverrideAddress {\n\t\t\t\tnewDest.Address = addr\n\t\t\t}\n\t\t\treturn &newDest, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// DialSystem calls system dialer to create a network connection.\nfunc DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {\n\tvar src net.Address\n\toutbounds := session.OutboundsFromContext(ctx)\n\tvar outboundName string\n\tvar origTargetAddr net.Address\n\tif len(outbounds) > 0 {\n\t\tob := outbounds[len(outbounds)-1]\n\t\tif sockopt == nil || len(sockopt.DialerProxy) == 0 {\n\t\t\tsrc = ob.Gateway\n\t\t}\n\t\toutboundName = ob.Name\n\t\torigTargetAddr = ob.OriginalTarget.Address\n\t\tif origTargetAddr == nil {\n\t\t\torigTargetAddr = ob.Target.Address\n\t\t}\n\t}\n\tif sockopt == nil {\n\t\treturn effectiveSystemDialer.Dial(ctx, src, dest, sockopt)\n\t}\n\n\tif newDest, err := checkAddressPortStrategy(ctx, dest, sockopt); err == nil && newDest != nil {\n\t\terrors.LogInfo(ctx, \"replace destination with \"+newDest.String())\n\t\tdest = *newDest\n\t}\n\n\tif sockopt.DomainStrategy.HasStrategy() && dest.Address.Family().IsDomain() {\n\t\tfinalStrategy := sockopt.DomainStrategy\n\t\tif outboundName == \"freedom\" && dest.Network == net.Network_UDP && origTargetAddr != nil && src == nil {\n\t\t\tfinalStrategy = finalStrategy.GetDynamicStrategy(origTargetAddr.Family())\n\t\t}\n\t\tips, err := LookupForIP(dest.Address.Domain(), finalStrategy, src)\n\t\tif err != nil {\n\t\t\terrors.LogErrorInner(ctx, err, \"failed to resolve ip\")\n\t\t\tif sockopt.DomainStrategy.ForceIP() {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else if sockopt.HappyEyeballs == nil || sockopt.HappyEyeballs.TryDelayMs == 0 || sockopt.HappyEyeballs.MaxConcurrentTry == 0 || len(ips) < 2 || len(sockopt.DialerProxy) > 0 || dest.Network != net.Network_TCP {\n\t\t\tdest.Address = net.IPAddress(ips[dice.Roll(len(ips))])\n\t\t\terrors.LogInfo(ctx, \"replace destination with \"+dest.String())\n\t\t} else {\n\t\t\treturn TcpRaceDial(ctx, src, ips, dest.Port, sockopt, dest.Address.String())\n\t\t}\n\t}\n\n\tif len(sockopt.DialerProxy) > 0 {\n\t\tif obm == nil {\n\t\t\treturn nil, errors.New(\"there is no outbound manager for dialerProxy\").AtError()\n\t\t}\n\t\th := obm.GetHandler(sockopt.DialerProxy)\n\t\tif h == nil {\n\t\t\treturn nil, errors.New(\"there is no outbound handler for dialerProxy\").AtError()\n\t\t}\n\t\treturn redirect(ctx, dest, sockopt.DialerProxy, h), nil\n\t}\n\n\treturn effectiveSystemDialer.Dial(ctx, src, dest, sockopt)\n}\n\nfunc InitSystemDialer(dc dns.Client, om outbound.Manager) {\n\tdnsClient = dc\n\tobm = om\n}\n"
  },
  {
    "path": "transport/internet/dialer_test.go",
    "content": "package internet_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t. \"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc TestDialWithLocalAddr(t *testing.T) {\n\tserver := &tcp.Server{}\n\tdest, err := server.Start()\n\tcommon.Must(err)\n\tdefer server.Close()\n\n\tconn, err := DialSystem(context.Background(), net.TCPDestination(net.LocalHostIP, dest.Port), nil)\n\tcommon.Must(err)\n\tif r := cmp.Diff(conn.RemoteAddr().String(), \"127.0.0.1:\"+dest.Port.String()); r != \"\" {\n\t\tt.Error(r)\n\t}\n\tconn.Close()\n}\n"
  },
  {
    "path": "transport/internet/filelocker.go",
    "content": "package internet\n\nimport (\n\t\"os\"\n)\n\n// FileLocker is UDS access lock\ntype FileLocker struct {\n\tpath string\n\tfile *os.File\n}\n"
  },
  {
    "path": "transport/internet/filelocker_other.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage internet\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"golang.org/x/sys/unix\"\n)\n\n// Acquire lock\nfunc (fl *FileLocker) Acquire() error {\n\tf, err := os.Create(fl.path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil {\n\t\tf.Close()\n\t\treturn errors.New(\"failed to lock file: \", fl.path).Base(err)\n\t}\n\tfl.file = f\n\treturn nil\n}\n\n// Release lock\nfunc (fl *FileLocker) Release() {\n\tif err := unix.Flock(int(fl.file.Fd()), unix.LOCK_UN); err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to unlock file: \", fl.path)\n\t}\n\tif err := fl.file.Close(); err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to close file: \", fl.path)\n\t}\n\tif err := os.Remove(fl.path); err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to remove file: \", fl.path)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/filelocker_windows.go",
    "content": "package internet\n\n// Acquire lock\nfunc (fl *FileLocker) Acquire() error {\n\treturn nil\n}\n\n// Release lock\nfunc (fl *FileLocker) Release() {\n\treturn\n}\n"
  },
  {
    "path": "transport/internet/finalmask/finalmask.go",
    "content": "package finalmask\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nconst (\n\tUDPSize = 4096 + 123\n)\n\ntype Udpmask interface {\n\tUDP()\n\n\tWrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)\n\tWrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)\n}\n\ntype UdpmaskManager struct {\n\tudpmasks []Udpmask\n}\n\nfunc NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {\n\treturn &UdpmaskManager{\n\t\tudpmasks: udpmasks,\n\t}\n}\n\nfunc (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {\n\tvar err error\n\tfor i, mask := range m.udpmasks {\n\t\traw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn raw, nil\n}\n\nfunc (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {\n\tvar err error\n\tfor i, mask := range m.udpmasks {\n\t\traw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn raw, nil\n}\n\ntype Tcpmask interface {\n\tTCP()\n\n\tWrapConnClient(net.Conn) (net.Conn, error)\n\tWrapConnServer(net.Conn) (net.Conn, error)\n}\n\ntype TcpmaskManager struct {\n\ttcpmasks []Tcpmask\n}\n\nfunc NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {\n\treturn &TcpmaskManager{\n\t\ttcpmasks: tcpmasks,\n\t}\n}\n\nfunc (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {\n\tvar err error\n\tfor _, mask := range m.tcpmasks {\n\t\traw, err = mask.WrapConnClient(raw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn raw, nil\n}\n\nfunc (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {\n\tvar err error\n\tfor _, mask := range m.tcpmasks {\n\t\traw, err = mask.WrapConnServer(raw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn raw, nil\n}\n\nfunc (m *TcpmaskManager) WrapListener(l net.Listener) (net.Listener, error) {\n\treturn NewTcpListener(m, l)\n}\n\ntype tcpListener struct {\n\tm *TcpmaskManager\n\tnet.Listener\n}\n\nfunc NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) {\n\treturn &tcpListener{\n\t\tm:        m,\n\t\tListener: l,\n\t}, nil\n}\n\nfunc (l *tcpListener) Accept() (net.Conn, error) {\n\tconn, err := l.Listener.Accept()\n\tif err != nil {\n\t\treturn conn, err\n\t}\n\n\tnewConn, err := l.m.WrapConnServer(conn)\n\tif err != nil {\n\t\terrors.LogDebugInner(context.Background(), err, \"mask err\")\n\t\t// conn.Close()\n\t\treturn conn, nil\n\t}\n\n\treturn newConn, nil\n}\n\ntype TcpMaskConn interface {\n\tTcpMaskConn()\n\tRawConn() net.Conn\n\tSplice() bool\n}\n\nfunc UnwrapTcpMask(conn net.Conn) net.Conn {\n\tfor {\n\t\tif v, ok := conn.(TcpMaskConn); ok {\n\t\t\tif !v.Splice() {\n\t\t\t\treturn conn\n\t\t\t}\n\t\t\tconn = v.RawConn()\n\t\t} else {\n\t\t\treturn conn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/fragment/config.go",
    "content": "package fragment\n\nimport \"net\"\n\nfunc (c *Config) TCP() {\n}\n\nfunc (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {\n\treturn NewConnClient(c, raw, false)\n}\n\nfunc (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {\n\treturn NewConnServer(c, raw, true)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/fragment/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/fragment/config.proto\n\npackage fragment\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPacketsFrom   int64                  `protobuf:\"varint,1,opt,name=packets_from,json=packetsFrom,proto3\" json:\"packets_from,omitempty\"`\n\tPacketsTo     int64                  `protobuf:\"varint,2,opt,name=packets_to,json=packetsTo,proto3\" json:\"packets_to,omitempty\"`\n\tLengthMin     int64                  `protobuf:\"varint,3,opt,name=length_min,json=lengthMin,proto3\" json:\"length_min,omitempty\"`\n\tLengthMax     int64                  `protobuf:\"varint,4,opt,name=length_max,json=lengthMax,proto3\" json:\"length_max,omitempty\"`\n\tDelayMin      int64                  `protobuf:\"varint,5,opt,name=delay_min,json=delayMin,proto3\" json:\"delay_min,omitempty\"`\n\tDelayMax      int64                  `protobuf:\"varint,6,opt,name=delay_max,json=delayMax,proto3\" json:\"delay_max,omitempty\"`\n\tMaxSplitMin   int64                  `protobuf:\"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3\" json:\"max_split_min,omitempty\"`\n\tMaxSplitMax   int64                  `protobuf:\"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3\" json:\"max_split_max,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_fragment_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_fragment_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_fragment_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetPacketsFrom() int64 {\n\tif x != nil {\n\t\treturn x.PacketsFrom\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetPacketsTo() int64 {\n\tif x != nil {\n\t\treturn x.PacketsTo\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetLengthMin() int64 {\n\tif x != nil {\n\t\treturn x.LengthMin\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetLengthMax() int64 {\n\tif x != nil {\n\t\treturn x.LengthMax\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetDelayMin() int64 {\n\tif x != nil {\n\t\treturn x.DelayMin\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetDelayMax() int64 {\n\tif x != nil {\n\t\treturn x.DelayMax\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetMaxSplitMin() int64 {\n\tif x != nil {\n\t\treturn x.MaxSplitMin\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetMaxSplitMax() int64 {\n\tif x != nil {\n\t\treturn x.MaxSplitMax\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_fragment_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"2transport/internet/finalmask/fragment/config.proto\\x12*xray.transport.internet.finalmask.fragment\\\"\\x8a\\x02\\n\" +\n\t\"\\x06Config\\x12!\\n\" +\n\t\"\\fpackets_from\\x18\\x01 \\x01(\\x03R\\vpacketsFrom\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"packets_to\\x18\\x02 \\x01(\\x03R\\tpacketsTo\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"length_min\\x18\\x03 \\x01(\\x03R\\tlengthMin\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"length_max\\x18\\x04 \\x01(\\x03R\\tlengthMax\\x12\\x1b\\n\" +\n\t\"\\tdelay_min\\x18\\x05 \\x01(\\x03R\\bdelayMin\\x12\\x1b\\n\" +\n\t\"\\tdelay_max\\x18\\x06 \\x01(\\x03R\\bdelayMax\\x12\\\"\\n\" +\n\t\"\\rmax_split_min\\x18\\a \\x01(\\x03R\\vmaxSplitMin\\x12\\\"\\n\" +\n\t\"\\rmax_split_max\\x18\\b \\x01(\\x03R\\vmaxSplitMaxB\\xa0\\x01\\n\" +\n\t\".com.xray.transport.internet.finalmask.fragmentP\\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\\xaa\\x02*Xray.Transport.Internet.Finalmask.Fragmentb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_fragment_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_fragment_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_fragment_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_fragment_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_fragment_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_fragment_config_proto_rawDesc), len(file_transport_internet_finalmask_fragment_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_fragment_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_fragment_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_fragment_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.fragment.Config\n}\nvar file_transport_internet_finalmask_fragment_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_fragment_config_proto_init() }\nfunc file_transport_internet_finalmask_fragment_config_proto_init() {\n\tif File_transport_internet_finalmask_fragment_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_fragment_config_proto_rawDesc), len(file_transport_internet_finalmask_fragment_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_fragment_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_fragment_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_fragment_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_fragment_config_proto = out.File\n\tfile_transport_internet_finalmask_fragment_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_fragment_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/fragment/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.fragment;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Fragment\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/fragment\";\noption java_package = \"com.xray.transport.internet.finalmask.fragment\";\noption java_multiple_files = true;\n\nmessage Config {\n  int64 packets_from = 1;\n  int64 packets_to = 2;\n  int64 length_min = 3;\n  int64 length_max = 4;\n  int64 delay_min = 5;\n  int64 delay_max = 6;\n  int64 max_split_min = 7;\n  int64 max_split_max = 8;\n}"
  },
  {
    "path": "transport/internet/finalmask/fragment/conn.go",
    "content": "package fragment\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/crypto\"\n)\n\ntype fragmentConn struct {\n\tnet.Conn\n\tconfig *Config\n\tcount  uint64\n\n\tserver bool\n}\n\nfunc NewConnClient(c *Config, raw net.Conn, server bool) (net.Conn, error) {\n\tconn := &fragmentConn{\n\t\tConn:   raw,\n\t\tconfig: c,\n\n\t\tserver: server,\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.Conn, server bool) (net.Conn, error) {\n\treturn NewConnClient(c, raw, server)\n}\n\nfunc (c *fragmentConn) TcpMaskConn() {}\n\nfunc (c *fragmentConn) RawConn() net.Conn {\n\tif c.server {\n\t\treturn c\n\t}\n\treturn c.Conn\n}\n\nfunc (c *fragmentConn) Splice() bool {\n\tif c.server {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (c *fragmentConn) Write(p []byte) (n int, err error) {\n\tc.count++\n\n\tif c.config.PacketsFrom == 0 && c.config.PacketsTo == 1 {\n\t\tif c.count != 1 || len(p) <= 5 || p[0] != 22 {\n\t\t\treturn c.Conn.Write(p)\n\t\t}\n\t\trecordLen := 5 + ((int(p[3]) << 8) | int(p[4]))\n\t\tif len(p) < recordLen {\n\t\t\treturn c.Conn.Write(p)\n\t\t}\n\t\tdata := p[5:recordLen]\n\t\tbuff := make([]byte, 2048)\n\t\tvar hello []byte\n\t\tmaxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)\n\t\tvar splitNum int64\n\t\tfor from := 0; ; {\n\t\t\tto := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))\n\t\t\tsplitNum++\n\t\t\tif to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) {\n\t\t\t\tto = len(data)\n\t\t\t}\n\t\t\tl := to - from\n\t\t\tif 5+l > len(buff) {\n\t\t\t\tbuff = make([]byte, 5+l)\n\t\t\t}\n\t\t\tcopy(buff[:3], p)\n\t\t\tcopy(buff[5:], data[from:to])\n\t\t\tfrom = to\n\t\t\tbuff[3] = byte(l >> 8)\n\t\t\tbuff[4] = byte(l)\n\t\t\tif c.config.DelayMax == 0 {\n\t\t\t\thello = append(hello, buff[:5+l]...)\n\t\t\t} else {\n\t\t\t\t_, err := c.Conn.Write(buff[:5+l])\n\t\t\t\ttime.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif from == len(data) {\n\t\t\t\tif len(hello) > 0 {\n\t\t\t\t\t_, err := c.Conn.Write(hello)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(p) > recordLen {\n\t\t\t\t\tn, err := c.Conn.Write(p[recordLen:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn recordLen + n, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn len(p), nil\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.config.PacketsFrom != 0 && (c.count < uint64(c.config.PacketsFrom) || c.count > uint64(c.config.PacketsTo)) {\n\t\treturn c.Conn.Write(p)\n\t}\n\tmaxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)\n\tvar splitNum int64\n\tfor from := 0; ; {\n\t\tto := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))\n\t\tsplitNum++\n\t\tif to > len(p) || (maxSplit > 0 && splitNum >= maxSplit) {\n\t\t\tto = len(p)\n\t\t}\n\t\tn, err := c.Conn.Write(p[from:to])\n\t\tfrom += n\n\t\tif err != nil {\n\t\t\treturn from, err\n\t\t}\n\t\ttime.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)\n\t\tif from >= len(p) {\n\t\t\treturn from, nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/custom/config.go",
    "content": "package custom\n\nimport (\n\t\"net\"\n)\n\nfunc (c *TCPConfig) TCP() {\n}\n\nfunc (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {\n\treturn NewConnClientTCP(c, raw)\n}\n\nfunc (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) {\n\treturn NewConnServerTCP(c, raw)\n}\n\nfunc (c *UDPConfig) UDP() {\n}\n\nfunc (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClientUDP(c, raw)\n}\n\nfunc (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServerUDP(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/custom/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/custom/config.proto\n\npackage custom\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype TCPItem struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tDelayMin      int64                  `protobuf:\"varint,1,opt,name=delay_min,json=delayMin,proto3\" json:\"delay_min,omitempty\"`\n\tDelayMax      int64                  `protobuf:\"varint,2,opt,name=delay_max,json=delayMax,proto3\" json:\"delay_max,omitempty\"`\n\tRand          int32                  `protobuf:\"varint,3,opt,name=rand,proto3\" json:\"rand,omitempty\"`\n\tPacket        []byte                 `protobuf:\"bytes,4,opt,name=packet,proto3\" json:\"packet,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TCPItem) Reset() {\n\t*x = TCPItem{}\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TCPItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TCPItem) ProtoMessage() {}\n\nfunc (x *TCPItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TCPItem.ProtoReflect.Descriptor instead.\nfunc (*TCPItem) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *TCPItem) GetDelayMin() int64 {\n\tif x != nil {\n\t\treturn x.DelayMin\n\t}\n\treturn 0\n}\n\nfunc (x *TCPItem) GetDelayMax() int64 {\n\tif x != nil {\n\t\treturn x.DelayMax\n\t}\n\treturn 0\n}\n\nfunc (x *TCPItem) GetRand() int32 {\n\tif x != nil {\n\t\treturn x.Rand\n\t}\n\treturn 0\n}\n\nfunc (x *TCPItem) GetPacket() []byte {\n\tif x != nil {\n\t\treturn x.Packet\n\t}\n\treturn nil\n}\n\ntype TCPSequence struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSequence      []*TCPItem             `protobuf:\"bytes,1,rep,name=sequence,proto3\" json:\"sequence,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TCPSequence) Reset() {\n\t*x = TCPSequence{}\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TCPSequence) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TCPSequence) ProtoMessage() {}\n\nfunc (x *TCPSequence) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TCPSequence.ProtoReflect.Descriptor instead.\nfunc (*TCPSequence) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *TCPSequence) GetSequence() []*TCPItem {\n\tif x != nil {\n\t\treturn x.Sequence\n\t}\n\treturn nil\n}\n\ntype TCPConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClients       []*TCPSequence         `protobuf:\"bytes,1,rep,name=clients,proto3\" json:\"clients,omitempty\"`\n\tServers       []*TCPSequence         `protobuf:\"bytes,2,rep,name=servers,proto3\" json:\"servers,omitempty\"`\n\tErrors        []*TCPSequence         `protobuf:\"bytes,3,rep,name=errors,proto3\" json:\"errors,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TCPConfig) Reset() {\n\t*x = TCPConfig{}\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TCPConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TCPConfig) ProtoMessage() {}\n\nfunc (x *TCPConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TCPConfig.ProtoReflect.Descriptor instead.\nfunc (*TCPConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *TCPConfig) GetClients() []*TCPSequence {\n\tif x != nil {\n\t\treturn x.Clients\n\t}\n\treturn nil\n}\n\nfunc (x *TCPConfig) GetServers() []*TCPSequence {\n\tif x != nil {\n\t\treturn x.Servers\n\t}\n\treturn nil\n}\n\nfunc (x *TCPConfig) GetErrors() []*TCPSequence {\n\tif x != nil {\n\t\treturn x.Errors\n\t}\n\treturn nil\n}\n\ntype UDPItem struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRand          int32                  `protobuf:\"varint,1,opt,name=rand,proto3\" json:\"rand,omitempty\"`\n\tPacket        []byte                 `protobuf:\"bytes,2,opt,name=packet,proto3\" json:\"packet,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UDPItem) Reset() {\n\t*x = UDPItem{}\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UDPItem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UDPItem) ProtoMessage() {}\n\nfunc (x *UDPItem) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UDPItem.ProtoReflect.Descriptor instead.\nfunc (*UDPItem) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *UDPItem) GetRand() int32 {\n\tif x != nil {\n\t\treturn x.Rand\n\t}\n\treturn 0\n}\n\nfunc (x *UDPItem) GetPacket() []byte {\n\tif x != nil {\n\t\treturn x.Packet\n\t}\n\treturn nil\n}\n\ntype UDPConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClient        []*UDPItem             `protobuf:\"bytes,1,rep,name=client,proto3\" json:\"client,omitempty\"`\n\tServer        []*UDPItem             `protobuf:\"bytes,2,rep,name=server,proto3\" json:\"server,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UDPConfig) Reset() {\n\t*x = UDPConfig{}\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UDPConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UDPConfig) ProtoMessage() {}\n\nfunc (x *UDPConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UDPConfig.ProtoReflect.Descriptor instead.\nfunc (*UDPConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *UDPConfig) GetClient() []*UDPItem {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\nfunc (x *UDPConfig) GetServer() []*UDPItem {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_finalmask_header_custom_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_custom_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"7transport/internet/finalmask/header/custom/config.proto\\x12/xray.transport.internet.finalmask.header.custom\\\"o\\n\" +\n\t\"\\aTCPItem\\x12\\x1b\\n\" +\n\t\"\\tdelay_min\\x18\\x01 \\x01(\\x03R\\bdelayMin\\x12\\x1b\\n\" +\n\t\"\\tdelay_max\\x18\\x02 \\x01(\\x03R\\bdelayMax\\x12\\x12\\n\" +\n\t\"\\x04rand\\x18\\x03 \\x01(\\x05R\\x04rand\\x12\\x16\\n\" +\n\t\"\\x06packet\\x18\\x04 \\x01(\\fR\\x06packet\\\"c\\n\" +\n\t\"\\vTCPSequence\\x12T\\n\" +\n\t\"\\bsequence\\x18\\x01 \\x03(\\v28.xray.transport.internet.finalmask.header.custom.TCPItemR\\bsequence\\\"\\x91\\x02\\n\" +\n\t\"\\tTCPConfig\\x12V\\n\" +\n\t\"\\aclients\\x18\\x01 \\x03(\\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\\aclients\\x12V\\n\" +\n\t\"\\aservers\\x18\\x02 \\x03(\\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\\aservers\\x12T\\n\" +\n\t\"\\x06errors\\x18\\x03 \\x03(\\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\\x06errors\\\"5\\n\" +\n\t\"\\aUDPItem\\x12\\x12\\n\" +\n\t\"\\x04rand\\x18\\x01 \\x01(\\x05R\\x04rand\\x12\\x16\\n\" +\n\t\"\\x06packet\\x18\\x02 \\x01(\\fR\\x06packet\\\"\\xaf\\x01\\n\" +\n\t\"\\tUDPConfig\\x12P\\n\" +\n\t\"\\x06client\\x18\\x01 \\x03(\\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\\x06client\\x12P\\n\" +\n\t\"\\x06server\\x18\\x02 \\x03(\\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\\x06serverB\\xaf\\x01\\n\" +\n\t\"3com.xray.transport.internet.finalmask.header.customP\\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/header/custom\\xaa\\x02/Xray.Transport.Internet.Finalmask.Header.Customb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_custom_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_custom_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_custom_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_custom_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_custom_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)\nvar file_transport_internet_finalmask_header_custom_config_proto_goTypes = []any{\n\t(*TCPItem)(nil),     // 0: xray.transport.internet.finalmask.header.custom.TCPItem\n\t(*TCPSequence)(nil), // 1: xray.transport.internet.finalmask.header.custom.TCPSequence\n\t(*TCPConfig)(nil),   // 2: xray.transport.internet.finalmask.header.custom.TCPConfig\n\t(*UDPItem)(nil),     // 3: xray.transport.internet.finalmask.header.custom.UDPItem\n\t(*UDPConfig)(nil),   // 4: xray.transport.internet.finalmask.header.custom.UDPConfig\n}\nvar file_transport_internet_finalmask_header_custom_config_proto_depIdxs = []int32{\n\t0, // 0: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem\n\t1, // 1: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence\n\t1, // 2: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence\n\t1, // 3: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence\n\t3, // 4: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem\n\t3, // 5: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem\n\t6, // [6:6] is the sub-list for method output_type\n\t6, // [6:6] is the sub-list for method input_type\n\t6, // [6:6] is the sub-list for extension type_name\n\t6, // [6:6] is the sub-list for extension extendee\n\t0, // [0:6] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_custom_config_proto_init() }\nfunc file_transport_internet_finalmask_header_custom_config_proto_init() {\n\tif File_transport_internet_finalmask_header_custom_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   5,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_custom_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_custom_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_custom_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_custom_config_proto = out.File\n\tfile_transport_internet_finalmask_header_custom_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_custom_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/custom/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.custom;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Custom\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/custom\";\noption java_package = \"com.xray.transport.internet.finalmask.header.custom\";\noption java_multiple_files = true;\n\nmessage TCPItem {\n    int64 delay_min = 1;\n    int64 delay_max = 2;\n    int32 rand = 3;\n    bytes packet = 4;\n}\n\nmessage TCPSequence {\n    repeated TCPItem sequence = 1;\n}\n\nmessage TCPConfig {\n    repeated TCPSequence clients = 1;\n    repeated TCPSequence servers = 2;\n    repeated TCPSequence errors = 3;\n}\n\nmessage UDPItem {\n    int32 rand = 1;\n    bytes packet = 2;\n}\n\nmessage UDPConfig {\n    repeated UDPItem client = 1;\n    repeated UDPItem server = 2;\n}"
  },
  {
    "path": "transport/internet/finalmask/header/custom/tcp.go",
    "content": "package custom\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype tcpCustomClient struct {\n\tclients []*TCPSequence\n\tservers []*TCPSequence\n}\n\ntype tcpCustomClientConn struct {\n\tnet.Conn\n\theader *tcpCustomClient\n\n\tauth bool\n\twg   sync.WaitGroup\n\tonce sync.Once\n}\n\nfunc NewConnClientTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {\n\tconn := &tcpCustomClientConn{\n\t\tConn: raw,\n\t\theader: &tcpCustomClient{\n\t\t\tclients: c.Clients,\n\t\t\tservers: c.Servers,\n\t\t},\n\t}\n\n\tconn.wg.Add(1)\n\n\treturn conn, nil\n}\n\nfunc (c *tcpCustomClientConn) TcpMaskConn() {}\n\nfunc (c *tcpCustomClientConn) RawConn() net.Conn {\n\t// c.wg.Wait()\n\n\treturn c.Conn\n}\n\nfunc (c *tcpCustomClientConn) Splice() bool {\n\treturn true\n}\n\nfunc (c *tcpCustomClientConn) Read(p []byte) (n int, err error) {\n\tc.wg.Wait()\n\n\tif !c.auth {\n\t\treturn 0, errors.New(\"header auth failed\")\n\t}\n\n\treturn c.Conn.Read(p)\n}\n\nfunc (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {\n\tc.once.Do(func() {\n\t\ti := 0\n\t\tj := 0\n\t\tfor i = range c.header.clients {\n\t\t\tif !writeSequence(c.Conn, c.header.clients[i]) {\n\t\t\t\tc.wg.Done()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif j < len(c.header.servers) {\n\t\t\t\tif !readSequence(c.Conn, c.header.servers[j]) {\n\t\t\t\t\tc.wg.Done()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tj++\n\t\t\t}\n\t\t}\n\n\t\tfor j < len(c.header.servers) {\n\t\t\tif !readSequence(c.Conn, c.header.servers[j]) {\n\t\t\t\tc.wg.Done()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tj++\n\t\t}\n\n\t\tc.auth = true\n\t\tc.wg.Done()\n\t})\n\n\tc.wg.Wait()\n\n\tif !c.auth {\n\t\treturn 0, errors.New(\"header auth failed\")\n\t}\n\n\treturn c.Conn.Write(p)\n}\n\ntype tcpCustomServer struct {\n\tclients []*TCPSequence\n\tservers []*TCPSequence\n\terrors  []*TCPSequence\n}\n\ntype tcpCustomServerConn struct {\n\tnet.Conn\n\theader *tcpCustomServer\n\n\tauth bool\n\twg   sync.WaitGroup\n\tonce sync.Once\n}\n\nfunc NewConnServerTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {\n\tconn := &tcpCustomServerConn{\n\t\tConn: raw,\n\t\theader: &tcpCustomServer{\n\t\t\tclients: c.Clients,\n\t\t\tservers: c.Servers,\n\t\t\terrors:  c.Errors,\n\t\t},\n\t}\n\n\tconn.wg.Add(1)\n\n\treturn conn, nil\n}\n\nfunc (c *tcpCustomServerConn) TcpMaskConn() {}\n\nfunc (c *tcpCustomServerConn) RawConn() net.Conn {\n\t// c.wg.Wait()\n\n\treturn c.Conn\n}\n\nfunc (c *tcpCustomServerConn) Splice() bool {\n\treturn true\n}\n\nfunc (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {\n\tc.once.Do(func() {\n\t\ti := 0\n\t\tj := 0\n\t\tfor i = range c.header.clients {\n\t\t\tif !readSequence(c.Conn, c.header.clients[i]) {\n\t\t\t\tif i < len(c.header.errors) {\n\t\t\t\t\twriteSequence(c.Conn, c.header.errors[i])\n\t\t\t\t}\n\t\t\t\tc.wg.Done()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif j < len(c.header.servers) {\n\t\t\t\tif !writeSequence(c.Conn, c.header.servers[j]) {\n\t\t\t\t\tc.wg.Done()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tj++\n\t\t\t}\n\t\t}\n\n\t\tfor j < len(c.header.servers) {\n\t\t\tif !writeSequence(c.Conn, c.header.servers[j]) {\n\t\t\t\tc.wg.Done()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tj++\n\t\t}\n\n\t\tc.auth = true\n\t\tc.wg.Done()\n\t})\n\n\tc.wg.Wait()\n\n\tif !c.auth {\n\t\treturn 0, errors.New(\"header auth failed\")\n\t}\n\n\treturn c.Conn.Read(p)\n}\n\nfunc (c *tcpCustomServerConn) Write(p []byte) (n int, err error) {\n\tc.wg.Wait()\n\n\tif !c.auth {\n\t\treturn 0, errors.New(\"header auth failed\")\n\t}\n\n\treturn c.Conn.Write(p)\n}\n\nfunc readSequence(r io.Reader, sequence *TCPSequence) bool {\n\tfor _, item := range sequence.Sequence {\n\t\tlength := max(int(item.Rand), len(item.Packet))\n\t\tbuf := make([]byte, length)\n\t\tn, err := io.ReadFull(r, buf)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif item.Rand > 0 && n != length {\n\t\t\treturn false\n\t\t}\n\t\tif len(item.Packet) > 0 && !bytes.Equal(item.Packet, buf[:n]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc writeSequence(w io.Writer, sequence *TCPSequence) bool {\n\tvar merged []byte\n\tfor _, item := range sequence.Sequence {\n\t\tif item.DelayMax > 0 {\n\t\t\tif len(merged) > 0 {\n\t\t\t\t_, err := w.Write(merged)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tmerged = nil\n\t\t\t}\n\t\t\ttime.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)\n\t\t}\n\t\tif item.Rand > 0 {\n\t\t\tbuf := make([]byte, item.Rand)\n\t\t\tcommon.Must2(rand.Read(buf))\n\t\t\tmerged = append(merged, buf...)\n\t\t} else {\n\t\t\tmerged = append(merged, item.Packet...)\n\t\t}\n\t}\n\tif len(merged) > 0 {\n\t\t_, err := w.Write(merged)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tmerged = nil\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/custom/udp.go",
    "content": "package custom\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype udpCustomClient struct {\n\tclient []*UDPItem\n\tserver []*UDPItem\n\tmerged []byte\n}\n\nfunc (h *udpCustomClient) Serialize(b []byte) {\n\tindex := 0\n\tfor _, item := range h.client {\n\t\tif item.Rand > 0 {\n\t\t\tcommon.Must2(rand.Read(h.merged[index : index+int(item.Rand)]))\n\t\t\tindex += int(item.Rand)\n\t\t} else {\n\t\t\tindex += len(item.Packet)\n\t\t}\n\t}\n\tcopy(b, h.merged)\n}\n\nfunc (h *udpCustomClient) Match(b []byte) bool {\n\tif len(b) < len(h.merged) {\n\t\treturn false\n\t}\n\n\tdata := b\n\tmatch := true\n\n\tfor _, item := range h.server {\n\t\tlength := max(int(item.Rand), len(item.Packet))\n\n\t\tif len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {\n\t\t\tmatch = false\n\t\t\tbreak\n\t\t}\n\n\t\tdata = data[length:]\n\t}\n\n\treturn match\n}\n\ntype udpCustomClientConn struct {\n\tnet.PacketConn\n\theader *udpCustomClient\n}\n\nfunc NewConnClientUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &udpCustomClientConn{\n\t\tPacketConn: raw,\n\t\theader: &udpCustomClient{\n\t\t\tclient: c.Client,\n\t\t\tserver: c.Server,\n\t\t},\n\t}\n\n\tindex := 0\n\tfor _, item := range conn.header.client {\n\t\tif item.Rand > 0 {\n\t\t\tconn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)\n\t\t\tindex += int(item.Rand)\n\t\t} else {\n\t\t\tconn.header.merged = append(conn.header.merged, item.Packet...)\n\t\t\tindex += len(item.Packet)\n\t\t}\n\t}\n\n\treturn conn, nil\n}\n\nfunc (c *udpCustomClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif !c.header.Match(buf[:n]) {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-len(c.header.merged) {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-len(c.header.merged))\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[len(c.header.merged):n])\n\n\treturn n - len(c.header.merged), addr, nil\n}\n\nfunc (c *udpCustomClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif len(c.header.merged)+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", len(c.header.merged)+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:len(c.header.merged)+len(p)]\n\t}\n\n\tcopy(buf[len(c.header.merged):], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:len(c.header.merged)+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n\ntype udpCustomServer struct {\n\tclient []*UDPItem\n\tserver []*UDPItem\n\tmerged []byte\n}\n\nfunc (h *udpCustomServer) Serialize(b []byte) {\n\tindex := 0\n\tfor _, item := range h.server {\n\t\tif item.Rand > 0 {\n\t\t\tcommon.Must2(rand.Read(h.merged[index : index+int(item.Rand)]))\n\t\t\tindex += int(item.Rand)\n\t\t} else {\n\t\t\tindex += len(item.Packet)\n\t\t}\n\t}\n\tcopy(b, h.merged)\n}\n\nfunc (h *udpCustomServer) Match(b []byte) bool {\n\tif len(b) < len(h.merged) {\n\t\treturn false\n\t}\n\n\tdata := b\n\tmatch := true\n\n\tfor _, item := range h.client {\n\t\tlength := max(int(item.Rand), len(item.Packet))\n\n\t\tif len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {\n\t\t\tmatch = false\n\t\t\tbreak\n\t\t}\n\n\t\tdata = data[length:]\n\t}\n\n\treturn match\n}\n\ntype udpCustomServerConn struct {\n\tnet.PacketConn\n\theader *udpCustomServer\n}\n\nfunc NewConnServerUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &udpCustomServerConn{\n\t\tPacketConn: raw,\n\t\theader: &udpCustomServer{\n\t\t\tclient: c.Client,\n\t\t\tserver: c.Server,\n\t\t},\n\t}\n\n\tindex := 0\n\tfor _, item := range conn.header.server {\n\t\tif item.Rand > 0 {\n\t\t\tconn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)\n\t\t\tindex += int(item.Rand)\n\t\t} else {\n\t\t\tconn.header.merged = append(conn.header.merged, item.Packet...)\n\t\t\tindex += len(item.Packet)\n\t\t}\n\t}\n\n\treturn conn, nil\n}\n\nfunc (c *udpCustomServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif !c.header.Match(buf[:n]) {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-len(c.header.merged) {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-len(c.header.merged))\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[len(c.header.merged):n])\n\n\treturn n - len(c.header.merged), addr, nil\n}\n\nfunc (c *udpCustomServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif len(c.header.merged)+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", len(c.header.merged)+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:len(c.header.merged)+len(p)]\n\t}\n\n\tcopy(buf[len(c.header.merged):], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:len(c.header.merged)+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dns/config.go",
    "content": "package dns\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dns/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/dns/config.proto\n\npackage dns\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tDomain        string                 `protobuf:\"bytes,1,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_header_dns_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_dns_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_dns_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\nvar File_transport_internet_finalmask_header_dns_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_dns_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"4transport/internet/finalmask/header/dns/config.proto\\x12,xray.transport.internet.finalmask.header.dns\\\" \\n\" +\n\t\"\\x06Config\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x01 \\x01(\\tR\\x06domainB\\xa6\\x01\\n\" +\n\t\"0com.xray.transport.internet.finalmask.header.dnsP\\x01ZAgithub.com/xtls/xray-core/transport/internet/finalmask/header/dns\\xaa\\x02,Xray.Transport.Internet.Finalmask.Header.Dnsb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_dns_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_dns_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_dns_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_dns_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_dns_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_dns_config_proto_rawDesc), len(file_transport_internet_finalmask_header_dns_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_dns_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_header_dns_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.header.dns.Config\n}\nvar file_transport_internet_finalmask_header_dns_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_dns_config_proto_init() }\nfunc file_transport_internet_finalmask_header_dns_config_proto_init() {\n\tif File_transport_internet_finalmask_header_dns_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_dns_config_proto_rawDesc), len(file_transport_internet_finalmask_header_dns_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_dns_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_dns_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_dns_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_dns_config_proto = out.File\n\tfile_transport_internet_finalmask_header_dns_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_dns_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dns/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.dns;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Dns\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/dns\";\noption java_package = \"com.xray.transport.internet.finalmask.header.dns\";\noption java_multiple_files = true;\n\nmessage Config {\n    string domain = 1;    \n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dns/conn.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\nfunc packDomainName(s string, msg []byte) (off1 int, err error) {\n\toff := 0\n\tls := len(s)\n\t// Each dot ends a segment of the name.\n\t// We trade each dot byte for a length byte.\n\t// Except for escaped dots (\\.), which are normal dots.\n\t// There is also a trailing zero.\n\n\t// Emit sequence of counted strings, chopping at dots.\n\tvar (\n\t\tbegin int\n\t\tbs    []byte\n\t)\n\tfor i := 0; i < ls; i++ {\n\t\tvar c byte\n\t\tif bs == nil {\n\t\t\tc = s[i]\n\t\t} else {\n\t\t\tc = bs[i]\n\t\t}\n\n\t\tswitch c {\n\t\tcase '\\\\':\n\t\t\tif off+1 > len(msg) {\n\t\t\t\treturn len(msg), errors.New(\"buffer size too small\")\n\t\t\t}\n\n\t\t\tif bs == nil {\n\t\t\t\tbs = []byte(s)\n\t\t\t}\n\n\t\t\tcopy(bs[i:ls-1], bs[i+1:])\n\t\t\tls--\n\t\tcase '.':\n\t\t\tlabelLen := i - begin\n\t\t\tif labelLen >= 1<<6 { // top two bits of length must be clear\n\t\t\t\treturn len(msg), errors.New(\"bad rdata\")\n\t\t\t}\n\n\t\t\t// off can already (we're in a loop) be bigger than len(msg)\n\t\t\t// this happens when a name isn't fully qualified\n\t\t\tif off+1+labelLen > len(msg) {\n\t\t\t\treturn len(msg), errors.New(\"buffer size too small\")\n\t\t\t}\n\n\t\t\t// The following is covered by the length check above.\n\t\t\tmsg[off] = byte(labelLen)\n\n\t\t\tif bs == nil {\n\t\t\t\tcopy(msg[off+1:], s[begin:i])\n\t\t\t} else {\n\t\t\t\tcopy(msg[off+1:], bs[begin:i])\n\t\t\t}\n\t\t\toff += 1 + labelLen\n\t\t\tbegin = i + 1\n\t\tdefault:\n\t\t}\n\t}\n\n\tif off < len(msg) {\n\t\tmsg[off] = 0\n\t}\n\n\treturn off + 1, nil\n}\n\ntype dns struct {\n\theader []byte\n}\n\nfunc (h *dns) Size() int {\n\treturn len(h.header)\n}\n\nfunc (h *dns) Serialize(b []byte) {\n\tcopy(b, h.header)\n\tbinary.BigEndian.PutUint16(b[0:], dice.RollUint16())\n}\n\ntype dnsConn struct {\n\tnet.PacketConn\n\theader *dns\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tvar header []byte\n\theader = binary.BigEndian.AppendUint16(header, 0x0000) // Transaction ID\n\theader = binary.BigEndian.AppendUint16(header, 0x0100) // Flags: Standard query\n\theader = binary.BigEndian.AppendUint16(header, 0x0001) // Questions\n\theader = binary.BigEndian.AppendUint16(header, 0x0000) // Answer RRs\n\theader = binary.BigEndian.AppendUint16(header, 0x0000) // Authority RRs\n\theader = binary.BigEndian.AppendUint16(header, 0x0000) // Additional RRs\n\tbuf := make([]byte, 0x100)\n\toff1, err := packDomainName(c.Domain+\".\", buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\theader = append(header, buf[:off1]...)\n\theader = binary.BigEndian.AppendUint16(header, 0x0001) // Type: A\n\theader = binary.BigEndian.AppendUint16(header, 0x0001) // Class: IN\n\n\tconn := &dnsConn{\n\t\tPacketConn: raw,\n\t\theader: &dns{\n\t\t\theader: header,\n\t\t},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *dnsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-c.header.Size())\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[c.header.Size():n])\n\n\treturn n - c.header.Size(), addr, nil\n}\n\nfunc (c *dnsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.header.Size()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.header.Size()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.header.Size()+len(p)]\n\t}\n\n\tcopy(buf[c.header.Size():], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dtls/config.go",
    "content": "package dtls\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dtls/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/dtls/config.proto\n\npackage dtls\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_header_dtls_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_dtls_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_dtls_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_finalmask_header_dtls_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_dtls_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"5transport/internet/finalmask/header/dtls/config.proto\\x12-xray.transport.internet.finalmask.header.dtls\\\"\\b\\n\" +\n\t\"\\x06ConfigB\\xa9\\x01\\n\" +\n\t\"1com.xray.transport.internet.finalmask.header.dtlsP\\x01ZBgithub.com/xtls/xray-core/transport/internet/finalmask/header/dtls\\xaa\\x02-Xray.Transport.Internet.Finalmask.Header.Dtlsb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_dtls_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_dtls_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_dtls_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_dtls_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_dtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_dtls_config_proto_rawDesc), len(file_transport_internet_finalmask_header_dtls_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_dtls_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_dtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_header_dtls_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.header.dtls.Config\n}\nvar file_transport_internet_finalmask_header_dtls_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_dtls_config_proto_init() }\nfunc file_transport_internet_finalmask_header_dtls_config_proto_init() {\n\tif File_transport_internet_finalmask_header_dtls_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_dtls_config_proto_rawDesc), len(file_transport_internet_finalmask_header_dtls_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_dtls_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_dtls_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_dtls_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_dtls_config_proto = out.File\n\tfile_transport_internet_finalmask_header_dtls_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_dtls_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dtls/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.dtls;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Dtls\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls\";\noption java_package = \"com.xray.transport.internet.finalmask.header.dtls\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/finalmask/header/dtls/conn.go",
    "content": "package dtls\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype dtls struct {\n\tepoch    uint16\n\tlength   uint16\n\tsequence uint32\n}\n\nfunc (*dtls) Size() int {\n\treturn 1 + 2 + 2 + 6 + 2\n}\n\nfunc (h *dtls) Serialize(b []byte) {\n\tb[0] = 23\n\tb[1] = 254\n\tb[2] = 253\n\tb[3] = byte(h.epoch >> 8)\n\tb[4] = byte(h.epoch)\n\tb[5] = 0\n\tb[6] = 0\n\tb[7] = byte(h.sequence >> 24)\n\tb[8] = byte(h.sequence >> 16)\n\tb[9] = byte(h.sequence >> 8)\n\tb[10] = byte(h.sequence)\n\th.sequence++\n\tb[11] = byte(h.length >> 8)\n\tb[12] = byte(h.length)\n\th.length += 17\n\tif h.length > 100 {\n\t\th.length -= 50\n\t}\n}\n\ntype dtlsConn struct {\n\tnet.PacketConn\n\theader *dtls\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &dtlsConn{\n\t\tPacketConn: raw,\n\t\theader: &dtls{\n\t\t\tepoch:    dice.RollUint16(),\n\t\t\tsequence: 0,\n\t\t\tlength:   17,\n\t\t},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *dtlsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-c.header.Size())\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[c.header.Size():n])\n\n\treturn n - c.header.Size(), addr, nil\n}\n\nfunc (c *dtlsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.header.Size()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.header.Size()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.header.Size()+len(p)]\n\t}\n\n\tcopy(buf[c.header.Size():], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/srtp/config.go",
    "content": "package srtp\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/srtp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/srtp/config.proto\n\npackage srtp\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_header_srtp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_srtp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_srtp_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_finalmask_header_srtp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_srtp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"5transport/internet/finalmask/header/srtp/config.proto\\x12-xray.transport.internet.finalmask.header.srtp\\\"\\b\\n\" +\n\t\"\\x06ConfigB\\xa9\\x01\\n\" +\n\t\"1com.xray.transport.internet.finalmask.header.srtpP\\x01ZBgithub.com/xtls/xray-core/transport/internet/finalmask/header/srtp\\xaa\\x02-Xray.Transport.Internet.Finalmask.Header.Srtpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_srtp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_srtp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_srtp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_srtp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_srtp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_srtp_config_proto_rawDesc), len(file_transport_internet_finalmask_header_srtp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_srtp_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_srtp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_header_srtp_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.header.srtp.Config\n}\nvar file_transport_internet_finalmask_header_srtp_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_srtp_config_proto_init() }\nfunc file_transport_internet_finalmask_header_srtp_config_proto_init() {\n\tif File_transport_internet_finalmask_header_srtp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_srtp_config_proto_rawDesc), len(file_transport_internet_finalmask_header_srtp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_srtp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_srtp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_srtp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_srtp_config_proto = out.File\n\tfile_transport_internet_finalmask_header_srtp_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_srtp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/srtp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.srtp;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Srtp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp\";\noption java_package = \"com.xray.transport.internet.finalmask.header.srtp\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/finalmask/header/srtp/conn.go",
    "content": "package srtp\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype srtp struct {\n\theader uint16\n\tnumber uint16\n}\n\nfunc (*srtp) Size() int {\n\treturn 4\n}\n\nfunc (h *srtp) Serialize(b []byte) {\n\th.number++\n\tbinary.BigEndian.PutUint16(b, h.header)\n\tbinary.BigEndian.PutUint16(b[2:], h.number)\n}\n\ntype srtpConn struct {\n\tnet.PacketConn\n\theader *srtp\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &srtpConn{\n\t\tPacketConn: raw,\n\t\theader: &srtp{\n\t\t\theader: 0xB5E8,\n\t\t\tnumber: dice.RollUint16(),\n\t\t},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *srtpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-c.header.Size())\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[c.header.Size():n])\n\n\treturn n - c.header.Size(), addr, nil\n}\n\nfunc (c *srtpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.header.Size()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.header.Size()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.header.Size()+len(p)]\n\t}\n\n\tcopy(buf[c.header.Size():], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/utp/config.go",
    "content": "package utp\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/utp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/utp/config.proto\n\npackage utp\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_header_utp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_utp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_utp_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_finalmask_header_utp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_utp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"4transport/internet/finalmask/header/utp/config.proto\\x12,xray.transport.internet.finalmask.header.utp\\\"\\b\\n\" +\n\t\"\\x06ConfigB\\xa6\\x01\\n\" +\n\t\"0com.xray.transport.internet.finalmask.header.utpP\\x01ZAgithub.com/xtls/xray-core/transport/internet/finalmask/header/utp\\xaa\\x02,Xray.Transport.Internet.Finalmask.Header.Utpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_utp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_utp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_utp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_utp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_utp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_utp_config_proto_rawDesc), len(file_transport_internet_finalmask_header_utp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_utp_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_utp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_header_utp_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.header.utp.Config\n}\nvar file_transport_internet_finalmask_header_utp_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_utp_config_proto_init() }\nfunc file_transport_internet_finalmask_header_utp_config_proto_init() {\n\tif File_transport_internet_finalmask_header_utp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_utp_config_proto_rawDesc), len(file_transport_internet_finalmask_header_utp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_utp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_utp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_utp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_utp_config_proto = out.File\n\tfile_transport_internet_finalmask_header_utp_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_utp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/utp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.utp;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Utp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/utp\";\noption java_package = \"com.xray.transport.internet.finalmask.header.utp\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/finalmask/header/utp/conn.go",
    "content": "package utp\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype utp struct {\n\theader       byte\n\textension    byte\n\tconnectionID uint16\n}\n\nfunc (*utp) Size() int {\n\treturn 4\n}\n\nfunc (h *utp) Serialize(b []byte) {\n\tbinary.BigEndian.PutUint16(b, h.connectionID)\n\tb[2] = h.header\n\tb[3] = h.extension\n}\n\ntype utpConn struct {\n\tnet.PacketConn\n\theader *utp\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &utpConn{\n\t\tPacketConn: raw,\n\t\theader: &utp{\n\t\t\theader:       1,\n\t\t\textension:    0,\n\t\t\tconnectionID: dice.RollUint16(),\n\t\t},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *utpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-c.header.Size())\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[c.header.Size():n])\n\n\treturn n - c.header.Size(), addr, nil\n}\n\nfunc (c *utpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.header.Size()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.header.Size()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.header.Size()+len(p)]\n\t}\n\n\tcopy(buf[c.header.Size():], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wechat/config.go",
    "content": "package wechat\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wechat/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/wechat/config.proto\n\npackage wechat\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_header_wechat_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_wechat_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_wechat_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_finalmask_header_wechat_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_wechat_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"7transport/internet/finalmask/header/wechat/config.proto\\x12/xray.transport.internet.finalmask.header.wechat\\\"\\b\\n\" +\n\t\"\\x06ConfigB\\xaf\\x01\\n\" +\n\t\"3com.xray.transport.internet.finalmask.header.wechatP\\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/header/wechat\\xaa\\x02/Xray.Transport.Internet.Finalmask.Header.Wechatb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_wechat_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_wechat_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_wechat_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_wechat_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_wechat_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_wechat_config_proto_rawDesc), len(file_transport_internet_finalmask_header_wechat_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_wechat_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_wechat_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_header_wechat_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.header.wechat.Config\n}\nvar file_transport_internet_finalmask_header_wechat_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_wechat_config_proto_init() }\nfunc file_transport_internet_finalmask_header_wechat_config_proto_init() {\n\tif File_transport_internet_finalmask_header_wechat_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_wechat_config_proto_rawDesc), len(file_transport_internet_finalmask_header_wechat_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_wechat_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_wechat_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_wechat_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_wechat_config_proto = out.File\n\tfile_transport_internet_finalmask_header_wechat_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_wechat_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wechat/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.wechat;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Wechat\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat\";\noption java_package = \"com.xray.transport.internet.finalmask.header.wechat\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wechat/conn.go",
    "content": "package wechat\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype wechat struct {\n\tsn uint32\n}\n\nfunc (*wechat) Size() int {\n\treturn 13\n}\n\nfunc (h *wechat) Serialize(b []byte) {\n\th.sn++\n\tb[0] = 0xa1\n\tb[1] = 0x08\n\tbinary.BigEndian.PutUint32(b[2:], h.sn)\n\tb[6] = 0x00\n\tb[7] = 0x10\n\tb[8] = 0x11\n\tb[9] = 0x18\n\tb[10] = 0x30\n\tb[11] = 0x22\n\tb[12] = 0x30\n}\n\ntype wechatConn struct {\n\tnet.PacketConn\n\theader *wechat\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &wechatConn{\n\t\tPacketConn: raw,\n\t\theader: &wechat{\n\t\t\tsn: uint32(dice.RollUint16()),\n\t\t},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *wechatConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-c.header.Size())\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[c.header.Size():n])\n\n\treturn n - c.header.Size(), addr, nil\n}\n\nfunc (c *wechatConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.header.Size()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.header.Size()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.header.Size()+len(p)]\n\t}\n\n\tcopy(buf[c.header.Size():], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wireguard/config.go",
    "content": "package wireguard\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wireguard/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/header/wireguard/config.proto\n\npackage wireguard\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_header_wireguard_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_header_wireguard_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_header_wireguard_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_finalmask_header_wireguard_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_header_wireguard_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\":transport/internet/finalmask/header/wireguard/config.proto\\x122xray.transport.internet.finalmask.header.wireguard\\\"\\b\\n\" +\n\t\"\\x06ConfigB\\xb8\\x01\\n\" +\n\t\"6com.xray.transport.internet.finalmask.header.wireguardP\\x01ZGgithub.com/xtls/xray-core/transport/internet/finalmask/header/wireguard\\xaa\\x022Xray.Transport.Internet.Finalmask.Header.Wireguardb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_header_wireguard_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_header_wireguard_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_header_wireguard_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_header_wireguard_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_header_wireguard_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_wireguard_config_proto_rawDesc), len(file_transport_internet_finalmask_header_wireguard_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_header_wireguard_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_header_wireguard_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_header_wireguard_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.header.wireguard.Config\n}\nvar file_transport_internet_finalmask_header_wireguard_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_header_wireguard_config_proto_init() }\nfunc file_transport_internet_finalmask_header_wireguard_config_proto_init() {\n\tif File_transport_internet_finalmask_header_wireguard_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_wireguard_config_proto_rawDesc), len(file_transport_internet_finalmask_header_wireguard_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_header_wireguard_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_header_wireguard_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_header_wireguard_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_header_wireguard_config_proto = out.File\n\tfile_transport_internet_finalmask_header_wireguard_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_header_wireguard_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wireguard/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.header.wireguard;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Header.Wireguard\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard\";\noption java_package = \"com.xray.transport.internet.finalmask.header.wireguard\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/finalmask/header/wireguard/conn.go",
    "content": "package wireguard\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype wireguare struct{}\n\nfunc (*wireguare) Size() int {\n\treturn 4\n}\n\nfunc (h *wireguare) Serialize(b []byte) {\n\tb[0] = 0x04\n\tb[1] = 0x00\n\tb[2] = 0x00\n\tb[3] = 0x00\n}\n\ntype wireguareConn struct {\n\tnet.PacketConn\n\theader *wireguare\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &wireguareConn{\n\t\tPacketConn: raw,\n\t\theader:     &wireguare{},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *wireguareConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err header mismatch\")\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-c.header.Size() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-c.header.Size())\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, buf[c.header.Size():n])\n\n\treturn n - c.header.Size(), addr, nil\n}\n\nfunc (c *wireguareConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.header.Size()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.header.Size()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.header.Size()+len(p)]\n\t}\n\n\tcopy(buf[c.header.Size():], p)\n\tc.header.Serialize(buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.header.Size()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/aes128gcm/aes128gcm_test.go",
    "content": "package aes128gcm_test\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n)\n\nfunc TestAes128GcmSealInPlace(t *testing.T) {\n\thashedPsk := sha256.Sum256([]byte(\"psk\"))\n\taead := crypto.NewAesGcm(hashedPsk[:16])\n\n\ttext := []byte(\"0123456789012\")\n\tbuf := make([]byte, 8192)\n\n\tnonceSize := aead.NonceSize()\n\tnonce := buf[:nonceSize]\n\trand.Read(nonce)\n\tcopy(buf[nonceSize:], text)\n\tplaintext := buf[nonceSize : nonceSize+len(text)]\n\n\tsealed := aead.Seal(nil, nonce, plaintext, nil)\n\n\t_ = aead.Seal(plaintext[:0], nonce, plaintext, nil)\n\n\tassert.Equal(t, sealed, buf[nonceSize:nonceSize+aead.Overhead()+len(text)])\n}\n\nfunc encrypted(plain []byte) ([]byte, []byte) {\n\thashedPsk := sha256.Sum256([]byte(\"psk\"))\n\taead := crypto.NewAesGcm(hashedPsk[:16])\n\n\tnonce := make([]byte, 12)\n\trand.Read(nonce)\n\n\treturn nonce, aead.Seal(nil, nonce, plain, nil)\n}\n\nfunc TestAes128GcmOpenInPlace(t *testing.T) {\n\ta, b := encrypted([]byte(\"0123456789012\"))\n\tbuf := make([]byte, 8192)\n\tcopy(buf, a)\n\tcopy(buf[len(a):], b)\n\n\thashedPsk := sha256.Sum256([]byte(\"psk\"))\n\taead := crypto.NewAesGcm(hashedPsk[:16])\n\n\tnonceSize := aead.NonceSize()\n\tnonce := buf[:nonceSize]\n\tciphertext := buf[nonceSize : nonceSize+len(b)]\n\n\topened, _ := aead.Open(nil, nonce, ciphertext, nil)\n\t_, _ = aead.Open(ciphertext[:0], nonce, ciphertext, nil)\n\n\tassert.Equal(t, opened, ciphertext[:len(ciphertext)-aead.Overhead()])\n}\n\nfunc TestAes128GcmBounce(t *testing.T) {\n\thashedPsk := sha256.Sum256([]byte(\"psk\"))\n\taead := crypto.NewAesGcm(hashedPsk[:16])\n\tbuf := make([]byte, aead.NonceSize()+aead.Overhead())\n\tfor i := 0; i < 1000; i++ {\n\t\t_, _ = rand.Read(buf)\n\t\t_, err := aead.Open(buf[aead.NonceSize():aead.NonceSize()], buf[:aead.NonceSize()], buf[aead.NonceSize():], nil)\n\t\tassert.NotEqual(t, err, nil)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/aes128gcm/config.go",
    "content": "package aes128gcm\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/aes128gcm/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/mkcp/aes128gcm/config.proto\n\npackage aes128gcm\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      string                 `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nvar File_transport_internet_finalmask_mkcp_aes128gcm_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"8transport/internet/finalmask/mkcp/aes128gcm/config.proto\\x120xray.transport.internet.finalmask.mkcp.aes128gcm\\\"$\\n\" +\n\t\"\\x06Config\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x01 \\x01(\\tR\\bpasswordB\\xb2\\x01\\n\" +\n\t\"4com.xray.transport.internet.finalmask.mkcp.aes128gcmP\\x01ZEgithub.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm\\xaa\\x020Xray.Transport.Internet.Finalmask.Mkcp.Aes128Gcmb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDesc), len(file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.mkcp.aes128gcm.Config\n}\nvar file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_init() }\nfunc file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_init() {\n\tif File_transport_internet_finalmask_mkcp_aes128gcm_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDesc), len(file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_mkcp_aes128gcm_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_mkcp_aes128gcm_config_proto = out.File\n\tfile_transport_internet_finalmask_mkcp_aes128gcm_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_mkcp_aes128gcm_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/aes128gcm/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.mkcp.aes128gcm;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Mkcp.Aes128Gcm\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm\";\noption java_package = \"com.xray.transport.internet.finalmask.mkcp.aes128gcm\";\noption java_multiple_files = true;\n\nmessage Config {\n    string password = 1;\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/aes128gcm/conn.go",
    "content": "package aes128gcm\n\nimport (\n\t\"context\"\n\t\"crypto/cipher\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype aes128gcmConn struct {\n\tnet.PacketConn\n\taead cipher.AEAD\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\thashedPsk := sha256.Sum256([]byte(c.Password))\n\n\tconn := &aes128gcmConn{\n\t\tPacketConn: raw,\n\t\taead:       crypto.NewAesGcm(hashedPsk[:16]),\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *aes128gcmConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf := make([]byte, finalmask.UDPSize)\n\n\t\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\t\tif err != nil || n == 0 {\n\t\t\treturn n, addr, err\n\t\t}\n\n\t\tif n < c.aead.NonceSize()+c.aead.Overhead() {\n\t\t\terrors.LogDebug(context.Background(), addr, \" mask read err aead short lenth \", n)\n\t\t\treturn 0, addr, nil\n\t\t}\n\n\t\tnonceSize := c.aead.NonceSize()\n\t\tnonce := buf[:nonceSize]\n\t\tciphertext := buf[nonceSize:n]\n\t\tplaintext, err := c.aead.Open(p[:0], nonce, ciphertext, nil)\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), addr, \" mask read err aead open \", err)\n\t\t\treturn 0, addr, nil\n\t\t}\n\n\t\tif len(plaintext) > len(p) {\n\t\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", len(plaintext))\n\t\t\treturn 0, addr, nil\n\t\t}\n\n\t\treturn n - c.aead.NonceSize() - c.aead.Overhead(), addr, nil\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(p)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.aead.NonceSize()+c.aead.Overhead() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err aead short lenth \", n)\n\t\treturn 0, addr, nil\n\t}\n\n\tnonceSize := c.aead.NonceSize()\n\tnonce := p[:nonceSize]\n\tciphertext := p[nonceSize:n]\n\t_, err = c.aead.Open(ciphertext[:0], nonce, ciphertext, nil)\n\tif err != nil {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err aead open \", err)\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, p[nonceSize:n-c.aead.Overhead()])\n\n\treturn n - c.aead.NonceSize() - c.aead.Overhead(), addr, nil\n}\n\nfunc (c *aes128gcmConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.aead.NonceSize()+c.aead.Overhead()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.aead.NonceSize()+c.aead.Overhead()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.aead.NonceSize()+c.aead.Overhead()+len(p)]\n\t\tcopy(buf[c.aead.NonceSize():], p)\n\t\tp = buf[c.aead.NonceSize() : c.aead.NonceSize()+len(p)]\n\t}\n\n\tnonceSize := c.aead.NonceSize()\n\tnonce := buf[:nonceSize]\n\tcommon.Must2(rand.Read(nonce))\n\tciphertext := buf[nonceSize : c.aead.NonceSize()+c.aead.Overhead()+len(p)]\n\t_ = c.aead.Seal(ciphertext[:0], nonce, p, nil)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.aead.NonceSize()+c.aead.Overhead()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/config.go",
    "content": "package original\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/mkcp/original/config.proto\n\npackage original\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_mkcp_original_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_mkcp_original_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_mkcp_original_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_finalmask_mkcp_original_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_mkcp_original_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"7transport/internet/finalmask/mkcp/original/config.proto\\x12/xray.transport.internet.finalmask.mkcp.original\\\"\\b\\n\" +\n\t\"\\x06ConfigB\\xaf\\x01\\n\" +\n\t\"3com.xray.transport.internet.finalmask.mkcp.originalP\\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/mkcp/original\\xaa\\x02/Xray.Transport.Internet.Finalmask.Mkcp.Originalb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_mkcp_original_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_mkcp_original_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_mkcp_original_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_mkcp_original_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_mkcp_original_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_mkcp_original_config_proto_rawDesc), len(file_transport_internet_finalmask_mkcp_original_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_mkcp_original_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_mkcp_original_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_mkcp_original_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.mkcp.original.Config\n}\nvar file_transport_internet_finalmask_mkcp_original_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_mkcp_original_config_proto_init() }\nfunc file_transport_internet_finalmask_mkcp_original_config_proto_init() {\n\tif File_transport_internet_finalmask_mkcp_original_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_mkcp_original_config_proto_rawDesc), len(file_transport_internet_finalmask_mkcp_original_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_mkcp_original_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_mkcp_original_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_mkcp_original_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_mkcp_original_config_proto = out.File\n\tfile_transport_internet_finalmask_mkcp_original_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_mkcp_original_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.mkcp.original;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Mkcp.Original\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original\";\noption java_package = \"com.xray.transport.internet.finalmask.mkcp.original\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/conn.go",
    "content": "package original\n\nimport (\n\t\"context\"\n\t\"crypto/cipher\"\n\t\"encoding/binary\"\n\t\"hash/fnv\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype simple struct{}\n\nfunc NewSimple() *simple {\n\treturn &simple{}\n}\n\nfunc (*simple) NonceSize() int {\n\treturn 0\n}\n\nfunc (*simple) Overhead() int {\n\treturn 6\n}\n\nfunc (a *simple) Seal(dst, nonce, plain, extra []byte) []byte {\n\tdst = append(dst, 0, 0, 0, 0, 0, 0)\n\tbinary.BigEndian.PutUint16(dst[4:], uint16(len(plain)))\n\tdst = append(dst, plain...)\n\n\tfnvHash := fnv.New32a()\n\tcommon.Must2(fnvHash.Write(dst[4:]))\n\tfnvHash.Sum(dst[:0])\n\n\tdstLen := len(dst)\n\txtra := 4 - dstLen%4\n\tif xtra != 4 {\n\t\tdst = append(dst, make([]byte, xtra)...)\n\t}\n\txorfwd(dst)\n\tif xtra != 4 {\n\t\tdst = dst[:dstLen]\n\t}\n\treturn dst\n}\n\nfunc (a *simple) Open(dst, nonce, cipherText, extra []byte) ([]byte, error) {\n\tdst = append(dst, cipherText...)\n\tdstLen := len(dst)\n\txtra := 4 - dstLen%4\n\tif xtra != 4 {\n\t\tdst = append(dst, make([]byte, xtra)...)\n\t}\n\txorbkd(dst)\n\tif xtra != 4 {\n\t\tdst = dst[:dstLen]\n\t}\n\n\tfnvHash := fnv.New32a()\n\tcommon.Must2(fnvHash.Write(dst[4:]))\n\tif binary.BigEndian.Uint32(dst[:4]) != fnvHash.Sum32() {\n\t\treturn nil, errors.New(\"invalid auth\")\n\t}\n\n\tlength := binary.BigEndian.Uint16(dst[4:6])\n\tif len(dst)-6 != int(length) {\n\t\treturn nil, errors.New(\"invalid auth\")\n\t}\n\n\treturn dst[6:], nil\n}\n\ntype simpleConn struct {\n\tnet.PacketConn\n\taead cipher.AEAD\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &simpleConn{\n\t\tPacketConn: raw,\n\t\taead:       &simple{},\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *simpleConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < c.aead.Overhead() {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err aead short lenth \", n)\n\t\treturn 0, addr, nil\n\t}\n\n\tciphertext := buf[:n]\n\topened, err := c.aead.Open(nil, nil, ciphertext, nil)\n\tif err != nil {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err aead open \", err)\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(opened) > len(p) {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", len(opened))\n\t\treturn 0, addr, nil\n\t}\n\n\tcopy(p, opened)\n\n\treturn n - c.aead.Overhead(), addr, nil\n}\n\nfunc (c *simpleConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif c.aead.Overhead()+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", c.aead.Overhead()+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:c.aead.Overhead()+len(p)]\n\t\tcopy(buf[c.aead.Overhead():], p)\n\t\tp = buf[c.aead.Overhead() : c.aead.Overhead()+len(p)]\n\t}\n\n\t_ = c.aead.Seal(buf[:0], nil, p, nil)\n\n\t_, err = c.PacketConn.WriteTo(buf[:c.aead.Overhead()+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/simple_test.go",
    "content": "package original_test\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original\"\n)\n\nfunc TestSimpleSealInPlace(t *testing.T) {\n\taead := original.NewSimple()\n\n\ttext := []byte(\"0123456789012\")\n\tbuf := make([]byte, 8192)\n\n\tcopy(buf[aead.Overhead():], text)\n\tplaintext := buf[aead.Overhead() : aead.Overhead()+len(text)]\n\n\tsealed := aead.Seal(nil, nil, plaintext, nil)\n\n\t_ = aead.Seal(buf[:0], nil, plaintext, nil)\n\n\tassert.Equal(t, sealed, buf[:aead.Overhead()+len(text)])\n}\n\nfunc TestOriginalBounce(t *testing.T) {\n\taead := original.NewSimple()\n\tbuf := make([]byte, aead.NonceSize()+aead.Overhead())\n\tfor i := 0; i < 1000; i++ {\n\t\t_, _ = rand.Read(buf)\n\t\t_, err := aead.Open(buf[:0], nil, buf, nil)\n\t\tassert.NotEqual(t, err, nil)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/xor.go",
    "content": "//go:build !amd64\n// +build !amd64\n\npackage original\n\n// xorfwd performs XOR forwards in words, x[i] ^= x[i-4], i from 0 to len\nfunc xorfwd(x []byte) {\n\tfor i := 4; i < len(x); i++ {\n\t\tx[i] ^= x[i-4]\n\t}\n}\n\n// xorbkd performs XOR backwords in words, x[i] ^= x[i-4], i from len to 0\nfunc xorbkd(x []byte) {\n\tfor i := len(x) - 1; i >= 4; i-- {\n\t\tx[i] ^= x[i-4]\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/xor_amd64.go",
    "content": "package original\n\n//go:noescape\nfunc xorfwd(x []byte)\n\n//go:noescape\nfunc xorbkd(x []byte)\n"
  },
  {
    "path": "transport/internet/finalmask/mkcp/original/xor_amd64.s",
    "content": "#include \"textflag.h\"\n\n// func xorfwd(x []byte)\nTEXT ·xorfwd(SB),NOSPLIT,$0\n  MOVQ x+0(FP), SI  // x[i]\n  MOVQ x_len+8(FP), CX  // x.len\n  MOVQ x+0(FP), DI\n  ADDQ $4, DI       // x[i+4]\n  SUBQ $4, CX\nxorfwdloop:\n  MOVL (SI), AX\n  XORL AX, (DI)\n  ADDQ $4, SI\n  ADDQ $4, DI\n  SUBQ $4, CX\n\n  CMPL CX, $0\n  JE xorfwddone\n\n  JMP xorfwdloop\nxorfwddone:        \n  RET\n\n// func xorbkd(x []byte)\nTEXT ·xorbkd(SB),NOSPLIT,$0\n  MOVQ x+0(FP), SI\n  MOVQ x_len+8(FP), CX  // x.len\n  MOVQ x+0(FP), DI\n  ADDQ CX, SI       // x[-8]\n  SUBQ $8, SI\n  ADDQ CX, DI       // x[-4]\n  SUBQ $4, DI\n  SUBQ $4, CX\nxorbkdloop:\n  MOVL (SI), AX\n  XORL AX, (DI)\n  SUBQ $4, SI\n  SUBQ $4, DI\n  SUBQ $4, CX\n\n  CMPL CX, $0\n  JE xorbkddone\n  \n  JMP xorbkdloop\n\nxorbkddone:        \n  RET\n"
  },
  {
    "path": "transport/internet/finalmask/noise/config.go",
    "content": "package noise\n\nimport \"net\"\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/noise/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/noise/config.proto\n\npackage noise\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Item struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRandMin       int64                  `protobuf:\"varint,1,opt,name=rand_min,json=randMin,proto3\" json:\"rand_min,omitempty\"`\n\tRandMax       int64                  `protobuf:\"varint,2,opt,name=rand_max,json=randMax,proto3\" json:\"rand_max,omitempty\"`\n\tPacket        []byte                 `protobuf:\"bytes,3,opt,name=packet,proto3\" json:\"packet,omitempty\"`\n\tDelayMin      int64                  `protobuf:\"varint,4,opt,name=delay_min,json=delayMin,proto3\" json:\"delay_min,omitempty\"`\n\tDelayMax      int64                  `protobuf:\"varint,5,opt,name=delay_max,json=delayMax,proto3\" json:\"delay_max,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Item) Reset() {\n\t*x = Item{}\n\tmi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Item) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Item) ProtoMessage() {}\n\nfunc (x *Item) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Item.ProtoReflect.Descriptor instead.\nfunc (*Item) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Item) GetRandMin() int64 {\n\tif x != nil {\n\t\treturn x.RandMin\n\t}\n\treturn 0\n}\n\nfunc (x *Item) GetRandMax() int64 {\n\tif x != nil {\n\t\treturn x.RandMax\n\t}\n\treturn 0\n}\n\nfunc (x *Item) GetPacket() []byte {\n\tif x != nil {\n\t\treturn x.Packet\n\t}\n\treturn nil\n}\n\nfunc (x *Item) GetDelayMin() int64 {\n\tif x != nil {\n\t\treturn x.DelayMin\n\t}\n\treturn 0\n}\n\nfunc (x *Item) GetDelayMax() int64 {\n\tif x != nil {\n\t\treturn x.DelayMax\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tResetMin      int64                  `protobuf:\"varint,1,opt,name=reset_min,json=resetMin,proto3\" json:\"reset_min,omitempty\"`\n\tResetMax      int64                  `protobuf:\"varint,2,opt,name=reset_max,json=resetMax,proto3\" json:\"reset_max,omitempty\"`\n\tItems         []*Item                `protobuf:\"bytes,3,rep,name=items,proto3\" json:\"items,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_noise_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_noise_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Config) GetResetMin() int64 {\n\tif x != nil {\n\t\treturn x.ResetMin\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetResetMax() int64 {\n\tif x != nil {\n\t\treturn x.ResetMax\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetItems() []*Item {\n\tif x != nil {\n\t\treturn x.Items\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_finalmask_noise_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_noise_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"/transport/internet/finalmask/noise/config.proto\\x12'xray.transport.internet.finalmask.noise\\\"\\x8e\\x01\\n\" +\n\t\"\\x04Item\\x12\\x19\\n\" +\n\t\"\\brand_min\\x18\\x01 \\x01(\\x03R\\arandMin\\x12\\x19\\n\" +\n\t\"\\brand_max\\x18\\x02 \\x01(\\x03R\\arandMax\\x12\\x16\\n\" +\n\t\"\\x06packet\\x18\\x03 \\x01(\\fR\\x06packet\\x12\\x1b\\n\" +\n\t\"\\tdelay_min\\x18\\x04 \\x01(\\x03R\\bdelayMin\\x12\\x1b\\n\" +\n\t\"\\tdelay_max\\x18\\x05 \\x01(\\x03R\\bdelayMax\\\"\\x87\\x01\\n\" +\n\t\"\\x06Config\\x12\\x1b\\n\" +\n\t\"\\treset_min\\x18\\x01 \\x01(\\x03R\\bresetMin\\x12\\x1b\\n\" +\n\t\"\\treset_max\\x18\\x02 \\x01(\\x03R\\bresetMax\\x12C\\n\" +\n\t\"\\x05items\\x18\\x03 \\x03(\\v2-.xray.transport.internet.finalmask.noise.ItemR\\x05itemsB\\x97\\x01\\n\" +\n\t\"+com.xray.transport.internet.finalmask.noiseP\\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/noise\\xaa\\x02'Xray.Transport.Internet.Finalmask.Noiseb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_noise_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_noise_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_noise_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_noise_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_noise_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_noise_config_proto_rawDesc), len(file_transport_internet_finalmask_noise_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_noise_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_noise_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_finalmask_noise_config_proto_goTypes = []any{\n\t(*Item)(nil),   // 0: xray.transport.internet.finalmask.noise.Item\n\t(*Config)(nil), // 1: xray.transport.internet.finalmask.noise.Config\n}\nvar file_transport_internet_finalmask_noise_config_proto_depIdxs = []int32{\n\t0, // 0: xray.transport.internet.finalmask.noise.Config.items:type_name -> xray.transport.internet.finalmask.noise.Item\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_noise_config_proto_init() }\nfunc file_transport_internet_finalmask_noise_config_proto_init() {\n\tif File_transport_internet_finalmask_noise_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_noise_config_proto_rawDesc), len(file_transport_internet_finalmask_noise_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_noise_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_noise_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_noise_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_noise_config_proto = out.File\n\tfile_transport_internet_finalmask_noise_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_noise_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/noise/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.noise;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Noise\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/noise\";\noption java_package = \"com.xray.transport.internet.finalmask.noise\";\noption java_multiple_files = true;\n\nmessage Item {\n    int64 rand_min = 1;\n    int64 rand_max = 2;\n    bytes packet = 3;\n    int64 delay_min = 4;\n    int64 delay_max = 5;\n}\n\nmessage Config {\n    int64 reset_min = 1;\n    int64 reset_max = 2;\n    repeated Item items = 3;\n}\n"
  },
  {
    "path": "transport/internet/finalmask/noise/conn.go",
    "content": "package noise\n\nimport (\n\t\"crypto/rand\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n)\n\ntype noiseConn struct {\n\tnet.PacketConn\n\tconfig *Config\n\tm      map[string]time.Time\n\tstop   chan struct{}\n\tonce   sync.Once\n\tmutex  sync.RWMutex\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tconn := &noiseConn{\n\t\tPacketConn: raw,\n\t\tconfig:     c,\n\t\tm:          make(map[string]time.Time),\n\t\tstop:       make(chan struct{}),\n\t}\n\n\tif conn.config.ResetMax > 0 {\n\t\tgo conn.reset()\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *noiseConn) reset() {\n\tticker := time.NewTicker(1 * time.Second)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tc.mutex.RLock()\n\t\t\tnow := time.Now()\n\t\t\ttimeOut := make([]string, 0, len(c.m))\n\t\t\tfor key, last := range c.m {\n\t\t\t\tif now.After(last) {\n\t\t\t\t\ttimeOut = append(timeOut, key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tc.mutex.RUnlock()\n\n\t\t\tfor _, key := range timeOut {\n\t\t\t\tc.mutex.Lock()\n\t\t\t\tdelete(c.m, key)\n\t\t\t\tc.mutex.Unlock()\n\t\t\t}\n\t\tcase <-c.stop:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *noiseConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tc.mutex.RLock()\n\t_, ready := c.m[addr.String()]\n\tc.mutex.RUnlock()\n\n\tif !ready {\n\t\tc.mutex.Lock()\n\t\t_, ready = c.m[addr.String()]\n\t\tif !ready {\n\t\t\tfor _, item := range c.config.Items {\n\t\t\t\tif item.RandMax > 0 {\n\t\t\t\t\titem.Packet = make([]byte, crypto.RandBetween(item.RandMin, item.RandMax))\n\t\t\t\t\tcommon.Must2(rand.Read(item.Packet))\n\t\t\t\t}\n\t\t\t\tc.PacketConn.WriteTo(item.Packet, addr)\n\t\t\t\ttime.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)\n\t\t\t}\n\t\t\tc.m[addr.String()] = time.Now().Add(time.Duration(crypto.RandBetween(c.config.ResetMin, c.config.ResetMax)) * time.Second)\n\t\t}\n\t\tc.mutex.Unlock()\n\t}\n\n\treturn c.PacketConn.WriteTo(p, addr)\n}\n\nfunc (c *noiseConn) Close() error {\n\tc.once.Do(func() {\n\t\tclose(c.stop)\n\t})\n\treturn c.PacketConn.Close()\n}\n"
  },
  {
    "path": "transport/internet/finalmask/salamander/config.go",
    "content": "package salamander\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/salamander/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/salamander/config.proto\n\npackage salamander\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      string                 `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_salamander_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_salamander_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_salamander_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nvar File_transport_internet_finalmask_salamander_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_salamander_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"4transport/internet/finalmask/salamander/config.proto\\x12,xray.transport.internet.finalmask.salamander\\\"$\\n\" +\n\t\"\\x06Config\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x01 \\x01(\\tR\\bpasswordB\\xa6\\x01\\n\" +\n\t\"0com.xray.transport.internet.finalmask.salamanderP\\x01ZAgithub.com/xtls/xray-core/transport/internet/finalmask/salamander\\xaa\\x02,Xray.Transport.Internet.Finalmask.Salamanderb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_salamander_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_salamander_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_salamander_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_salamander_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_salamander_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_salamander_config_proto_rawDesc), len(file_transport_internet_finalmask_salamander_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_salamander_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_salamander_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_salamander_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.salamander.Config\n}\nvar file_transport_internet_finalmask_salamander_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_salamander_config_proto_init() }\nfunc file_transport_internet_finalmask_salamander_config_proto_init() {\n\tif File_transport_internet_finalmask_salamander_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_salamander_config_proto_rawDesc), len(file_transport_internet_finalmask_salamander_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_salamander_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_salamander_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_salamander_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_salamander_config_proto = out.File\n\tfile_transport_internet_finalmask_salamander_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_salamander_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/salamander/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.salamander;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Salamander\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/salamander\";\noption java_package = \"com.xray.transport.internet.finalmask.salamander\";\noption java_multiple_files = true;\n\nmessage Config {\n  string password = 1;\n}\n\n"
  },
  {
    "path": "transport/internet/finalmask/salamander/conn.go",
    "content": "package salamander\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\ntype salamanderConn struct {\n\tnet.PacketConn\n\tobfs *SalamanderObfuscator\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tob, err := NewSalamanderObfuscator([]byte(c.Password))\n\tif err != nil {\n\t\treturn nil, errors.New(\"salamander err\").Base(err)\n\t}\n\n\tconn := &salamanderConn{\n\t\tPacketConn: raw,\n\t\tobfs:       ob,\n\t}\n\n\treturn conn, nil\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *salamanderConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tbuf := p\n\tif len(p) < finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t}\n\n\tn, addr, err = c.PacketConn.ReadFrom(buf)\n\tif err != nil || n == 0 {\n\t\treturn n, addr, err\n\t}\n\n\tif n < smSaltLen {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short lenth \", n)\n\t\treturn 0, addr, nil\n\t}\n\n\tif len(p) < n-smSaltLen {\n\t\terrors.LogDebug(context.Background(), addr, \" mask read err short buffer \", len(p), \" \", n-smSaltLen)\n\t\treturn 0, addr, nil\n\t}\n\n\tc.obfs.Deobfuscate(buf[:n], p)\n\n\treturn n - smSaltLen, addr, nil\n}\n\nfunc (c *salamanderConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif smSaltLen+len(p) > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", smSaltLen+len(p), \" \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tvar buf []byte\n\tif cap(p) != finalmask.UDPSize {\n\t\tbuf = make([]byte, finalmask.UDPSize)\n\t} else {\n\t\tbuf = p[:smSaltLen+len(p)]\n\t\tcopy(buf[smSaltLen:], p)\n\t\tp = buf[smSaltLen:]\n\t}\n\n\tc.obfs.Obfuscate(p, buf)\n\n\t_, err = c.PacketConn.WriteTo(buf[:smSaltLen+len(p)], addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/salamander/salamander.go",
    "content": "package salamander\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/blake2b\"\n)\n\nconst (\n\tsmPSKMinLen = 4\n\tsmSaltLen   = 8\n\tsmKeyLen    = blake2b.Size256\n)\n\nvar ErrPSKTooShort = fmt.Errorf(\"PSK must be at least %d bytes\", smPSKMinLen)\n\n// SalamanderObfuscator is an obfuscator that obfuscates each packet with\n// the BLAKE2b-256 hash of a pre-shared key combined with a random salt.\n// Packet format: [8-byte salt][payload]\ntype SalamanderObfuscator struct {\n\tPSK     []byte\n\tRandSrc *rand.Rand\n\n\tlk sync.Mutex\n}\n\nfunc NewSalamanderObfuscator(psk []byte) (*SalamanderObfuscator, error) {\n\tif len(psk) < smPSKMinLen {\n\t\treturn nil, ErrPSKTooShort\n\t}\n\treturn &SalamanderObfuscator{\n\t\tPSK:     psk,\n\t\tRandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),\n\t}, nil\n}\n\nfunc (o *SalamanderObfuscator) Obfuscate(in, out []byte) int {\n\toutLen := len(in) + smSaltLen\n\tif len(out) < outLen {\n\t\treturn 0\n\t}\n\to.lk.Lock()\n\t_, _ = o.RandSrc.Read(out[:smSaltLen])\n\to.lk.Unlock()\n\tkey := o.key(out[:smSaltLen])\n\tfor i, c := range in {\n\t\tout[i+smSaltLen] = c ^ key[i%smKeyLen]\n\t}\n\treturn outLen\n}\n\nfunc (o *SalamanderObfuscator) Deobfuscate(in, out []byte) int {\n\toutLen := len(in) - smSaltLen\n\tif outLen <= 0 || len(out) < outLen {\n\t\treturn 0\n\t}\n\tkey := o.key(in[:smSaltLen])\n\tfor i, c := range in[smSaltLen:] {\n\t\tout[i] = c ^ key[i%smKeyLen]\n\t}\n\treturn outLen\n}\n\nfunc (o *SalamanderObfuscator) key(salt []byte) [smKeyLen]byte {\n\treturn blake2b.Sum256(append(o.PSK, salt...))\n}\n"
  },
  {
    "path": "transport/internet/finalmask/salamander/salamander_test.go",
    "content": "package salamander_test\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/salamander\"\n)\n\nconst (\n\tsmSaltLen = 8\n)\n\nfunc BenchmarkSalamanderObfuscator_Obfuscate(b *testing.B) {\n\to, _ := salamander.NewSalamanderObfuscator([]byte(\"average_password\"))\n\tin := make([]byte, 1200)\n\t_, _ = rand.Read(in)\n\tout := make([]byte, 2048)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\to.Obfuscate(in, out)\n\t}\n}\n\nfunc BenchmarkSalamanderObfuscator_Deobfuscate(b *testing.B) {\n\to, _ := salamander.NewSalamanderObfuscator([]byte(\"average_password\"))\n\tin := make([]byte, 1200)\n\t_, _ = rand.Read(in)\n\tout := make([]byte, 2048)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\to.Deobfuscate(in, out)\n\t}\n}\n\nfunc TestSalamanderObfuscator(t *testing.T) {\n\to, _ := salamander.NewSalamanderObfuscator([]byte(\"average_password\"))\n\tin := make([]byte, 1200)\n\toOut := make([]byte, 2048)\n\tdOut := make([]byte, 2048)\n\tfor i := 0; i < 1000; i++ {\n\t\t_, _ = rand.Read(in)\n\t\tn := o.Obfuscate(in, oOut)\n\t\tassert.Equal(t, len(in)+smSaltLen, n)\n\t\tn = o.Deobfuscate(oOut[:n], dOut)\n\t\tassert.Equal(t, len(in), n)\n\t\tassert.Equal(t, in, dOut[:n])\n\t}\n}\n\nfunc TestSalamanderInPlace(t *testing.T) {\n\to, _ := salamander.NewSalamanderObfuscator([]byte(\"average_password\"))\n\n\tin := make([]byte, 1200)\n\tout := make([]byte, 2048)\n\t_, _ = rand.Read(in)\n\to.Obfuscate(in, out)\n\n\tout2 := make([]byte, 2048)\n\tcopy(out2[smSaltLen:], in)\n\to.Obfuscate(out2[smSaltLen:], out2)\n\n\tdOut := make([]byte, 2048)\n\to.Deobfuscate(out, dOut)\n\n\to.Deobfuscate(out2, out2)\n\n\tassert.Equal(t, in, dOut[:1200])\n\tassert.Equal(t, in, out2[:1200])\n}\n\nfunc TestSalamanderBounce(t *testing.T) {\n\to, _ := salamander.NewSalamanderObfuscator([]byte(\"average_password\"))\n\tbuf := make([]byte, 8)\n\tfor i := 0; i < 1000; i++ {\n\t\t_, _ = rand.Read(buf)\n\t\tn := o.Deobfuscate(buf, buf)\n\t\tassert.Equal(t, 0, n)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/codec.go",
    "content": "package sudoku\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nvar perm4 = [24][4]byte{\n\t{0, 1, 2, 3},\n\t{0, 1, 3, 2},\n\t{0, 2, 1, 3},\n\t{0, 2, 3, 1},\n\t{0, 3, 1, 2},\n\t{0, 3, 2, 1},\n\t{1, 0, 2, 3},\n\t{1, 0, 3, 2},\n\t{1, 2, 0, 3},\n\t{1, 2, 3, 0},\n\t{1, 3, 0, 2},\n\t{1, 3, 2, 0},\n\t{2, 0, 1, 3},\n\t{2, 0, 3, 1},\n\t{2, 1, 0, 3},\n\t{2, 1, 3, 0},\n\t{2, 3, 0, 1},\n\t{2, 3, 1, 0},\n\t{3, 0, 1, 2},\n\t{3, 0, 2, 1},\n\t{3, 1, 0, 2},\n\t{3, 1, 2, 0},\n\t{3, 2, 0, 1},\n\t{3, 2, 1, 0},\n}\n\ntype codec struct {\n\ttables        []*table\n\trng           *rand.Rand\n\tpaddingChance int\n\ttableIndex    int\n}\n\nfunc newCodec(tables []*table, pMin, pMax int) *codec {\n\tif len(tables) == 0 {\n\t\ttables = nil\n\t}\n\trng := newSeededRand()\n\treturn &codec{\n\t\ttables:        tables,\n\t\trng:           rng,\n\t\tpaddingChance: pickPaddingChance(rng, pMin, pMax),\n\t}\n}\n\nfunc pickPaddingChance(rng *rand.Rand, pMin, pMax int) int {\n\tif pMin < 0 {\n\t\tpMin = 0\n\t}\n\tif pMax < pMin {\n\t\tpMax = pMin\n\t}\n\tif pMin > 100 {\n\t\tpMin = 100\n\t}\n\tif pMax > 100 {\n\t\tpMax = 100\n\t}\n\tif pMax == pMin {\n\t\treturn pMin\n\t}\n\treturn pMin + rng.Intn(pMax-pMin+1)\n}\n\nfunc (c *codec) shouldPad() bool {\n\tif c.paddingChance <= 0 {\n\t\treturn false\n\t}\n\tif c.paddingChance >= 100 {\n\t\treturn true\n\t}\n\treturn c.rng.Intn(100) < c.paddingChance\n}\n\nfunc (c *codec) currentTable() *table {\n\tif len(c.tables) == 0 {\n\t\treturn nil\n\t}\n\treturn c.tables[c.tableIndex%len(c.tables)]\n}\n\nfunc (c *codec) randomPadding(t *table) byte {\n\tpool := t.layout.paddingPool\n\treturn pool[c.rng.Intn(len(pool))]\n}\n\nfunc (c *codec) encode(in []byte) ([]byte, error) {\n\tif len(in) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tout := make([]byte, 0, len(in)*6+8)\n\tfor _, b := range in {\n\t\tt := c.currentTable()\n\t\tif t == nil {\n\t\t\treturn nil, fmt.Errorf(\"sudoku table set missing\")\n\t\t}\n\t\tif c.shouldPad() {\n\t\t\tout = append(out, c.randomPadding(t))\n\t\t}\n\n\t\tenc := t.encode[b]\n\t\tif len(enc) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"sudoku encode table missing for byte %d\", b)\n\t\t}\n\n\t\thints := enc[c.rng.Intn(len(enc))]\n\t\tperm := perm4[c.rng.Intn(len(perm4))]\n\t\tfor _, idx := range perm {\n\t\t\tif c.shouldPad() {\n\t\t\t\tout = append(out, c.randomPadding(t))\n\t\t\t}\n\t\t\tout = append(out, hints[idx])\n\t\t}\n\t\tc.tableIndex++\n\t}\n\n\tif c.shouldPad() {\n\t\tif t := c.currentTable(); t != nil {\n\t\t\tout = append(out, c.randomPadding(t))\n\t\t}\n\t}\n\n\treturn out, nil\n}\n\nfunc decodeBytes(tables []*table, tableIndex *int, in []byte, hintBuf []byte, out []byte) ([]byte, []byte, error) {\n\tif len(tables) == 0 {\n\t\treturn hintBuf, out, fmt.Errorf(\"sudoku table set missing\")\n\t}\n\tfor _, b := range in {\n\t\tt := tables[*tableIndex%len(tables)]\n\t\tif !t.layout.isHint(b) {\n\t\t\tcontinue\n\t\t}\n\n\t\thintBuf = append(hintBuf, b)\n\t\tif len(hintBuf) < 4 {\n\t\t\tcontinue\n\t\t}\n\n\t\tkeyBytes := sort4([4]byte{hintBuf[0], hintBuf[1], hintBuf[2], hintBuf[3]})\n\t\tkey := packKey(keyBytes)\n\t\tdecoded, ok := t.decode[key]\n\t\tif !ok {\n\t\t\treturn hintBuf[:0], out, fmt.Errorf(\"invalid sudoku hint tuple\")\n\t\t}\n\n\t\tout = append(out, decoded)\n\t\thintBuf = hintBuf[:0]\n\t\t*tableIndex++\n\t}\n\n\treturn hintBuf, out, nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/config.go",
    "content": "package sudoku\n\nimport (\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nfunc (c *Config) TCP() {\n}\n\nfunc (c *Config) UDP() {\n}\n\n// Sudoku in finalmask mode is a pure appearance transform with no standalone handshake.\n// TCP always keeps classic sudoku on uplink and uses packed downlink optimization on server writes.\nfunc (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {\n\treturn newPackedDirectionalConn(raw, c, true)\n}\n\nfunc (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {\n\treturn newPackedDirectionalConn(raw, c, false)\n}\n\nfunc newPackedDirectionalConn(raw net.Conn, config *Config, readPacked bool) (net.Conn, error) {\n\tpureReader, pureWriter, err := newPureReaderWriter(raw, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpackedReader, packedWriter, err := newPackedReaderWriter(raw, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treader, writer := pureReader, pureWriter\n\tif readPacked {\n\t\treader = packedReader\n\t} else {\n\t\twriter = packedWriter\n\t}\n\n\treturn newWrappedConn(raw, reader, writer), nil\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\tif level != levelCount {\n\t\treturn nil, errors.New(\"sudoku udp mask must be the innermost mask in chain\")\n\t}\n\treturn NewUDPConn(raw, c)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\tif level != levelCount {\n\t\treturn nil, errors.New(\"sudoku udp mask must be the innermost mask in chain\")\n\t}\n\treturn NewUDPConn(raw, c)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/sudoku/config.proto\n\npackage sudoku\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      string                 `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tAscii         string                 `protobuf:\"bytes,2,opt,name=ascii,proto3\" json:\"ascii,omitempty\"`\n\tCustomTable   string                 `protobuf:\"bytes,3,opt,name=custom_table,json=customTable,proto3\" json:\"custom_table,omitempty\"`\n\tPaddingMin    uint32                 `protobuf:\"varint,4,opt,name=padding_min,json=paddingMin,proto3\" json:\"padding_min,omitempty\"`\n\tPaddingMax    uint32                 `protobuf:\"varint,5,opt,name=padding_max,json=paddingMax,proto3\" json:\"padding_max,omitempty\"`\n\tCustomTables  []string               `protobuf:\"bytes,7,rep,name=custom_tables,json=customTables,proto3\" json:\"custom_tables,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_sudoku_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_sudoku_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_sudoku_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetAscii() string {\n\tif x != nil {\n\t\treturn x.Ascii\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetCustomTable() string {\n\tif x != nil {\n\t\treturn x.CustomTable\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetPaddingMin() uint32 {\n\tif x != nil {\n\t\treturn x.PaddingMin\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetPaddingMax() uint32 {\n\tif x != nil {\n\t\treturn x.PaddingMax\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetCustomTables() []string {\n\tif x != nil {\n\t\treturn x.CustomTables\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_finalmask_sudoku_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_sudoku_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"0transport/internet/finalmask/sudoku/config.proto\\x12(xray.transport.internet.finalmask.sudoku\\\"\\xc4\\x01\\n\" +\n\t\"\\x06Config\\x12\\x1a\\n\" +\n\t\"\\bpassword\\x18\\x01 \\x01(\\tR\\bpassword\\x12\\x14\\n\" +\n\t\"\\x05ascii\\x18\\x02 \\x01(\\tR\\x05ascii\\x12!\\n\" +\n\t\"\\fcustom_table\\x18\\x03 \\x01(\\tR\\vcustomTable\\x12\\x1f\\n\" +\n\t\"\\vpadding_min\\x18\\x04 \\x01(\\rR\\n\" +\n\t\"paddingMin\\x12\\x1f\\n\" +\n\t\"\\vpadding_max\\x18\\x05 \\x01(\\rR\\n\" +\n\t\"paddingMax\\x12#\\n\" +\n\t\"\\rcustom_tables\\x18\\a \\x03(\\tR\\fcustomTablesB\\x9a\\x01\\n\" +\n\t\",com.xray.transport.internet.finalmask.sudokuP\\x01Z=github.com/xtls/xray-core/transport/internet/finalmask/sudoku\\xaa\\x02(Xray.Transport.Internet.Finalmask.Sudokub\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_sudoku_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_sudoku_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_sudoku_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_sudoku_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_sudoku_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_sudoku_config_proto_rawDesc), len(file_transport_internet_finalmask_sudoku_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_sudoku_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_sudoku_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_sudoku_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.sudoku.Config\n}\nvar file_transport_internet_finalmask_sudoku_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_sudoku_config_proto_init() }\nfunc file_transport_internet_finalmask_sudoku_config_proto_init() {\n\tif File_transport_internet_finalmask_sudoku_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_sudoku_config_proto_rawDesc), len(file_transport_internet_finalmask_sudoku_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_sudoku_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_sudoku_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_sudoku_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_sudoku_config_proto = out.File\n\tfile_transport_internet_finalmask_sudoku_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_sudoku_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.sudoku;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Sudoku\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/sudoku\";\noption java_package = \"com.xray.transport.internet.finalmask.sudoku\";\noption java_multiple_files = true;\n\nmessage Config {\n  string password = 1;\n  string ascii = 2;\n  string custom_table = 3;\n  uint32 padding_min = 4;\n  uint32 padding_max = 5;\n  repeated string custom_tables = 7;\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/conn_tcp.go",
    "content": "package sudoku\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\nconst ioBufferSize = 32 * 1024\n\nvar _ finalmask.TcpMaskConn = (*wrappedConn)(nil)\n\ntype streamDecoder interface {\n\tdecodeChunk(in []byte, pending []byte) ([]byte, error)\n\treset()\n}\n\ntype streamReader struct {\n\treader  *bufio.Reader\n\trawBuf  []byte\n\tpending []byte\n\tdecode  streamDecoder\n\tmu      sync.Mutex\n}\n\nfunc newStreamReader(raw net.Conn, decode streamDecoder) io.Reader {\n\treturn &streamReader{\n\t\treader:  bufio.NewReaderSize(raw, ioBufferSize),\n\t\trawBuf:  make([]byte, ioBufferSize),\n\t\tpending: make([]byte, 0, 4096),\n\t\tdecode:  decode,\n\t}\n}\n\nfunc (r *streamReader) Read(p []byte) (int, error) {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif n, ok := drainPending(p, &r.pending); ok {\n\t\treturn n, nil\n\t}\n\n\tfor len(r.pending) == 0 {\n\t\tnr, rErr := r.reader.Read(r.rawBuf)\n\t\tif nr > 0 {\n\t\t\tvar dErr error\n\t\t\tr.pending, dErr = r.decode.decodeChunk(r.rawBuf[:nr], r.pending)\n\t\t\tif dErr != nil {\n\t\t\t\treturn 0, dErr\n\t\t\t}\n\t\t}\n\n\t\tif rErr != nil {\n\t\t\tif rErr == io.EOF {\n\t\t\t\tr.decode.reset()\n\t\t\t\tif len(r.pending) > 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0, rErr\n\t\t}\n\t}\n\n\tn, _ := drainPending(p, &r.pending)\n\treturn n, nil\n}\n\ntype streamWriter struct {\n\tconn   net.Conn\n\tencode func([]byte) ([]byte, error)\n\tmu     sync.Mutex\n}\n\nfunc newStreamWriter(raw net.Conn, encode func([]byte) ([]byte, error)) io.Writer {\n\treturn &streamWriter{\n\t\tconn:   raw,\n\t\tencode: encode,\n\t}\n}\n\nfunc (w *streamWriter) Write(p []byte) (int, error) {\n\tif len(p) == 0 {\n\t\treturn 0, nil\n\t}\n\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tencoded, err := w.encode(p)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif err := writeAll(w.conn, encoded); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(p), nil\n}\n\ntype wrappedConn struct {\n\tnet.Conn\n\treader io.Reader\n\twriter io.Writer\n}\n\ntype closeWriteConn interface {\n\tCloseWrite() error\n}\n\nfunc newWrappedConn(raw net.Conn, reader io.Reader, writer io.Writer) net.Conn {\n\treturn &wrappedConn{\n\t\tConn:   raw,\n\t\treader: reader,\n\t\twriter: writer,\n\t}\n}\n\nfunc (c *wrappedConn) Read(p []byte) (int, error) {\n\treturn c.reader.Read(p)\n}\n\nfunc (c *wrappedConn) Write(p []byte) (int, error) {\n\treturn c.writer.Write(p)\n}\n\nfunc (c *wrappedConn) TcpMaskConn() {}\n\nfunc (c *wrappedConn) RawConn() net.Conn {\n\treturn c.Conn\n}\n\nfunc (c *wrappedConn) Splice() bool {\n\t// Sudoku transforms the entire stream; bypassing it would disable masking.\n\treturn false\n}\n\nfunc (c *wrappedConn) CloseWrite() error {\n\tif raw, ok := c.Conn.(closeWriteConn); ok {\n\t\treturn raw.CloseWrite()\n\t}\n\treturn net.ErrClosed\n}\n\nfunc NewTCPConn(raw net.Conn, config *Config) (net.Conn, error) {\n\treader, writer, err := newPureReaderWriter(raw, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newWrappedConn(raw, reader, writer), nil\n}\n\nfunc newPureReaderWriter(raw net.Conn, config *Config) (io.Reader, io.Writer, error) {\n\ttables, err := getTables(config)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tpMin, pMax := normalizedPadding(config)\n\tc := newCodec(tables, pMin, pMax)\n\treturn newStreamReader(raw, newHintStreamDecoder(tables)), newStreamWriter(raw, c.encode), nil\n}\n\ntype hintStreamDecoder struct {\n\ttables     []*table\n\ttableIndex int\n\thintBuf    []byte\n}\n\nfunc newHintStreamDecoder(tables []*table) *hintStreamDecoder {\n\treturn &hintStreamDecoder{\n\t\ttables:  tables,\n\t\thintBuf: make([]byte, 0, 4),\n\t}\n}\n\nfunc (d *hintStreamDecoder) decodeChunk(in []byte, pending []byte) ([]byte, error) {\n\tvar err error\n\td.hintBuf, pending, err = decodeBytes(d.tables, &d.tableIndex, in, d.hintBuf, pending)\n\treturn pending, err\n}\n\nfunc (d *hintStreamDecoder) reset() {}\n\nfunc drainPending(p []byte, pending *[]byte) (int, bool) {\n\tif len(*pending) == 0 {\n\t\treturn 0, false\n\t}\n\n\tn := copy(p, *pending)\n\tif n >= len(*pending) {\n\t\t*pending = (*pending)[:0]\n\t\treturn n, true\n\t}\n\n\tremaining := len(*pending) - n\n\tcopy(*pending, (*pending)[n:])\n\t*pending = (*pending)[:remaining]\n\treturn n, true\n}\n\nfunc writeAll(conn net.Conn, b []byte) error {\n\tfor len(b) > 0 {\n\t\tn, err := conn.Write(b)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tb = b[n:]\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/conn_tcp_packed.go",
    "content": "package sudoku\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n)\n\ntype packedEncoder struct {\n\tlayouts    []*byteLayout\n\tcodec      *codec\n\tgroupIndex int\n}\n\nfunc newPackedEncoder(tables []*table, pMin, pMax int) *packedEncoder {\n\tlayouts := make([]*byteLayout, 0, len(tables))\n\tfor _, t := range tables {\n\t\tlayouts = append(layouts, t.layout)\n\t}\n\tif len(layouts) == 0 {\n\t\tlayouts = append(layouts, entropyLayout())\n\t}\n\treturn &packedEncoder{\n\t\tlayouts: layouts,\n\t\tcodec:   newCodec(nil, pMin, pMax),\n\t}\n}\n\nfunc (e *packedEncoder) encode(p []byte) ([]byte, error) {\n\tout := make([]byte, 0, len(p)*2+8)\n\tvar bitBuf uint64\n\tvar bitCount uint8\n\n\tfor _, b := range p {\n\t\tbitBuf = (bitBuf << 8) | uint64(b)\n\t\tbitCount += 8\n\n\t\tfor bitCount >= 6 {\n\t\t\tbitCount -= 6\n\t\t\tlayout := e.layouts[e.groupIndex%len(e.layouts)]\n\t\t\tgroup := byte(bitBuf >> bitCount)\n\t\t\tout = e.maybePad(out, layout)\n\t\t\tout = append(out, layout.encodeGroup(group&0x3f))\n\t\t\te.groupIndex++\n\t\t\tif bitCount > 0 {\n\t\t\t\tbitBuf &= (uint64(1) << bitCount) - 1\n\t\t\t} else {\n\t\t\t\tbitBuf = 0\n\t\t\t}\n\t\t}\n\t}\n\n\tif bitCount > 0 {\n\t\tlayout := e.layouts[e.groupIndex%len(e.layouts)]\n\t\tgroup := byte(bitBuf << (6 - bitCount))\n\t\tout = e.maybePad(out, layout)\n\t\tout = append(out, layout.encodeGroup(group&0x3f))\n\t\te.groupIndex++\n\t\tnextLayout := e.layouts[e.groupIndex%len(e.layouts)]\n\t\tout = append(out, nextLayout.padMarker)\n\t}\n\n\tout = e.maybePad(out, e.layouts[e.groupIndex%len(e.layouts)])\n\treturn out, nil\n}\n\nfunc (e *packedEncoder) maybePad(out []byte, layout *byteLayout) []byte {\n\tif !e.codec.shouldPad() {\n\t\treturn out\n\t}\n\tif len(layout.paddingPool) == 1 {\n\t\treturn append(out, layout.paddingPool[0])\n\t}\n\tfor {\n\t\tb := layout.paddingPool[e.codec.rng.Intn(len(layout.paddingPool))]\n\t\tif b != layout.padMarker {\n\t\t\treturn append(out, b)\n\t\t}\n\t}\n}\n\ntype packedStreamDecoder struct {\n\tlayouts    []*byteLayout\n\tgroupIndex int\n\tbitBuf     uint64\n\tbitCount   int\n}\n\nfunc (d *packedStreamDecoder) decodeChunk(in []byte, pending []byte) ([]byte, error) {\n\tvar err error\n\td.bitBuf, d.bitCount, d.groupIndex, pending, err = decodePackedBytes(\n\t\td.layouts,\n\t\tin,\n\t\td.bitBuf,\n\t\td.bitCount,\n\t\td.groupIndex,\n\t\tpending,\n\t)\n\treturn pending, err\n}\n\nfunc (d *packedStreamDecoder) reset() {\n\td.bitBuf = 0\n\td.bitCount = 0\n}\n\nfunc NewPackedTCPConn(raw net.Conn, config *Config) (net.Conn, error) {\n\treader, writer, err := newPackedReaderWriter(raw, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newWrappedConn(raw, reader, writer), nil\n}\n\nfunc newPackedReaderWriter(raw net.Conn, config *Config) (io.Reader, io.Writer, error) {\n\ttables, err := getTables(config)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tpMin, pMax := normalizedPadding(config)\n\tencoder := newPackedEncoder(tables, pMin, pMax)\n\tdecoder := &packedStreamDecoder{\n\t\tlayouts: tablesToLayouts(tables),\n\t}\n\treturn newStreamReader(raw, decoder), newStreamWriter(raw, encoder.encode), nil\n}\n\nfunc tablesToLayouts(tables []*table) []*byteLayout {\n\tlayouts := make([]*byteLayout, 0, len(tables))\n\tfor _, t := range tables {\n\t\tlayouts = append(layouts, t.layout)\n\t}\n\tif len(layouts) == 0 {\n\t\tlayouts = append(layouts, entropyLayout())\n\t}\n\treturn layouts\n}\n\nfunc decodePackedBytes(\n\tlayouts []*byteLayout,\n\tin []byte,\n\tbitBuf uint64,\n\tbitCount int,\n\tgroupIndex int,\n\tout []byte,\n) (uint64, int, int, []byte, error) {\n\tif len(layouts) == 0 {\n\t\treturn bitBuf, bitCount, groupIndex, out, fmt.Errorf(\"sudoku layout set missing\")\n\t}\n\tfor _, b := range in {\n\t\tlayout := layouts[groupIndex%len(layouts)]\n\t\tif !layout.isHint(b) {\n\t\t\tif b == layout.padMarker {\n\t\t\t\tbitBuf = 0\n\t\t\t\tbitCount = 0\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tgroup, ok := layout.decodeGroup(b)\n\t\tif !ok {\n\t\t\treturn bitBuf, bitCount, groupIndex, out, fmt.Errorf(\"invalid packed sudoku byte: %d\", b)\n\t\t}\n\t\tgroupIndex++\n\n\t\tbitBuf = (bitBuf << 6) | uint64(group)\n\t\tbitCount += 6\n\n\t\tfor bitCount >= 8 {\n\t\t\tbitCount -= 8\n\t\t\tout = append(out, byte(bitBuf>>bitCount))\n\t\t\tif bitCount > 0 {\n\t\t\t\tbitBuf &= (uint64(1) << bitCount) - 1\n\t\t\t} else {\n\t\t\t\tbitBuf = 0\n\t\t\t}\n\t\t}\n\t}\n\n\treturn bitBuf, bitCount, groupIndex, out, nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/conn_udp.go",
    "content": "package sudoku\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype udpConn struct {\n\tconn   net.PacketConn\n\ttables []*table\n\tpMin   int\n\tpMax   int\n\n\treadBuf []byte\n\n\treadMu  sync.Mutex\n\twriteMu sync.Mutex\n}\n\nfunc NewUDPConn(raw net.PacketConn, config *Config) (net.PacketConn, error) {\n\ttables, err := getTables(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpMin, pMax := normalizedPadding(config)\n\treturn &udpConn{\n\t\tconn:    raw,\n\t\ttables:  tables,\n\t\tpMin:    pMin,\n\t\tpMax:    pMax,\n\t\treadBuf: make([]byte, 65535),\n\t}, nil\n}\n\nfunc (c *udpConn) Size() int32 {\n\treturn 0\n}\n\nfunc (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tc.readMu.Lock()\n\tdefer c.readMu.Unlock()\n\n\tn, addr, err = c.conn.ReadFrom(c.readBuf)\n\tif err != nil {\n\t\treturn n, addr, err\n\t}\n\n\tdecoded := make([]byte, 0, n/4+1)\n\thints := make([]byte, 0, 4)\n\ttableIndex := 0\n\thints, decoded, err = decodeBytes(c.tables, &tableIndex, c.readBuf[:n], hints, decoded)\n\tif err != nil {\n\t\treturn 0, addr, err\n\t}\n\tif len(hints) != 0 {\n\t\treturn 0, addr, io.ErrUnexpectedEOF\n\t}\n\tif len(p) < len(decoded) {\n\t\treturn 0, addr, io.ErrShortBuffer\n\t}\n\tcopy(p, decoded)\n\treturn len(decoded), addr, nil\n}\n\nfunc (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tc.writeMu.Lock()\n\tdefer c.writeMu.Unlock()\n\n\t// UDP decoding restarts at table 0 for every datagram, so encoding must do the same.\n\tencoded, err := newCodec(c.tables, c.pMin, c.pMax).encode(p)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tnn, err := c.conn.WriteTo(encoded, addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif nn != len(encoded) {\n\t\treturn 0, io.ErrShortWrite\n\t}\n\treturn len(p), nil\n}\n\nfunc (c *udpConn) Close() error {\n\treturn c.conn.Close()\n}\n\nfunc (c *udpConn) LocalAddr() net.Addr {\n\treturn c.conn.LocalAddr()\n}\n\nfunc (c *udpConn) SetDeadline(t time.Time) error {\n\treturn c.conn.SetDeadline(t)\n}\n\nfunc (c *udpConn) SetReadDeadline(t time.Time) error {\n\treturn c.conn.SetReadDeadline(t)\n}\n\nfunc (c *udpConn) SetWriteDeadline(t time.Time) error {\n\treturn c.conn.SetWriteDeadline(t)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/sudoku_test.go",
    "content": "package sudoku\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdh\"\n\t\"crypto/rand\"\n\tcryptotls \"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\tstdnet \"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/app/log\"\n\t\"github.com/xtls/xray-core/app/proxyman\"\n\tclog \"github.com/xtls/xray-core/common/log\"\n\txnet \"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/common/serial\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\tcore \"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/proxy/dokodemo\"\n\t\"github.com/xtls/xray-core/proxy/freedom\"\n\thyproxy \"github.com/xtls/xray-core/proxy/hysteria\"\n\thyaccount \"github.com/xtls/xray-core/proxy/hysteria/account\"\n\t\"github.com/xtls/xray-core/proxy/vless\"\n\tvin \"github.com/xtls/xray-core/proxy/vless/inbound\"\n\tvout \"github.com/xtls/xray-core/proxy/vless/outbound\"\n\ttestingtcp \"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\thytransport \"github.com/xtls/xray-core/transport/internet/hysteria\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\tsplithttp \"github.com/xtls/xray-core/transport/internet/splithttp\"\n\ttranstcp \"github.com/xtls/xray-core/transport/internet/tcp\"\n\txtls \"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar (\n\te2eBinaryOnce sync.Once\n\te2eBinaryPath string\n\te2eBinaryErr  error\n)\n\ntype trafficMode struct {\n\tname   string\n\tconfig *Config\n}\n\ntype protocolCase struct {\n\tname      string\n\ttransport string\n\trun       func(t *testing.T, bin string, mode trafficMode) caseResult\n}\n\ntype caseResult struct {\n\tProtocol         string\n\tMode             string\n\tTotalBytes       int\n\tASCIIBytes       int\n\tASCIIRatio       float64\n\tAvgHammingOnes   float64\n\tRotationSeen     int\n\tRotationExpected int\n\tDecodedUnits     int\n\tClientToServer   directionResult\n\tServerToClient   directionResult\n}\n\ntype directionResult struct {\n\tRawBytes       int\n\tASCIIBytes     int\n\tASCIIRatio     float64\n\tAvgHammingOnes float64\n\tRotationSeen   int\n\tDecodedUnits   int\n}\n\ntype tcpRelay struct {\n\tlistener stdnet.Listener\n\ttarget   string\n\n\tmu       sync.Mutex\n\tcaptures []*tcpCapture\n\twg       sync.WaitGroup\n\tstopCh   chan struct{}\n}\n\ntype tcpCapture struct {\n\tmu  sync.Mutex\n\tc2s []byte\n\ts2c []byte\n}\n\ntype udpRelay struct {\n\tconn      stdnet.PacketConn\n\ttarget    *stdnet.UDPAddr\n\tclientMu  sync.Mutex\n\tclient    *stdnet.UDPAddr\n\tstopCh    chan struct{}\n\twg        sync.WaitGroup\n\tcaptureMu sync.Mutex\n\tc2s       [][]byte\n\ts2c       [][]byte\n}\n\ntype tlsDecoy struct {\n\tln   stdnet.Listener\n\tdone chan struct{}\n\twg   sync.WaitGroup\n}\n\nfunc TestSudokuE2ETemp(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skipping sudoku e2e harness in short mode\")\n\t}\n\n\tbin := buildE2EBinary(t)\n\tpayloadSize := 192 * 1024\n\tmodes := []trafficMode{\n\t\t{\n\t\t\tname: \"prefer_ascii\",\n\t\t\tconfig: &Config{\n\t\t\t\tPassword: \"sudoku-e2e-shared-secret\",\n\t\t\t\tAscii:    \"prefer_ascii\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"prefer_entropy\",\n\t\t\tconfig: &Config{\n\t\t\t\tPassword: \"sudoku-e2e-shared-secret\",\n\t\t\t\tAscii:    \"prefer_entropy\",\n\t\t\t\tCustomTables: []string{\n\t\t\t\t\t\"xpxvvpvv\",\n\t\t\t\t\t\"vxpvxvvp\",\n\t\t\t\t\t\"pxvvxvvp\",\n\t\t\t\t\t\"vpxvxvpv\",\n\t\t\t\t\t\"xvpvvxpv\",\n\t\t\t\t\t\"vvxpxpvv\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tcases := []protocolCase{\n\t\t{name: \"vless-reality\", transport: \"tcp\", run: func(t *testing.T, bin string, mode trafficMode) caseResult {\n\t\t\treturn runVLESSRealityCase(t, bin, mode, payloadSize)\n\t\t}},\n\t\t{name: \"hysteria2\", transport: \"udp\", run: func(t *testing.T, bin string, mode trafficMode) caseResult {\n\t\t\treturn runHysteria2Case(t, bin, mode, payloadSize)\n\t\t}},\n\t\t{name: \"vless-enc\", transport: \"tcp\", run: func(t *testing.T, bin string, mode trafficMode) caseResult {\n\t\t\treturn runVLesseEncCase(t, bin, mode, payloadSize)\n\t\t}},\n\t\t{name: \"vless-xhttp\", transport: \"tcp\", run: func(t *testing.T, bin string, mode trafficMode) caseResult {\n\t\t\treturn runVLESSXHTTPCase(t, bin, mode, payloadSize)\n\t\t}},\n\t}\n\n\tresults := make([]caseResult, 0, len(cases)*len(modes))\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfor _, mode := range modes {\n\t\t\t\tmode := mode\n\t\t\t\tt.Run(mode.name, func(t *testing.T) {\n\t\t\t\t\tresult := tc.run(t, bin, mode)\n\t\t\t\t\tif mode.name == \"prefer_ascii\" && result.ASCIIRatio < 0.97 {\n\t\t\t\t\t\tt.Fatalf(\"%s %s ascii ratio %.4f < 0.97\", tc.name, mode.name, result.ASCIIRatio)\n\t\t\t\t\t}\n\t\t\t\t\tif mode.name == \"prefer_entropy\" {\n\t\t\t\t\t\tif result.RotationSeen != result.RotationExpected {\n\t\t\t\t\t\t\tt.Fatalf(\"%s %s saw %d/%d rotation tables\", tc.name, mode.name, result.RotationSeen, result.RotationExpected)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif diff := result.AvgHammingOnes - 5.0; diff < -0.3 || diff > 0.3 {\n\t\t\t\t\t\t\tt.Fatalf(\"%s %s average ones %.4f too far from 5\", tc.name, mode.name, result.AvgHammingOnes)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tt.Logf(\n\t\t\t\t\t\t\"%s %s total=%d ascii=%.4f avg_ones=%.4f rotation=%d/%d c2s_ascii=%.4f s2c_ascii=%.4f\",\n\t\t\t\t\t\ttc.name,\n\t\t\t\t\t\tmode.name,\n\t\t\t\t\t\tresult.TotalBytes,\n\t\t\t\t\t\tresult.ASCIIRatio,\n\t\t\t\t\t\tresult.AvgHammingOnes,\n\t\t\t\t\t\tresult.RotationSeen,\n\t\t\t\t\t\tresult.RotationExpected,\n\t\t\t\t\t\tresult.ClientToServer.ASCIIRatio,\n\t\t\t\t\t\tresult.ServerToClient.ASCIIRatio,\n\t\t\t\t\t)\n\t\t\t\t\tresults = append(results, result)\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n\n\tfor _, result := range results {\n\t\tt.Logf(\n\t\t\t\"summary protocol=%s mode=%s bytes=%d ascii=%.4f avg_ones=%.4f rotation=%d/%d decoded=%d\",\n\t\t\tresult.Protocol,\n\t\t\tresult.Mode,\n\t\t\tresult.TotalBytes,\n\t\t\tresult.ASCIIRatio,\n\t\t\tresult.AvgHammingOnes,\n\t\t\tresult.RotationSeen,\n\t\t\tresult.RotationExpected,\n\t\t\tresult.DecodedUnits,\n\t\t)\n\t}\n}\n\nfunc runVLESSRealityCase(t *testing.T, bin string, mode trafficMode, payloadSize int) caseResult {\n\tbackend := startXOREchoServer(t)\n\tdefer backend.Close()\n\n\tdecoyCert, _ := cert.MustGenerate(nil, cert.CommonName(\"localhost\"), cert.DNSNames(\"localhost\"))\n\tdecoy := startTLSEchoDecoy(t, decoyCert)\n\tdefer decoy.Close()\n\n\tserverPort := testingtcp.PickPort()\n\trelayPort := testingtcp.PickPort()\n\tclientPort := testingtcp.PickPort()\n\n\trelay := startTCPRelay(t, int(relayPort), fmt.Sprintf(\"127.0.0.1:%d\", serverPort))\n\tdefer relay.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\trealityPriv, realityPub := mustX25519Keypair(t)\n\tshortID := mustDecodeHex(t, \"0123456789abcdef\")\n\n\tserverConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&transtcp.Config{}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&reality.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&reality.Config{\n\t\t\t\t\t\t\t\tShow:        true,\n\t\t\t\t\t\t\t\tDest:        fmt.Sprintf(\"localhost:%d\", decoy.Port()),\n\t\t\t\t\t\t\t\tServerNames: []string{\"localhost\"},\n\t\t\t\t\t\t\t\tPrivateKey:  realityPriv,\n\t\t\t\t\t\t\t\tShortIds:    [][]byte{shortID},\n\t\t\t\t\t\t\t\tType:        \"tcp\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTcpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&vin.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},\n\t\t},\n\t})\n\n\tclientConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  xnet.NewIPOrDomain(backend.Address()),\n\t\t\t\t\tPort:     uint32(backend.Port()),\n\t\t\t\t\tNetworks: []xnet.Network{xnet.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&vout.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(relayPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\t\t\tSettings:     serial.ToTypedMessage(&transtcp.Config{}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&reality.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&reality.Config{\n\t\t\t\t\t\t\t\tShow:        true,\n\t\t\t\t\t\t\t\tFingerprint: \"chrome\",\n\t\t\t\t\t\t\t\tServerName:  \"localhost\",\n\t\t\t\t\t\t\t\tPublicKey:   realityPub,\n\t\t\t\t\t\t\t\tShortId:     shortID,\n\t\t\t\t\t\t\t\tSpiderX:     \"/\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTcpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t})\n\n\tserverCmd, clientCmd := runXrayPair(t, bin, serverConfig, clientConfig)\n\tdefer stopCmd(clientCmd)\n\tdefer stopCmd(serverCmd)\n\texerciseTCPClient(t, int(clientPort), payloadSize)\n\n\treturn analyzeTCPRelay(t, \"vless-reality\", mode, relay.Snapshots())\n}\n\nfunc runHysteria2Case(t *testing.T, bin string, mode trafficMode, payloadSize int) caseResult {\n\tbackend := startXOREchoServer(t)\n\tdefer backend.Close()\n\n\tserverPort := testingtcp.PickPort()\n\trelayPort := testingtcp.PickPort()\n\tclientPort := testingtcp.PickPort()\n\n\trelay := startUDPRelay(t, int(relayPort), int(serverPort))\n\tdefer relay.Close()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"), cert.DNSNames(\"localhost\"))\n\tauth := \"hy2-auth-secret\"\n\n\tserverConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"hysteria\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"hysteria\",\n\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&hytransport.Config{\n\t\t\t\t\t\t\t\t\tVersion:        2,\n\t\t\t\t\t\t\t\t\tAuth:           auth,\n\t\t\t\t\t\t\t\t\tUdpIdleTimeout: 60,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&xtls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&xtls.Config{\n\t\t\t\t\t\t\t\tCertificate:  []*xtls.Certificate{xtls.ParseCertificate(ct)},\n\t\t\t\t\t\t\t\tNextProtocol: []string{\"h3\"},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tUdpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&hyproxy.ServerConfig{\n\t\t\t\t\tUsers: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&hyaccount.Account{Auth: auth}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},\n\t\t},\n\t})\n\n\tclientConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  xnet.NewIPOrDomain(backend.Address()),\n\t\t\t\t\tPort:     uint32(backend.Port()),\n\t\t\t\t\tNetworks: []xnet.Network{xnet.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&hyproxy.ClientConfig{\n\t\t\t\t\tVersion: 2,\n\t\t\t\t\tServer: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(relayPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&hyaccount.Account{Auth: auth}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"hysteria\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tProtocolName: \"hysteria\",\n\t\t\t\t\t\t\t\tSettings: serial.ToTypedMessage(&hytransport.Config{\n\t\t\t\t\t\t\t\t\tVersion:        2,\n\t\t\t\t\t\t\t\t\tAuth:           auth,\n\t\t\t\t\t\t\t\t\tUdpIdleTimeout: 60,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSecurityType: serial.GetMessageType(&xtls.Config{}),\n\t\t\t\t\t\tSecuritySettings: []*serial.TypedMessage{\n\t\t\t\t\t\t\tserial.ToTypedMessage(&xtls.Config{\n\t\t\t\t\t\t\t\tServerName:           \"localhost\",\n\t\t\t\t\t\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\t\t\t\t\t\tNextProtocol:         []string{\"h3\"},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tUdpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t})\n\n\tserverCmd, clientCmd := runXrayPair(t, bin, serverConfig, clientConfig)\n\tdefer stopCmd(clientCmd)\n\tdefer stopCmd(serverCmd)\n\tif err := exerciseTCPClientErr(t, int(clientPort), payloadSize); err != nil {\n\t\tc2s, s2c := relay.Snapshots()\n\t\tt.Fatalf(\"hy2 traffic failed: %v (udp packets c2s=%d s2c=%d first_c2s=%d first_s2c=%d)\", err, len(c2s), len(s2c), firstChunkLen(c2s), firstChunkLen(s2c))\n\t}\n\n\tc2s, s2c := relay.Snapshots()\n\treturn analyzeUDPRelay(t, \"hysteria2\", mode, c2s, s2c)\n}\n\nfunc runVLesseEncCase(t *testing.T, bin string, mode trafficMode, payloadSize int) caseResult {\n\tbackend := startXOREchoServer(t)\n\tdefer backend.Close()\n\n\tserverPort := testingtcp.PickPort()\n\trelayPort := testingtcp.PickPort()\n\tclientPort := testingtcp.PickPort()\n\n\trelay := startTCPRelay(t, int(relayPort), fmt.Sprintf(\"127.0.0.1:%d\", serverPort))\n\tdefer relay.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\tpriv, pub := mustX25519Keypair(t)\n\tpubB64 := base64.RawURLEncoding.EncodeToString(pub)\n\tprivB64 := base64.RawURLEncoding.EncodeToString(priv)\n\n\tserverConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{ProtocolName: \"tcp\", Settings: serial.ToTypedMessage(&transtcp.Config{})},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTcpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&vin.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tDecryption:  privB64,\n\t\t\t\t\tXorMode:     1,\n\t\t\t\t\tSecondsFrom: 0,\n\t\t\t\t\tSecondsTo:   0,\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},\n\t\t},\n\t})\n\n\tclientConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  xnet.NewIPOrDomain(backend.Address()),\n\t\t\t\t\tPort:     uint32(backend.Port()),\n\t\t\t\t\tNetworks: []xnet.Network{xnet.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&vout.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(relayPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId:         userID.String(),\n\t\t\t\t\t\t\t\tEncryption: pubB64,\n\t\t\t\t\t\t\t\tXorMode:    1,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"tcp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{ProtocolName: \"tcp\", Settings: serial.ToTypedMessage(&transtcp.Config{})},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTcpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t})\n\n\tserverCmd, clientCmd := runXrayPair(t, bin, serverConfig, clientConfig)\n\tdefer stopCmd(clientCmd)\n\tdefer stopCmd(serverCmd)\n\texerciseTCPClient(t, int(clientPort), payloadSize)\n\n\treturn analyzeTCPRelay(t, \"vless-enc\", mode, relay.Snapshots())\n}\n\nfunc runVLESSXHTTPCase(t *testing.T, bin string, mode trafficMode, payloadSize int) caseResult {\n\tbackend := startXOREchoServer(t)\n\tdefer backend.Close()\n\n\tserverPort := testingtcp.PickPort()\n\trelayPort := testingtcp.PickPort()\n\tclientPort := testingtcp.PickPort()\n\n\trelay := startTCPRelay(t, int(relayPort), fmt.Sprintf(\"127.0.0.1:%d\", serverPort))\n\tdefer relay.Close()\n\n\tuserID := protocol.NewID(uuid.New())\n\txhttpConfig := &splithttp.Config{\n\t\tHost: \"localhost\",\n\t\tPath: \"/sudoku\",\n\t\tMode: \"auto\",\n\t}\n\n\tserverConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(serverPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"splithttp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{ProtocolName: \"splithttp\", Settings: serial.ToTypedMessage(xhttpConfig)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTcpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&vin.Config{\n\t\t\t\t\tClients: []*protocol.User{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{ProxySettings: serial.ToTypedMessage(&freedom.Config{})},\n\t\t},\n\t})\n\n\tclientConfig := defaultApps(&core.Config{\n\t\tInbound: []*core.InboundHandlerConfig{\n\t\t\t{\n\t\t\t\tReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{\n\t\t\t\t\tPortList: &xnet.PortList{Range: []*xnet.PortRange{xnet.SinglePortRange(clientPort)}},\n\t\t\t\t\tListen:   xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t}),\n\t\t\t\tProxySettings: serial.ToTypedMessage(&dokodemo.Config{\n\t\t\t\t\tAddress:  xnet.NewIPOrDomain(backend.Address()),\n\t\t\t\t\tPort:     uint32(backend.Port()),\n\t\t\t\t\tNetworks: []xnet.Network{xnet.Network_TCP},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tOutbound: []*core.OutboundHandlerConfig{\n\t\t\t{\n\t\t\t\tProxySettings: serial.ToTypedMessage(&vout.Config{\n\t\t\t\t\tVnext: &protocol.ServerEndpoint{\n\t\t\t\t\t\tAddress: xnet.NewIPOrDomain(xnet.LocalHostIP),\n\t\t\t\t\t\tPort:    uint32(relayPort),\n\t\t\t\t\t\tUser: &protocol.User{\n\t\t\t\t\t\t\tAccount: serial.ToTypedMessage(&vless.Account{\n\t\t\t\t\t\t\t\tId: userID.String(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tSenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{\n\t\t\t\t\tStreamSettings: &internet.StreamConfig{\n\t\t\t\t\t\tProtocolName: \"splithttp\",\n\t\t\t\t\t\tTransportSettings: []*internet.TransportConfig{\n\t\t\t\t\t\t\t{ProtocolName: \"splithttp\", Settings: serial.ToTypedMessage(xhttpConfig)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTcpmasks: []*serial.TypedMessage{serial.ToTypedMessage(cloneConfig(mode.config))},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t})\n\n\tserverCmd, clientCmd := runXrayPair(t, bin, serverConfig, clientConfig)\n\tdefer stopCmd(clientCmd)\n\tdefer stopCmd(serverCmd)\n\texerciseTCPClient(t, int(clientPort), payloadSize)\n\n\treturn analyzeTCPRelay(t, \"vless-xhttp\", mode, relay.Snapshots())\n}\n\nfunc analyzeTCPRelay(t *testing.T, protocol string, mode trafficMode, captures []*tcpCapture) caseResult {\n\ttables, err := getTables(mode.config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tallC2S := make([][]byte, 0, len(captures))\n\tallS2C := make([][]byte, 0, len(captures))\n\tfor _, capture := range captures {\n\t\tc2s, s2c := capture.snapshot()\n\t\tif len(c2s) > 0 {\n\t\t\tallC2S = append(allC2S, c2s)\n\t\t}\n\t\tif len(s2c) > 0 {\n\t\t\tallS2C = append(allS2C, s2c)\n\t\t}\n\t}\n\n\tc2sMetrics := metricFromBytes(flattenChunks(allC2S))\n\ts2cMetrics := metricFromBytes(flattenChunks(allS2C))\n\n\tc2sUsed, c2sDecoded, err := analyzePureChunks(tables, allC2S)\n\tif err != nil {\n\t\tt.Fatalf(\"%s %s pure decode failed: %v\", protocol, mode.name, err)\n\t}\n\ts2cUsed, s2cDecoded, err := analyzePackedChunks(tables, allS2C)\n\tif err != nil {\n\t\tt.Fatalf(\"%s %s packed decode failed: %v\", protocol, mode.name, err)\n\t}\n\n\tallBytes := append(append([]byte{}, flattenChunks(allC2S)...), flattenChunks(allS2C)...)\n\ttotalMetrics := metricFromBytes(allBytes)\n\trotationSeen := len(unionKeys(c2sUsed, s2cUsed))\n\n\treturn caseResult{\n\t\tProtocol:         protocol,\n\t\tMode:             mode.name,\n\t\tTotalBytes:       len(allBytes),\n\t\tASCIIBytes:       totalMetrics.asciiBytes,\n\t\tASCIIRatio:       totalMetrics.asciiRatio,\n\t\tAvgHammingOnes:   totalMetrics.avgOnes,\n\t\tRotationSeen:     rotationSeen,\n\t\tRotationExpected: expectedRotation(mode.config),\n\t\tDecodedUnits:     c2sDecoded + s2cDecoded,\n\t\tClientToServer: directionResult{\n\t\t\tRawBytes:       len(flattenChunks(allC2S)),\n\t\t\tASCIIBytes:     c2sMetrics.asciiBytes,\n\t\t\tASCIIRatio:     c2sMetrics.asciiRatio,\n\t\t\tAvgHammingOnes: c2sMetrics.avgOnes,\n\t\t\tRotationSeen:   len(c2sUsed),\n\t\t\tDecodedUnits:   c2sDecoded,\n\t\t},\n\t\tServerToClient: directionResult{\n\t\t\tRawBytes:       len(flattenChunks(allS2C)),\n\t\t\tASCIIBytes:     s2cMetrics.asciiBytes,\n\t\t\tASCIIRatio:     s2cMetrics.asciiRatio,\n\t\t\tAvgHammingOnes: s2cMetrics.avgOnes,\n\t\t\tRotationSeen:   len(s2cUsed),\n\t\t\tDecodedUnits:   s2cDecoded,\n\t\t},\n\t}\n}\n\nfunc analyzeUDPRelay(t *testing.T, protocol string, mode trafficMode, c2s [][]byte, s2c [][]byte) caseResult {\n\ttables, err := getTables(mode.config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc2sMetrics := metricFromBytes(flattenChunks(c2s))\n\ts2cMetrics := metricFromBytes(flattenChunks(s2c))\n\n\tc2sUsed, c2sDecoded, err := analyzePureChunks(tables, c2s)\n\tif err != nil {\n\t\tt.Fatalf(\"%s %s udp c2s decode failed: %v\", protocol, mode.name, err)\n\t}\n\ts2cUsed, s2cDecoded, err := analyzePureChunks(tables, s2c)\n\tif err != nil {\n\t\tt.Fatalf(\"%s %s udp s2c decode failed: %v\", protocol, mode.name, err)\n\t}\n\n\tallBytes := append(append([]byte{}, flattenChunks(c2s)...), flattenChunks(s2c)...)\n\ttotalMetrics := metricFromBytes(allBytes)\n\trotationSeen := len(unionKeys(c2sUsed, s2cUsed))\n\n\treturn caseResult{\n\t\tProtocol:         protocol,\n\t\tMode:             mode.name,\n\t\tTotalBytes:       len(allBytes),\n\t\tASCIIBytes:       totalMetrics.asciiBytes,\n\t\tASCIIRatio:       totalMetrics.asciiRatio,\n\t\tAvgHammingOnes:   totalMetrics.avgOnes,\n\t\tRotationSeen:     rotationSeen,\n\t\tRotationExpected: expectedRotation(mode.config),\n\t\tDecodedUnits:     c2sDecoded + s2cDecoded,\n\t\tClientToServer: directionResult{\n\t\t\tRawBytes:       len(flattenChunks(c2s)),\n\t\t\tASCIIBytes:     c2sMetrics.asciiBytes,\n\t\t\tASCIIRatio:     c2sMetrics.asciiRatio,\n\t\t\tAvgHammingOnes: c2sMetrics.avgOnes,\n\t\t\tRotationSeen:   len(c2sUsed),\n\t\t\tDecodedUnits:   c2sDecoded,\n\t\t},\n\t\tServerToClient: directionResult{\n\t\t\tRawBytes:       len(flattenChunks(s2c)),\n\t\t\tASCIIBytes:     s2cMetrics.asciiBytes,\n\t\t\tASCIIRatio:     s2cMetrics.asciiRatio,\n\t\t\tAvgHammingOnes: s2cMetrics.avgOnes,\n\t\t\tRotationSeen:   len(s2cUsed),\n\t\t\tDecodedUnits:   s2cDecoded,\n\t\t},\n\t}\n}\n\ntype byteMetrics struct {\n\tasciiBytes int\n\tasciiRatio float64\n\tavgOnes    float64\n}\n\nfunc metricFromBytes(b []byte) byteMetrics {\n\tif len(b) == 0 {\n\t\treturn byteMetrics{}\n\t}\n\tvar ascii, ones int\n\tfor _, v := range b {\n\t\tif v < 0x80 {\n\t\t\tascii++\n\t\t}\n\t\tones += bitsInByte(v)\n\t}\n\treturn byteMetrics{\n\t\tasciiBytes: ascii,\n\t\tasciiRatio: float64(ascii) / float64(len(b)),\n\t\tavgOnes:    float64(ones) / float64(len(b)),\n\t}\n}\n\nfunc bitsInByte(b byte) int {\n\tn := 0\n\tfor b != 0 {\n\t\tn += int(b & 1)\n\t\tb >>= 1\n\t}\n\treturn n\n}\n\nfunc analyzePureChunks(tables []*table, chunks [][]byte) (map[int]int, int, error) {\n\tif len(tables) == 0 {\n\t\treturn nil, 0, fmt.Errorf(\"no sudoku tables\")\n\t}\n\tused := make(map[int]int)\n\tdecoded := 0\n\tfor _, chunk := range chunks {\n\t\thintBuf := make([]byte, 0, 4)\n\t\ttableIndex := 0\n\t\tfor _, b := range chunk {\n\t\t\tt := tables[tableIndex%len(tables)]\n\t\t\tif !t.layout.isHint(b) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\thintBuf = append(hintBuf, b)\n\t\t\tif len(hintBuf) < 4 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyBytes := sort4([4]byte{hintBuf[0], hintBuf[1], hintBuf[2], hintBuf[3]})\n\t\t\tkey := packKey(keyBytes)\n\t\t\tif _, ok := t.decode[key]; !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"invalid pure tuple at table %d\", tableIndex%len(tables))\n\t\t\t}\n\t\t\tused[tableIndex%len(tables)]++\n\t\t\tdecoded++\n\t\t\ttableIndex++\n\t\t\thintBuf = hintBuf[:0]\n\t\t}\n\t\tif len(hintBuf) != 0 {\n\t\t\treturn nil, 0, fmt.Errorf(\"leftover pure hints\")\n\t\t}\n\t}\n\treturn used, decoded, nil\n}\n\nfunc analyzePackedChunks(tables []*table, chunks [][]byte) (map[int]int, int, error) {\n\tlayouts := tablesToLayouts(tables)\n\tif len(layouts) == 0 {\n\t\treturn nil, 0, fmt.Errorf(\"no sudoku layouts\")\n\t}\n\tused := make(map[int]int)\n\tdecoded := 0\n\tfor _, chunk := range chunks {\n\t\tvar bitBuf uint64\n\t\tvar bitCount int\n\t\tgroupIndex := 0\n\t\tfor _, b := range chunk {\n\t\t\tlayout := layouts[groupIndex%len(layouts)]\n\t\t\tif !layout.isHint(b) {\n\t\t\t\tif b == layout.padMarker {\n\t\t\t\t\tbitBuf = 0\n\t\t\t\t\tbitCount = 0\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgroup, ok := layout.decodeGroup(b)\n\t\t\tif !ok {\n\t\t\t\treturn nil, 0, fmt.Errorf(\"invalid packed byte %d\", b)\n\t\t\t}\n\t\t\tused[groupIndex%len(layouts)]++\n\t\t\tgroupIndex++\n\t\t\tbitBuf = (bitBuf << 6) | uint64(group)\n\t\t\tbitCount += 6\n\t\t\tfor bitCount >= 8 {\n\t\t\t\tbitCount -= 8\n\t\t\t\tdecoded++\n\t\t\t\tif bitCount > 0 {\n\t\t\t\t\tbitBuf &= (uint64(1) << bitCount) - 1\n\t\t\t\t} else {\n\t\t\t\t\tbitBuf = 0\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn used, decoded, nil\n}\n\nfunc expectedRotation(cfg *Config) int {\n\ttables, err := getTables(cfg)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn len(tables)\n}\n\nfunc unionKeys(a, b map[int]int) map[int]struct{} {\n\tout := make(map[int]struct{}, len(a)+len(b))\n\tfor k := range a {\n\t\tout[k] = struct{}{}\n\t}\n\tfor k := range b {\n\t\tout[k] = struct{}{}\n\t}\n\treturn out\n}\n\nfunc flattenChunks(chunks [][]byte) []byte {\n\ttotal := 0\n\tfor _, chunk := range chunks {\n\t\ttotal += len(chunk)\n\t}\n\tout := make([]byte, 0, total)\n\tfor _, chunk := range chunks {\n\t\tout = append(out, chunk...)\n\t}\n\treturn out\n}\n\nfunc cloneConfig(cfg *Config) *Config {\n\tif cfg == nil {\n\t\treturn nil\n\t}\n\tout := proto.Clone(cfg).(*Config)\n\treturn out\n}\n\nfunc defaultApps(cfg *core.Config) *core.Config {\n\tcfg.App = append(cfg.App,\n\t\tserial.ToTypedMessage(&log.Config{\n\t\t\tErrorLogLevel: clog.Severity_Warning,\n\t\t\tErrorLogType:  log.LogType_Console,\n\t\t}),\n\t\tserial.ToTypedMessage(&dispatcher.Config{}),\n\t\tserial.ToTypedMessage(&proxyman.InboundConfig{}),\n\t\tserial.ToTypedMessage(&proxyman.OutboundConfig{}),\n\t)\n\treturn cfg\n}\n\nfunc buildE2EBinary(t *testing.T) string {\n\tt.Helper()\n\te2eBinaryOnce.Do(func() {\n\t\ttempDir, err := os.MkdirTemp(\"\", \"xray-sudoku-e2e-*\")\n\t\tif err != nil {\n\t\t\te2eBinaryErr = err\n\t\t\treturn\n\t\t}\n\t\te2eBinaryPath = filepath.Join(tempDir, \"xray.test\")\n\t\tcmd := exec.Command(\"go\", \"build\", \"-o\", e2eBinaryPath, \"./main\")\n\t\tcmd.Dir = repoRoot(t)\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Stderr = os.Stderr\n\t\te2eBinaryErr = cmd.Run()\n\t})\n\tif e2eBinaryErr != nil {\n\t\tt.Fatal(e2eBinaryErr)\n\t}\n\treturn e2eBinaryPath\n}\n\nfunc repoRoot(t *testing.T) string {\n\tt.Helper()\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor {\n\t\tif _, err := os.Stat(filepath.Join(dir, \"go.mod\")); err == nil {\n\t\t\treturn dir\n\t\t}\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\tt.Fatal(\"failed to locate repo root\")\n\t\t}\n\t\tdir = parent\n\t}\n}\n\nfunc runXrayPair(t *testing.T, bin string, serverCfg, clientCfg *core.Config) (*exec.Cmd, *exec.Cmd) {\n\tt.Helper()\n\tserverCmd := runXray(t, bin, serverCfg)\n\n\ttime.Sleep(500 * time.Millisecond)\n\n\tclientCmd := runXray(t, bin, clientCfg)\n\n\ttime.Sleep(1500 * time.Millisecond)\n\treturn serverCmd, clientCmd\n}\n\nfunc runXray(t *testing.T, bin string, cfg *core.Config) *exec.Cmd {\n\tt.Helper()\n\tcfgBytes, err := proto.Marshal(cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcmd := exec.Command(bin, \"-config=stdin:\", \"-format=pb\")\n\tcmd.Stdin = bytes.NewReader(cfgBytes)\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Start(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn cmd\n}\n\nfunc stopCmd(cmd *exec.Cmd) {\n\tif cmd == nil || cmd.Process == nil {\n\t\treturn\n\t}\n\t_ = cmd.Process.Signal(syscall.SIGTERM)\n\tdone := make(chan struct{})\n\tgo func() {\n\t\t_, _ = cmd.Process.Wait()\n\t\tclose(done)\n\t}()\n\tselect {\n\tcase <-done:\n\tcase <-time.After(3 * time.Second):\n\t\t_ = cmd.Process.Kill()\n\t\t<-done\n\t}\n}\n\nfunc startTCPRelay(t *testing.T, listenPort int, target string) *tcpRelay {\n\tt.Helper()\n\tln, err := stdnet.Listen(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", listenPort))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tr := &tcpRelay{\n\t\tlistener: ln,\n\t\ttarget:   target,\n\t\tstopCh:   make(chan struct{}),\n\t}\n\tr.wg.Add(1)\n\tgo func() {\n\t\tdefer r.wg.Done()\n\t\tfor {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tselect {\n\t\t\t\tcase <-r.stopCh:\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttargetConn, err := stdnet.Dial(\"tcp\", target)\n\t\t\tif err != nil {\n\t\t\t\t_ = conn.Close()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcapture := &tcpCapture{}\n\t\t\tr.mu.Lock()\n\t\t\tr.captures = append(r.captures, capture)\n\t\t\tr.mu.Unlock()\n\t\t\tr.wg.Add(1)\n\t\t\tgo func(client, server stdnet.Conn, cap *tcpCapture) {\n\t\t\t\tdefer r.wg.Done()\n\t\t\t\tdefer client.Close()\n\t\t\t\tdefer server.Close()\n\t\t\t\tvar inner sync.WaitGroup\n\t\t\t\tinner.Add(2)\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer inner.Done()\n\t\t\t\t\t_, _ = io.Copy(server, io.TeeReader(client, &captureWriter{capture: cap, dir: \"c2s\"}))\n\t\t\t\t\tif tcp, ok := server.(*stdnet.TCPConn); ok {\n\t\t\t\t\t\t_ = tcp.CloseWrite()\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\tgo func() {\n\t\t\t\t\tdefer inner.Done()\n\t\t\t\t\t_, _ = io.Copy(client, io.TeeReader(server, &captureWriter{capture: cap, dir: \"s2c\"}))\n\t\t\t\t\tif tcp, ok := client.(*stdnet.TCPConn); ok {\n\t\t\t\t\t\t_ = tcp.CloseWrite()\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\tinner.Wait()\n\t\t\t}(conn, targetConn, capture)\n\t\t}\n\t}()\n\treturn r\n}\n\nfunc (r *tcpRelay) Close() {\n\tclose(r.stopCh)\n\t_ = r.listener.Close()\n\tr.wg.Wait()\n}\n\nfunc (r *tcpRelay) Snapshots() []*tcpCapture {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tout := make([]*tcpCapture, 0, len(r.captures))\n\tfor _, capture := range r.captures {\n\t\tout = append(out, capture)\n\t}\n\treturn out\n}\n\nfunc (c *tcpCapture) snapshot() ([]byte, []byte) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\treturn append([]byte{}, c.c2s...), append([]byte{}, c.s2c...)\n}\n\ntype captureWriter struct {\n\tcapture *tcpCapture\n\tdir     string\n}\n\nfunc (w *captureWriter) Write(p []byte) (int, error) {\n\tw.capture.mu.Lock()\n\tdefer w.capture.mu.Unlock()\n\tif w.dir == \"c2s\" {\n\t\tw.capture.c2s = append(w.capture.c2s, p...)\n\t} else {\n\t\tw.capture.s2c = append(w.capture.s2c, p...)\n\t}\n\treturn len(p), nil\n}\n\nfunc startUDPRelay(t *testing.T, listenPort, targetPort int) *udpRelay {\n\tt.Helper()\n\tconn, err := stdnet.ListenPacket(\"udp\", fmt.Sprintf(\"127.0.0.1:%d\", listenPort))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttargetAddr := &stdnet.UDPAddr{IP: stdnet.IPv4(127, 0, 0, 1), Port: targetPort}\n\tr := &udpRelay{\n\t\tconn:   conn,\n\t\ttarget: targetAddr,\n\t\tstopCh: make(chan struct{}),\n\t}\n\tr.wg.Add(1)\n\tgo func() {\n\t\tdefer r.wg.Done()\n\t\tbuf := make([]byte, 64*1024)\n\t\tfor {\n\t\t\tn, addr, err := conn.ReadFrom(buf)\n\t\t\tif err != nil {\n\t\t\t\tselect {\n\t\t\t\tcase <-r.stopCh:\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpayload := append([]byte{}, buf[:n]...)\n\t\t\tudpAddr := addr.(*stdnet.UDPAddr)\n\t\t\tif udpAddr.IP.Equal(r.target.IP) && udpAddr.Port == r.target.Port {\n\t\t\t\tr.captureMu.Lock()\n\t\t\t\tr.s2c = append(r.s2c, payload)\n\t\t\t\tr.captureMu.Unlock()\n\t\t\t\tr.clientMu.Lock()\n\t\t\t\tclient := r.client\n\t\t\t\tr.clientMu.Unlock()\n\t\t\t\tif client != nil {\n\t\t\t\t\t_, _ = conn.WriteTo(payload, client)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.clientMu.Lock()\n\t\t\tr.client = udpAddr\n\t\t\tr.clientMu.Unlock()\n\t\t\tr.captureMu.Lock()\n\t\t\tr.c2s = append(r.c2s, payload)\n\t\t\tr.captureMu.Unlock()\n\t\t\t_, _ = conn.WriteTo(payload, r.target)\n\t\t}\n\t}()\n\treturn r\n}\n\nfunc (r *udpRelay) Close() {\n\tclose(r.stopCh)\n\t_ = r.conn.Close()\n\tr.wg.Wait()\n}\n\nfunc (r *udpRelay) Snapshots() ([][]byte, [][]byte) {\n\tr.captureMu.Lock()\n\tdefer r.captureMu.Unlock()\n\tc2s := make([][]byte, 0, len(r.c2s))\n\ts2c := make([][]byte, 0, len(r.s2c))\n\tfor _, packet := range r.c2s {\n\t\tc2s = append(c2s, append([]byte{}, packet...))\n\t}\n\tfor _, packet := range r.s2c {\n\t\ts2c = append(s2c, append([]byte{}, packet...))\n\t}\n\treturn c2s, s2c\n}\n\ntype xorEchoServer struct {\n\tln stdnet.Listener\n\twg sync.WaitGroup\n}\n\nfunc startXOREchoServer(t *testing.T) *xorEchoServer {\n\tt.Helper()\n\tln, err := stdnet.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ts := &xorEchoServer{ln: ln}\n\ts.wg.Add(1)\n\tgo func() {\n\t\tdefer s.wg.Done()\n\t\tfor {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.wg.Add(1)\n\t\t\tgo func(c stdnet.Conn) {\n\t\t\t\tdefer s.wg.Done()\n\t\t\t\tdefer c.Close()\n\t\t\t\tbuf := make([]byte, 4096)\n\t\t\t\tfor {\n\t\t\t\t\tn, err := c.Read(buf)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\t\t\tbuf[i] ^= 'c'\n\t\t\t\t\t}\n\t\t\t\t\tif _, err := c.Write(buf[:n]); err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\t\t\tbuf[i] ^= 'c'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\t\t}\n\t}()\n\treturn s\n}\n\nfunc (s *xorEchoServer) Address() xnet.Address {\n\treturn xnet.IPAddress(s.ln.Addr().(*stdnet.TCPAddr).IP)\n}\n\nfunc (s *xorEchoServer) Port() xnet.Port {\n\treturn xnet.Port(s.ln.Addr().(*stdnet.TCPAddr).Port)\n}\n\nfunc (s *xorEchoServer) Close() {\n\t_ = s.ln.Close()\n\ts.wg.Wait()\n}\n\nfunc startTLSEchoDecoy(t *testing.T, c *cert.Certificate) *tlsDecoy {\n\tt.Helper()\n\tcertPEM, keyPEM := c.ToPEM()\n\tkeyPair, err := cryptotls.X509KeyPair(certPEM, keyPEM)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfig := &cryptotls.Config{\n\t\tCertificates: []cryptotls.Certificate{keyPair},\n\t}\n\tln, err := stdnet.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttlsLn := cryptotls.NewListener(ln, config)\n\td := &tlsDecoy{\n\t\tln:   tlsLn,\n\t\tdone: make(chan struct{}),\n\t}\n\td.wg.Add(1)\n\tgo func() {\n\t\tdefer d.wg.Done()\n\t\tfor {\n\t\t\tconn, err := tlsLn.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\td.wg.Add(1)\n\t\t\tgo func(c stdnet.Conn) {\n\t\t\t\tdefer d.wg.Done()\n\t\t\t\tdefer c.Close()\n\t\t\t\tbuf := make([]byte, 2048)\n\t\t\t\t_, _ = c.Read(buf)\n\t\t\t\t_, _ = c.Write([]byte(\"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nOK\"))\n\t\t\t}(conn)\n\t\t}\n\t}()\n\treturn d\n}\n\nfunc (d *tlsDecoy) Port() int {\n\treturn d.ln.Addr().(*stdnet.TCPAddr).Port\n}\n\nfunc (d *tlsDecoy) Close() {\n\t_ = d.ln.Close()\n\td.wg.Wait()\n}\n\nfunc exerciseTCPClient(t *testing.T, port int, payloadSize int) {\n\tt.Helper()\n\tif err := exerciseTCPClientErr(t, port, payloadSize); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc exerciseTCPClientErr(t *testing.T, port int, payloadSize int) error {\n\tconn := waitTCPConn(t, port, 10*time.Second)\n\tdefer conn.Close()\n\tpayload := make([]byte, payloadSize)\n\tif _, err := rand.Read(payload); err != nil {\n\t\treturn err\n\t}\n\toffset := 0\n\tfor offset < len(payload) {\n\t\tchunk := 1024\n\t\tif remain := len(payload) - offset; remain < chunk {\n\t\t\tchunk = remain\n\t\t}\n\t\tpart := payload[offset : offset+chunk]\n\t\tif _, err := conn.Write(part); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tresp := make([]byte, chunk)\n\t\tif _, err := io.ReadFull(conn, resp); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor i := range part {\n\t\t\tif resp[i] != (part[i] ^ 'c') {\n\t\t\t\treturn fmt.Errorf(\"unexpected xor response at offset %d\", offset+i)\n\t\t\t}\n\t\t}\n\t\toffset += chunk\n\t}\n\treturn nil\n}\n\nfunc firstChunkLen(chunks [][]byte) int {\n\tif len(chunks) == 0 {\n\t\treturn 0\n\t}\n\treturn len(chunks[0])\n}\n\nfunc waitTCPConn(t *testing.T, port int, timeout time.Duration) stdnet.Conn {\n\tt.Helper()\n\tdeadline := time.Now().Add(timeout)\n\tfor {\n\t\tconn, err := stdnet.DialTimeout(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", port), 500*time.Millisecond)\n\t\tif err == nil {\n\t\t\treturn conn\n\t\t}\n\t\tif time.Now().After(deadline) {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n}\n\nfunc mustX25519Keypair(t *testing.T) ([]byte, []byte) {\n\tt.Helper()\n\tpriv, err := ecdh.X25519().GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn priv.Bytes(), priv.PublicKey().Bytes()\n}\n\nfunc mustDecodeHex(t *testing.T, s string) []byte {\n\tt.Helper()\n\tout := make([]byte, len(s)/2)\n\tif _, err := hex.Decode(out, []byte(s)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn out\n}\n\nfunc init() {\n\tch := make(chan os.Signal, 1)\n\tsignal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)\n\tgo func() {\n\t\t<-ch\n\t\tos.Exit(130)\n\t}()\n}\n"
  },
  {
    "path": "transport/internet/finalmask/sudoku/table.go",
    "content": "package sudoku\n\nimport (\n\tcrypto_rand \"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math/bits\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype table struct {\n\tencode [256][][4]byte\n\tdecode map[uint32]byte\n\tlayout *byteLayout\n}\n\ntype tableCacheKey struct {\n\tpassword    string\n\tascii       string\n\tcustomTable string\n}\n\nvar (\n\ttableCache    sync.Map\n\ttableSetCache sync.Map\n\n\tbasePatternsOnce sync.Once\n\tbasePatterns     [][][4]byte\n\tbasePatternsErr  error\n)\n\ntype byteLayout struct {\n\thintMask    byte\n\thintValue   byte\n\tpadMarker   byte\n\tpaddingPool []byte\n\tencodeHint  func(group byte) byte\n\tencodeGroup func(group byte) byte\n\tdecodeGroup func(b byte) (byte, bool)\n}\n\nfunc (l *byteLayout) isHint(b byte) bool {\n\tif (b & l.hintMask) == l.hintValue {\n\t\treturn true\n\t}\n\t// ASCII layout maps 0x7f to '\\n' to avoid DEL on the wire.\n\treturn l.hintMask == 0x40 && b == '\\n'\n}\n\nfunc getTable(config *Config) (*table, error) {\n\ttables, err := getTables(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(tables) == 0 {\n\t\treturn nil, fmt.Errorf(\"empty sudoku table set\")\n\t}\n\treturn tables[0], nil\n}\n\nfunc getTables(config *Config) ([]*table, error) {\n\tif config == nil {\n\t\treturn nil, fmt.Errorf(\"nil sudoku config\")\n\t}\n\n\tmode, err := normalizeASCII(config.GetAscii())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpatterns, err := normalizedCustomPatterns(config, mode)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcacheKey := tableCacheKey{\n\t\tpassword:    config.GetPassword(),\n\t\tascii:       mode,\n\t\tcustomTable: strings.Join(patterns, \"\\x00\"),\n\t}\n\tif cached, ok := tableSetCache.Load(cacheKey); ok {\n\t\treturn cached.([]*table), nil\n\t}\n\n\ttables := make([]*table, 0, len(patterns))\n\tfor _, pattern := range patterns {\n\t\tlayout, err := resolveLayout(mode, pattern)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tt, err := buildTable(config.GetPassword(), layout)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttables = append(tables, t)\n\t}\n\n\tactual, _ := tableSetCache.LoadOrStore(cacheKey, tables)\n\treturn actual.([]*table), nil\n}\n\nfunc normalizedCustomPatterns(config *Config, mode string) ([]string, error) {\n\tif config == nil {\n\t\treturn []string{\"\"}, nil\n\t}\n\tif mode == \"prefer_ascii\" {\n\t\treturn []string{\"\"}, nil\n\t}\n\n\trawPatterns := config.GetCustomTables()\n\tif len(rawPatterns) == 0 {\n\t\trawPatterns = []string{config.GetCustomTable()}\n\t}\n\n\tpatterns := make([]string, 0, len(rawPatterns))\n\tseen := make(map[string]struct{}, len(rawPatterns))\n\tfor _, raw := range rawPatterns {\n\t\tpattern := strings.TrimSpace(raw)\n\t\tif pattern != \"\" {\n\t\t\tvar err error\n\t\t\tpattern, err = normalizeCustomTable(pattern)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif _, ok := seen[pattern]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tseen[pattern] = struct{}{}\n\t\tpatterns = append(patterns, pattern)\n\t}\n\n\tif len(patterns) == 0 {\n\t\treturn []string{\"\"}, nil\n\t}\n\n\treturn patterns, nil\n}\n\nfunc normalizedPadding(config *Config) (int, int) {\n\tif config == nil {\n\t\treturn 0, 0\n\t}\n\n\tpMin := int(config.GetPaddingMin())\n\tpMax := int(config.GetPaddingMax())\n\n\tif pMin > 100 {\n\t\tpMin = 100\n\t}\n\tif pMax > 100 {\n\t\tpMax = 100\n\t}\n\tif pMax < pMin {\n\t\tpMax = pMin\n\t}\n\treturn pMin, pMax\n}\n\nfunc normalizeASCII(mode string) (string, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"entropy\", \"prefer_entropy\":\n\t\treturn \"prefer_entropy\", nil\n\tcase \"ascii\", \"prefer_ascii\":\n\t\treturn \"prefer_ascii\", nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"invalid sudoku ascii mode: %s\", mode)\n\t}\n}\n\nfunc normalizeCustomTable(pattern string) (string, error) {\n\tcleaned := strings.ToLower(strings.TrimSpace(pattern))\n\tcleaned = strings.ReplaceAll(cleaned, \" \", \"\")\n\tif len(cleaned) != 8 {\n\t\treturn \"\", fmt.Errorf(\"customTable must be 8 chars, got %d\", len(cleaned))\n\t}\n\n\tvar xCount, pCount, vCount int\n\tfor _, ch := range cleaned {\n\t\tswitch ch {\n\t\tcase 'x':\n\t\t\txCount++\n\t\tcase 'p':\n\t\t\tpCount++\n\t\tcase 'v':\n\t\t\tvCount++\n\t\tdefault:\n\t\t\treturn \"\", fmt.Errorf(\"customTable has invalid char %q\", ch)\n\t\t}\n\t}\n\tif xCount != 2 || pCount != 2 || vCount != 4 {\n\t\treturn \"\", fmt.Errorf(\"customTable must contain exactly 2 x, 2 p and 4 v\")\n\t}\n\treturn cleaned, nil\n}\n\nfunc resolveLayout(mode, customTable string) (*byteLayout, error) {\n\tif mode == \"prefer_ascii\" {\n\t\treturn asciiLayout(), nil\n\t}\n\n\tif customTable != \"\" {\n\t\treturn customLayout(customTable)\n\t}\n\treturn entropyLayout(), nil\n}\n\nfunc asciiLayout() *byteLayout {\n\tpadding := make([]byte, 0, 32)\n\tfor i := 0; i < 32; i++ {\n\t\tpadding = append(padding, byte(0x20+i))\n\t}\n\n\tencodeGroup := func(group byte) byte {\n\t\tb := byte(0x40 | (group & 0x3f))\n\t\tif b == 0x7f {\n\t\t\treturn '\\n'\n\t\t}\n\t\treturn b\n\t}\n\n\treturn &byteLayout{\n\t\thintMask:    0x40,\n\t\thintValue:   0x40,\n\t\tpadMarker:   0x3f,\n\t\tpaddingPool: padding,\n\t\tencodeHint:  encodeGroup,\n\t\tencodeGroup: encodeGroup,\n\t\tdecodeGroup: func(b byte) (byte, bool) {\n\t\t\tif b == '\\n' {\n\t\t\t\treturn 0x3f, true\n\t\t\t}\n\t\t\tif (b & 0x40) == 0 {\n\t\t\t\treturn 0, false\n\t\t\t}\n\t\t\treturn b & 0x3f, true\n\t\t},\n\t}\n}\n\nfunc entropyLayout() *byteLayout {\n\tpadding := make([]byte, 0, 16)\n\tfor i := 0; i < 8; i++ {\n\t\tpadding = append(padding, byte(0x80+i), byte(0x10+i))\n\t}\n\n\tencodeGroup := func(group byte) byte {\n\t\tv := group & 0x3f\n\t\treturn ((v & 0x30) << 1) | (v & 0x0f)\n\t}\n\n\treturn &byteLayout{\n\t\thintMask:    0x90,\n\t\thintValue:   0x00,\n\t\tpadMarker:   0x80,\n\t\tpaddingPool: padding,\n\t\tencodeHint:  encodeGroup,\n\t\tencodeGroup: encodeGroup,\n\t\tdecodeGroup: func(b byte) (byte, bool) {\n\t\t\tif (b & 0x90) != 0 {\n\t\t\t\treturn 0, false\n\t\t\t}\n\t\t\treturn ((b >> 1) & 0x30) | (b & 0x0f), true\n\t\t},\n\t}\n}\n\nfunc customLayout(pattern string) (*byteLayout, error) {\n\tpattern, err := normalizeCustomTable(pattern)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar xBits, pBits, vBits []uint8\n\tfor i, c := range pattern {\n\t\tbit := uint8(7 - i)\n\t\tswitch c {\n\t\tcase 'x':\n\t\t\txBits = append(xBits, bit)\n\t\tcase 'p':\n\t\t\tpBits = append(pBits, bit)\n\t\tcase 'v':\n\t\t\tvBits = append(vBits, bit)\n\t\t}\n\t}\n\n\txMask := byte(0)\n\tfor _, bit := range xBits {\n\t\txMask |= 1 << bit\n\t}\n\n\tencodeGroupWithDropX := func(group byte, dropX int) byte {\n\t\tout := xMask\n\t\tif dropX >= 0 {\n\t\t\tout &^= 1 << xBits[dropX]\n\t\t}\n\n\t\tval := (group >> 4) & 0x03\n\t\tpos := group & 0x0f\n\n\t\tif (val & 0x02) != 0 {\n\t\t\tout |= 1 << pBits[0]\n\t\t}\n\t\tif (val & 0x01) != 0 {\n\t\t\tout |= 1 << pBits[1]\n\t\t}\n\t\tfor i, bit := range vBits {\n\t\t\tif (pos>>(3-uint8(i)))&0x01 == 1 {\n\t\t\t\tout |= 1 << bit\n\t\t\t}\n\t\t}\n\n\t\treturn out\n\t}\n\n\tpaddingSet := make(map[byte]struct{}, 64)\n\tpadding := make([]byte, 0, 64)\n\tfor drop := range xBits {\n\t\tfor val := byte(0); val < 4; val++ {\n\t\t\tfor pos := byte(0); pos < 16; pos++ {\n\t\t\t\tgroup := (val << 4) | pos\n\t\t\t\tb := encodeGroupWithDropX(group, drop)\n\t\t\t\tif bits.OnesCount8(b) >= 5 {\n\t\t\t\t\tif _, exists := paddingSet[b]; !exists {\n\t\t\t\t\t\tpaddingSet[b] = struct{}{}\n\t\t\t\t\t\tpadding = append(padding, b)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tsort.Slice(padding, func(i, j int) bool { return padding[i] < padding[j] })\n\tif len(padding) == 0 {\n\t\treturn nil, fmt.Errorf(\"customTable produced empty padding pool\")\n\t}\n\n\tdecodeGroup := func(b byte) (byte, bool) {\n\t\tif (b & xMask) != xMask {\n\t\t\treturn 0, false\n\t\t}\n\n\t\tvar val, pos byte\n\t\tif b&(1<<pBits[0]) != 0 {\n\t\t\tval |= 0x02\n\t\t}\n\t\tif b&(1<<pBits[1]) != 0 {\n\t\t\tval |= 0x01\n\t\t}\n\t\tfor i, bit := range vBits {\n\t\t\tif b&(1<<bit) != 0 {\n\t\t\t\tpos |= 1 << (3 - uint8(i))\n\t\t\t}\n\t\t}\n\n\t\treturn ((val & 0x03) << 4) | (pos & 0x0f), true\n\t}\n\tencodeGroup := func(group byte) byte {\n\t\treturn encodeGroupWithDropX(group, -1)\n\t}\n\n\treturn &byteLayout{\n\t\thintMask:    xMask,\n\t\thintValue:   xMask,\n\t\tpadMarker:   padding[0],\n\t\tpaddingPool: padding,\n\t\tencodeHint:  encodeGroup,\n\t\tencodeGroup: encodeGroup,\n\t\tdecodeGroup: decodeGroup,\n\t}, nil\n}\n\nfunc buildTable(password string, layout *byteLayout) (*table, error) {\n\tpatterns, err := getBasePatterns()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(patterns) < 256 {\n\t\treturn nil, fmt.Errorf(\"not enough sudoku grids: %d\", len(patterns))\n\t}\n\n\torder := make([]int, len(patterns))\n\tfor i := range order {\n\t\torder[i] = i\n\t}\n\n\thash := sha256.Sum256([]byte(password))\n\tseed := int64(binary.BigEndian.Uint64(hash[:8]))\n\trng := rand.New(rand.NewSource(seed))\n\trng.Shuffle(len(order), func(i, j int) {\n\t\torder[i], order[j] = order[j], order[i]\n\t})\n\n\tt := &table{\n\t\tdecode: make(map[uint32]byte, 1<<16),\n\t\tlayout: layout,\n\t}\n\tfor b := 0; b < 256; b++ {\n\t\tpatList := patterns[order[b]]\n\t\tif len(patList) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"grid %d has no valid clue set\", order[b])\n\t\t}\n\n\t\tenc := make([][4]byte, 0, len(patList))\n\t\tfor _, groups := range patList {\n\t\t\thints := [4]byte{\n\t\t\t\tlayout.encodeHint(groups[0]),\n\t\t\t\tlayout.encodeHint(groups[1]),\n\t\t\t\tlayout.encodeHint(groups[2]),\n\t\t\t\tlayout.encodeHint(groups[3]),\n\t\t\t}\n\t\t\tsortedHints := sort4(hints)\n\t\t\tkey := packKey(sortedHints)\n\t\t\tif old, exists := t.decode[key]; exists && old != byte(b) {\n\t\t\t\treturn nil, fmt.Errorf(\"decode key collision for byte %d and %d\", old, b)\n\t\t\t}\n\t\t\tt.decode[key] = byte(b)\n\t\t\tenc = append(enc, hints)\n\t\t}\n\n\t\tt.encode[b] = enc\n\t}\n\n\treturn t, nil\n}\n\nfunc getBasePatterns() ([][][4]byte, error) {\n\tbasePatternsOnce.Do(func() {\n\t\tbasePatterns, basePatternsErr = buildBasePatterns()\n\t})\n\treturn basePatterns, basePatternsErr\n}\n\ntype grid [16]byte\n\nfunc buildBasePatterns() ([][][4]byte, error) {\n\tgrids := generateAllGrids()\n\tpositions := hintPositions()\n\n\tpatterns := make([][][4]byte, len(grids))\n\tfor _, ps := range positions {\n\t\tcounts := make(map[uint32]uint16, len(grids))\n\t\tkeys := make([]uint32, len(grids))\n\t\tgroupsByGrid := make([][4]byte, len(grids))\n\n\t\tfor gi, g := range grids {\n\t\t\tgroups := [4]byte{\n\t\t\t\tclueGroup(g, ps[0]),\n\t\t\t\tclueGroup(g, ps[1]),\n\t\t\t\tclueGroup(g, ps[2]),\n\t\t\t\tclueGroup(g, ps[3]),\n\t\t\t}\n\t\t\tgroups = sort4(groups)\n\t\t\tkey := packKey(groups)\n\t\t\tkeys[gi] = key\n\t\t\tgroupsByGrid[gi] = groups\n\t\t\tcounts[key]++\n\t\t}\n\n\t\tfor gi, key := range keys {\n\t\t\tif counts[key] == 1 {\n\t\t\t\tpatterns[gi] = append(patterns[gi], groupsByGrid[gi])\n\t\t\t}\n\t\t}\n\t}\n\n\tfor gi, list := range patterns {\n\t\tif len(list) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"grid %d has no uniquely decodable clue set\", gi)\n\t\t}\n\t}\n\n\treturn patterns, nil\n}\n\nfunc clueGroup(g grid, pos byte) byte {\n\t// 2 bits of value + 4 bits of position.\n\treturn ((g[pos] - 1) << 4) | (pos & 0x0f)\n}\n\nfunc generateAllGrids() []grid {\n\tgrids := make([]grid, 0, 288)\n\tvar g grid\n\n\tvar dfs func(idx int)\n\tdfs = func(idx int) {\n\t\tif idx == 16 {\n\t\t\tgrids = append(grids, g)\n\t\t\treturn\n\t\t}\n\n\t\trow := idx / 4\n\t\tcol := idx % 4\n\t\tboxRow := (row / 2) * 2\n\t\tboxCol := (col / 2) * 2\n\n\t\tfor num := byte(1); num <= 4; num++ {\n\t\t\tvalid := true\n\t\t\tfor i := 0; i < 4; i++ {\n\t\t\t\tif g[row*4+i] == num || g[i*4+col] == num {\n\t\t\t\t\tvalid = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !valid {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor r := 0; r < 2 && valid; r++ {\n\t\t\t\tfor c := 0; c < 2; c++ {\n\t\t\t\t\tif g[(boxRow+r)*4+(boxCol+c)] == num {\n\t\t\t\t\t\tvalid = false\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !valid {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tg[idx] = num\n\t\t\tdfs(idx + 1)\n\t\t\tg[idx] = 0\n\t\t}\n\t}\n\n\tdfs(0)\n\treturn grids\n}\n\nfunc hintPositions() [][4]byte {\n\t// C(16, 4) = 1820.\n\tpositions := make([][4]byte, 0, 1820)\n\tfor a := 0; a < 13; a++ {\n\t\tfor b := a + 1; b < 14; b++ {\n\t\t\tfor c := b + 1; c < 15; c++ {\n\t\t\t\tfor d := c + 1; d < 16; d++ {\n\t\t\t\t\tpositions = append(positions, [4]byte{byte(a), byte(b), byte(c), byte(d)})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn positions\n}\n\nfunc packKey(in [4]byte) uint32 {\n\treturn uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])\n}\n\nfunc sort4(in [4]byte) [4]byte {\n\tif in[0] > in[1] {\n\t\tin[0], in[1] = in[1], in[0]\n\t}\n\tif in[2] > in[3] {\n\t\tin[2], in[3] = in[3], in[2]\n\t}\n\tif in[0] > in[2] {\n\t\tin[0], in[2] = in[2], in[0]\n\t}\n\tif in[1] > in[3] {\n\t\tin[1], in[3] = in[3], in[1]\n\t}\n\tif in[1] > in[2] {\n\t\tin[1], in[2] = in[2], in[1]\n\t}\n\treturn in\n}\n\nfunc newSeededRand() *rand.Rand {\n\tseed := time.Now().UnixNano()\n\tvar seedBytes [8]byte\n\tif _, err := crypto_rand.Read(seedBytes[:]); err == nil {\n\t\tseed = int64(binary.BigEndian.Uint64(seedBytes[:]))\n\t}\n\treturn rand.New(rand.NewSource(seed))\n}\n"
  },
  {
    "path": "transport/internet/finalmask/tcp_test.go",
    "content": "package finalmask_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/custom\"\n)\n\nfunc mustSendRecvTcp(\n\tt *testing.T,\n\tfrom net.Conn,\n\tto net.Conn,\n\tmsg []byte,\n) {\n\tt.Helper()\n\n\tgo func() {\n\t\t_, err := from.Write(msg)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tbuf := make([]byte, 1024)\n\tn, err := io.ReadFull(to, buf[:len(msg)])\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif n != len(msg) {\n\t\tt.Fatalf(\"unexpected size: %d\", n)\n\t}\n\n\tif !bytes.Equal(buf[:n], msg) {\n\t\tt.Fatalf(\"unexpected data %q\", buf[:n])\n\t}\n}\n\ntype layerMaskTcp struct {\n\tname string\n\tmask finalmask.Tcpmask\n}\n\nfunc TestConnReadWrite(t *testing.T) {\n\tcases := []layerMaskTcp{\n\t\t{\n\t\t\tname: \"custom\",\n\t\t\tmask: &custom.TCPConfig{\n\t\t\t\tClients: []*custom.TCPSequence{\n\t\t\t\t\t{\n\t\t\t\t\t\tSequence: []*custom.TCPItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tPacket: []byte{1},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tRand: 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tServers: []*custom.TCPSequence{\n\t\t\t\t\t{\n\t\t\t\t\t\tSequence: []*custom.TCPItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tPacket: []byte{2},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tRand: 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tmask := c.mask\n\n\t\t\tmaskManager := finalmask.NewTcpmaskManager([]finalmask.Tcpmask{mask})\n\n\t\t\tln, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient, err := net.Dial(\"tcp\", ln.Addr().String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient, err = maskManager.WrapConnClient(client)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserver, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserver, err = maskManager.WrapConnServer(server)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_ = client.SetDeadline(time.Now().Add(time.Second))\n\t\t\t_ = server.SetDeadline(time.Now().Add(time.Second))\n\n\t\t\tmustSendRecvTcp(t, client, server, []byte(\"client -> server\"))\n\t\t\tmustSendRecvTcp(t, server, client, []byte(\"server -> client\"))\n\n\t\t\tmustSendRecvTcp(t, client, server, []byte{})\n\t\t\tmustSendRecvTcp(t, server, client, []byte{})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/udp_test.go",
    "content": "package finalmask_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/proxy\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/custom\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/dns\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/utp\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/salamander\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask/sudoku\"\n)\n\nfunc mustSendRecv(\n\tt *testing.T,\n\tfrom net.PacketConn,\n\tto net.PacketConn,\n\tmsg []byte,\n) {\n\tt.Helper()\n\n\tgo func() {\n\t\t_, err := from.WriteTo(msg, to.LocalAddr())\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tbuf := make([]byte, 1024)\n\tn, _, err := to.ReadFrom(buf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif n != len(msg) {\n\t\tt.Fatalf(\"unexpected size: %d\", n)\n\t}\n\n\tif !bytes.Equal(buf[:n], msg) {\n\t\tt.Fatalf(\"unexpected data\")\n\t}\n}\n\ntype layerMask struct {\n\tname   string\n\tmask   finalmask.Udpmask\n\tlayers int\n}\n\ntype countingConn struct {\n\tnet.Conn\n\twritten atomic.Int64\n}\n\nfunc (c *countingConn) Write(p []byte) (int, error) {\n\tn, err := c.Conn.Write(p)\n\tc.written.Add(int64(n))\n\treturn n, err\n}\n\nfunc (c *countingConn) Written() int64 {\n\treturn c.written.Load()\n}\n\nfunc TestPacketConnReadWrite(t *testing.T) {\n\tcases := []layerMask{\n\t\t{\n\t\t\tname:   \"aes128gcm\",\n\t\t\tmask:   &aes128gcm.Config{Password: \"123\"},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"original\",\n\t\t\tmask:   &original.Config{},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"dns\",\n\t\t\tmask:   &dns.Config{Domain: \"www.baidu.com\"},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"srtp\",\n\t\t\tmask:   &srtp.Config{},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"utp\",\n\t\t\tmask:   &utp.Config{},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"wechat\",\n\t\t\tmask:   &wechat.Config{},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"wireguard\",\n\t\t\tmask:   &wireguard.Config{},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname:   \"salamander\",\n\t\t\tmask:   &salamander.Config{Password: \"1234\"},\n\t\t\tlayers: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"sudoku-prefer-ascii\",\n\t\t\tmask: &sudoku.Config{\n\t\t\t\tPassword: \"sudoku-mask\",\n\t\t\t\tAscii:    \"prefer_ascii\",\n\t\t\t},\n\t\t\tlayers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"sudoku-custom-table\",\n\t\t\tmask: &sudoku.Config{\n\t\t\t\tPassword:    \"sudoku-mask\",\n\t\t\t\tAscii:       \"prefer_entropy\",\n\t\t\t\tCustomTable: \"xpxvvpvv\",\n\t\t\t},\n\t\t\tlayers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"sudoku-custom-tables\",\n\t\t\tmask: &sudoku.Config{\n\t\t\t\tPassword:     \"sudoku-mask\",\n\t\t\t\tAscii:        \"prefer_entropy\",\n\t\t\t\tCustomTables: []string{\"xpxvvpvv\", \"vxpvxvvp\"},\n\t\t\t},\n\t\t\tlayers: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"custom\",\n\t\t\tmask: &custom.UDPConfig{\n\t\t\t\tClient: []*custom.UDPItem{\n\t\t\t\t\t{\n\t\t\t\t\t\tPacket: []byte{1},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRand: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tServer: []*custom.UDPItem{\n\t\t\t\t\t{\n\t\t\t\t\t\tPacket: []byte{1},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tRand: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlayers: 1,\n\t\t},\n\t\t{\n\t\t\tname:   \"salamander-single\",\n\t\t\tmask:   &salamander.Config{Password: \"1234\"},\n\t\t\tlayers: 1,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tmask := c.mask\n\t\t\tlayers := c.layers\n\t\t\tif layers <= 0 {\n\t\t\t\tlayers = 1\n\t\t\t}\n\t\t\tmasks := make([]finalmask.Udpmask, 0, layers)\n\t\t\tfor i := 0; i < layers; i++ {\n\t\t\t\tmasks = append(masks, mask)\n\t\t\t}\n\t\t\tmaskManager := finalmask.NewUdpmaskManager(masks)\n\n\t\t\tclient, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient, err = maskManager.WrapPacketConnClient(client)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserver, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserver, err = maskManager.WrapPacketConnServer(server)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_ = client.SetDeadline(time.Now().Add(time.Second))\n\t\t\t_ = server.SetDeadline(time.Now().Add(time.Second))\n\n\t\t\tmustSendRecv(t, client, server, []byte(\"client -> server\"))\n\t\t\tmustSendRecv(t, server, client, []byte(\"server -> client\"))\n\n\t\t\tmustSendRecv(t, client, server, []byte{})\n\t\t\tmustSendRecv(t, server, client, []byte{})\n\t\t})\n\t}\n}\n\nfunc TestSudokuBDD(t *testing.T) {\n\tt.Run(\"GivenSudokuTCPMask_WhenRoundTripWithAsciiPreference_ThenPayloadMatches\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword: \"sudoku-tcp\",\n\t\t\tAscii:    \"prefer_ascii\",\n\t\t}\n\n\t\tclientRaw, serverRaw := net.Pipe()\n\t\tdefer clientRaw.Close()\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tserverConn, err := cfg.WrapConnServer(serverRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsend := bytes.Repeat([]byte(\"client->server\"), 1024)\n\t\trecv := make([]byte, len(send))\n\n\t\twriteErr := make(chan error, 1)\n\t\tgo func() {\n\t\t\t_, wErr := clientConn.Write(send)\n\t\t\twriteErr <- wErr\n\t\t}()\n\n\t\tif _, err := io.ReadFull(serverConn, recv); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := <-writeErr; err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(send, recv) {\n\t\t\tt.Fatal(\"tcp sudoku payload mismatch\")\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuTCPMask_WhenRoundTrip_ThenBothDirectionsMatch\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword:   \"sudoku-packed\",\n\t\t\tAscii:      \"prefer_ascii\",\n\t\t\tPaddingMin: 0,\n\t\t\tPaddingMax: 0,\n\t\t}\n\n\t\tclientRaw, serverRaw := net.Pipe()\n\t\tdefer clientRaw.Close()\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tserverConn, err := cfg.WrapConnServer(serverRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tclientToServer := bytes.Repeat([]byte(\"client-packed->server\"), 257)\n\t\tserverToClient := bytes.Repeat([]byte(\"server-packed->client\"), 263)\n\n\t\tc2sRecv := make([]byte, len(clientToServer))\n\t\tc2sErr := make(chan error, 1)\n\t\tgo func() {\n\t\t\t_, err := clientConn.Write(clientToServer)\n\t\t\tc2sErr <- err\n\t\t}()\n\t\tif _, err := io.ReadFull(serverConn, c2sRecv); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := <-c2sErr; err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(clientToServer, c2sRecv) {\n\t\t\tt.Fatal(\"tcp client->server payload mismatch\")\n\t\t}\n\n\t\ts2cRecv := make([]byte, len(serverToClient))\n\t\ts2cErr := make(chan error, 1)\n\t\tgo func() {\n\t\t\t_, err := serverConn.Write(serverToClient)\n\t\t\ts2cErr <- err\n\t\t}()\n\t\tif _, err := io.ReadFull(clientConn, s2cRecv); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := <-s2cErr; err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(serverToClient, s2cRecv) {\n\t\t\tt.Fatal(\"tcp server->client payload mismatch\")\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuTCPMask_WhenServerWritesDownlink_ThenWireBytesAreReduced\", func(t *testing.T) {\n\t\tpayload := bytes.Repeat([]byte(\"0123456789abcdef\"), 192) // 3072 bytes, divisible by 3.\n\n\t\tcountWireBytes := func(wrapServer func(net.Conn, *sudoku.Config) (net.Conn, error), cfg *sudoku.Config) int64 {\n\t\t\tt.Helper()\n\n\t\t\tclientRaw, serverRaw := net.Pipe()\n\t\t\twatchedServerRaw := &countingConn{Conn: serverRaw}\n\n\t\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tserverConn, err := wrapServer(watchedServerRaw, cfg)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\treadErr := make(chan error, 1)\n\t\t\tgo func() {\n\t\t\t\t_, err := io.CopyN(io.Discard, clientConn, int64(len(payload)))\n\t\t\t\treadErr <- err\n\t\t\t}()\n\n\t\t\tif _, err := serverConn.Write(payload); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err := <-readErr; err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_ = clientConn.Close()\n\t\t\t_ = serverConn.Close()\n\t\t\treturn watchedServerRaw.Written()\n\t\t}\n\n\t\tpureUplinkPackedDownlink := &sudoku.Config{\n\t\t\tPassword:   \"sudoku-bandwidth\",\n\t\t\tAscii:      \"prefer_entropy\",\n\t\t\tPaddingMin: 0,\n\t\t\tPaddingMax: 0,\n\t\t}\n\t\tpackedDownlinkBytes := countWireBytes(func(raw net.Conn, cfg *sudoku.Config) (net.Conn, error) {\n\t\t\treturn cfg.WrapConnServer(raw)\n\t\t}, pureUplinkPackedDownlink)\n\t\tlegacyPureBytes := countWireBytes(func(raw net.Conn, cfg *sudoku.Config) (net.Conn, error) {\n\t\t\treturn sudoku.NewTCPConn(raw, cfg)\n\t\t}, pureUplinkPackedDownlink)\n\n\t\tif packedDownlinkBytes >= legacyPureBytes {\n\t\t\tt.Fatalf(\"expected default packed downlink bytes < legacy pure bytes, got packed=%d pure=%d\", packedDownlinkBytes, legacyPureBytes)\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuMultiTableTCPMask_WhenRoundTrip_ThenPayloadMatches\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword:     \"sudoku-multi-tcp\",\n\t\t\tAscii:        \"prefer_entropy\",\n\t\t\tCustomTables: []string{\"xpxvvpvv\", \"vxpvxvvp\"},\n\t\t}\n\n\t\tclientRaw, serverRaw := net.Pipe()\n\t\tdefer clientRaw.Close()\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tserverConn, err := cfg.WrapConnServer(serverRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsend := bytes.Repeat([]byte(\"rotate-table\"), 513)\n\t\trecv := make([]byte, len(send))\n\n\t\twriteErr := make(chan error, 1)\n\t\tgo func() {\n\t\t\t_, wErr := clientConn.Write(send)\n\t\t\twriteErr <- wErr\n\t\t}()\n\n\t\tif _, err := io.ReadFull(serverConn, recv); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := <-writeErr; err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(send, recv) {\n\t\t\tt.Fatal(\"multi-table tcp sudoku payload mismatch\")\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuMultiTableTCPMask_WhenPackedDownlink_ThenPayloadMatches\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword:     \"sudoku-multi-packed\",\n\t\t\tAscii:        \"prefer_entropy\",\n\t\t\tCustomTables: []string{\"xpxvvpvv\", \"vxpvxvvp\"},\n\t\t\tPaddingMin:   0,\n\t\t\tPaddingMax:   0,\n\t\t}\n\n\t\tclientRaw, serverRaw := net.Pipe()\n\t\tdefer clientRaw.Close()\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tserverConn, err := cfg.WrapConnServer(serverRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsend := bytes.Repeat([]byte(\"packed-rotate\"), 257)\n\t\trecv := make([]byte, len(send))\n\n\t\twriteErr := make(chan error, 1)\n\t\tgo func() {\n\t\t\t_, wErr := clientConn.Write(send)\n\t\t\twriteErr <- wErr\n\t\t}()\n\n\t\tif _, err := io.ReadFull(serverConn, recv); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif err := <-writeErr; err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(send, recv) {\n\t\t\tt.Fatal(\"multi-table tcp sudoku payload mismatch\")\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuUDPMask_WhenNotInnermost_ThenWrapFails\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{Password: \"sudoku-udp\"}\n\t\traw, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer raw.Close()\n\n\t\tif _, err := cfg.WrapPacketConnClient(raw, 0, 1); err == nil {\n\t\t\tt.Fatal(\"expected innermost check failure\")\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuMultiTableUDPMask_WhenClientSendsMultipleDatagrams_ThenPayloadMatches\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword:     \"sudoku-udp-multi\",\n\t\t\tAscii:        \"prefer_entropy\",\n\t\t\tCustomTables: []string{\"xpxvvpvv\", \"vxpvxvvp\"},\n\t\t\tPaddingMin:   0,\n\t\t\tPaddingMax:   0,\n\t\t}\n\t\tmaskManager := finalmask.NewUdpmaskManager([]finalmask.Udpmask{cfg})\n\n\t\tclientRaw, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer clientRaw.Close()\n\n\t\tserverRaw, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer serverRaw.Close()\n\n\t\tclient, err := maskManager.WrapPacketConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tserver, err := maskManager.WrapPacketConnServer(serverRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t_ = client.SetDeadline(time.Now().Add(2 * time.Second))\n\t\t_ = server.SetDeadline(time.Now().Add(2 * time.Second))\n\n\t\tmustSendRecv(t, client, server, []byte(\"first-datagram\"))\n\t\tmustSendRecv(t, client, server, []byte(\"second-datagram\"))\n\t\tmustSendRecv(t, client, server, []byte(\"third-datagram\"))\n\t})\n\n\tt.Run(\"GivenSudokuTCPMask_WhenCloseWriteIsCalled_ThenEOFPropagates\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword:   \"sudoku-closewrite\",\n\t\t\tAscii:      \"prefer_ascii\",\n\t\t\tPaddingMin: 0,\n\t\t\tPaddingMax: 0,\n\t\t}\n\n\t\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer listener.Close()\n\n\t\tacceptCh := make(chan net.Conn, 1)\n\t\terrCh := make(chan error, 1)\n\t\tgo func() {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\terrCh <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\tacceptCh <- conn\n\t\t}()\n\n\t\tclientRaw, err := net.Dial(\"tcp\", listener.Addr().String())\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer clientRaw.Close()\n\n\t\tvar serverRaw net.Conn\n\t\tselect {\n\t\tcase serverRaw = <-acceptCh:\n\t\tcase err := <-errCh:\n\t\t\tt.Fatal(err)\n\t\tcase <-time.After(2 * time.Second):\n\t\t\tt.Fatal(\"accept timeout\")\n\t\t}\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tserverConn, err := cfg.WrapConnServer(serverRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tcloseWriter, ok := clientConn.(interface{ CloseWrite() error })\n\t\tif !ok {\n\t\t\tt.Fatalf(\"wrapped conn does not expose CloseWrite: %T\", clientConn)\n\t\t}\n\n\t\twriteErr := make(chan error, 1)\n\t\tgo func() {\n\t\t\tif _, err := clientConn.Write([]byte(\"closewrite\")); err != nil {\n\t\t\t\twriteErr <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\twriteErr <- closeWriter.CloseWrite()\n\t\t}()\n\n\t\tbuf := make([]byte, len(\"closewrite\"))\n\t\tif _, err := io.ReadFull(serverConn, buf); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(buf, []byte(\"closewrite\")) {\n\t\t\tt.Fatal(\"unexpected payload before closewrite\")\n\t\t}\n\t\tif err := <-writeErr; err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tone := make([]byte, 1)\n\t\tn, err := serverConn.Read(one)\n\t\tif n != 0 || err != io.EOF {\n\t\t\tt.Fatalf(\"expected EOF after CloseWrite, got n=%d err=%v\", n, err)\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuTCPMask_WhenProxyUnwrapRawConn_ThenMaskConnIsRetained\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword: \"sudoku-unwrap\",\n\t\t\tAscii:    \"prefer_entropy\",\n\t\t}\n\n\t\tclientRaw, serverRaw := net.Pipe()\n\t\tdefer clientRaw.Close()\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tunwrapped, readCounter, writeCounter := proxy.UnwrapRawConn(clientConn)\n\t\tif readCounter != nil || writeCounter != nil {\n\t\t\tt.Fatal(\"unexpected stat counters while unwrapping sudoku conn\")\n\t\t}\n\t\tif unwrapped != clientConn {\n\t\t\tt.Fatalf(\"expected sudoku conn to stay wrapped, got %T\", unwrapped)\n\t\t}\n\t})\n\n\tt.Run(\"GivenSudokuTCPMask_WhenProxyUnwrapRawConn_AfterDownlinkOptimization_ThenMaskConnIsRetained\", func(t *testing.T) {\n\t\tcfg := &sudoku.Config{\n\t\t\tPassword: \"sudoku-packed-unwrap\",\n\t\t\tAscii:    \"prefer_entropy\",\n\t\t}\n\n\t\tclientRaw, serverRaw := net.Pipe()\n\t\tdefer clientRaw.Close()\n\t\tdefer serverRaw.Close()\n\n\t\tclientConn, err := cfg.WrapConnClient(clientRaw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tunwrapped, readCounter, writeCounter := proxy.UnwrapRawConn(clientConn)\n\t\tif readCounter != nil || writeCounter != nil {\n\t\t\tt.Fatal(\"unexpected stat counters while unwrapping sudoku conn\")\n\t\t}\n\t\tif unwrapped != clientConn {\n\t\t\tt.Fatalf(\"expected sudoku conn to stay wrapped, got %T\", unwrapped)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/client.go",
    "content": "package xdns\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base32\"\n\t\"encoding/binary\"\n\tgo_errors \"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\nconst (\n\tnumPadding          = 3\n\tnumPaddingForPoll   = 8\n\tinitPollDelay       = 500 * time.Millisecond\n\tmaxPollDelay        = 10 * time.Second\n\tpollDelayMultiplier = 2.0\n\tpollLimit           = 16\n)\n\nvar base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)\n\ntype packet struct {\n\tp    []byte\n\taddr net.Addr\n}\n\ntype xdnsConnClient struct {\n\tnet.PacketConn\n\n\tclientID []byte\n\tdomain   Name\n\n\tpollChan   chan struct{}\n\treadQueue  chan *packet\n\twriteQueue chan *packet\n\n\tclosed bool\n\tmutex  sync.Mutex\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tdomain, err := ParseName(c.Domain)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn := &xdnsConnClient{\n\t\tPacketConn: raw,\n\n\t\tclientID: make([]byte, 8),\n\t\tdomain:   domain,\n\n\t\tpollChan:   make(chan struct{}, pollLimit),\n\t\treadQueue:  make(chan *packet, 256),\n\t\twriteQueue: make(chan *packet, 256),\n\t}\n\n\tcommon.Must2(rand.Read(conn.clientID))\n\n\tgo conn.recvLoop()\n\tgo conn.sendLoop()\n\n\treturn conn, nil\n}\n\nfunc (c *xdnsConnClient) recvLoop() {\n\tvar buf [finalmask.UDPSize]byte\n\n\tfor {\n\t\tif c.closed {\n\t\t\tbreak\n\t\t}\n\n\t\tn, addr, err := c.PacketConn.ReadFrom(buf[:])\n\t\tif err != nil || n == 0 {\n\t\t\tif go_errors.Is(err, net.ErrClosed) || go_errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tresp, err := MessageFromWireFormat(buf[:n])\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), addr, \" xdns from wireformat err \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tpayload := dnsResponsePayload(&resp, c.domain)\n\n\t\tr := bytes.NewReader(payload)\n\t\tanyPacket := false\n\t\tfor {\n\t\t\tp, err := nextPacket(r)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tanyPacket = true\n\n\t\t\tbuf := make([]byte, len(p))\n\t\t\tcopy(buf, p)\n\t\t\tselect {\n\t\t\tcase c.readQueue <- &packet{\n\t\t\t\tp:    buf,\n\t\t\t\taddr: addr,\n\t\t\t}:\n\t\t\tdefault:\n\t\t\t\terrors.LogDebug(context.Background(), addr, \" mask read err queue full\")\n\t\t\t}\n\t\t}\n\n\t\tif anyPacket {\n\t\t\tselect {\n\t\t\tcase c.pollChan <- struct{}{}:\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t}\n\n\terrors.LogDebug(context.Background(), \"xdns closed\")\n\n\tclose(c.pollChan)\n\tclose(c.readQueue)\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.closed = true\n\tclose(c.writeQueue)\n}\n\nfunc (c *xdnsConnClient) sendLoop() {\n\tvar addr net.Addr\n\n\tpollDelay := initPollDelay\n\tpollTimer := time.NewTimer(pollDelay)\n\tfor {\n\t\tvar p *packet\n\t\tpollTimerExpired := false\n\n\t\tselect {\n\t\tcase p = <-c.writeQueue:\n\t\tdefault:\n\t\t\tselect {\n\t\t\tcase p = <-c.writeQueue:\n\t\t\tcase <-c.pollChan:\n\t\t\tcase <-pollTimer.C:\n\t\t\t\tpollTimerExpired = true\n\t\t\t}\n\t\t}\n\n\t\tif p != nil {\n\t\t\taddr = p.addr\n\n\t\t\tselect {\n\t\t\tcase <-c.pollChan:\n\t\t\tdefault:\n\t\t\t}\n\t\t} else if addr != nil {\n\t\t\tencoded, _ := encode(nil, c.clientID, c.domain)\n\t\t\tp = &packet{\n\t\t\t\tp:    encoded,\n\t\t\t\taddr: addr,\n\t\t\t}\n\t\t}\n\n\t\tif pollTimerExpired {\n\t\t\tpollDelay = time.Duration(float64(pollDelay) * pollDelayMultiplier)\n\t\t\tif pollDelay > maxPollDelay {\n\t\t\t\tpollDelay = maxPollDelay\n\t\t\t}\n\t\t} else {\n\t\t\tif !pollTimer.Stop() {\n\t\t\t\t<-pollTimer.C\n\t\t\t}\n\t\t\tpollDelay = initPollDelay\n\t\t}\n\t\tpollTimer.Reset(pollDelay)\n\n\t\tif c.closed {\n\t\t\treturn\n\t\t}\n\n\t\tif p != nil {\n\t\t\t_, err := c.PacketConn.WriteTo(p.p, p.addr)\n\t\t\tif go_errors.Is(err, net.ErrClosed) || go_errors.Is(err, io.ErrClosedPipe) {\n\t\t\t\tc.closed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (c *xdnsConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpacket, ok := <-c.readQueue\n\tif !ok {\n\t\treturn 0, nil, net.ErrClosed\n\t}\n\tif len(p) < len(packet.p) {\n\t\terrors.LogDebug(context.Background(), packet.addr, \" mask read err short buffer \", len(p), \" \", len(packet.p))\n\t\treturn 0, packet.addr, nil\n\t}\n\tcopy(p, packet.p)\n\treturn len(packet.p), packet.addr, nil\n}\n\nfunc (c *xdnsConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tif c.closed {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\tencoded, err := encode(p, c.clientID, c.domain)\n\tif err != nil {\n\t\terrors.LogDebug(context.Background(), addr, \" xdns wireformat err \", err, \" \", len(p))\n\t\treturn 0, nil\n\t}\n\n\tselect {\n\tcase c.writeQueue <- &packet{\n\t\tp:    encoded,\n\t\taddr: addr,\n\t}:\n\t\treturn len(p), nil\n\tdefault:\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err queue full\")\n\t\treturn 0, nil\n\t}\n}\n\nfunc (c *xdnsConnClient) Close() error {\n\tc.closed = true\n\treturn c.PacketConn.Close()\n}\n\nfunc encode(p []byte, clientID []byte, domain Name) ([]byte, error) {\n\tvar decoded []byte\n\t{\n\t\tif len(p) >= 224 {\n\t\t\treturn nil, errors.New(\"too long\")\n\t\t}\n\t\tvar buf bytes.Buffer\n\t\tbuf.Write(clientID[:])\n\t\tn := numPadding\n\t\tif len(p) == 0 {\n\t\t\tn = numPaddingForPoll\n\t\t}\n\t\tbuf.WriteByte(byte(224 + n))\n\t\t_, _ = io.CopyN(&buf, rand.Reader, int64(n))\n\t\tif len(p) > 0 {\n\t\t\tbuf.WriteByte(byte(len(p)))\n\t\t\tbuf.Write(p)\n\t\t}\n\t\tdecoded = buf.Bytes()\n\t}\n\n\tencoded := make([]byte, base32Encoding.EncodedLen(len(decoded)))\n\tbase32Encoding.Encode(encoded, decoded)\n\tencoded = bytes.ToLower(encoded)\n\tlabels := chunks(encoded, 63)\n\tlabels = append(labels, domain...)\n\tname, err := NewName(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar id uint16\n\t_ = binary.Read(rand.Reader, binary.BigEndian, &id)\n\tquery := &Message{\n\t\tID:    id,\n\t\tFlags: 0x0100,\n\t\tQuestion: []Question{\n\t\t\t{\n\t\t\t\tName:  name,\n\t\t\t\tType:  RRTypeTXT,\n\t\t\t\tClass: ClassIN,\n\t\t\t},\n\t\t},\n\t\tAdditional: []RR{\n\t\t\t{\n\t\t\t\tName:  Name{},\n\t\t\t\tType:  RRTypeOPT,\n\t\t\t\tClass: 4096,\n\t\t\t\tTTL:   0,\n\t\t\t\tData:  []byte{},\n\t\t\t},\n\t\t},\n\t}\n\n\tbuf, err := query.WireFormat()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf, nil\n}\n\nfunc chunks(p []byte, n int) [][]byte {\n\tvar result [][]byte\n\tfor len(p) > 0 {\n\t\tsz := len(p)\n\t\tif sz > n {\n\t\t\tsz = n\n\t\t}\n\t\tresult = append(result, p[:sz])\n\t\tp = p[sz:]\n\t}\n\treturn result\n}\n\nfunc nextPacket(r *bytes.Reader) ([]byte, error) {\n\tvar n uint16\n\terr := binary.Read(r, binary.BigEndian, &n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp := make([]byte, n)\n\t_, err = io.ReadFull(r, p)\n\tif err == io.EOF {\n\t\terr = io.ErrUnexpectedEOF\n\t}\n\treturn p, err\n}\n\nfunc dnsResponsePayload(resp *Message, domain Name) []byte {\n\tif resp.Flags&0x8000 != 0x8000 {\n\t\treturn nil\n\t}\n\tif resp.Flags&0x000f != RcodeNoError {\n\t\treturn nil\n\t}\n\n\tif len(resp.Answer) != 1 {\n\t\treturn nil\n\t}\n\tanswer := resp.Answer[0]\n\n\t_, ok := answer.Name.TrimSuffix(domain)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tif answer.Type != RRTypeTXT {\n\t\treturn nil\n\t}\n\tpayload, err := DecodeRDataTXT(answer.Data)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\treturn payload\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/config.go",
    "content": "package xdns\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/xdns/config.proto\n\npackage xdns\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tDomain        string                 `protobuf:\"bytes,1,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetDomain() string {\n\tif x != nil {\n\t\treturn x.Domain\n\t}\n\treturn \"\"\n}\n\nvar File_transport_internet_finalmask_xdns_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_xdns_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\".transport/internet/finalmask/xdns/config.proto\\x12&xray.transport.internet.finalmask.xdns\\\" \\n\" +\n\t\"\\x06Config\\x12\\x16\\n\" +\n\t\"\\x06domain\\x18\\x01 \\x01(\\tR\\x06domainB\\x94\\x01\\n\" +\n\t\"*com.xray.transport.internet.finalmask.xdnsP\\x01Z;github.com/xtls/xray-core/transport/internet/finalmask/xdns\\xaa\\x02&Xray.Transport.Internet.Finalmask.Xdnsb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_xdns_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_xdns_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_xdns_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_xdns_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_xdns_config_proto_rawDesc), len(file_transport_internet_finalmask_xdns_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_xdns_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_xdns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.xdns.Config\n}\nvar file_transport_internet_finalmask_xdns_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_xdns_config_proto_init() }\nfunc file_transport_internet_finalmask_xdns_config_proto_init() {\n\tif File_transport_internet_finalmask_xdns_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_xdns_config_proto_rawDesc), len(file_transport_internet_finalmask_xdns_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_xdns_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_xdns_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_xdns_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_xdns_config_proto = out.File\n\tfile_transport_internet_finalmask_xdns_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_xdns_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.xdns;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Xdns\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/xdns\";\noption java_package = \"com.xray.transport.internet.finalmask.xdns\";\noption java_multiple_files = true;\n\nmessage Config {\n  string domain = 1;\n}\n\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/dns.go",
    "content": "// Package dns deals with encoding and decoding DNS wire format.\npackage xdns\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\n// The maximum number of DNS name compression pointers we are willing to follow.\n// Without something like this, infinite loops are possible.\nconst compressionPointerLimit = 10\n\nvar (\n\t// ErrZeroLengthLabel is the error returned for names that contain a\n\t// zero-length label, like \"example..com\".\n\tErrZeroLengthLabel = errors.New(\"name contains a zero-length label\")\n\n\t// ErrLabelTooLong is the error returned for labels that are longer than\n\t// 63 octets.\n\tErrLabelTooLong = errors.New(\"name contains a label longer than 63 octets\")\n\n\t// ErrNameTooLong is the error returned for names whose encoded\n\t// representation is longer than 255 octets.\n\tErrNameTooLong = errors.New(\"name is longer than 255 octets\")\n\n\t// ErrReservedLabelType is the error returned when reading a label type\n\t// prefix whose two most significant bits are not 00 or 11.\n\tErrReservedLabelType = errors.New(\"reserved label type\")\n\n\t// ErrTooManyPointers is the error returned when reading a compressed\n\t// name that has too many compression pointers.\n\tErrTooManyPointers = errors.New(\"too many compression pointers\")\n\n\t// ErrTrailingBytes is the error returned when bytes remain in the parse\n\t// buffer after parsing a message.\n\tErrTrailingBytes = errors.New(\"trailing bytes after message\")\n\n\t// ErrIntegerOverflow is the error returned when trying to encode an\n\t// integer greater than 65535 into a 16-bit field.\n\tErrIntegerOverflow = errors.New(\"integer overflow\")\n)\n\nconst (\n\t// https://tools.ietf.org/html/rfc1035#section-3.2.2\n\tRRTypeTXT = 16\n\t// https://tools.ietf.org/html/rfc6891#section-6.1.1\n\tRRTypeOPT = 41\n\n\t// https://tools.ietf.org/html/rfc1035#section-3.2.4\n\tClassIN = 1\n\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.1\n\tRcodeNoError        = 0 // a.k.a. NOERROR\n\tRcodeFormatError    = 1 // a.k.a. FORMERR\n\tRcodeNameError      = 3 // a.k.a. NXDOMAIN\n\tRcodeNotImplemented = 4 // a.k.a. NOTIMPL\n\t// https://tools.ietf.org/html/rfc6891#section-9\n\tExtendedRcodeBadVers = 16 // a.k.a. BADVERS\n)\n\n// Name represents a domain name, a sequence of labels each of which is 63\n// octets or less in length.\n//\n// https://tools.ietf.org/html/rfc1035#section-3.1\ntype Name [][]byte\n\n// NewName returns a Name from a slice of labels, after checking the labels for\n// validity. Does not include a zero-length label at the end of the slice.\nfunc NewName(labels [][]byte) (Name, error) {\n\tname := Name(labels)\n\t// https://tools.ietf.org/html/rfc1035#section-2.3.4\n\t// Various objects and parameters in the DNS have size limits.\n\t//   labels          63 octets or less\n\t//   names           255 octets or less\n\tfor _, label := range labels {\n\t\tif len(label) == 0 {\n\t\t\treturn nil, ErrZeroLengthLabel\n\t\t}\n\t\tif len(label) > 63 {\n\t\t\treturn nil, ErrLabelTooLong\n\t\t}\n\t}\n\t// Check the total length.\n\tbuilder := newMessageBuilder()\n\tbuilder.WriteName(name)\n\tif len(builder.Bytes()) > 255 {\n\t\treturn nil, ErrNameTooLong\n\t}\n\treturn name, nil\n}\n\n// ParseName returns a new Name from a string of labels separated by dots, after\n// checking the name for validity. A single dot at the end of the string is\n// ignored.\nfunc ParseName(s string) (Name, error) {\n\tb := bytes.TrimSuffix([]byte(s), []byte(\".\"))\n\tif len(b) == 0 {\n\t\t// bytes.Split(b, \".\") would return [\"\"] in this case\n\t\treturn NewName([][]byte{})\n\t} else {\n\t\treturn NewName(bytes.Split(b, []byte(\".\")))\n\t}\n}\n\n// String returns a reversible string representation of name. Labels are\n// separated by dots, and any bytes in a label that are outside the set\n// [0-9A-Za-z-] are replaced with a \\xXX hex escape sequence.\nfunc (name Name) String() string {\n\tif len(name) == 0 {\n\t\treturn \".\"\n\t}\n\n\tvar buf strings.Builder\n\tfor i, label := range name {\n\t\tif i > 0 {\n\t\t\tbuf.WriteByte('.')\n\t\t}\n\t\tfor _, b := range label {\n\t\t\tif b == '-' ||\n\t\t\t\t('0' <= b && b <= '9') ||\n\t\t\t\t('A' <= b && b <= 'Z') ||\n\t\t\t\t('a' <= b && b <= 'z') {\n\t\t\t\tbuf.WriteByte(b)\n\t\t\t} else {\n\t\t\t\tfmt.Fprintf(&buf, \"\\\\x%02x\", b)\n\t\t\t}\n\t\t}\n\t}\n\treturn buf.String()\n}\n\n// TrimSuffix returns a Name with the given suffix removed, if it was present.\n// The second return value indicates whether the suffix was present. If the\n// suffix was not present, the first return value is nil.\nfunc (name Name) TrimSuffix(suffix Name) (Name, bool) {\n\tif len(name) < len(suffix) {\n\t\treturn nil, false\n\t}\n\tsplit := len(name) - len(suffix)\n\tfore, aft := name[:split], name[split:]\n\tfor i := 0; i < len(aft); i++ {\n\t\tif !bytes.Equal(bytes.ToLower(aft[i]), bytes.ToLower(suffix[i])) {\n\t\t\treturn nil, false\n\t\t}\n\t}\n\treturn fore, true\n}\n\n// Message represents a DNS message.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1\ntype Message struct {\n\tID    uint16\n\tFlags uint16\n\n\tQuestion   []Question\n\tAnswer     []RR\n\tAuthority  []RR\n\tAdditional []RR\n}\n\n// Opcode extracts the OPCODE part of the Flags field.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1.1\nfunc (message *Message) Opcode() uint16 {\n\treturn (message.Flags >> 11) & 0xf\n}\n\n// Rcode extracts the RCODE part of the Flags field.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1.1\nfunc (message *Message) Rcode() uint16 {\n\treturn message.Flags & 0x000f\n}\n\n// Question represents an entry in the question section of a message.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1.2\ntype Question struct {\n\tName  Name\n\tType  uint16\n\tClass uint16\n}\n\n// RR represents a resource record.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1.3\ntype RR struct {\n\tName  Name\n\tType  uint16\n\tClass uint16\n\tTTL   uint32\n\tData  []byte\n}\n\n// readName parses a DNS name from r. It leaves r positioned just after the\n// parsed name.\nfunc readName(r io.ReadSeeker) (Name, error) {\n\tvar labels [][]byte\n\t// We limit the number of compression pointers we are willing to follow.\n\tnumPointers := 0\n\t// If we followed any compression pointers, we must finally seek to just\n\t// past the first pointer.\n\tvar seekTo int64\nloop:\n\tfor {\n\t\tvar labelType byte\n\t\terr := binary.Read(r, binary.BigEndian, &labelType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch labelType & 0xc0 {\n\t\tcase 0x00:\n\t\t\t// This is an ordinary label.\n\t\t\t// https://tools.ietf.org/html/rfc1035#section-3.1\n\t\t\tlength := int(labelType & 0x3f)\n\t\t\tif length == 0 {\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t\tlabel := make([]byte, length)\n\t\t\t_, err := io.ReadFull(r, label)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tlabels = append(labels, label)\n\t\tcase 0xc0:\n\t\t\t// This is a compression pointer.\n\t\t\t// https://tools.ietf.org/html/rfc1035#section-4.1.4\n\t\t\tupper := labelType & 0x3f\n\t\t\tvar lower byte\n\t\t\terr := binary.Read(r, binary.BigEndian, &lower)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\toffset := (uint16(upper) << 8) | uint16(lower)\n\n\t\t\tif numPointers == 0 {\n\t\t\t\t// The first time we encounter a pointer,\n\t\t\t\t// remember our position so we can seek back to\n\t\t\t\t// it when done.\n\t\t\t\tseekTo, err = r.Seek(0, io.SeekCurrent)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tnumPointers++\n\t\t\tif numPointers > compressionPointerLimit {\n\t\t\t\treturn nil, ErrTooManyPointers\n\t\t\t}\n\n\t\t\t// Follow the pointer and continue.\n\t\t\t_, err = r.Seek(int64(offset), io.SeekStart)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tdefault:\n\t\t\t// \"The 10 and 01 combinations are reserved for future\n\t\t\t// use.\"\n\t\t\treturn nil, ErrReservedLabelType\n\t\t}\n\t}\n\t// If we followed any pointers, then seek back to just after the first\n\t// one.\n\tif numPointers > 0 {\n\t\t_, err := r.Seek(seekTo, io.SeekStart)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn NewName(labels)\n}\n\n// readQuestion parses one entry from the Question section. It leaves r\n// positioned just after the parsed entry.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1.2\nfunc readQuestion(r io.ReadSeeker) (Question, error) {\n\tvar question Question\n\tvar err error\n\tquestion.Name, err = readName(r)\n\tif err != nil {\n\t\treturn question, err\n\t}\n\tfor _, ptr := range []*uint16{&question.Type, &question.Class} {\n\t\terr := binary.Read(r, binary.BigEndian, ptr)\n\t\tif err != nil {\n\t\t\treturn question, err\n\t\t}\n\t}\n\n\treturn question, nil\n}\n\n// readRR parses one resource record. It leaves r positioned just after the\n// parsed resource record.\n//\n// https://tools.ietf.org/html/rfc1035#section-4.1.3\nfunc readRR(r io.ReadSeeker) (RR, error) {\n\tvar rr RR\n\tvar err error\n\trr.Name, err = readName(r)\n\tif err != nil {\n\t\treturn rr, err\n\t}\n\tfor _, ptr := range []*uint16{&rr.Type, &rr.Class} {\n\t\terr := binary.Read(r, binary.BigEndian, ptr)\n\t\tif err != nil {\n\t\t\treturn rr, err\n\t\t}\n\t}\n\terr = binary.Read(r, binary.BigEndian, &rr.TTL)\n\tif err != nil {\n\t\treturn rr, err\n\t}\n\tvar rdLength uint16\n\terr = binary.Read(r, binary.BigEndian, &rdLength)\n\tif err != nil {\n\t\treturn rr, err\n\t}\n\trr.Data = make([]byte, rdLength)\n\t_, err = io.ReadFull(r, rr.Data)\n\tif err != nil {\n\t\treturn rr, err\n\t}\n\n\treturn rr, nil\n}\n\n// readMessage parses a complete DNS message. It leaves r positioned just after\n// the parsed message.\nfunc readMessage(r io.ReadSeeker) (Message, error) {\n\tvar message Message\n\n\t// Header section\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.1\n\tvar qdCount, anCount, nsCount, arCount uint16\n\tfor _, ptr := range []*uint16{\n\t\t&message.ID, &message.Flags,\n\t\t&qdCount, &anCount, &nsCount, &arCount,\n\t} {\n\t\terr := binary.Read(r, binary.BigEndian, ptr)\n\t\tif err != nil {\n\t\t\treturn message, err\n\t\t}\n\t}\n\n\t// Question section\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.2\n\tfor i := 0; i < int(qdCount); i++ {\n\t\tquestion, err := readQuestion(r)\n\t\tif err != nil {\n\t\t\treturn message, err\n\t\t}\n\t\tmessage.Question = append(message.Question, question)\n\t}\n\n\t// Answer, Authority, and Additional sections\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.3\n\tfor _, rec := range []struct {\n\t\tptr   *[]RR\n\t\tcount uint16\n\t}{\n\t\t{&message.Answer, anCount},\n\t\t{&message.Authority, nsCount},\n\t\t{&message.Additional, arCount},\n\t} {\n\t\tfor i := 0; i < int(rec.count); i++ {\n\t\t\trr, err := readRR(r)\n\t\t\tif err != nil {\n\t\t\t\treturn message, err\n\t\t\t}\n\t\t\t*rec.ptr = append(*rec.ptr, rr)\n\t\t}\n\t}\n\n\treturn message, nil\n}\n\n// MessageFromWireFormat parses a message from buf and returns a Message object.\n// It returns ErrTrailingBytes if there are bytes remaining in buf after parsing\n// is done.\nfunc MessageFromWireFormat(buf []byte) (Message, error) {\n\tr := bytes.NewReader(buf)\n\tmessage, err := readMessage(r)\n\tif err == io.EOF {\n\t\terr = io.ErrUnexpectedEOF\n\t} else if err == nil {\n\t\t// Check for trailing bytes.\n\t\t_, err = r.ReadByte()\n\t\tif err == io.EOF {\n\t\t\terr = nil\n\t\t} else if err == nil {\n\t\t\terr = ErrTrailingBytes\n\t\t}\n\t}\n\treturn message, err\n}\n\n// messageBuilder manages the state of serializing a DNS message. Its main\n// function is to keep track of names already written for the purpose of name\n// compression.\ntype messageBuilder struct {\n\tw         bytes.Buffer\n\tnameCache map[string]int\n}\n\n// newMessageBuilder creates a new messageBuilder with an empty name cache.\nfunc newMessageBuilder() *messageBuilder {\n\treturn &messageBuilder{\n\t\tnameCache: make(map[string]int),\n\t}\n}\n\n// Bytes returns the serialized DNS message as a slice of bytes.\nfunc (builder *messageBuilder) Bytes() []byte {\n\treturn builder.w.Bytes()\n}\n\n// WriteName appends name to the in-progress messageBuilder, employing\n// compression pointers to previously written names if possible.\nfunc (builder *messageBuilder) WriteName(name Name) {\n\t// https://tools.ietf.org/html/rfc1035#section-3.1\n\tfor i := range name {\n\t\t// Has this suffix already been encoded in the message?\n\t\tif ptr, ok := builder.nameCache[name[i:].String()]; ok && ptr&0x3fff == ptr {\n\t\t\t// If so, we can write a compression pointer.\n\t\t\tbinary.Write(&builder.w, binary.BigEndian, uint16(0xc000|ptr))\n\t\t\treturn\n\t\t}\n\t\t// Not cached; we must encode this label verbatim. Store a cache\n\t\t// entry pointing to the beginning of it.\n\t\tbuilder.nameCache[name[i:].String()] = builder.w.Len()\n\t\tlength := len(name[i])\n\t\tif length == 0 || length > 63 {\n\t\t\tpanic(length)\n\t\t}\n\t\tbuilder.w.WriteByte(byte(length))\n\t\tbuilder.w.Write(name[i])\n\t}\n\tbuilder.w.WriteByte(0)\n}\n\n// WriteQuestion appends a Question section entry to the in-progress\n// messageBuilder.\nfunc (builder *messageBuilder) WriteQuestion(question *Question) {\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.2\n\tbuilder.WriteName(question.Name)\n\tbinary.Write(&builder.w, binary.BigEndian, question.Type)\n\tbinary.Write(&builder.w, binary.BigEndian, question.Class)\n}\n\n// WriteRR appends a resource record to the in-progress messageBuilder. It\n// returns ErrIntegerOverflow if the length of rr.Data does not fit in 16 bits.\nfunc (builder *messageBuilder) WriteRR(rr *RR) error {\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.3\n\tbuilder.WriteName(rr.Name)\n\tbinary.Write(&builder.w, binary.BigEndian, rr.Type)\n\tbinary.Write(&builder.w, binary.BigEndian, rr.Class)\n\tbinary.Write(&builder.w, binary.BigEndian, rr.TTL)\n\trdLength := uint16(len(rr.Data))\n\tif int(rdLength) != len(rr.Data) {\n\t\treturn ErrIntegerOverflow\n\t}\n\tbinary.Write(&builder.w, binary.BigEndian, rdLength)\n\tbuilder.w.Write(rr.Data)\n\treturn nil\n}\n\n// WriteMessage appends a complete DNS message to the in-progress\n// messageBuilder. It returns ErrIntegerOverflow if the number of entries in any\n// section, or the length of the data in any resource record, does not fit in 16\n// bits.\nfunc (builder *messageBuilder) WriteMessage(message *Message) error {\n\t// Header section\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.1\n\tbinary.Write(&builder.w, binary.BigEndian, message.ID)\n\tbinary.Write(&builder.w, binary.BigEndian, message.Flags)\n\tfor _, count := range []int{\n\t\tlen(message.Question),\n\t\tlen(message.Answer),\n\t\tlen(message.Authority),\n\t\tlen(message.Additional),\n\t} {\n\t\tcount16 := uint16(count)\n\t\tif int(count16) != count {\n\t\t\treturn ErrIntegerOverflow\n\t\t}\n\t\tbinary.Write(&builder.w, binary.BigEndian, count16)\n\t}\n\n\t// Question section\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.2\n\tfor _, question := range message.Question {\n\t\tbuilder.WriteQuestion(&question)\n\t}\n\n\t// Answer, Authority, and Additional sections\n\t// https://tools.ietf.org/html/rfc1035#section-4.1.3\n\tfor _, rrs := range [][]RR{message.Answer, message.Authority, message.Additional} {\n\t\tfor _, rr := range rrs {\n\t\t\terr := builder.WriteRR(&rr)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// WireFormat encodes a Message as a slice of bytes in DNS wire format. It\n// returns ErrIntegerOverflow if the number of entries in any section, or the\n// length of the data in any resource record, does not fit in 16 bits.\nfunc (message *Message) WireFormat() ([]byte, error) {\n\tbuilder := newMessageBuilder()\n\terr := builder.WriteMessage(message)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn builder.Bytes(), nil\n}\n\n// DecodeRDataTXT decodes TXT-DATA (as found in the RDATA for a resource record\n// with TYPE=TXT) as a raw byte slice, by concatenating all the\n// <character-string>s it contains.\n//\n// https://tools.ietf.org/html/rfc1035#section-3.3.14\nfunc DecodeRDataTXT(p []byte) ([]byte, error) {\n\tvar buf bytes.Buffer\n\tfor {\n\t\tif len(p) == 0 {\n\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t}\n\t\tn := int(p[0])\n\t\tp = p[1:]\n\t\tif len(p) < n {\n\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t}\n\t\tbuf.Write(p[:n])\n\t\tp = p[n:]\n\t\tif len(p) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn buf.Bytes(), nil\n}\n\n// EncodeRDataTXT encodes a slice of bytes as TXT-DATA, as appropriate for the\n// RDATA of a resource record with TYPE=TXT. No length restriction is enforced\n// here; that must be checked at a higher level.\n//\n// https://tools.ietf.org/html/rfc1035#section-3.3.14\nfunc EncodeRDataTXT(p []byte) []byte {\n\t// https://tools.ietf.org/html/rfc1035#section-3.3\n\t// https://tools.ietf.org/html/rfc1035#section-3.3.14\n\t// TXT data is a sequence of one or more <character-string>s, where\n\t// <character-string> is a length octet followed by that number of\n\t// octets.\n\tvar buf bytes.Buffer\n\tfor len(p) > 255 {\n\t\tbuf.WriteByte(255)\n\t\tbuf.Write(p[:255])\n\t\tp = p[255:]\n\t}\n\t// Must write here, even if len(p) == 0, because it's \"*one or more*\n\t// <character-string>s\".\n\tbuf.WriteByte(byte(len(p)))\n\tbuf.Write(p)\n\treturn buf.Bytes()\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/dns_test.go",
    "content": "package xdns\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc namesEqual(a, b Name) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a); i++ {\n\t\tif !bytes.Equal(a[i], b[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestName(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tlabels [][]byte\n\t\terr    error\n\t\ts      string\n\t}{\n\t\t{[][]byte{}, nil, \".\"},\n\t\t{[][]byte{[]byte(\"test\")}, nil, \"test\"},\n\t\t{[][]byte{[]byte(\"a\"), []byte(\"b\"), []byte(\"c\")}, nil, \"a.b.c\"},\n\n\t\t{[][]byte{{}}, ErrZeroLengthLabel, \"\"},\n\t\t{[][]byte{[]byte(\"a\"), {}, []byte(\"c\")}, ErrZeroLengthLabel, \"\"},\n\n\t\t// 63 octets.\n\t\t{[][]byte{[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\")}, nil,\n\t\t\t\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"},\n\t\t// 64 octets.\n\t\t{[][]byte{[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF\")}, ErrLabelTooLong, \"\"},\n\n\t\t// 64+64+64+62 octets.\n\t\t{[][]byte{\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"),\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"),\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"),\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABC\"),\n\t\t}, nil,\n\t\t\t\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE.0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE.0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE.0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABC\"},\n\t\t// 64+64+64+63 octets.\n\t\t{[][]byte{\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"),\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"),\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE\"),\n\t\t\t[]byte(\"0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD\"),\n\t\t}, ErrNameTooLong, \"\"},\n\t\t// 127 one-octet labels.\n\t\t{[][]byte{\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'},\n\t\t}, nil,\n\t\t\t\"0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F.0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F.0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F.0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E\"},\n\t\t// 128 one-octet labels.\n\t\t{[][]byte{\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'},\n\t\t\t{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'A'}, {'B'}, {'C'}, {'D'}, {'E'}, {'F'},\n\t\t}, ErrNameTooLong, \"\"},\n\t} {\n\t\t// Test that NewName returns proper error codes, and otherwise\n\t\t// returns an equal slice of labels.\n\t\tname, err := NewName(test.labels)\n\t\tif err != test.err || (err == nil && !namesEqual(name, test.labels)) {\n\t\t\tt.Errorf(\"%+q returned (%+q, %v), expected (%+q, %v)\",\n\t\t\t\ttest.labels, name, err, test.labels, test.err)\n\t\t\tcontinue\n\t\t}\n\t\tif test.err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Test that the string version of the name comes out as\n\t\t// expected.\n\t\ts := name.String()\n\t\tif s != test.s {\n\t\t\tt.Errorf(\"%+q became string %+q, expected %+q\", test.labels, s, test.s)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Test that parsing from a string back to a Name results in the\n\t\t// original slice of labels.\n\t\tname, err = ParseName(s)\n\t\tif err != nil || !namesEqual(name, test.labels) {\n\t\t\tt.Errorf(\"%+q parsing %+q returned (%+q, %v), expected (%+q, %v)\",\n\t\t\t\ttest.labels, s, name, err, test.labels, nil)\n\t\t\tcontinue\n\t\t}\n\t\t// A trailing dot should be ignored.\n\t\tif !strings.HasSuffix(s, \".\") {\n\t\t\tdotName, dotErr := ParseName(s + \".\")\n\t\t\tif dotErr != err || !namesEqual(dotName, name) {\n\t\t\t\tt.Errorf(\"%+q parsing %+q returned (%+q, %v), expected (%+q, %v)\",\n\t\t\t\t\ttest.labels, s+\".\", dotName, dotErr, name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestParseName(t *testing.T) {\n\tfor _, test := range []struct {\n\t\ts    string\n\t\tname Name\n\t\terr  error\n\t}{\n\t\t// This case can't be tested by TestName above because String\n\t\t// will never produce \"\" (it produces \".\" instead).\n\t\t{\"\", [][]byte{}, nil},\n\t} {\n\t\tname, err := ParseName(test.s)\n\t\tif err != test.err || (err == nil && !namesEqual(name, test.name)) {\n\t\t\tt.Errorf(\"%+q returned (%+q, %v), expected (%+q, %v)\",\n\t\t\t\ttest.s, name, err, test.name, test.err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc unescapeString(s string) ([][]byte, error) {\n\tif s == \".\" {\n\t\treturn [][]byte{}, nil\n\t}\n\n\tvar result [][]byte\n\tfor _, label := range strings.Split(s, \".\") {\n\t\tvar buf bytes.Buffer\n\t\ti := 0\n\t\tfor i < len(label) {\n\t\t\tswitch label[i] {\n\t\t\tcase '\\\\':\n\t\t\t\tif i+3 >= len(label) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"truncated escape sequence at index %v\", i)\n\t\t\t\t}\n\t\t\t\tif label[i+1] != 'x' {\n\t\t\t\t\treturn nil, fmt.Errorf(\"malformed escape sequence at index %v\", i)\n\t\t\t\t}\n\t\t\t\tb, err := strconv.ParseUint(string(label[i+2:i+4]), 16, 8)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"malformed hex sequence at index %v\", i+2)\n\t\t\t\t}\n\t\t\t\tbuf.WriteByte(byte(b))\n\t\t\t\ti += 4\n\t\t\tdefault:\n\t\t\t\tbuf.WriteByte(label[i])\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t\tresult = append(result, buf.Bytes())\n\t}\n\treturn result, nil\n}\n\nfunc TestNameString(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tname Name\n\t\ts    string\n\t}{\n\t\t{[][]byte{}, \".\"},\n\t\t{[][]byte{[]byte(\"\\x00\"), []byte(\"a.b\"), []byte(\"c\\nd\\\\\")}, \"\\\\x00.a\\\\x2eb.c\\\\x0ad\\\\x5c\"},\n\t\t{[][]byte{\n\t\t\t[]byte(\"\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\\\"#$%&'()*+,-./0123456789:;<=>\"),\n\t\t\t[]byte(\"?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}\"),\n\t\t\t[]byte(\"~\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\"),\n\t\t\t[]byte(\"\\xbd\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\"),\n\t\t\t[]byte(\"\\xfc\\xfd\\xfe\\xff\"),\n\t\t}, \"\\\\x00\\\\x01\\\\x02\\\\x03\\\\x04\\\\x05\\\\x06\\\\x07\\\\x08\\\\x09\\\\x0a\\\\x0b\\\\x0c\\\\x0d\\\\x0e\\\\x0f\\\\x10\\\\x11\\\\x12\\\\x13\\\\x14\\\\x15\\\\x16\\\\x17\\\\x18\\\\x19\\\\x1a\\\\x1b\\\\x1c\\\\x1d\\\\x1e\\\\x1f\\\\x20\\\\x21\\\\x22\\\\x23\\\\x24\\\\x25\\\\x26\\\\x27\\\\x28\\\\x29\\\\x2a\\\\x2b\\\\x2c-\\\\x2e\\\\x2f0123456789\\\\x3a\\\\x3b\\\\x3c\\\\x3d\\\\x3e.\\\\x3f\\\\x40ABCDEFGHIJKLMNOPQRSTUVWXYZ\\\\x5b\\\\x5c\\\\x5d\\\\x5e\\\\x5f\\\\x60abcdefghijklmnopqrstuvwxyz\\\\x7b\\\\x7c\\\\x7d.\\\\x7e\\\\x7f\\\\x80\\\\x81\\\\x82\\\\x83\\\\x84\\\\x85\\\\x86\\\\x87\\\\x88\\\\x89\\\\x8a\\\\x8b\\\\x8c\\\\x8d\\\\x8e\\\\x8f\\\\x90\\\\x91\\\\x92\\\\x93\\\\x94\\\\x95\\\\x96\\\\x97\\\\x98\\\\x99\\\\x9a\\\\x9b\\\\x9c\\\\x9d\\\\x9e\\\\x9f\\\\xa0\\\\xa1\\\\xa2\\\\xa3\\\\xa4\\\\xa5\\\\xa6\\\\xa7\\\\xa8\\\\xa9\\\\xaa\\\\xab\\\\xac\\\\xad\\\\xae\\\\xaf\\\\xb0\\\\xb1\\\\xb2\\\\xb3\\\\xb4\\\\xb5\\\\xb6\\\\xb7\\\\xb8\\\\xb9\\\\xba\\\\xbb\\\\xbc.\\\\xbd\\\\xbe\\\\xbf\\\\xc0\\\\xc1\\\\xc2\\\\xc3\\\\xc4\\\\xc5\\\\xc6\\\\xc7\\\\xc8\\\\xc9\\\\xca\\\\xcb\\\\xcc\\\\xcd\\\\xce\\\\xcf\\\\xd0\\\\xd1\\\\xd2\\\\xd3\\\\xd4\\\\xd5\\\\xd6\\\\xd7\\\\xd8\\\\xd9\\\\xda\\\\xdb\\\\xdc\\\\xdd\\\\xde\\\\xdf\\\\xe0\\\\xe1\\\\xe2\\\\xe3\\\\xe4\\\\xe5\\\\xe6\\\\xe7\\\\xe8\\\\xe9\\\\xea\\\\xeb\\\\xec\\\\xed\\\\xee\\\\xef\\\\xf0\\\\xf1\\\\xf2\\\\xf3\\\\xf4\\\\xf5\\\\xf6\\\\xf7\\\\xf8\\\\xf9\\\\xfa\\\\xfb.\\\\xfc\\\\xfd\\\\xfe\\\\xff\"},\n\t} {\n\t\ts := test.name.String()\n\t\tif s != test.s {\n\t\t\tt.Errorf(\"%+q escaped to %+q, expected %+q\", test.name, s, test.s)\n\t\t\tcontinue\n\t\t}\n\t\tunescaped, err := unescapeString(s)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%+q unescaping %+q resulted in error %v\", test.name, s, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !namesEqual(Name(unescaped), test.name) {\n\t\t\tt.Errorf(\"%+q roundtripped through %+q to %+q\", test.name, s, unescaped)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestNameTrimSuffix(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tname, suffix string\n\t\ttrimmed      string\n\t\tok           bool\n\t}{\n\t\t{\"\", \"\", \".\", true},\n\t\t{\".\", \".\", \".\", true},\n\t\t{\"abc\", \"\", \"abc\", true},\n\t\t{\"abc\", \".\", \"abc\", true},\n\t\t{\"\", \"abc\", \".\", false},\n\t\t{\".\", \"abc\", \".\", false},\n\t\t{\"example.com\", \"com\", \"example\", true},\n\t\t{\"example.com\", \"net\", \".\", false},\n\t\t{\"example.com\", \"example.com\", \".\", true},\n\t\t{\"example.com\", \"test.com\", \".\", false},\n\t\t{\"example.com\", \"xample.com\", \".\", false},\n\t\t{\"example.com\", \"example\", \".\", false},\n\t\t{\"example.com\", \"COM\", \"example\", true},\n\t\t{\"EXAMPLE.COM\", \"com\", \"EXAMPLE\", true},\n\t} {\n\t\ttmp, ok := mustParseName(test.name).TrimSuffix(mustParseName(test.suffix))\n\t\ttrimmed := tmp.String()\n\t\tif ok != test.ok || trimmed != test.trimmed {\n\t\t\tt.Errorf(\"TrimSuffix %+q %+q returned (%+q, %v), expected (%+q, %v)\",\n\t\t\t\ttest.name, test.suffix, trimmed, ok, test.trimmed, test.ok)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestReadName(t *testing.T) {\n\t// Good tests.\n\tfor _, test := range []struct {\n\t\tstart int64\n\t\tend   int64\n\t\tinput string\n\t\ts     string\n\t}{\n\t\t// Empty name.\n\t\t{0, 1, \"\\x00abcd\", \".\"},\n\t\t// No pointers.\n\t\t{12, 25, \"AAAABBBBCCCC\\x07example\\x03com\\x00\", \"example.com\"},\n\t\t// Backward pointer.\n\t\t{25, 31, \"AAAABBBBCCCC\\x07example\\x03com\\x00\\x03sub\\xc0\\x0c\", \"sub.example.com\"},\n\t\t// Forward pointer.\n\t\t{0, 4, \"\\x01a\\xc0\\x04\\x03bcd\\x00\", \"a.bcd\"},\n\t\t// Two backwards pointers.\n\t\t{31, 38, \"AAAABBBBCCCC\\x07example\\x03com\\x00\\x03sub\\xc0\\x0c\\x04sub2\\xc0\\x19\", \"sub2.sub.example.com\"},\n\t\t// Forward then backward pointer.\n\t\t{25, 31, \"AAAABBBBCCCC\\x07example\\x03com\\x00\\x03sub\\xc0\\x1f\\x04sub2\\xc0\\x0c\", \"sub.sub2.example.com\"},\n\t\t// Overlapping codons.\n\t\t{0, 4, \"\\x01a\\xc0\\x03bcd\\x00\", \"a.bcd\"},\n\t\t// Pointer to empty label.\n\t\t{0, 10, \"\\x07example\\xc0\\x0a\\x00\", \"example\"},\n\t\t{1, 11, \"\\x00\\x07example\\xc0\\x00\", \"example\"},\n\t\t// Pointer to pointer to empty label.\n\t\t{0, 10, \"\\x07example\\xc0\\x0a\\xc0\\x0c\\x00\", \"example\"},\n\t\t{1, 11, \"\\x00\\x07example\\xc0\\x0c\\xc0\\x00\", \"example\"},\n\t} {\n\t\tr := bytes.NewReader([]byte(test.input))\n\t\t_, err := r.Seek(test.start, io.SeekStart)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tname, err := readName(r)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%+q returned error %s\", test.input, err)\n\t\t\tcontinue\n\t\t}\n\t\ts := name.String()\n\t\tif s != test.s {\n\t\t\tt.Errorf(\"%+q returned %+q, expected %+q\", test.input, s, test.s)\n\t\t\tcontinue\n\t\t}\n\t\tcur, _ := r.Seek(0, io.SeekCurrent)\n\t\tif cur != test.end {\n\t\t\tt.Errorf(\"%+q left offset %d, expected %d\", test.input, cur, test.end)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\t// Bad tests.\n\tfor _, test := range []struct {\n\t\tstart int64\n\t\tinput string\n\t\terr   error\n\t}{\n\t\t{0, \"\", io.ErrUnexpectedEOF},\n\t\t// Reserved label type.\n\t\t{0, \"\\x80example\", ErrReservedLabelType},\n\t\t// Reserved label type.\n\t\t{0, \"\\x40example\", ErrReservedLabelType},\n\t\t// No Terminating empty label.\n\t\t{0, \"\\x07example\\x03com\", io.ErrUnexpectedEOF},\n\t\t// Pointer past end of buffer.\n\t\t{0, \"\\x07example\\xc0\\xff\", io.ErrUnexpectedEOF},\n\t\t// Pointer to self.\n\t\t{0, \"\\x07example\\x03com\\xc0\\x0c\", ErrTooManyPointers},\n\t\t// Pointer to self with intermediate label.\n\t\t{0, \"\\x07example\\x03com\\xc0\\x08\", ErrTooManyPointers},\n\t\t// Two pointers that point to each other.\n\t\t{0, \"\\xc0\\x02\\xc0\\x00\", ErrTooManyPointers},\n\t\t// Two pointers that point to each other, with intermediate labels.\n\t\t{0, \"\\x01a\\xc0\\x04\\x01b\\xc0\\x00\", ErrTooManyPointers},\n\t\t// EOF while reading label.\n\t\t{0, \"\\x0aexample\", io.ErrUnexpectedEOF},\n\t\t// EOF before second byte of pointer.\n\t\t{0, \"\\xc0\", io.ErrUnexpectedEOF},\n\t\t{0, \"\\x07example\\xc0\", io.ErrUnexpectedEOF},\n\t} {\n\t\tr := bytes.NewReader([]byte(test.input))\n\t\t_, err := r.Seek(test.start, io.SeekStart)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tname, err := readName(r)\n\t\tif err == io.EOF {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t\tif err != test.err {\n\t\t\tt.Errorf(\"%+q returned (%+q, %v), expected %v\", test.input, name, err, test.err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc mustParseName(s string) Name {\n\tname, err := ParseName(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn name\n}\n\nfunc questionsEqual(a, b *Question) bool {\n\tif !namesEqual(a.Name, b.Name) {\n\t\treturn false\n\t}\n\tif a.Type != b.Type || a.Class != b.Class {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc rrsEqual(a, b *RR) bool {\n\tif !namesEqual(a.Name, b.Name) {\n\t\treturn false\n\t}\n\tif a.Type != b.Type || a.Class != b.Class || a.TTL != b.TTL {\n\t\treturn false\n\t}\n\tif !bytes.Equal(a.Data, b.Data) {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc messagesEqual(a, b *Message) bool {\n\tif a.ID != b.ID || a.Flags != b.Flags {\n\t\treturn false\n\t}\n\tif len(a.Question) != len(b.Question) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a.Question); i++ {\n\t\tif !questionsEqual(&a.Question[i], &b.Question[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\tfor _, rec := range []struct{ rrA, rrB []RR }{\n\t\t{a.Answer, b.Answer},\n\t\t{a.Authority, b.Authority},\n\t\t{a.Additional, b.Additional},\n\t} {\n\t\tif len(rec.rrA) != len(rec.rrB) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := 0; i < len(rec.rrA); i++ {\n\t\t\tif !rrsEqual(&rec.rrA[i], &rec.rrB[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestMessageFromWireFormat(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tbuf      string\n\t\texpected Message\n\t\terr      error\n\t}{\n\t\t{\n\t\t\t\"\\x12\\x34\",\n\t\t\tMessage{},\n\t\t\tio.ErrUnexpectedEOF,\n\t\t},\n\t\t{\n\t\t\t\"\\x12\\x34\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x03www\\x07example\\x03com\\x00\\x00\\x01\\x00\\x01\",\n\t\t\tMessage{\n\t\t\t\tID:    0x1234,\n\t\t\t\tFlags: 0x0100,\n\t\t\t\tQuestion: []Question{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  mustParseName(\"www.example.com\"),\n\t\t\t\t\t\tType:  1,\n\t\t\t\t\t\tClass: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tAnswer:     []RR{},\n\t\t\t\tAuthority:  []RR{},\n\t\t\t\tAdditional: []RR{},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"\\x12\\x34\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x03www\\x07example\\x03com\\x00\\x00\\x01\\x00\\x01X\",\n\t\t\tMessage{},\n\t\t\tErrTrailingBytes,\n\t\t},\n\t\t{\n\t\t\t\"\\x12\\x34\\x81\\x80\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x03www\\x07example\\x03com\\x00\\x00\\x01\\x00\\x01\\x03www\\x07example\\x03com\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x80\\x00\\x04\\xc0\\x00\\x02\\x01\",\n\t\t\tMessage{\n\t\t\t\tID:    0x1234,\n\t\t\t\tFlags: 0x8180,\n\t\t\t\tQuestion: []Question{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  mustParseName(\"www.example.com\"),\n\t\t\t\t\t\tType:  1,\n\t\t\t\t\t\tClass: 1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tAnswer: []RR{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  mustParseName(\"www.example.com\"),\n\t\t\t\t\t\tType:  1,\n\t\t\t\t\t\tClass: 1,\n\t\t\t\t\t\tTTL:   128,\n\t\t\t\t\t\tData:  []byte{192, 0, 2, 1},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tAuthority:  []RR{},\n\t\t\t\tAdditional: []RR{},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t} {\n\t\tmessage, err := MessageFromWireFormat([]byte(test.buf))\n\t\tif err != test.err || (err == nil && !messagesEqual(&message, &test.expected)) {\n\t\t\tt.Errorf(\"%+q\\nreturned (%+v, %v)\\nexpected (%+v, %v)\",\n\t\t\t\ttest.buf, message, err, test.expected, test.err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestMessageWireFormatRoundTrip(t *testing.T) {\n\tfor _, message := range []Message{\n\t\t{\n\t\t\tID:    0x1234,\n\t\t\tFlags: 0x0100,\n\t\t\tQuestion: []Question{\n\t\t\t\t{\n\t\t\t\t\tName:  mustParseName(\"www.example.com\"),\n\t\t\t\t\tType:  1,\n\t\t\t\t\tClass: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  mustParseName(\"www2.example.com\"),\n\t\t\t\t\tType:  2,\n\t\t\t\t\tClass: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tAnswer: []RR{\n\t\t\t\t{\n\t\t\t\t\tName:  mustParseName(\"abc\"),\n\t\t\t\t\tType:  2,\n\t\t\t\t\tClass: 3,\n\t\t\t\t\tTTL:   0xffffffff,\n\t\t\t\t\tData:  []byte{1},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  mustParseName(\"xyz\"),\n\t\t\t\t\tType:  2,\n\t\t\t\t\tClass: 3,\n\t\t\t\t\tTTL:   255,\n\t\t\t\t\tData:  []byte{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tAuthority: []RR{\n\t\t\t\t{\n\t\t\t\t\tName:  mustParseName(\".\"),\n\t\t\t\t\tType:  65535,\n\t\t\t\t\tClass: 65535,\n\t\t\t\t\tTTL:   0,\n\t\t\t\t\tData:  []byte(\"XXXXXXXXXXXXXXXXXXX\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tAdditional: []RR{},\n\t\t},\n\t} {\n\t\tbuf, err := message.WireFormat()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%+v cannot make wire format: %v\", message, err)\n\t\t\tcontinue\n\t\t}\n\t\tmessage2, err := MessageFromWireFormat(buf)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%+q cannot parse wire format: %v\", buf, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !messagesEqual(&message, &message2) {\n\t\t\tt.Errorf(\"messages unequal\\nbefore: %+v\\n after: %+v\", message, message2)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestDecodeRDataTXT(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tp       []byte\n\t\tdecoded []byte\n\t\terr     error\n\t}{\n\t\t{[]byte{}, nil, io.ErrUnexpectedEOF},\n\t\t{[]byte(\"\\x00\"), []byte{}, nil},\n\t\t{[]byte(\"\\x01\"), nil, io.ErrUnexpectedEOF},\n\t} {\n\t\tdecoded, err := DecodeRDataTXT(test.p)\n\t\tif err != test.err || (err == nil && !bytes.Equal(decoded, test.decoded)) {\n\t\t\tt.Errorf(\"%+q\\nreturned (%+q, %v)\\nexpected (%+q, %v)\",\n\t\t\t\ttest.p, decoded, err, test.decoded, test.err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc TestEncodeRDataTXT(t *testing.T) {\n\t// Encoding 0 bytes needs to return at least a single length octet of\n\t// zero, not an empty slice.\n\tp := make([]byte, 0)\n\tencoded := EncodeRDataTXT(p)\n\tif len(encoded) < 0 {\n\t\tt.Errorf(\"EncodeRDataTXT(%v) returned %v\", p, encoded)\n\t}\n\n\t// 255 bytes should be able to be encoded into 256 bytes.\n\tp = make([]byte, 255)\n\tencoded = EncodeRDataTXT(p)\n\tif len(encoded) > 256 {\n\t\tt.Errorf(\"EncodeRDataTXT(%d bytes) returned %d bytes\", len(p), len(encoded))\n\t}\n\n\tfmt.Println(EncodeRDataTXT(nil))\n}\n\nfunc TestRDataTXTRoundTrip(t *testing.T) {\n\tfor _, p := range [][]byte{\n\t\t{},\n\t\t[]byte(\"\\x00\"),\n\t\t{\n\t\t\t0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,\n\t\t\t0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,\n\t\t\t0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,\n\t\t\t0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,\n\t\t\t0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,\n\t\t\t0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,\n\t\t\t0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,\n\t\t\t0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,\n\t\t\t0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,\n\t\t\t0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,\n\t\t\t0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,\n\t\t\t0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,\n\t\t\t0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,\n\t\t\t0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,\n\t\t\t0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,\n\t\t\t0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,\n\t\t},\n\t} {\n\t\trdata := EncodeRDataTXT(p)\n\t\tdecoded, err := DecodeRDataTXT(rdata)\n\t\tif err != nil || !bytes.Equal(decoded, p) {\n\t\t\tt.Errorf(\"%+q returned (%+q, %v)\", p, decoded, err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xdns/server.go",
    "content": "package xdns\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/binary\"\n\tgo_errors \"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\nconst (\n\tidleTimeout      = 10 * time.Second\n\tresponseTTL      = 60\n\tmaxResponseDelay = 1 * time.Second\n)\n\nvar (\n\tmaxUDPPayload     = 1280 - 40 - 8\n\tmaxEncodedPayload = computeMaxEncodedPayload(maxUDPPayload)\n)\n\nfunc clientIDToAddr(clientID [8]byte) *net.UDPAddr {\n\tip := make(net.IP, 16)\n\n\tcopy(ip, []byte{0xfd, 0x00, 0, 0, 0, 0, 0, 0})\n\tcopy(ip[8:], clientID[:])\n\n\treturn &net.UDPAddr{\n\t\tIP: ip,\n\t}\n}\n\ntype record struct {\n\tResp *Message\n\tAddr net.Addr\n\t// ClientID [8]byte\n\tClientAddr net.Addr\n}\n\ntype queue struct {\n\tlast  time.Time\n\tqueue chan []byte\n\tstash chan []byte\n}\n\ntype xdnsConnServer struct {\n\tnet.PacketConn\n\n\tdomain Name\n\n\tch            chan *record\n\treadQueue     chan *packet\n\twriteQueueMap map[string]*queue\n\n\tclosed bool\n\tmutex  sync.Mutex\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {\n\tdomain, err := ParseName(c.Domain)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn := &xdnsConnServer{\n\t\tPacketConn: raw,\n\n\t\tdomain: domain,\n\n\t\tch:            make(chan *record, 500),\n\t\treadQueue:     make(chan *packet, 512),\n\t\twriteQueueMap: make(map[string]*queue),\n\t}\n\n\tgo conn.clean()\n\tgo conn.recvLoop()\n\tgo conn.sendLoop()\n\n\treturn conn, nil\n}\n\nfunc (c *xdnsConnServer) clean() {\n\tf := func() bool {\n\t\tc.mutex.Lock()\n\t\tdefer c.mutex.Unlock()\n\n\t\tif c.closed {\n\t\t\treturn true\n\t\t}\n\n\t\tnow := time.Now()\n\n\t\tfor key, q := range c.writeQueueMap {\n\t\t\tif now.Sub(q.last) >= idleTimeout {\n\t\t\t\tclose(q.queue)\n\t\t\t\tclose(q.stash)\n\t\t\t\tdelete(c.writeQueueMap, key)\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t}\n\n\tfor {\n\t\ttime.Sleep(idleTimeout / 2)\n\t\tif f() {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *xdnsConnServer) ensureQueue(addr net.Addr) *queue {\n\tif c.closed {\n\t\treturn nil\n\t}\n\n\tq, ok := c.writeQueueMap[addr.String()]\n\tif !ok {\n\t\tq = &queue{\n\t\t\tqueue: make(chan []byte, 512),\n\t\t\tstash: make(chan []byte, 1),\n\t\t}\n\t\tc.writeQueueMap[addr.String()] = q\n\t}\n\tq.last = time.Now()\n\n\treturn q\n}\n\nfunc (c *xdnsConnServer) stash(queue *queue, p []byte) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tif c.closed {\n\t\treturn\n\t}\n\n\tselect {\n\tcase queue.stash <- p:\n\tdefault:\n\t}\n}\n\nfunc (c *xdnsConnServer) recvLoop() {\n\tvar buf [finalmask.UDPSize]byte\n\n\tfor {\n\t\tif c.closed {\n\t\t\tbreak\n\t\t}\n\n\t\tn, addr, err := c.PacketConn.ReadFrom(buf[:])\n\t\tif err != nil || n == 0 {\n\t\t\tif go_errors.Is(err, net.ErrClosed) || go_errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tquery, err := MessageFromWireFormat(buf[:n])\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), addr, \" xdns from wireformat err \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tresp, payload := responseFor(&query, c.domain)\n\n\t\tvar clientID [8]byte\n\t\tn = copy(clientID[:], payload)\n\t\tpayload = payload[n:]\n\t\tif n == len(clientID) {\n\t\t\tr := bytes.NewReader(payload)\n\t\t\tfor {\n\t\t\t\tp, err := nextPacketServer(r)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tbuf := make([]byte, len(p))\n\t\t\t\tcopy(buf, p)\n\t\t\t\tselect {\n\t\t\t\tcase c.readQueue <- &packet{\n\t\t\t\t\tp:    buf,\n\t\t\t\t\taddr: clientIDToAddr(clientID),\n\t\t\t\t}:\n\t\t\t\tdefault:\n\t\t\t\t\terrors.LogDebug(context.Background(), addr, \" \", clientID, \" mask read err queue full\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif resp != nil && resp.Rcode() == RcodeNoError {\n\t\t\t\tresp.Flags |= RcodeNameError\n\t\t\t}\n\t\t}\n\n\t\tif resp != nil {\n\t\t\tselect {\n\t\t\tcase c.ch <- &record{resp, addr, clientIDToAddr(clientID)}:\n\t\t\tdefault:\n\t\t\t\terrors.LogDebug(context.Background(), addr, \" \", clientID, \" mask read err record queue full\")\n\t\t\t}\n\t\t}\n\t}\n\n\terrors.LogDebug(context.Background(), \"xdns closed\")\n\n\tclose(c.ch)\n\tclose(c.readQueue)\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.closed = true\n\tfor key, q := range c.writeQueueMap {\n\t\tclose(q.queue)\n\t\tclose(q.stash)\n\t\tdelete(c.writeQueueMap, key)\n\t}\n}\n\nfunc (c *xdnsConnServer) sendLoop() {\n\tvar nextRec *record\n\tfor {\n\t\trec := nextRec\n\t\tnextRec = nil\n\n\t\tif rec == nil {\n\t\t\tvar ok bool\n\t\t\trec, ok = <-c.ch\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif rec.Resp.Rcode() == RcodeNoError && len(rec.Resp.Question) == 1 {\n\t\t\trec.Resp.Answer = []RR{\n\t\t\t\t{\n\t\t\t\t\tName:  rec.Resp.Question[0].Name,\n\t\t\t\t\tType:  rec.Resp.Question[0].Type,\n\t\t\t\t\tClass: rec.Resp.Question[0].Class,\n\t\t\t\t\tTTL:   responseTTL,\n\t\t\t\t\tData:  nil,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tvar payload bytes.Buffer\n\t\t\tlimit := maxEncodedPayload\n\t\t\ttimer := time.NewTimer(maxResponseDelay)\n\n\t\t\tfor {\n\t\t\t\tc.mutex.Lock()\n\t\t\t\tq := c.ensureQueue(rec.ClientAddr)\n\t\t\t\tif q == nil {\n\t\t\t\t\tc.mutex.Unlock()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tc.mutex.Unlock()\n\n\t\t\t\tvar p []byte\n\n\t\t\t\tselect {\n\t\t\t\tcase p = <-q.stash:\n\t\t\t\tdefault:\n\t\t\t\t\tselect {\n\t\t\t\t\tcase p = <-q.stash:\n\t\t\t\t\tcase p = <-q.queue:\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase p = <-q.stash:\n\t\t\t\t\t\tcase p = <-q.queue:\n\t\t\t\t\t\tcase <-timer.C:\n\t\t\t\t\t\tcase nextRec = <-c.ch:\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttimer.Reset(0)\n\n\t\t\t\tif len(p) == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tlimit -= 2 + len(p)\n\t\t\t\tif payload.Len() > 0 && limit < 0 {\n\t\t\t\t\tc.stash(q, p)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// if len(p) > 65535 {\n\t\t\t\t// \tpanic(len(p))\n\t\t\t\t// }\n\n\t\t\t\t_ = binary.Write(&payload, binary.BigEndian, uint16(len(p)))\n\t\t\t\tpayload.Write(p)\n\t\t\t}\n\n\t\t\ttimer.Stop()\n\t\t\trec.Resp.Answer[0].Data = EncodeRDataTXT(payload.Bytes())\n\t\t}\n\n\t\tbuf, err := rec.Resp.WireFormat()\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), rec.Addr, \" \", rec.ClientAddr, \" xdns wireformat err \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(buf) > maxUDPPayload {\n\t\t\terrors.LogDebug(context.Background(), rec.Addr, \" \", rec.ClientAddr, \" xdns truncate \", len(buf))\n\t\t\tbuf = buf[:maxUDPPayload]\n\t\t\tbuf[2] |= 0x02\n\t\t}\n\n\t\tif c.closed {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = c.PacketConn.WriteTo(buf, rec.Addr)\n\t\tif go_errors.Is(err, net.ErrClosed) || go_errors.Is(err, io.ErrClosedPipe) {\n\t\t\tc.closed = true\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (c *xdnsConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpacket, ok := <-c.readQueue\n\tif !ok {\n\t\treturn 0, nil, net.ErrClosed\n\t}\n\tif len(p) < len(packet.p) {\n\t\terrors.LogDebug(context.Background(), packet.addr, \" mask read err short buffer \", len(p), \" \", len(packet.p))\n\t\treturn 0, packet.addr, nil\n\t}\n\tcopy(p, packet.p)\n\treturn len(packet.p), packet.addr, nil\n}\n\nfunc (c *xdnsConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif len(p)+2 > maxEncodedPayload {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", len(p), \"+2 > \", maxEncodedPayload)\n\t\treturn 0, nil\n\t}\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tq := c.ensureQueue(addr)\n\tif q == nil {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\tbuf := make([]byte, len(p))\n\tcopy(buf, p)\n\n\tselect {\n\tcase q.queue <- buf:\n\t\treturn len(p), nil\n\tdefault:\n\t\t// errors.LogDebug(context.Background(), addr, \" mask write err queue full\")\n\t\treturn 0, nil\n\t}\n}\n\nfunc (c *xdnsConnServer) Close() error {\n\tc.closed = true\n\treturn c.PacketConn.Close()\n}\n\nfunc nextPacketServer(r *bytes.Reader) ([]byte, error) {\n\teof := func(err error) error {\n\t\tif err == io.EOF {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t\treturn err\n\t}\n\n\tfor {\n\t\tprefix, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif prefix >= 224 {\n\t\t\tpaddingLen := prefix - 224\n\t\t\t_, err := io.CopyN(io.Discard, r, int64(paddingLen))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, eof(err)\n\t\t\t}\n\t\t} else {\n\t\t\tp := make([]byte, int(prefix))\n\t\t\t_, err = io.ReadFull(r, p)\n\t\t\treturn p, eof(err)\n\t\t}\n\t}\n}\n\nfunc responseFor(query *Message, domain Name) (*Message, []byte) {\n\tresp := &Message{\n\t\tID:       query.ID,\n\t\tFlags:    0x8000,\n\t\tQuestion: query.Question,\n\t}\n\n\tif query.Flags&0x8000 != 0 {\n\t\treturn nil, nil\n\t}\n\n\tpayloadSize := 0\n\tfor _, rr := range query.Additional {\n\t\tif rr.Type != RRTypeOPT {\n\t\t\tcontinue\n\t\t}\n\t\tif len(resp.Additional) != 0 {\n\t\t\tresp.Flags |= RcodeFormatError\n\t\t\treturn resp, nil\n\t\t}\n\t\tresp.Additional = append(resp.Additional, RR{\n\t\t\tName:  Name{},\n\t\t\tType:  RRTypeOPT,\n\t\t\tClass: 4096,\n\t\t\tTTL:   0,\n\t\t\tData:  []byte{},\n\t\t})\n\t\tadditional := &resp.Additional[0]\n\n\t\tversion := (rr.TTL >> 16) & 0xff\n\t\tif version != 0 {\n\t\t\tresp.Flags |= ExtendedRcodeBadVers & 0xf\n\t\t\tadditional.TTL = (ExtendedRcodeBadVers >> 4) << 24\n\t\t\treturn resp, nil\n\t\t}\n\n\t\tpayloadSize = int(rr.Class)\n\t}\n\tif payloadSize < 512 {\n\t\tpayloadSize = 512\n\t}\n\n\tif len(query.Question) != 1 {\n\t\tresp.Flags |= RcodeFormatError\n\t\treturn resp, nil\n\t}\n\tquestion := query.Question[0]\n\n\tprefix, ok := question.Name.TrimSuffix(domain)\n\tif !ok {\n\t\tresp.Flags |= RcodeNameError\n\t\treturn resp, nil\n\t}\n\tresp.Flags |= 0x0400\n\n\tif query.Opcode() != 0 {\n\t\tresp.Flags |= RcodeNotImplemented\n\t\treturn resp, nil\n\t}\n\n\tif question.Type != RRTypeTXT {\n\t\tresp.Flags |= RcodeNameError\n\t\treturn resp, nil\n\t}\n\n\tencoded := bytes.ToUpper(bytes.Join(prefix, nil))\n\tpayload := make([]byte, base32Encoding.DecodedLen(len(encoded)))\n\tn, err := base32Encoding.Decode(payload, encoded)\n\tif err != nil {\n\t\tresp.Flags |= RcodeNameError\n\t\treturn resp, nil\n\t}\n\tpayload = payload[:n]\n\n\tif payloadSize < maxUDPPayload {\n\t\tresp.Flags |= RcodeFormatError\n\t\treturn resp, nil\n\t}\n\n\treturn resp, payload\n}\n\nfunc computeMaxEncodedPayload(limit int) int {\n\tmaxLengthName, err := NewName([][]byte{\n\t\t[]byte(\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"),\n\t\t[]byte(\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"),\n\t\t[]byte(\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"),\n\t\t[]byte(\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"),\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t{\n\t\tn := 0\n\t\tfor _, label := range maxLengthName {\n\t\t\tn += len(label) + 1\n\t\t}\n\t\tn += 1\n\t\tif n != 255 {\n\t\t\tpanic(\"computeMaxEncodedPayload n != 255\")\n\t\t}\n\t}\n\n\tqueryLimit := uint16(limit)\n\tif int(queryLimit) != limit {\n\t\tqueryLimit = 0xffff\n\t}\n\tquery := &Message{\n\t\tQuestion: []Question{\n\t\t\t{\n\t\t\t\tName:  maxLengthName,\n\t\t\t\tType:  RRTypeTXT,\n\t\t\t\tClass: RRTypeTXT,\n\t\t\t},\n\t\t},\n\n\t\tAdditional: []RR{\n\t\t\t{\n\t\t\t\tName:  Name{},\n\t\t\t\tType:  RRTypeOPT,\n\t\t\t\tClass: queryLimit,\n\t\t\t\tTTL:   0,\n\t\t\t\tData:  []byte{},\n\t\t\t},\n\t\t},\n\t}\n\tresp, _ := responseFor(query, [][]byte{})\n\n\tresp.Answer = []RR{\n\t\t{\n\t\t\tName:  query.Question[0].Name,\n\t\t\tType:  query.Question[0].Type,\n\t\t\tClass: query.Question[0].Class,\n\t\t\tTTL:   responseTTL,\n\t\t\tData:  nil,\n\t\t},\n\t}\n\n\tlow := 0\n\thigh := 32768\n\tfor low+1 < high {\n\t\tmid := (low + high) / 2\n\t\tresp.Answer[0].Data = EncodeRDataTXT(make([]byte, mid))\n\t\tbuf, err := resp.WireFormat()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif len(buf) <= limit {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\treturn low\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xicmp/client.go",
    "content": "package xicmp\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/udphop\"\n\t\"golang.org/x/net/icmp\"\n\t\"golang.org/x/net/ipv4\"\n\t\"golang.org/x/net/ipv6\"\n)\n\nconst (\n\tinitPollDelay       = 500 * time.Millisecond\n\tmaxPollDelay        = 10 * time.Second\n\tpollDelayMultiplier = 2.0\n\tpollLimit           = 16\n\twindowSize          = 1000\n)\n\ntype packet struct {\n\tp    []byte\n\taddr net.Addr\n}\n\ntype seqStatus struct {\n\tneedSeqByte bool\n\tseqByte     byte\n}\n\ntype xicmpConnClient struct {\n\tconn     net.PacketConn\n\ticmpConn *icmp.PacketConn\n\n\ttyp       icmp.Type\n\tid        int\n\tseq       int\n\tproto     int\n\tseqStatus map[int]*seqStatus\n\n\tpollChan   chan struct{}\n\treadQueue  chan *packet\n\twriteQueue chan *packet\n\n\tclosed bool\n\tmutex  sync.Mutex\n}\n\nfunc NewConnClient(c *Config, raw net.PacketConn, level int) (net.PacketConn, error) {\n\t_, ok1 := raw.(*internet.FakePacketConn)\n\t_, ok2 := raw.(*udphop.UdpHopPacketConn)\n\tif level != 0 || ok1 || ok2 {\n\t\treturn nil, errors.New(\"xicmp requires being at the outermost level\")\n\t}\n\n\tnetwork := \"ip4:icmp\"\n\ttyp := icmp.Type(ipv4.ICMPTypeEcho)\n\tproto := 1\n\tif strings.Contains(c.Ip, \":\") {\n\t\tnetwork = \"ip6:ipv6-icmp\"\n\t\ttyp = ipv6.ICMPTypeEchoRequest\n\t\tproto = 58\n\t}\n\n\ticmpConn, err := icmp.ListenPacket(network, c.Ip)\n\tif err != nil {\n\t\treturn nil, errors.New(\"xicmp listen err\").Base(err)\n\t}\n\n\tif c.Id == 0 {\n\t\tc.Id = int32(crypto.RandBetween(0, 65535))\n\t}\n\n\tconn := &xicmpConnClient{\n\t\tconn:     raw,\n\t\ticmpConn: icmpConn,\n\n\t\ttyp:       typ,\n\t\tid:        int(c.Id),\n\t\tseq:       1,\n\t\tproto:     proto,\n\t\tseqStatus: make(map[int]*seqStatus),\n\n\t\tpollChan:   make(chan struct{}, pollLimit),\n\t\treadQueue:  make(chan *packet, 256),\n\t\twriteQueue: make(chan *packet, 256),\n\t}\n\n\tgo conn.recvLoop()\n\tgo conn.sendLoop()\n\n\treturn conn, nil\n}\n\nfunc (c *xicmpConnClient) encode(p []byte) ([]byte, error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tneedSeqByte := false\n\tvar seqByte byte\n\tdata := p\n\tif len(p) > 0 {\n\t\tneedSeqByte = true\n\t\tseqByte = p[0]\n\t}\n\n\tmsg := icmp.Message{\n\t\tType: c.typ,\n\t\tCode: 0,\n\t\tBody: &icmp.Echo{\n\t\t\tID:   c.id,\n\t\t\tSeq:  c.seq,\n\t\t\tData: data,\n\t\t},\n\t}\n\n\tbuf, err := msg.Marshal(nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(buf) > finalmask.UDPSize {\n\t\treturn nil, errors.New(\"xicmp len(buf) > finalmask.UDPSize\")\n\t}\n\n\tc.seqStatus[c.seq] = &seqStatus{\n\t\tneedSeqByte: needSeqByte,\n\t\tseqByte:     seqByte,\n\t}\n\n\tdelete(c.seqStatus, int(uint16(c.seq-windowSize)))\n\n\tc.seq++\n\n\tif c.seq == 65536 {\n\t\tdelete(c.seqStatus, int(uint16(c.seq-windowSize)))\n\t\tc.seq = 1\n\t}\n\n\treturn buf, nil\n}\n\nfunc (c *xicmpConnClient) recvLoop() {\n\tvar buf [finalmask.UDPSize]byte\n\n\tfor {\n\t\tif c.closed {\n\t\t\tbreak\n\t\t}\n\n\t\tn, addr, err := c.icmpConn.ReadFrom(buf[:])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tmsg, err := icmp.ParseMessage(c.proto, buf[:n])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif msg.Type != ipv4.ICMPTypeEchoReply && msg.Type != ipv6.ICMPTypeEchoReply {\n\t\t\tcontinue\n\t\t}\n\n\t\techo, ok := msg.Body.(*icmp.Echo)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tc.mutex.Lock()\n\t\tseqStatus, ok := c.seqStatus[echo.Seq]\n\t\tc.mutex.Unlock()\n\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif seqStatus.needSeqByte {\n\t\t\tif len(echo.Data) <= 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif echo.Data[0] == seqStatus.seqByte {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\techo.Data = echo.Data[1:]\n\t\t}\n\n\t\tif len(echo.Data) > 0 {\n\t\t\tc.mutex.Lock()\n\t\t\tdelete(c.seqStatus, echo.Seq)\n\t\t\tc.mutex.Unlock()\n\n\t\t\tbuf := make([]byte, len(echo.Data))\n\t\t\tcopy(buf, echo.Data)\n\t\t\tselect {\n\t\t\tcase c.readQueue <- &packet{\n\t\t\t\tp:    buf,\n\t\t\t\taddr: &net.UDPAddr{IP: addr.(*net.IPAddr).IP},\n\t\t\t}:\n\t\t\tdefault:\n\t\t\t\terrors.LogDebug(context.Background(), addr, \" \", echo.Seq, \" \", echo.ID, \" mask read err queue full\")\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase c.pollChan <- struct{}{}:\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t}\n\n\terrors.LogDebug(context.Background(), \"xicmp closed\")\n\n\tclose(c.pollChan)\n\tclose(c.readQueue)\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.closed = true\n\tclose(c.writeQueue)\n}\n\nfunc (c *xicmpConnClient) sendLoop() {\n\tvar addr net.Addr\n\n\tpollDelay := initPollDelay\n\tpollTimer := time.NewTimer(pollDelay)\n\tfor {\n\t\tvar p *packet\n\t\tpollTimerExpired := false\n\n\t\tselect {\n\t\tcase p = <-c.writeQueue:\n\t\tdefault:\n\t\t\tselect {\n\t\t\tcase p = <-c.writeQueue:\n\t\t\tcase <-c.pollChan:\n\t\t\tcase <-pollTimer.C:\n\t\t\t\tpollTimerExpired = true\n\t\t\t}\n\t\t}\n\n\t\tif p != nil {\n\t\t\taddr = p.addr\n\n\t\t\tselect {\n\t\t\tcase <-c.pollChan:\n\t\t\tdefault:\n\t\t\t}\n\t\t} else if addr != nil {\n\t\t\tencoded, _ := c.encode(nil)\n\t\t\tp = &packet{\n\t\t\t\tp:    encoded,\n\t\t\t\taddr: addr,\n\t\t\t}\n\t\t}\n\n\t\tif pollTimerExpired {\n\t\t\tpollDelay = time.Duration(float64(pollDelay) * pollDelayMultiplier)\n\t\t\tif pollDelay > maxPollDelay {\n\t\t\t\tpollDelay = maxPollDelay\n\t\t\t}\n\t\t} else {\n\t\t\tif !pollTimer.Stop() {\n\t\t\t\t<-pollTimer.C\n\t\t\t}\n\t\t\tpollDelay = initPollDelay\n\t\t}\n\t\tpollTimer.Reset(pollDelay)\n\n\t\tif c.closed {\n\t\t\treturn\n\t\t}\n\n\t\tif p != nil {\n\t\t\t_, err := c.icmpConn.WriteTo(p.p, p.addr)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogDebug(context.Background(), p.addr, \" xicmp writeto err \", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (c *xicmpConnClient) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpacket, ok := <-c.readQueue\n\tif !ok {\n\t\treturn 0, nil, net.ErrClosed\n\t}\n\tif len(p) < len(packet.p) {\n\t\terrors.LogDebug(context.Background(), packet.addr, \" mask read err short buffer \", len(p), \" \", len(packet.p))\n\t\treturn 0, packet.addr, nil\n\t}\n\tcopy(p, packet.p)\n\treturn len(packet.p), packet.addr, nil\n}\n\nfunc (c *xicmpConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tencoded, err := c.encode(p)\n\tif err != nil {\n\t\terrors.LogDebug(context.Background(), addr, \" xicmp wireformat err \", err)\n\t\treturn 0, nil\n\t}\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tif c.closed {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\tselect {\n\tcase c.writeQueue <- &packet{\n\t\tp:    encoded,\n\t\taddr: &net.IPAddr{IP: addr.(*net.UDPAddr).IP},\n\t}:\n\t\treturn len(p), nil\n\tdefault:\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err queue full\")\n\t\treturn 0, nil\n\t}\n}\n\nfunc (c *xicmpConnClient) Close() error {\n\tc.closed = true\n\t_ = c.icmpConn.Close()\n\treturn c.conn.Close()\n}\n\nfunc (c *xicmpConnClient) LocalAddr() net.Addr {\n\treturn &net.UDPAddr{\n\t\tIP:   c.icmpConn.LocalAddr().(*net.IPAddr).IP,\n\t\tPort: c.id,\n\t}\n}\n\nfunc (c *xicmpConnClient) SetDeadline(t time.Time) error {\n\treturn c.icmpConn.SetDeadline(t)\n}\n\nfunc (c *xicmpConnClient) SetReadDeadline(t time.Time) error {\n\treturn c.icmpConn.SetReadDeadline(t)\n}\n\nfunc (c *xicmpConnClient) SetWriteDeadline(t time.Time) error {\n\treturn c.icmpConn.SetWriteDeadline(t)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xicmp/config.go",
    "content": "package xicmp\n\nimport (\n\t\"net\"\n)\n\nfunc (c *Config) UDP() {\n}\n\nfunc (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnClient(c, raw, level)\n}\n\nfunc (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {\n\treturn NewConnServer(c, raw, level)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xicmp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/finalmask/xicmp/config.proto\n\npackage xicmp\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIp            string                 `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\tId            int32                  `protobuf:\"varint,2,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_finalmask_xicmp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_finalmask_xicmp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_finalmask_xicmp_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetIp() string {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetId() int32 {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_finalmask_xicmp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_finalmask_xicmp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"/transport/internet/finalmask/xicmp/config.proto\\x12'xray.transport.internet.finalmask.xicmp\\\"(\\n\" +\n\t\"\\x06Config\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\tR\\x02ip\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x02 \\x01(\\x05R\\x02idB\\x97\\x01\\n\" +\n\t\"+com.xray.transport.internet.finalmask.xicmpP\\x01Z<github.com/xtls/xray-core/transport/internet/finalmask/xicmp\\xaa\\x02'Xray.Transport.Internet.Finalmask.Xicmpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_finalmask_xicmp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_finalmask_xicmp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_finalmask_xicmp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_finalmask_xicmp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_finalmask_xicmp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_xicmp_config_proto_rawDesc), len(file_transport_internet_finalmask_xicmp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_finalmask_xicmp_config_proto_rawDescData\n}\n\nvar file_transport_internet_finalmask_xicmp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_finalmask_xicmp_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.finalmask.xicmp.Config\n}\nvar file_transport_internet_finalmask_xicmp_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_finalmask_xicmp_config_proto_init() }\nfunc file_transport_internet_finalmask_xicmp_config_proto_init() {\n\tif File_transport_internet_finalmask_xicmp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_xicmp_config_proto_rawDesc), len(file_transport_internet_finalmask_xicmp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_finalmask_xicmp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_finalmask_xicmp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_finalmask_xicmp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_finalmask_xicmp_config_proto = out.File\n\tfile_transport_internet_finalmask_xicmp_config_proto_goTypes = nil\n\tfile_transport_internet_finalmask_xicmp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xicmp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.finalmask.xicmp;\noption csharp_namespace = \"Xray.Transport.Internet.Finalmask.Xicmp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/finalmask/xicmp\";\noption java_package = \"com.xray.transport.internet.finalmask.xicmp\";\noption java_multiple_files = true;\n\nmessage Config {\n  string ip = 1;\n  int32 id = 2;\n}\n\n"
  },
  {
    "path": "transport/internet/finalmask/xicmp/server.go",
    "content": "package xicmp\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n\t\"golang.org/x/net/icmp\"\n\t\"golang.org/x/net/ipv4\"\n\t\"golang.org/x/net/ipv6\"\n)\n\nconst (\n\tidleTimeout      = 10 * time.Second\n\tmaxResponseDelay = 1 * time.Second\n)\n\ntype record struct {\n\tid          int\n\tseq         int\n\tneedSeqByte bool\n\tseqByte     byte\n\taddr        net.Addr\n}\n\ntype queue struct {\n\tlast  time.Time\n\tqueue chan []byte\n}\n\ntype xicmpConnServer struct {\n\tconn     net.PacketConn\n\ticmpConn *icmp.PacketConn\n\n\ttyp    icmp.Type\n\tproto  int\n\tconfig *Config\n\n\tch            chan *record\n\treadQueue     chan *packet\n\twriteQueueMap map[string]*queue\n\n\tclosed bool\n\tmutex  sync.Mutex\n}\n\nfunc NewConnServer(c *Config, raw net.PacketConn, level int) (net.PacketConn, error) {\n\tif level != 0 {\n\t\treturn nil, errors.New(\"xicmp requires being at the outermost level\")\n\t}\n\n\tnetwork := \"ip4:icmp\"\n\ttyp := icmp.Type(ipv4.ICMPTypeEchoReply)\n\tproto := 1\n\tif strings.Contains(c.Ip, \":\") {\n\t\tnetwork = \"ip6:ipv6-icmp\"\n\t\ttyp = ipv6.ICMPTypeEchoReply\n\t\tproto = 58\n\t}\n\n\ticmpConn, err := icmp.ListenPacket(network, c.Ip)\n\tif err != nil {\n\t\treturn nil, errors.New(\"xicmp listen err\").Base(err)\n\t}\n\n\tconn := &xicmpConnServer{\n\t\tconn:     raw,\n\t\ticmpConn: icmpConn,\n\n\t\ttyp:    typ,\n\t\tproto:  proto,\n\t\tconfig: c,\n\n\t\tch:            make(chan *record, 500),\n\t\treadQueue:     make(chan *packet, 512),\n\t\twriteQueueMap: make(map[string]*queue),\n\t}\n\n\tgo conn.clean()\n\tgo conn.recvLoop()\n\tgo conn.sendLoop()\n\n\treturn conn, nil\n}\n\nfunc (c *xicmpConnServer) clean() {\n\tf := func() bool {\n\t\tc.mutex.Lock()\n\t\tdefer c.mutex.Unlock()\n\n\t\tif c.closed {\n\t\t\treturn true\n\t\t}\n\n\t\tnow := time.Now()\n\n\t\tfor key, q := range c.writeQueueMap {\n\t\t\tif now.Sub(q.last) >= idleTimeout {\n\t\t\t\tclose(q.queue)\n\t\t\t\tdelete(c.writeQueueMap, key)\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t}\n\n\tfor {\n\t\ttime.Sleep(idleTimeout / 2)\n\t\tif f() {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *xicmpConnServer) ensureQueue(addr net.Addr) *queue {\n\tif c.closed {\n\t\treturn nil\n\t}\n\n\tq, ok := c.writeQueueMap[addr.String()]\n\tif !ok {\n\t\tq = &queue{\n\t\t\tqueue: make(chan []byte, 512),\n\t\t}\n\t\tc.writeQueueMap[addr.String()] = q\n\t}\n\tq.last = time.Now()\n\n\treturn q\n}\n\nfunc (c *xicmpConnServer) encode(p []byte, id int, seq int, needSeqByte bool, seqByte byte) ([]byte, error) {\n\tdata := p\n\tif needSeqByte {\n\t\tb2 := c.randUntil(seqByte)\n\t\tdata = append([]byte{b2}, p...)\n\t}\n\n\tmsg := icmp.Message{\n\t\tType: c.typ,\n\t\tCode: 0,\n\t\tBody: &icmp.Echo{\n\t\t\tID:   id,\n\t\t\tSeq:  seq,\n\t\t\tData: data,\n\t\t},\n\t}\n\n\tbuf, err := msg.Marshal(nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(buf) > finalmask.UDPSize {\n\t\treturn nil, errors.New(\"xicmp len(buf) > finalmask.UDPSize\")\n\t}\n\n\treturn buf, nil\n}\n\nfunc (c *xicmpConnServer) randUntil(b1 byte) byte {\n\tb2 := byte(crypto.RandBetween(0, 255))\n\tfor {\n\t\tif b2 != b1 {\n\t\t\treturn b2\n\t\t}\n\t\tb2 = byte(crypto.RandBetween(0, 255))\n\t}\n}\n\nfunc (c *xicmpConnServer) recvLoop() {\n\tvar buf [finalmask.UDPSize]byte\n\n\tfor {\n\t\tif c.closed {\n\t\t\tbreak\n\t\t}\n\n\t\tn, addr, err := c.icmpConn.ReadFrom(buf[:])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tmsg, err := icmp.ParseMessage(c.proto, buf[:n])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif msg.Type != ipv4.ICMPTypeEcho && msg.Type != ipv6.ICMPTypeEchoRequest {\n\t\t\tcontinue\n\t\t}\n\n\t\techo, ok := msg.Body.(*icmp.Echo)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif c.config.Id != 0 && echo.ID != int(c.config.Id) {\n\t\t\tcontinue\n\t\t}\n\n\t\tneedSeqByte := false\n\t\tvar seqByte byte\n\n\t\tif len(echo.Data) > 0 {\n\t\t\tneedSeqByte = true\n\t\t\tseqByte = echo.Data[0]\n\n\t\t\tbuf := make([]byte, len(echo.Data))\n\t\t\tcopy(buf, echo.Data)\n\t\t\tselect {\n\t\t\tcase c.readQueue <- &packet{\n\t\t\t\tp: buf,\n\t\t\t\taddr: &net.UDPAddr{\n\t\t\t\t\tIP:   addr.(*net.IPAddr).IP,\n\t\t\t\t\tPort: echo.ID,\n\t\t\t\t},\n\t\t\t}:\n\t\t\tdefault:\n\t\t\t\terrors.LogDebug(context.Background(), addr, \" \", echo.ID, \" \", echo.Seq, \" mask read err queue full\")\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase c.ch <- &record{\n\t\t\tid:          echo.ID,\n\t\t\tseq:         echo.Seq,\n\t\t\tneedSeqByte: needSeqByte,\n\t\t\tseqByte:     seqByte,\n\t\t\taddr: &net.UDPAddr{\n\t\t\t\tIP:   addr.(*net.IPAddr).IP,\n\t\t\t\tPort: echo.ID,\n\t\t\t},\n\t\t}:\n\t\tdefault:\n\t\t\terrors.LogDebug(context.Background(), addr, \" \", echo.ID, \" \", echo.Seq, \" mask read err record queue full\")\n\t\t}\n\t}\n\n\terrors.LogDebug(context.Background(), \"xicmp closed\")\n\n\tclose(c.ch)\n\tclose(c.readQueue)\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.closed = true\n\tfor key, q := range c.writeQueueMap {\n\t\tclose(q.queue)\n\t\tdelete(c.writeQueueMap, key)\n\t}\n}\n\nfunc (c *xicmpConnServer) sendLoop() {\n\tvar nextRec *record\n\tfor {\n\t\trec := nextRec\n\t\tnextRec = nil\n\n\t\tif rec == nil {\n\t\t\tvar ok bool\n\t\t\trec, ok = <-c.ch\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tc.mutex.Lock()\n\t\tq := c.ensureQueue(rec.addr)\n\t\tif q == nil {\n\t\t\tc.mutex.Unlock()\n\t\t\treturn\n\t\t}\n\t\tc.mutex.Unlock()\n\n\t\tvar p []byte\n\n\t\ttimer := time.NewTimer(maxResponseDelay)\n\n\t\tselect {\n\t\tcase p = <-q.queue:\n\t\tdefault:\n\t\t\tselect {\n\t\t\tcase p = <-q.queue:\n\t\t\tcase <-timer.C:\n\t\t\tcase nextRec = <-c.ch:\n\t\t\t}\n\t\t}\n\n\t\ttimer.Stop()\n\n\t\tif len(p) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tbuf, err := c.encode(p, rec.id, rec.seq, rec.needSeqByte, rec.seqByte)\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), rec.addr, \" \", rec.id, \" \", rec.seq, \" xicmp wireformat err \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif c.closed {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = c.icmpConn.WriteTo(buf, &net.IPAddr{IP: rec.addr.(*net.UDPAddr).IP})\n\t\tif err != nil {\n\t\t\terrors.LogDebug(context.Background(), rec.addr, \" \", rec.id, \" \", rec.seq, \" xicmp writeto err \", err)\n\t\t}\n\t}\n}\n\nfunc (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpacket, ok := <-c.readQueue\n\tif !ok {\n\t\treturn 0, nil, net.ErrClosed\n\t}\n\tif len(p) < len(packet.p) {\n\t\terrors.LogDebug(context.Background(), packet.addr, \" mask read err short buffer \", len(p), \" \", len(packet.p))\n\t\treturn 0, packet.addr, nil\n\t}\n\tcopy(p, packet.p)\n\treturn len(packet.p), packet.addr, nil\n}\n\nfunc (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tif len(p)+8+1 > finalmask.UDPSize {\n\t\terrors.LogDebug(context.Background(), addr, \" mask write err short write \", len(p), \"+8+1 > \", finalmask.UDPSize)\n\t\treturn 0, nil\n\t}\n\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tq := c.ensureQueue(addr)\n\tif q == nil {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\tbuf := make([]byte, len(p))\n\tcopy(buf, p)\n\n\tselect {\n\tcase q.queue <- buf:\n\t\treturn len(p), nil\n\tdefault:\n\t\t// errors.LogDebug(context.Background(), addr, \" mask write err queue full\")\n\t\treturn 0, nil\n\t}\n}\n\nfunc (c *xicmpConnServer) Close() error {\n\tc.closed = true\n\t_ = c.icmpConn.Close()\n\treturn c.conn.Close()\n}\n\nfunc (c *xicmpConnServer) LocalAddr() net.Addr {\n\treturn &net.UDPAddr{IP: c.icmpConn.LocalAddr().(*net.IPAddr).IP}\n}\n\nfunc (c *xicmpConnServer) SetDeadline(t time.Time) error {\n\treturn c.icmpConn.SetDeadline(t)\n}\n\nfunc (c *xicmpConnServer) SetReadDeadline(t time.Time) error {\n\treturn c.icmpConn.SetReadDeadline(t)\n}\n\nfunc (c *xicmpConnServer) SetWriteDeadline(t time.Time) error {\n\treturn c.icmpConn.SetWriteDeadline(t)\n}\n"
  },
  {
    "path": "transport/internet/finalmask/xicmp/xicmp_test.go",
    "content": "package xicmp_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"golang.org/x/net/icmp\"\n\t\"golang.org/x/net/ipv4\"\n\t\"golang.org/x/net/ipv6\"\n)\n\nfunc TestICMPEchoMarshal(t *testing.T) {\n\tmsg := icmp.Message{\n\t\tType: ipv4.ICMPTypeEcho,\n\t\tCode: 0,\n\t\tBody: &icmp.Echo{\n\t\t\tID:   65535,\n\t\t\tSeq:  65537,\n\t\t\tData: nil,\n\t\t},\n\t}\n\tICMPTypeEcho, _ := msg.Marshal(nil)\n\tfmt.Println(\"ICMPTypeEcho\", len(ICMPTypeEcho), ICMPTypeEcho)\n\n\tmsg = icmp.Message{\n\t\tType: ipv4.ICMPTypeEchoReply,\n\t\tCode: 0,\n\t\tBody: &icmp.Echo{\n\t\t\tID:   65535,\n\t\t\tSeq:  65537,\n\t\t\tData: nil,\n\t\t},\n\t}\n\tICMPTypeEchoReply, _ := msg.Marshal(nil)\n\tfmt.Println(\"ICMPTypeEchoReply\", len(ICMPTypeEchoReply), ICMPTypeEchoReply)\n\n\tmsg = icmp.Message{\n\t\tType: ipv6.ICMPTypeEchoRequest,\n\t\tCode: 0,\n\t\tBody: &icmp.Echo{\n\t\t\tID:   65535,\n\t\t\tSeq:  65537,\n\t\t\tData: nil,\n\t\t},\n\t}\n\tICMPTypeEchoRequest, _ := msg.Marshal(nil)\n\tfmt.Println(\"ICMPTypeEchoRequest\", len(ICMPTypeEchoRequest), ICMPTypeEchoRequest)\n\n\tmsg = icmp.Message{\n\t\tType: ipv6.ICMPTypeEchoReply,\n\t\tCode: 0,\n\t\tBody: &icmp.Echo{\n\t\t\tID:   65535,\n\t\t\tSeq:  65537,\n\t\t\tData: nil,\n\t\t},\n\t}\n\tV6ICMPTypeEchoReply, _ := msg.Marshal(nil)\n\tfmt.Println(\"V6ICMPTypeEchoReply\", len(V6ICMPTypeEchoReply), V6ICMPTypeEchoReply)\n\n\tif !bytes.Equal(ICMPTypeEcho[0:2], []byte{8, 0}) || !bytes.Equal(ICMPTypeEcho[4:], []byte{255, 255, 0, 1}) {\n\t\tt.Fatalf(\"ICMPTypeEcho Type/Code or ID/Seq mismatch: %v\", ICMPTypeEcho)\n\t}\n\tif !bytes.Equal(ICMPTypeEchoReply[0:2], []byte{0, 0}) || !bytes.Equal(ICMPTypeEchoReply[4:], []byte{255, 255, 0, 1}) {\n\t\tt.Fatalf(\"ICMPTypeEchoReply Type/Code or ID/Seq mismatch: %v\", ICMPTypeEchoReply)\n\t}\n\tif !bytes.Equal(ICMPTypeEchoRequest[0:2], []byte{128, 0}) || !bytes.Equal(ICMPTypeEchoRequest[4:], []byte{255, 255, 0, 1}) {\n\t\tt.Fatalf(\"ICMPTypeEchoRequest Type/Code or ID/Seq mismatch: %v\", ICMPTypeEchoRequest)\n\t}\n\tif !bytes.Equal(V6ICMPTypeEchoReply[0:2], []byte{129, 0}) || !bytes.Equal(V6ICMPTypeEchoReply[4:], []byte{255, 255, 0, 1}) {\n\t\tt.Fatalf(\"V6ICMPTypeEchoReply Type/Code or ID/Seq mismatch: %v\", V6ICMPTypeEchoReply)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/grpc/config.go",
    "content": "package grpc\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n\nfunc (c *Config) getServiceName() string {\n\t// Normal old school config\n\tif !strings.HasPrefix(c.ServiceName, \"/\") {\n\t\treturn url.PathEscape(c.ServiceName)\n\t}\n\n\t// Otherwise new custom paths\n\tlastIndex := strings.LastIndex(c.ServiceName, \"/\")\n\tif lastIndex < 1 {\n\t\tlastIndex = 1\n\t}\n\trawServiceName := c.ServiceName[1:lastIndex] // trim from first to last '/'\n\tserviceNameParts := strings.Split(rawServiceName, \"/\")\n\tfor i := range serviceNameParts {\n\t\tserviceNameParts[i] = url.PathEscape(serviceNameParts[i])\n\t}\n\treturn strings.Join(serviceNameParts, \"/\")\n}\n\nfunc (c *Config) getTunStreamName() string {\n\t// Normal old school config\n\tif !strings.HasPrefix(c.ServiceName, \"/\") {\n\t\treturn \"Tun\"\n\t}\n\t// Otherwise new custom paths\n\tendingPath := c.ServiceName[strings.LastIndex(c.ServiceName, \"/\")+1:] // from the last '/' to end of string\n\treturn url.PathEscape(strings.Split(endingPath, \"|\")[0])\n}\n\nfunc (c *Config) getTunMultiStreamName() string {\n\t// Normal old school config\n\tif !strings.HasPrefix(c.ServiceName, \"/\") {\n\t\treturn \"TunMulti\"\n\t}\n\t// Otherwise new custom paths\n\tendingPath := c.ServiceName[strings.LastIndex(c.ServiceName, \"/\")+1:] // from the last '/' to end of string\n\tstreamNames := strings.Split(endingPath, \"|\")\n\tif len(streamNames) == 1 { // client side. Service name is the full path to multi tun\n\t\treturn url.PathEscape(streamNames[0])\n\t} else { // server side. The second part is the path to multi tun\n\t\treturn url.PathEscape(streamNames[1])\n\t}\n}\n"
  },
  {
    "path": "transport/internet/grpc/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/grpc/config.proto\n\npackage grpc\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate               protoimpl.MessageState `protogen:\"open.v1\"`\n\tAuthority           string                 `protobuf:\"bytes,1,opt,name=authority,proto3\" json:\"authority,omitempty\"`\n\tServiceName         string                 `protobuf:\"bytes,2,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\tMultiMode           bool                   `protobuf:\"varint,3,opt,name=multi_mode,json=multiMode,proto3\" json:\"multi_mode,omitempty\"`\n\tIdleTimeout         int32                  `protobuf:\"varint,4,opt,name=idle_timeout,json=idleTimeout,proto3\" json:\"idle_timeout,omitempty\"`\n\tHealthCheckTimeout  int32                  `protobuf:\"varint,5,opt,name=health_check_timeout,json=healthCheckTimeout,proto3\" json:\"health_check_timeout,omitempty\"`\n\tPermitWithoutStream bool                   `protobuf:\"varint,6,opt,name=permit_without_stream,json=permitWithoutStream,proto3\" json:\"permit_without_stream,omitempty\"`\n\tInitialWindowsSize  int32                  `protobuf:\"varint,7,opt,name=initial_windows_size,json=initialWindowsSize,proto3\" json:\"initial_windows_size,omitempty\"`\n\tUserAgent           string                 `protobuf:\"bytes,8,opt,name=user_agent,json=userAgent,proto3\" json:\"user_agent,omitempty\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_grpc_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_grpc_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_grpc_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetAuthority() string {\n\tif x != nil {\n\t\treturn x.Authority\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetServiceName() string {\n\tif x != nil {\n\t\treturn x.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMultiMode() bool {\n\tif x != nil {\n\t\treturn x.MultiMode\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetIdleTimeout() int32 {\n\tif x != nil {\n\t\treturn x.IdleTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetHealthCheckTimeout() int32 {\n\tif x != nil {\n\t\treturn x.HealthCheckTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetPermitWithoutStream() bool {\n\tif x != nil {\n\t\treturn x.PermitWithoutStream\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetInitialWindowsSize() int32 {\n\tif x != nil {\n\t\treturn x.InitialWindowsSize\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetUserAgent() string {\n\tif x != nil {\n\t\treturn x.UserAgent\n\t}\n\treturn \"\"\n}\n\nvar File_transport_internet_grpc_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_grpc_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"$transport/internet/grpc/config.proto\\x12%xray.transport.internet.grpc.encoding\\\"\\xc2\\x02\\n\" +\n\t\"\\x06Config\\x12\\x1c\\n\" +\n\t\"\\tauthority\\x18\\x01 \\x01(\\tR\\tauthority\\x12!\\n\" +\n\t\"\\fservice_name\\x18\\x02 \\x01(\\tR\\vserviceName\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"multi_mode\\x18\\x03 \\x01(\\bR\\tmultiMode\\x12!\\n\" +\n\t\"\\fidle_timeout\\x18\\x04 \\x01(\\x05R\\vidleTimeout\\x120\\n\" +\n\t\"\\x14health_check_timeout\\x18\\x05 \\x01(\\x05R\\x12healthCheckTimeout\\x122\\n\" +\n\t\"\\x15permit_without_stream\\x18\\x06 \\x01(\\bR\\x13permitWithoutStream\\x120\\n\" +\n\t\"\\x14initial_windows_size\\x18\\a \\x01(\\x05R\\x12initialWindowsSize\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"user_agent\\x18\\b \\x01(\\tR\\tuserAgentB3Z1github.com/xtls/xray-core/transport/internet/grpcb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_grpc_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_grpc_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_grpc_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_grpc_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_grpc_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_grpc_config_proto_rawDesc), len(file_transport_internet_grpc_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_grpc_config_proto_rawDescData\n}\n\nvar file_transport_internet_grpc_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_grpc_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.grpc.encoding.Config\n}\nvar file_transport_internet_grpc_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_grpc_config_proto_init() }\nfunc file_transport_internet_grpc_config_proto_init() {\n\tif File_transport_internet_grpc_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_grpc_config_proto_rawDesc), len(file_transport_internet_grpc_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_grpc_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_grpc_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_grpc_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_grpc_config_proto = out.File\n\tfile_transport_internet_grpc_config_proto_goTypes = nil\n\tfile_transport_internet_grpc_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/grpc/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.grpc.encoding;\noption go_package = \"github.com/xtls/xray-core/transport/internet/grpc\";\n\nmessage Config {\n  string authority = 1;\n  string service_name = 2;\n  bool multi_mode = 3;\n  int32 idle_timeout = 4;\n  int32 health_check_timeout = 5;\n  bool permit_without_stream = 6;\n  int32 initial_windows_size = 7;\n  string user_agent = 8;\n}\n"
  },
  {
    "path": "transport/internet/grpc/config_test.go",
    "content": "package grpc\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nfunc TestConfig_GetServiceName(t *testing.T) {\n\ttests := []struct {\n\t\tTestName    string\n\t\tServiceName string\n\t\tExpected    string\n\t}{\n\t\t{\n\t\t\tTestName:    \"simple no absolute path\",\n\t\t\tServiceName: \"hello\",\n\t\t\tExpected:    \"hello\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"escape no absolute path\",\n\t\t\tServiceName: \"hello/world!\",\n\t\t\tExpected:    \"hello%2Fworld%21\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"absolute path\",\n\t\t\tServiceName: \"/my/sample/path/a|b\",\n\t\t\tExpected:    \"my/sample/path\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"escape absolute path\",\n\t\t\tServiceName: \"/hello /world!/a|b\",\n\t\t\tExpected:    \"hello%20/world%21\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"path with only one '/'\",\n\t\t\tServiceName: \"/foo\",\n\t\t\tExpected:    \"\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.TestName, func(t *testing.T) {\n\t\t\tconfig := Config{ServiceName: test.ServiceName}\n\t\t\tassert.Equal(t, test.Expected, config.getServiceName())\n\t\t})\n\t}\n}\n\nfunc TestConfig_GetTunStreamName(t *testing.T) {\n\ttests := []struct {\n\t\tTestName    string\n\t\tServiceName string\n\t\tExpected    string\n\t}{\n\t\t{\n\t\t\tTestName:    \"no absolute path\",\n\t\t\tServiceName: \"hello\",\n\t\t\tExpected:    \"Tun\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"absolute path server\",\n\t\t\tServiceName: \"/my/sample/path/tun_service|multi_service\",\n\t\t\tExpected:    \"tun_service\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"absolute path client\",\n\t\t\tServiceName: \"/my/sample/path/tun_service\",\n\t\t\tExpected:    \"tun_service\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"escape absolute path client\",\n\t\t\tServiceName: \"/m y/sa !mple/pa\\\\th/tun\\\\_serv!ice\",\n\t\t\tExpected:    \"tun%5C_serv%21ice\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.TestName, func(t *testing.T) {\n\t\t\tconfig := Config{ServiceName: test.ServiceName}\n\t\t\tassert.Equal(t, test.Expected, config.getTunStreamName())\n\t\t})\n\t}\n}\n\nfunc TestConfig_GetTunMultiStreamName(t *testing.T) {\n\ttests := []struct {\n\t\tTestName    string\n\t\tServiceName string\n\t\tExpected    string\n\t}{\n\t\t{\n\t\t\tTestName:    \"no absolute path\",\n\t\t\tServiceName: \"hello\",\n\t\t\tExpected:    \"TunMulti\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"absolute path server\",\n\t\t\tServiceName: \"/my/sample/path/tun_service|multi_service\",\n\t\t\tExpected:    \"multi_service\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"absolute path client\",\n\t\t\tServiceName: \"/my/sample/path/multi_service\",\n\t\t\tExpected:    \"multi_service\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"escape absolute path client\",\n\t\t\tServiceName: \"/m y/sa !mple/pa\\\\th/mu%lti\\\\_serv!ice\",\n\t\t\tExpected:    \"mu%25lti%5C_serv%21ice\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.TestName, func(t *testing.T) {\n\t\t\tconfig := Config{ServiceName: test.ServiceName}\n\t\t\tassert.Equal(t, test.Expected, config.getTunMultiStreamName())\n\t\t})\n\t}\n}\n\nfunc TestSetUserAgent(t *testing.T) {\n\tua := \"Test/1.0\"\n\tconn, err := grpc.NewClient(\"localhost:50051\", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUserAgent(ua))\n\tassert.NoError(t, err)\n\tdefer conn.Close()\n\tsetUserAgent(conn, ua)\n\tassert.Equal(t, ua, reflect.ValueOf(conn).Elem().FieldByName(\"dopts\").FieldByName(\"copts\").FieldByName(\"UserAgent\").String())\n}\n"
  },
  {
    "path": "transport/internet/grpc/dial.go",
    "content": "package grpc\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\tc \"github.com/xtls/xray-core/common/ctx\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/grpc/encoding\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/backoff\"\n\t\"google.golang.org/grpc/connectivity\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\terrors.LogInfo(ctx, \"creating connection to \", dest)\n\n\tconn, err := dialgRPC(ctx, dest, streamSettings)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to dial gRPC\").Base(err)\n\t}\n\treturn stat.Connection(conn), nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, Dial))\n}\n\ntype dialerConf struct {\n\tnet.Destination\n\t*internet.MemoryStreamConfig\n}\n\nvar (\n\tglobalDialerMap    map[dialerConf]*grpc.ClientConn\n\tglobalDialerAccess sync.Mutex\n)\n\nfunc dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {\n\tgrpcSettings := streamSettings.ProtocolSettings.(*Config)\n\n\tconn, err := getGrpcClient(ctx, dest, streamSettings)\n\tif err != nil {\n\t\treturn nil, errors.New(\"Cannot dial gRPC\").Base(err)\n\t}\n\tclient := encoding.NewGRPCServiceClient(conn)\n\tif grpcSettings.MultiMode {\n\t\terrors.LogDebug(ctx, \"using gRPC multi mode service name: `\"+grpcSettings.getServiceName()+\"` stream name: `\"+grpcSettings.getTunMultiStreamName()+\"`\")\n\t\tgrpcService, err := client.(encoding.GRPCServiceClientX).TunMultiCustomName(ctx, grpcSettings.getServiceName(), grpcSettings.getTunMultiStreamName())\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"Cannot dial gRPC\").Base(err)\n\t\t}\n\t\treturn encoding.NewMultiHunkConn(grpcService, nil), nil\n\t}\n\n\terrors.LogDebug(ctx, \"using gRPC tun mode service name: `\"+grpcSettings.getServiceName()+\"` stream name: `\"+grpcSettings.getTunStreamName()+\"`\")\n\tgrpcService, err := client.(encoding.GRPCServiceClientX).TunCustomName(ctx, grpcSettings.getServiceName(), grpcSettings.getTunStreamName())\n\tif err != nil {\n\t\treturn nil, errors.New(\"Cannot dial gRPC\").Base(err)\n\t}\n\n\treturn encoding.NewHunkConn(grpcService, nil), nil\n}\n\nfunc getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) {\n\tglobalDialerAccess.Lock()\n\tdefer globalDialerAccess.Unlock()\n\n\tif globalDialerMap == nil {\n\t\tglobalDialerMap = make(map[dialerConf]*grpc.ClientConn)\n\t}\n\ttlsConfig := tls.ConfigFromStreamSettings(streamSettings)\n\trealityConfig := reality.ConfigFromStreamSettings(streamSettings)\n\tsockopt := streamSettings.SocketSettings\n\tgrpcSettings := streamSettings.ProtocolSettings.(*Config)\n\n\tif client, found := globalDialerMap[dialerConf{dest, streamSettings}]; found && client.GetState() != connectivity.Shutdown {\n\t\treturn client, nil\n\t}\n\n\tdialOptions := []grpc.DialOption{\n\t\tgrpc.WithConnectParams(grpc.ConnectParams{\n\t\t\tBackoff: backoff.Config{\n\t\t\t\tBaseDelay:  500 * time.Millisecond,\n\t\t\t\tMultiplier: 1.5,\n\t\t\t\tJitter:     0.2,\n\t\t\t\tMaxDelay:   19 * time.Second,\n\t\t\t},\n\t\t\tMinConnectTimeout: 5 * time.Second,\n\t\t}),\n\t\tgrpc.WithContextDialer(func(gctx context.Context, s string) (net.Conn, error) {\n\t\t\tselect {\n\t\t\tcase <-gctx.Done():\n\t\t\t\treturn nil, gctx.Err()\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\trawHost, rawPort, err := net.SplitHostPort(s)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif len(rawPort) == 0 {\n\t\t\t\trawPort = \"443\"\n\t\t\t}\n\t\t\tport, err := net.PortFromString(rawPort)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\taddress := net.ParseAddress(rawHost)\n\n\t\t\tgctx = c.ContextWithID(gctx, c.IDFromContext(ctx))\n\t\t\tgctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx))\n\t\t\tgctx = session.ContextWithTimeoutOnly(gctx, true)\n\n\t\t\tc, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt)\n\t\t\tif err == nil {\n\t\t\t\tif streamSettings.TcpmaskManager != nil {\n\t\t\t\t\tnewConn, err := streamSettings.TcpmaskManager.WrapConnClient(c)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tc.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tc = newConn\n\t\t\t\t}\n\n\t\t\t\tif tlsConfig != nil {\n\t\t\t\t\tconfig := tlsConfig.GetTLSConfig()\n\t\t\t\t\tif config.ServerName == \"\" && address.Family().IsDomain() {\n\t\t\t\t\t\tconfig.ServerName = address.Domain()\n\t\t\t\t\t}\n\t\t\t\t\tif fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {\n\t\t\t\t\t\treturn tls.UClient(c, config, fingerprint), nil\n\t\t\t\t\t} else { // Fallback to normal gRPC TLS\n\t\t\t\t\t\treturn tls.Client(c, config), nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif realityConfig != nil {\n\t\t\t\t\treturn reality.UClient(c, realityConfig, gctx, dest)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn c, err\n\t\t}),\n\t}\n\n\tdialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\n\tauthority := \"\"\n\tif grpcSettings.Authority != \"\" {\n\t\tauthority = grpcSettings.Authority\n\t} else if tlsConfig != nil && tlsConfig.ServerName != \"\" {\n\t\tauthority = tlsConfig.ServerName\n\t} else if realityConfig == nil && dest.Address.Family().IsDomain() {\n\t\tauthority = dest.Address.Domain()\n\t}\n\tdialOptions = append(dialOptions, grpc.WithAuthority(authority))\n\n\tif grpcSettings.IdleTimeout > 0 || grpcSettings.HealthCheckTimeout > 0 || grpcSettings.PermitWithoutStream {\n\t\tdialOptions = append(dialOptions, grpc.WithKeepaliveParams(keepalive.ClientParameters{\n\t\t\tTime:                time.Second * time.Duration(grpcSettings.IdleTimeout),\n\t\t\tTimeout:             time.Second * time.Duration(grpcSettings.HealthCheckTimeout),\n\t\t\tPermitWithoutStream: grpcSettings.PermitWithoutStream,\n\t\t}))\n\t}\n\n\tif grpcSettings.InitialWindowsSize > 0 {\n\t\tdialOptions = append(dialOptions, grpc.WithInitialWindowSize(grpcSettings.InitialWindowsSize))\n\t}\n\n\tvar grpcDestHost string\n\tif dest.Address.Family().IsDomain() {\n\t\tgrpcDestHost = dest.Address.Domain()\n\t} else {\n\t\tgrpcDestHost = dest.Address.IP().String()\n\t}\n\n\tconn, err := grpc.NewClient(\n\t\t\"passthrough:///\"+net.JoinHostPort(grpcDestHost, dest.Port.String()),\n\t\tdialOptions...,\n\t)\n\tif err == nil {\n\t\tuserAgent := grpcSettings.UserAgent\n\t\tif userAgent == \"\" {\n\t\t\tuserAgent = utils.ChromeUA\n\t\t}\n\t\tsetUserAgent(conn, userAgent)\n\t\tconn.Connect()\n\t}\n\tglobalDialerMap[dialerConf{dest, streamSettings}] = conn\n\treturn conn, err\n}\n\n// setUserAgent overrides the user-agent on a ClientConn to remove the\n// \"grpc-go/version\" suffix that grpc.WithUserAgent unconditionally appends.\nfunc setUserAgent(conn *grpc.ClientConn, ua string) {\n\tif f := reflect.ValueOf(conn).Elem().FieldByName(\"dopts\").FieldByName(\"copts\").FieldByName(\"UserAgent\"); f.IsValid() {\n\t\t*(*string)(f.Addr().UnsafePointer()) = ua\n\t}\n}\n"
  },
  {
    "path": "transport/internet/grpc/encoding/customSeviceName.go",
    "content": "package encoding\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n)\n\nfunc ServerDesc(name, tun, tunMulti string) grpc.ServiceDesc {\n\treturn grpc.ServiceDesc{\n\t\tServiceName: name,\n\t\tHandlerType: (*GRPCServiceServer)(nil),\n\t\tMethods:     []grpc.MethodDesc{},\n\t\tStreams: []grpc.StreamDesc{\n\t\t\t{\n\t\t\t\tStreamName:    tun,\n\t\t\t\tHandler:       _GRPCService_Tun_Handler,\n\t\t\t\tServerStreams: true,\n\t\t\t\tClientStreams: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tStreamName:    tunMulti,\n\t\t\t\tHandler:       _GRPCService_TunMulti_Handler,\n\t\t\t\tServerStreams: true,\n\t\t\t\tClientStreams: true,\n\t\t\t},\n\t\t},\n\t\tMetadata: \"grpc.proto\",\n\t}\n}\n\nfunc (c *gRPCServiceClient) TunCustomName(ctx context.Context, name, tun string, opts ...grpc.CallOption) (GRPCService_TunClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ServerDesc(name, tun, \"\").Streams[0], \"/\"+name+\"/\"+tun, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream}\n\treturn x, nil\n}\n\nfunc (c *gRPCServiceClient) TunMultiCustomName(ctx context.Context, name, tunMulti string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &ServerDesc(name, \"\", tunMulti).Streams[1], \"/\"+name+\"/\"+tunMulti, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[MultiHunk, MultiHunk]{ClientStream: stream}\n\treturn x, nil\n}\n\ntype GRPCServiceClientX interface {\n\tTunCustomName(ctx context.Context, name, tun string, opts ...grpc.CallOption) (GRPCService_TunClient, error)\n\tTunMultiCustomName(ctx context.Context, name, tunMulti string, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error)\n\tTun(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunClient, error)\n\tTunMulti(ctx context.Context, opts ...grpc.CallOption) (GRPCService_TunMultiClient, error)\n}\n\nfunc RegisterGRPCServiceServerX(s *grpc.Server, srv GRPCServiceServer, name, tun, tunMulti string) {\n\tdesc := ServerDesc(name, tun, tunMulti)\n\ts.RegisterService(&desc, srv)\n}\n"
  },
  {
    "path": "transport/internet/grpc/encoding/encoding.go",
    "content": "package encoding\n"
  },
  {
    "path": "transport/internet/grpc/encoding/hunkconn.go",
    "content": "package encoding\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/peer\"\n)\n\ntype HunkConn interface {\n\tContext() context.Context\n\tSend(*Hunk) error\n\tRecv() (*Hunk, error)\n\tSendMsg(m interface{}) error\n\tRecvMsg(m interface{}) error\n}\n\ntype StreamCloser interface {\n\tCloseSend() error\n}\n\ntype HunkReaderWriter struct {\n\thc     HunkConn\n\tcancel context.CancelFunc\n\tdone   *done.Instance\n\n\tbuf   []byte\n\tindex int\n}\n\nfunc NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter {\n\treturn &HunkReaderWriter{hc, cancel, done.New(), nil, 0}\n}\n\nfunc NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn {\n\tvar rAddr net.Addr\n\tpr, ok := peer.FromContext(hc.Context())\n\tif ok {\n\t\trAddr = pr.Addr\n\t} else {\n\t\trAddr = &net.TCPAddr{\n\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\tPort: 0,\n\t\t}\n\t}\n\n\tmd, ok := metadata.FromIncomingContext(hc.Context())\n\tif ok {\n\t\theader := md.Get(\"x-real-ip\")\n\t\tif len(header) > 0 {\n\t\t\trealip := net.ParseAddress(header[0])\n\t\t\tif realip.Family().IsIP() {\n\t\t\t\trAddr = &net.TCPAddr{\n\t\t\t\t\tIP:   realip.IP(),\n\t\t\t\t\tPort: 0,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\twrc := NewHunkReadWriter(hc, cancel)\n\treturn cnc.NewConnection(\n\t\tcnc.ConnectionInput(wrc),\n\t\tcnc.ConnectionOutput(wrc),\n\t\tcnc.ConnectionOnClose(wrc),\n\t\tcnc.ConnectionRemoteAddr(rAddr),\n\t)\n}\n\nfunc (h *HunkReaderWriter) forceFetch() error {\n\thunk, err := h.hc.Recv()\n\tif err != nil {\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\n\t\treturn errors.New(\"failed to fetch hunk from gRPC tunnel\").Base(err)\n\t}\n\n\th.buf = hunk.Data\n\th.index = 0\n\n\treturn nil\n}\n\nfunc (h *HunkReaderWriter) Read(buf []byte) (int, error) {\n\tif h.done.Done() {\n\t\treturn 0, io.EOF\n\t}\n\n\tif h.index >= len(h.buf) {\n\t\tif err := h.forceFetch(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\tn := copy(buf, h.buf[h.index:])\n\th.index += n\n\n\treturn n, nil\n}\n\nfunc (h *HunkReaderWriter) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tif h.done.Done() {\n\t\treturn nil, io.EOF\n\t}\n\tif h.index >= len(h.buf) {\n\t\tif err := h.forceFetch(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif cap(h.buf) >= buf.Size {\n\t\tb := h.buf\n\t\th.index = len(h.buf)\n\t\treturn buf.MultiBuffer{buf.NewExisted(b)}, nil\n\t}\n\n\tb := buf.New()\n\t_, err := b.ReadFrom(h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.MultiBuffer{b}, nil\n}\n\nfunc (h *HunkReaderWriter) Write(buf []byte) (int, error) {\n\tif h.done.Done() {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\n\terr := h.hc.Send(&Hunk{Data: buf[:]})\n\tif err != nil {\n\t\treturn 0, errors.New(\"failed to send data over gRPC tunnel\").Base(err)\n\t}\n\treturn len(buf), nil\n}\n\nfunc (h *HunkReaderWriter) Close() error {\n\tif h.cancel != nil {\n\t\th.cancel()\n\t}\n\tif sc, match := h.hc.(StreamCloser); match {\n\t\treturn sc.CloseSend()\n\t}\n\n\treturn h.done.Close()\n}\n"
  },
  {
    "path": "transport/internet/grpc/encoding/multiconn.go",
    "content": "package encoding\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\txnet \"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/peer\"\n)\n\ntype MultiHunkConn interface {\n\tContext() context.Context\n\tSend(*MultiHunk) error\n\tRecv() (*MultiHunk, error)\n\tSendMsg(m interface{}) error\n\tRecvMsg(m interface{}) error\n}\n\ntype MultiHunkReaderWriter struct {\n\thc     MultiHunkConn\n\tcancel context.CancelFunc\n\tdone   *done.Instance\n\n\tbuf [][]byte\n}\n\nfunc NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiHunkReaderWriter {\n\treturn &MultiHunkReaderWriter{hc, cancel, done.New(), nil}\n}\n\nfunc NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn {\n\tvar rAddr net.Addr\n\tpr, ok := peer.FromContext(hc.Context())\n\tif ok {\n\t\trAddr = pr.Addr\n\t} else {\n\t\trAddr = &net.TCPAddr{\n\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\tPort: 0,\n\t\t}\n\t}\n\n\tmd, ok := metadata.FromIncomingContext(hc.Context())\n\tif ok {\n\t\theader := md.Get(\"x-real-ip\")\n\t\tif len(header) > 0 {\n\t\t\trealip := xnet.ParseAddress(header[0])\n\t\t\tif realip.Family().IsIP() {\n\t\t\t\trAddr = &net.TCPAddr{\n\t\t\t\t\tIP:   realip.IP(),\n\t\t\t\t\tPort: 0,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\twrc := NewMultiHunkReadWriter(hc, cancel)\n\treturn cnc.NewConnection(\n\t\tcnc.ConnectionInputMulti(wrc),\n\t\tcnc.ConnectionOutputMulti(wrc),\n\t\tcnc.ConnectionOnClose(wrc),\n\t\tcnc.ConnectionRemoteAddr(rAddr),\n\t)\n}\n\nfunc (h *MultiHunkReaderWriter) forceFetch() error {\n\thunk, err := h.hc.Recv()\n\tif err != nil {\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\n\t\treturn errors.New(\"failed to fetch hunk from gRPC tunnel\").Base(err)\n\t}\n\n\th.buf = hunk.Data\n\n\treturn nil\n}\n\nfunc (h *MultiHunkReaderWriter) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tif h.done.Done() {\n\t\treturn nil, io.EOF\n\t}\n\n\tif err := h.forceFetch(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tmb := make(buf.MultiBuffer, 0, len(h.buf))\n\tfor _, b := range h.buf {\n\t\tif len(b) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif cap(b) >= buf.Size {\n\t\t\tmb = append(mb, buf.NewExisted(b))\n\t\t} else {\n\t\t\tnb := buf.New()\n\t\t\tnb.Extend(int32(len(b)))\n\t\t\tcopy(nb.Bytes(), b)\n\n\t\t\tmb = append(mb, nb)\n\t\t}\n\n\t}\n\treturn mb, nil\n}\n\nfunc (h *MultiHunkReaderWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tdefer buf.ReleaseMulti(mb)\n\tif h.done.Done() {\n\t\treturn io.ErrClosedPipe\n\t}\n\n\thunks := make([][]byte, 0, len(mb))\n\n\tfor _, b := range mb {\n\t\tif b.Len() > 0 {\n\t\t\thunks = append(hunks, b.Bytes())\n\t\t}\n\t}\n\n\terr := h.hc.Send(&MultiHunk{Data: hunks})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (h *MultiHunkReaderWriter) Close() error {\n\tif h.cancel != nil {\n\t\th.cancel()\n\t}\n\tif sc, match := h.hc.(StreamCloser); match {\n\t\treturn sc.CloseSend()\n\t}\n\n\treturn h.done.Close()\n}\n"
  },
  {
    "path": "transport/internet/grpc/encoding/stream.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/grpc/encoding/stream.proto\n\npackage encoding\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Hunk struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tData          []byte                 `protobuf:\"bytes,1,opt,name=data,proto3\" json:\"data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Hunk) Reset() {\n\t*x = Hunk{}\n\tmi := &file_transport_internet_grpc_encoding_stream_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Hunk) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Hunk) ProtoMessage() {}\n\nfunc (x *Hunk) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_grpc_encoding_stream_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Hunk.ProtoReflect.Descriptor instead.\nfunc (*Hunk) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_grpc_encoding_stream_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Hunk) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\ntype MultiHunk struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tData          [][]byte               `protobuf:\"bytes,1,rep,name=data,proto3\" json:\"data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *MultiHunk) Reset() {\n\t*x = MultiHunk{}\n\tmi := &file_transport_internet_grpc_encoding_stream_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *MultiHunk) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MultiHunk) ProtoMessage() {}\n\nfunc (x *MultiHunk) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_grpc_encoding_stream_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MultiHunk.ProtoReflect.Descriptor instead.\nfunc (*MultiHunk) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_grpc_encoding_stream_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *MultiHunk) GetData() [][]byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_grpc_encoding_stream_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_grpc_encoding_stream_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"-transport/internet/grpc/encoding/stream.proto\\x12%xray.transport.internet.grpc.encoding\\\"\\x1a\\n\" +\n\t\"\\x04Hunk\\x12\\x12\\n\" +\n\t\"\\x04data\\x18\\x01 \\x01(\\fR\\x04data\\\"\\x1f\\n\" +\n\t\"\\tMultiHunk\\x12\\x12\\n\" +\n\t\"\\x04data\\x18\\x01 \\x03(\\fR\\x04data2\\xe6\\x01\\n\" +\n\t\"\\vGRPCService\\x12c\\n\" +\n\t\"\\x03Tun\\x12+.xray.transport.internet.grpc.encoding.Hunk\\x1a+.xray.transport.internet.grpc.encoding.Hunk(\\x010\\x01\\x12r\\n\" +\n\t\"\\bTunMulti\\x120.xray.transport.internet.grpc.encoding.MultiHunk\\x1a0.xray.transport.internet.grpc.encoding.MultiHunk(\\x010\\x01B<Z:github.com/xtls/xray-core/transport/internet/grpc/encodingb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_grpc_encoding_stream_proto_rawDescOnce sync.Once\n\tfile_transport_internet_grpc_encoding_stream_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_grpc_encoding_stream_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_grpc_encoding_stream_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_grpc_encoding_stream_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_grpc_encoding_stream_proto_rawDesc), len(file_transport_internet_grpc_encoding_stream_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_grpc_encoding_stream_proto_rawDescData\n}\n\nvar file_transport_internet_grpc_encoding_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_grpc_encoding_stream_proto_goTypes = []any{\n\t(*Hunk)(nil),      // 0: xray.transport.internet.grpc.encoding.Hunk\n\t(*MultiHunk)(nil), // 1: xray.transport.internet.grpc.encoding.MultiHunk\n}\nvar file_transport_internet_grpc_encoding_stream_proto_depIdxs = []int32{\n\t0, // 0: xray.transport.internet.grpc.encoding.GRPCService.Tun:input_type -> xray.transport.internet.grpc.encoding.Hunk\n\t1, // 1: xray.transport.internet.grpc.encoding.GRPCService.TunMulti:input_type -> xray.transport.internet.grpc.encoding.MultiHunk\n\t0, // 2: xray.transport.internet.grpc.encoding.GRPCService.Tun:output_type -> xray.transport.internet.grpc.encoding.Hunk\n\t1, // 3: xray.transport.internet.grpc.encoding.GRPCService.TunMulti:output_type -> xray.transport.internet.grpc.encoding.MultiHunk\n\t2, // [2:4] is the sub-list for method output_type\n\t0, // [0:2] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_grpc_encoding_stream_proto_init() }\nfunc file_transport_internet_grpc_encoding_stream_proto_init() {\n\tif File_transport_internet_grpc_encoding_stream_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_grpc_encoding_stream_proto_rawDesc), len(file_transport_internet_grpc_encoding_stream_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_transport_internet_grpc_encoding_stream_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_grpc_encoding_stream_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_grpc_encoding_stream_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_grpc_encoding_stream_proto = out.File\n\tfile_transport_internet_grpc_encoding_stream_proto_goTypes = nil\n\tfile_transport_internet_grpc_encoding_stream_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/grpc/encoding/stream.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.grpc.encoding;\noption go_package = \"github.com/xtls/xray-core/transport/internet/grpc/encoding\";\n\nmessage Hunk {\n  bytes data = 1;\n}\n\nmessage MultiHunk {\n   repeated bytes data = 1;\n}\n\nservice GRPCService {\n  rpc Tun (stream Hunk) returns (stream Hunk);\n  rpc TunMulti (stream MultiHunk) returns (stream MultiHunk);\n}\n"
  },
  {
    "path": "transport/internet/grpc/encoding/stream_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.0\n// - protoc             v6.33.5\n// source: transport/internet/grpc/encoding/stream.proto\n\npackage encoding\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tGRPCService_Tun_FullMethodName      = \"/xray.transport.internet.grpc.encoding.GRPCService/Tun\"\n\tGRPCService_TunMulti_FullMethodName = \"/xray.transport.internet.grpc.encoding.GRPCService/TunMulti\"\n)\n\n// GRPCServiceClient is the client API for GRPCService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype GRPCServiceClient interface {\n\tTun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error)\n\tTunMulti(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[MultiHunk, MultiHunk], error)\n}\n\ntype gRPCServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewGRPCServiceClient(cc grpc.ClientConnInterface) GRPCServiceClient {\n\treturn &gRPCServiceClient{cc}\n}\n\nfunc (c *gRPCServiceClient) Tun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Hunk, Hunk], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[0], GRPCService_Tun_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[Hunk, Hunk]{ClientStream: stream}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype GRPCService_TunClient = grpc.BidiStreamingClient[Hunk, Hunk]\n\nfunc (c *gRPCServiceClient) TunMulti(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[MultiHunk, MultiHunk], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &GRPCService_ServiceDesc.Streams[1], GRPCService_TunMulti_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[MultiHunk, MultiHunk]{ClientStream: stream}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype GRPCService_TunMultiClient = grpc.BidiStreamingClient[MultiHunk, MultiHunk]\n\n// GRPCServiceServer is the server API for GRPCService service.\n// All implementations must embed UnimplementedGRPCServiceServer\n// for forward compatibility.\ntype GRPCServiceServer interface {\n\tTun(grpc.BidiStreamingServer[Hunk, Hunk]) error\n\tTunMulti(grpc.BidiStreamingServer[MultiHunk, MultiHunk]) error\n\tmustEmbedUnimplementedGRPCServiceServer()\n}\n\n// UnimplementedGRPCServiceServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedGRPCServiceServer struct{}\n\nfunc (UnimplementedGRPCServiceServer) Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error {\n\treturn status.Error(codes.Unimplemented, \"method Tun not implemented\")\n}\nfunc (UnimplementedGRPCServiceServer) TunMulti(grpc.BidiStreamingServer[MultiHunk, MultiHunk]) error {\n\treturn status.Error(codes.Unimplemented, \"method TunMulti not implemented\")\n}\nfunc (UnimplementedGRPCServiceServer) mustEmbedUnimplementedGRPCServiceServer() {}\nfunc (UnimplementedGRPCServiceServer) testEmbeddedByValue()                     {}\n\n// UnsafeGRPCServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to GRPCServiceServer will\n// result in compilation errors.\ntype UnsafeGRPCServiceServer interface {\n\tmustEmbedUnimplementedGRPCServiceServer()\n}\n\nfunc RegisterGRPCServiceServer(s grpc.ServiceRegistrar, srv GRPCServiceServer) {\n\t// If the following call panics, it indicates UnimplementedGRPCServiceServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&GRPCService_ServiceDesc, srv)\n}\n\nfunc _GRPCService_Tun_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(GRPCServiceServer).Tun(&grpc.GenericServerStream[Hunk, Hunk]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype GRPCService_TunServer = grpc.BidiStreamingServer[Hunk, Hunk]\n\nfunc _GRPCService_TunMulti_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(GRPCServiceServer).TunMulti(&grpc.GenericServerStream[MultiHunk, MultiHunk]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype GRPCService_TunMultiServer = grpc.BidiStreamingServer[MultiHunk, MultiHunk]\n\n// GRPCService_ServiceDesc is the grpc.ServiceDesc for GRPCService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar GRPCService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"xray.transport.internet.grpc.encoding.GRPCService\",\n\tHandlerType: (*GRPCServiceServer)(nil),\n\tMethods:     []grpc.MethodDesc{},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"Tun\",\n\t\t\tHandler:       _GRPCService_Tun_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"TunMulti\",\n\t\t\tHandler:       _GRPCService_TunMulti_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t},\n\tMetadata: \"transport/internet/grpc/encoding/stream.proto\",\n}\n"
  },
  {
    "path": "transport/internet/grpc/grpc.go",
    "content": "package grpc\n\nconst protocolName = \"grpc\"\n"
  },
  {
    "path": "transport/internet/grpc/hub.go",
    "content": "package grpc\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tgoreality \"github.com/xtls/reality\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/grpc/encoding\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\ntype Listener struct {\n\tencoding.UnimplementedGRPCServiceServer\n\tctx     context.Context\n\thandler internet.ConnHandler\n\tlocal   net.Addr\n\tconfig  *Config\n\n\ts *grpc.Server\n}\n\nfunc (l Listener) Tun(server encoding.GRPCService_TunServer) error {\n\ttunCtx, cancel := context.WithCancel(l.ctx)\n\tl.handler(encoding.NewHunkConn(server, cancel))\n\t<-tunCtx.Done()\n\treturn nil\n}\n\nfunc (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error {\n\ttunCtx, cancel := context.WithCancel(l.ctx)\n\tl.handler(encoding.NewMultiHunkConn(server, cancel))\n\t<-tunCtx.Done()\n\treturn nil\n}\n\nfunc (l Listener) Close() error {\n\tl.s.Stop()\n\treturn nil\n}\n\nfunc (l Listener) Addr() net.Addr {\n\treturn l.local\n}\n\nfunc Listen(ctx context.Context, address net.Address, port net.Port, settings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {\n\tgrpcSettings := settings.ProtocolSettings.(*Config)\n\tvar listener *Listener\n\tif port == net.Port(0) { // unix\n\t\tlistener = &Listener{\n\t\t\thandler: handler,\n\t\t\tlocal: &net.UnixAddr{\n\t\t\t\tName: address.Domain(),\n\t\t\t\tNet:  \"unix\",\n\t\t\t},\n\t\t\tconfig: grpcSettings,\n\t\t}\n\t} else { // tcp\n\t\tlistener = &Listener{\n\t\t\thandler: handler,\n\t\t\tlocal: &net.TCPAddr{\n\t\t\t\tIP:   address.IP(),\n\t\t\t\tPort: int(port),\n\t\t\t},\n\t\t\tconfig: grpcSettings,\n\t\t}\n\t}\n\n\tlistener.ctx = ctx\n\n\tconfig := tls.ConfigFromStreamSettings(settings)\n\n\tvar options []grpc.ServerOption\n\tvar s *grpc.Server\n\tif config != nil {\n\t\t// gRPC server may silently ignore TLS errors\n\t\toptions = append(options, grpc.Creds(credentials.NewTLS(config.GetTLSConfig(tls.WithNextProto(\"h2\")))))\n\t}\n\tif grpcSettings.IdleTimeout > 0 || grpcSettings.HealthCheckTimeout > 0 {\n\t\toptions = append(options, grpc.KeepaliveParams(keepalive.ServerParameters{\n\t\t\tTime:    time.Second * time.Duration(grpcSettings.IdleTimeout),\n\t\t\tTimeout: time.Second * time.Duration(grpcSettings.HealthCheckTimeout),\n\t\t}))\n\t}\n\n\ts = grpc.NewServer(options...)\n\tlistener.s = s\n\n\tif settings.SocketSettings != nil && settings.SocketSettings.AcceptProxyProtocol {\n\t\terrors.LogWarning(ctx, \"accepting PROXY protocol\")\n\t}\n\n\tgo func() {\n\t\tvar streamListener net.Listener\n\t\tvar err error\n\t\tif port == net.Port(0) { // unix\n\t\t\tstreamListener, err = internet.ListenSystem(ctx, &net.UnixAddr{\n\t\t\t\tName: address.Domain(),\n\t\t\t\tNet:  \"unix\",\n\t\t\t}, settings.SocketSettings)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to listen on \", address)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else { // tcp\n\t\t\tstreamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{\n\t\t\t\tIP:   address.IP(),\n\t\t\t\tPort: int(port),\n\t\t\t}, settings.SocketSettings)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to listen on \", address, \":\", port)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif settings.TcpmaskManager != nil {\n\t\t\tstreamListener, _ = settings.TcpmaskManager.WrapListener(streamListener)\n\t\t}\n\n\t\terrors.LogDebug(ctx, \"gRPC listen for service name `\"+grpcSettings.getServiceName()+\"` tun `\"+grpcSettings.getTunStreamName()+\"` multi tun `\"+grpcSettings.getTunMultiStreamName()+\"`\")\n\t\tencoding.RegisterGRPCServiceServerX(s, listener, grpcSettings.getServiceName(), grpcSettings.getTunStreamName(), grpcSettings.getTunMultiStreamName())\n\n\t\tif config := reality.ConfigFromStreamSettings(settings); config != nil {\n\t\t\tstreamListener = goreality.NewListener(streamListener, config.GetREALITYConfig())\n\t\t}\n\t\tif err = s.Serve(streamListener); err != nil {\n\t\t\terrors.LogInfoInner(ctx, err, \"Listener for gRPC ended\")\n\t\t}\n\t}()\n\n\treturn listener, nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, Listen))\n}\n"
  },
  {
    "path": "transport/internet/happy_eyeballs.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"time\"\n)\n\ntype result struct {\n\terr   error\n\tconn  net.Conn\n\tindex int\n}\n\nfunc TcpRaceDial(ctx context.Context, src net.Address, ips []net.IP, port net.Port, sockopt *SocketConfig, domain string) (net.Conn, error) {\n\tif len(ips) < 2 {\n\t\tpanic(\"at least 2 ips is required to race dial\")\n\t}\n\n\tprioritizeIPv6 := sockopt.HappyEyeballs.PrioritizeIpv6\n\tinterleave := sockopt.HappyEyeballs.Interleave\n\ttryDelayMs := time.Duration(sockopt.HappyEyeballs.TryDelayMs) * time.Millisecond\n\tmaxConcurrentTry := sockopt.HappyEyeballs.MaxConcurrentTry\n\n\tips = sortIPs(ips, prioritizeIPv6, interleave)\n\tnewCtx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tvar resultCh = make(chan *result, len(ips))\n\tnextTryIndex := 0\n\tactiveNum := uint32(0)\n\ttimer := time.NewTimer(0)\n\tvar winConn net.Conn\n\terrors.LogDebug(ctx, \"happy eyeballs racing dial for \", domain, \" with IPs \", ips)\n\tfor {\n\t\tselect {\n\t\tcase r := <-resultCh:\n\t\t\tactiveNum--\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tcancel()\n\t\t\t\ttimer.Stop()\n\t\t\t\tif winConn != nil {\n\t\t\t\t\twinConn.Close()\n\t\t\t\t}\n\t\t\t\tif r.conn != nil {\n\t\t\t\t\tr.conn.Close()\n\t\t\t\t}\n\t\t\t\tif activeNum == 0 {\n\t\t\t\t\treturn nil, ctx.Err()\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\tdefault:\n\t\t\t\tif r.conn != nil {\n\t\t\t\t\tcancel()\n\t\t\t\t\ttimer.Stop()\n\t\t\t\t\tif winConn == nil {\n\t\t\t\t\t\twinConn = r.conn\n\t\t\t\t\t\terrors.LogDebug(ctx, \"happy eyeballs established connection for \", domain, \" with IP \", ips[r.index])\n\t\t\t\t\t} else {\n\t\t\t\t\t\tr.conn.Close()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif winConn != nil && activeNum == 0 {\n\t\t\t\t\treturn winConn, nil\n\t\t\t\t}\n\t\t\t\tif winConn != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif nextTryIndex < len(ips) {\n\t\t\t\t\ttimer.Reset(0)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif activeNum == 0 {\n\t\t\t\t\terrors.LogDebugInner(ctx, r.err, \"happy eyeballs no connection established for \", domain)\n\t\t\t\t\treturn nil, r.err\n\t\t\t\t}\n\t\t\t\ttimer.Stop()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\tcase <-timer.C:\n\t\t\tif nextTryIndex == len(ips) || activeNum == maxConcurrentTry {\n\t\t\t\tpanic(\"impossible situation\")\n\t\t\t}\n\t\t\tgo tcpTryDial(newCtx, src, sockopt, ips[nextTryIndex], port, nextTryIndex, resultCh)\n\t\t\tactiveNum++\n\t\t\tnextTryIndex++\n\t\t\tif nextTryIndex == len(ips) || activeNum == maxConcurrentTry {\n\t\t\t\ttimer.Stop()\n\t\t\t} else {\n\t\t\t\ttimer.Reset(tryDelayMs)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\n// sortIPs sort IPs according to rfc 8305.\nfunc sortIPs(ips []net.IP, prioritizeIPv6 bool, interleave uint32) []net.IP {\n\tif len(ips) == 0 {\n\t\treturn ips\n\t}\n\tvar ip4 = make([]net.IP, 0, len(ips))\n\tvar ip6 = make([]net.IP, 0, len(ips))\n\tfor _, ip := range ips {\n\t\tparsedIp := net.IPAddress(ip).IP()\n\t\tif len(parsedIp) == net.IPv4len {\n\t\t\tip4 = append(ip4, parsedIp)\n\t\t} else {\n\t\t\tip6 = append(ip6, parsedIp)\n\t\t}\n\t}\n\n\tif len(ip4) == 0 || len(ip6) == 0 {\n\t\treturn ips\n\t}\n\n\tvar newIPs = make([]net.IP, 0, len(ips))\n\tconsumeIP4 := 0\n\tconsumeIP6 := 0\n\tconsumeTurn := uint32(0)\n\tip4turn := true\n\tif prioritizeIPv6 {\n\t\tip4turn = false\n\t}\n\tfor {\n\t\tif ip4turn {\n\t\t\tnewIPs = append(newIPs, ip4[consumeIP4])\n\t\t\tconsumeIP4++\n\t\t\tif consumeIP4 == len(ip4) {\n\t\t\t\tnewIPs = append(newIPs, ip6[consumeIP6:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tconsumeTurn++\n\t\t\tif consumeTurn == interleave {\n\t\t\t\tip4turn = false\n\t\t\t\tconsumeTurn = uint32(0)\n\t\t\t}\n\t\t} else {\n\t\t\tnewIPs = append(newIPs, ip6[consumeIP6])\n\t\t\tconsumeIP6++\n\t\t\tif consumeIP6 == len(ip6) {\n\t\t\t\tnewIPs = append(newIPs, ip4[consumeIP4:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tconsumeTurn++\n\t\t\tif consumeTurn == interleave {\n\t\t\t\tip4turn = true\n\t\t\t\tconsumeTurn = uint32(0)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newIPs\n}\n\nfunc tcpTryDial(ctx context.Context, src net.Address, sockopt *SocketConfig, ip net.IP, port net.Port, index int, resultCh chan<- *result) {\n\tconn, err := effectiveSystemDialer.Dial(ctx, src, net.Destination{Address: net.IPAddress(ip), Network: net.Network_TCP, Port: port}, sockopt)\n\tselect {\n\tcase <-ctx.Done():\n\t\tif conn != nil {\n\t\t\tconn.Close()\n\t\t}\n\t\tresultCh <- &result{err: ctx.Err(), index: index}\n\t\treturn\n\tdefault:\n\t\tif err != nil {\n\t\t\tresultCh <- &result{err: err, index: index}\n\t\t\treturn\n\t\t}\n\t\tresultCh <- &result{conn: conn, index: index}\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "transport/internet/header.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype PacketHeader interface {\n\tSize() int32\n\tSerialize([]byte)\n}\n\nfunc CreatePacketHeader(config interface{}) (PacketHeader, error) {\n\theader, err := common.CreateObject(context.Background(), config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif h, ok := header.(PacketHeader); ok {\n\t\treturn h, nil\n\t}\n\treturn nil, errors.New(\"not a packet header\")\n}\n\ntype ConnectionAuthenticator interface {\n\tClient(net.Conn) net.Conn\n\tServer(net.Conn) net.Conn\n}\n\nfunc CreateConnectionAuthenticator(config interface{}) (ConnectionAuthenticator, error) {\n\tauth, err := common.CreateObject(context.Background(), config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif a, ok := auth.(ConnectionAuthenticator); ok {\n\t\treturn a, nil\n\t}\n\treturn nil, errors.New(\"not a ConnectionAuthenticator\")\n}\n"
  },
  {
    "path": "transport/internet/headers/http/config.go",
    "content": "package http\n\nimport (\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common/dice\"\n)\n\nfunc pickString(arr []string) string {\n\tn := len(arr)\n\tswitch n {\n\tcase 0:\n\t\treturn \"\"\n\tcase 1:\n\t\treturn arr[0]\n\tdefault:\n\t\treturn arr[dice.Roll(n)]\n\t}\n}\n\nfunc (v *RequestConfig) PickURI() string {\n\treturn pickString(v.Uri)\n}\n\nfunc (v *RequestConfig) PickHeaders() []string {\n\tn := len(v.Header)\n\tif n == 0 {\n\t\treturn nil\n\t}\n\theaders := make([]string, n)\n\tfor idx, headerConfig := range v.Header {\n\t\theaderName := headerConfig.Name\n\t\theaderValue := pickString(headerConfig.Value)\n\t\theaders[idx] = headerName + \": \" + headerValue\n\t}\n\treturn headers\n}\n\nfunc (v *RequestConfig) GetVersionValue() string {\n\tif v == nil || v.Version == nil {\n\t\treturn \"1.1\"\n\t}\n\treturn v.Version.Value\n}\n\nfunc (v *RequestConfig) GetMethodValue() string {\n\tif v == nil || v.Method == nil {\n\t\treturn \"GET\"\n\t}\n\treturn v.Method.Value\n}\n\nfunc (v *RequestConfig) GetFullVersion() string {\n\treturn \"HTTP/\" + v.GetVersionValue()\n}\n\nfunc (v *ResponseConfig) HasHeader(header string) bool {\n\tcHeader := strings.ToLower(header)\n\tfor _, tHeader := range v.Header {\n\t\tif strings.EqualFold(tHeader.Name, cHeader) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (v *ResponseConfig) PickHeaders() []string {\n\tn := len(v.Header)\n\tif n == 0 {\n\t\treturn nil\n\t}\n\theaders := make([]string, n)\n\tfor idx, headerConfig := range v.Header {\n\t\theaderName := headerConfig.Name\n\t\theaderValue := pickString(headerConfig.Value)\n\t\theaders[idx] = headerName + \": \" + headerValue\n\t}\n\treturn headers\n}\n\nfunc (v *ResponseConfig) GetVersionValue() string {\n\tif v == nil || v.Version == nil {\n\t\treturn \"1.1\"\n\t}\n\treturn v.Version.Value\n}\n\nfunc (v *ResponseConfig) GetFullVersion() string {\n\treturn \"HTTP/\" + v.GetVersionValue()\n}\n\nfunc (v *ResponseConfig) GetStatusValue() *Status {\n\tif v == nil || v.Status == nil {\n\t\treturn &Status{\n\t\t\tCode:   \"200\",\n\t\t\tReason: \"OK\",\n\t\t}\n\t}\n\treturn v.Status\n}\n"
  },
  {
    "path": "transport/internet/headers/http/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/headers/http/config.proto\n\npackage http\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Header struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// \"Accept\", \"Cookie\", etc\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Each entry must be valid in one piece. Random entry will be chosen if\n\t// multiple entries present.\n\tValue         []string `protobuf:\"bytes,2,rep,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Header) Reset() {\n\t*x = Header{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Header) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Header) ProtoMessage() {}\n\nfunc (x *Header) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Header.ProtoReflect.Descriptor instead.\nfunc (*Header) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Header) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Header) GetValue() []string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn nil\n}\n\n// HTTP version. Default value \"1.1\".\ntype Version struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         string                 `protobuf:\"bytes,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Version) Reset() {\n\t*x = Version{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Version) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Version) ProtoMessage() {}\n\nfunc (x *Version) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Version.ProtoReflect.Descriptor instead.\nfunc (*Version) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Version) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\n// HTTP method. Default value \"GET\".\ntype Method struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         string                 `protobuf:\"bytes,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Method) Reset() {\n\t*x = Method{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Method) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Method) ProtoMessage() {}\n\nfunc (x *Method) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Method.ProtoReflect.Descriptor instead.\nfunc (*Method) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Method) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\ntype RequestConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Full HTTP version like \"1.1\".\n\tVersion *Version `protobuf:\"bytes,1,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// GET, POST, CONNECT etc\n\tMethod *Method `protobuf:\"bytes,2,opt,name=method,proto3\" json:\"method,omitempty\"`\n\t// URI like \"/login.php\"\n\tUri           []string  `protobuf:\"bytes,3,rep,name=uri,proto3\" json:\"uri,omitempty\"`\n\tHeader        []*Header `protobuf:\"bytes,4,rep,name=header,proto3\" json:\"header,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RequestConfig) Reset() {\n\t*x = RequestConfig{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RequestConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RequestConfig) ProtoMessage() {}\n\nfunc (x *RequestConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RequestConfig.ProtoReflect.Descriptor instead.\nfunc (*RequestConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *RequestConfig) GetVersion() *Version {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn nil\n}\n\nfunc (x *RequestConfig) GetMethod() *Method {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn nil\n}\n\nfunc (x *RequestConfig) GetUri() []string {\n\tif x != nil {\n\t\treturn x.Uri\n\t}\n\treturn nil\n}\n\nfunc (x *RequestConfig) GetHeader() []*Header {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\ntype Status struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Status code. Default \"200\".\n\tCode string `protobuf:\"bytes,1,opt,name=code,proto3\" json:\"code,omitempty\"`\n\t// Statue reason. Default \"OK\".\n\tReason        string `protobuf:\"bytes,2,opt,name=reason,proto3\" json:\"reason,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Status) Reset() {\n\t*x = Status{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Status) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Status) ProtoMessage() {}\n\nfunc (x *Status) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Status.ProtoReflect.Descriptor instead.\nfunc (*Status) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Status) GetCode() string {\n\tif x != nil {\n\t\treturn x.Code\n\t}\n\treturn \"\"\n}\n\nfunc (x *Status) GetReason() string {\n\tif x != nil {\n\t\treturn x.Reason\n\t}\n\treturn \"\"\n}\n\ntype ResponseConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tVersion       *Version               `protobuf:\"bytes,1,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tStatus        *Status                `protobuf:\"bytes,2,opt,name=status,proto3\" json:\"status,omitempty\"`\n\tHeader        []*Header              `protobuf:\"bytes,3,rep,name=header,proto3\" json:\"header,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ResponseConfig) Reset() {\n\t*x = ResponseConfig{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResponseConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResponseConfig) ProtoMessage() {}\n\nfunc (x *ResponseConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ResponseConfig.ProtoReflect.Descriptor instead.\nfunc (*ResponseConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ResponseConfig) GetVersion() *Version {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn nil\n}\n\nfunc (x *ResponseConfig) GetStatus() *Status {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn nil\n}\n\nfunc (x *ResponseConfig) GetHeader() []*Header {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\ntype Config struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Settings for authenticating requests. If not set, client side will not send\n\t// authentication header, and server side will bypass authentication.\n\tRequest *RequestConfig `protobuf:\"bytes,1,opt,name=request,proto3\" json:\"request,omitempty\"`\n\t// Settings for authenticating responses. If not set, client side will bypass\n\t// authentication, and server side will not send authentication header.\n\tResponse      *ResponseConfig `protobuf:\"bytes,2,opt,name=response,proto3\" json:\"response,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_http_config_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_http_config_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Config) GetRequest() *RequestConfig {\n\tif x != nil {\n\t\treturn x.Request\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetResponse() *ResponseConfig {\n\tif x != nil {\n\t\treturn x.Response\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_headers_http_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_headers_http_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\",transport/internet/headers/http/config.proto\\x12$xray.transport.internet.headers.http\\\"2\\n\" +\n\t\"\\x06Header\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x03(\\tR\\x05value\\\"\\x1f\\n\" +\n\t\"\\aVersion\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\tR\\x05value\\\"\\x1e\\n\" +\n\t\"\\x06Method\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\tR\\x05value\\\"\\xf6\\x01\\n\" +\n\t\"\\rRequestConfig\\x12G\\n\" +\n\t\"\\aversion\\x18\\x01 \\x01(\\v2-.xray.transport.internet.headers.http.VersionR\\aversion\\x12D\\n\" +\n\t\"\\x06method\\x18\\x02 \\x01(\\v2,.xray.transport.internet.headers.http.MethodR\\x06method\\x12\\x10\\n\" +\n\t\"\\x03uri\\x18\\x03 \\x03(\\tR\\x03uri\\x12D\\n\" +\n\t\"\\x06header\\x18\\x04 \\x03(\\v2,.xray.transport.internet.headers.http.HeaderR\\x06header\\\"4\\n\" +\n\t\"\\x06Status\\x12\\x12\\n\" +\n\t\"\\x04code\\x18\\x01 \\x01(\\tR\\x04code\\x12\\x16\\n\" +\n\t\"\\x06reason\\x18\\x02 \\x01(\\tR\\x06reason\\\"\\xe5\\x01\\n\" +\n\t\"\\x0eResponseConfig\\x12G\\n\" +\n\t\"\\aversion\\x18\\x01 \\x01(\\v2-.xray.transport.internet.headers.http.VersionR\\aversion\\x12D\\n\" +\n\t\"\\x06status\\x18\\x02 \\x01(\\v2,.xray.transport.internet.headers.http.StatusR\\x06status\\x12D\\n\" +\n\t\"\\x06header\\x18\\x03 \\x03(\\v2,.xray.transport.internet.headers.http.HeaderR\\x06header\\\"\\xa9\\x01\\n\" +\n\t\"\\x06Config\\x12M\\n\" +\n\t\"\\arequest\\x18\\x01 \\x01(\\v23.xray.transport.internet.headers.http.RequestConfigR\\arequest\\x12P\\n\" +\n\t\"\\bresponse\\x18\\x02 \\x01(\\v24.xray.transport.internet.headers.http.ResponseConfigR\\bresponseB\\x8e\\x01\\n\" +\n\t\"(com.xray.transport.internet.headers.httpP\\x01Z9github.com/xtls/xray-core/transport/internet/headers/http\\xaa\\x02$Xray.Transport.Internet.Headers.Httpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_headers_http_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_headers_http_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_headers_http_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_headers_http_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_headers_http_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_headers_http_config_proto_rawDesc), len(file_transport_internet_headers_http_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_headers_http_config_proto_rawDescData\n}\n\nvar file_transport_internet_headers_http_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)\nvar file_transport_internet_headers_http_config_proto_goTypes = []any{\n\t(*Header)(nil),         // 0: xray.transport.internet.headers.http.Header\n\t(*Version)(nil),        // 1: xray.transport.internet.headers.http.Version\n\t(*Method)(nil),         // 2: xray.transport.internet.headers.http.Method\n\t(*RequestConfig)(nil),  // 3: xray.transport.internet.headers.http.RequestConfig\n\t(*Status)(nil),         // 4: xray.transport.internet.headers.http.Status\n\t(*ResponseConfig)(nil), // 5: xray.transport.internet.headers.http.ResponseConfig\n\t(*Config)(nil),         // 6: xray.transport.internet.headers.http.Config\n}\nvar file_transport_internet_headers_http_config_proto_depIdxs = []int32{\n\t1, // 0: xray.transport.internet.headers.http.RequestConfig.version:type_name -> xray.transport.internet.headers.http.Version\n\t2, // 1: xray.transport.internet.headers.http.RequestConfig.method:type_name -> xray.transport.internet.headers.http.Method\n\t0, // 2: xray.transport.internet.headers.http.RequestConfig.header:type_name -> xray.transport.internet.headers.http.Header\n\t1, // 3: xray.transport.internet.headers.http.ResponseConfig.version:type_name -> xray.transport.internet.headers.http.Version\n\t4, // 4: xray.transport.internet.headers.http.ResponseConfig.status:type_name -> xray.transport.internet.headers.http.Status\n\t0, // 5: xray.transport.internet.headers.http.ResponseConfig.header:type_name -> xray.transport.internet.headers.http.Header\n\t3, // 6: xray.transport.internet.headers.http.Config.request:type_name -> xray.transport.internet.headers.http.RequestConfig\n\t5, // 7: xray.transport.internet.headers.http.Config.response:type_name -> xray.transport.internet.headers.http.ResponseConfig\n\t8, // [8:8] is the sub-list for method output_type\n\t8, // [8:8] is the sub-list for method input_type\n\t8, // [8:8] is the sub-list for extension type_name\n\t8, // [8:8] is the sub-list for extension extendee\n\t0, // [0:8] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_headers_http_config_proto_init() }\nfunc file_transport_internet_headers_http_config_proto_init() {\n\tif File_transport_internet_headers_http_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_headers_http_config_proto_rawDesc), len(file_transport_internet_headers_http_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   7,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_headers_http_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_headers_http_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_headers_http_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_headers_http_config_proto = out.File\n\tfile_transport_internet_headers_http_config_proto_goTypes = nil\n\tfile_transport_internet_headers_http_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/headers/http/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.headers.http;\noption csharp_namespace = \"Xray.Transport.Internet.Headers.Http\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/headers/http\";\noption java_package = \"com.xray.transport.internet.headers.http\";\noption java_multiple_files = true;\n\nmessage Header {\n  // \"Accept\", \"Cookie\", etc\n  string name = 1;\n\n  // Each entry must be valid in one piece. Random entry will be chosen if\n  // multiple entries present.\n  repeated string value = 2;\n}\n\n// HTTP version. Default value \"1.1\".\nmessage Version {\n  string value = 1;\n}\n\n// HTTP method. Default value \"GET\".\nmessage Method {\n  string value = 1;\n}\n\nmessage RequestConfig {\n  // Full HTTP version like \"1.1\".\n  Version version = 1;\n\n  // GET, POST, CONNECT etc\n  Method method = 2;\n\n  // URI like \"/login.php\"\n  repeated string uri = 3;\n\n  repeated Header header = 4;\n}\n\nmessage Status {\n  // Status code. Default \"200\".\n  string code = 1;\n\n  // Statue reason. Default \"OK\".\n  string reason = 2;\n}\n\nmessage ResponseConfig {\n  Version version = 1;\n\n  Status status = 2;\n\n  repeated Header header = 3;\n}\n\nmessage Config {\n  // Settings for authenticating requests. If not set, client side will not send\n  // authentication header, and server side will bypass authentication.\n  RequestConfig request = 1;\n\n  // Settings for authenticating responses. If not set, client side will bypass\n  // authentication, and server side will not send authentication header.\n  ResponseConfig response = 2;\n}\n"
  },
  {
    "path": "transport/internet/headers/http/http.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nconst (\n\t// CRLF is the line ending in HTTP header\n\tCRLF = \"\\r\\n\"\n\n\t// ENDING is the double line ending between HTTP header and body.\n\tENDING = CRLF + CRLF\n\n\t// max length of HTTP header. Safety precaution for DDoS attack.\n\tmaxHeaderLength = 8192\n)\n\nvar (\n\tErrHeaderToLong = errors.New(\"Header too long.\")\n\n\tErrHeaderMisMatch = errors.New(\"Header Mismatch.\")\n)\n\ntype Reader interface {\n\tRead(io.Reader) (*buf.Buffer, error)\n}\n\ntype Writer interface {\n\tWrite(io.Writer) error\n}\n\ntype NoOpReader struct{}\n\nfunc (NoOpReader) Read(io.Reader) (*buf.Buffer, error) {\n\treturn nil, nil\n}\n\ntype NoOpWriter struct{}\n\nfunc (NoOpWriter) Write(io.Writer) error {\n\treturn nil\n}\n\ntype HeaderReader struct {\n\treq            *http.Request\n\texpectedHeader *RequestConfig\n}\n\nfunc (h *HeaderReader) ExpectThisRequest(expectedHeader *RequestConfig) *HeaderReader {\n\th.expectedHeader = expectedHeader\n\treturn h\n}\n\nfunc (h *HeaderReader) Read(reader io.Reader) (*buf.Buffer, error) {\n\tbuffer := buf.New()\n\ttotalBytes := int32(0)\n\tendingDetected := false\n\n\tvar headerBuf bytes.Buffer\n\n\tfor totalBytes < maxHeaderLength {\n\t\t_, err := buffer.ReadFrom(reader)\n\t\tif err != nil {\n\t\t\tbuffer.Release()\n\t\t\treturn nil, err\n\t\t}\n\t\tif n := bytes.Index(buffer.Bytes(), []byte(ENDING)); n != -1 {\n\t\t\theaderBuf.Write(buffer.BytesRange(0, int32(n+len(ENDING))))\n\t\t\tbuffer.Advance(int32(n + len(ENDING)))\n\t\t\tendingDetected = true\n\t\t\tbreak\n\t\t}\n\t\tlenEnding := int32(len(ENDING))\n\t\tif buffer.Len() >= lenEnding {\n\t\t\ttotalBytes += buffer.Len() - lenEnding\n\t\t\theaderBuf.Write(buffer.BytesRange(0, buffer.Len()-lenEnding))\n\t\t\tleftover := buffer.BytesFrom(-lenEnding)\n\t\t\tbuffer.Clear()\n\t\t\tcopy(buffer.Extend(lenEnding), leftover)\n\n\t\t\tif _, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes()))); err != io.ErrUnexpectedEOF {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tif !endingDetected {\n\t\tbuffer.Release()\n\t\treturn nil, ErrHeaderToLong\n\t}\n\n\tif h.expectedHeader == nil {\n\t\tif buffer.IsEmpty() {\n\t\t\tbuffer.Release()\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn buffer, nil\n\t}\n\n\t// Parse the request\n\tif req, err := readRequest(bufio.NewReader(bytes.NewReader(headerBuf.Bytes()))); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\th.req = req\n\t}\n\n\t// Check req\n\tpath := h.req.URL.Path\n\thasThisURI := false\n\tfor _, u := range h.expectedHeader.Uri {\n\t\tif u == path {\n\t\t\thasThisURI = true\n\t\t}\n\t}\n\n\tif !hasThisURI {\n\t\treturn nil, ErrHeaderMisMatch\n\t}\n\n\tif buffer.IsEmpty() {\n\t\tbuffer.Release()\n\t\treturn nil, nil\n\t}\n\n\treturn buffer, nil\n}\n\ntype HeaderWriter struct {\n\theader *buf.Buffer\n}\n\nfunc NewHeaderWriter(header *buf.Buffer) *HeaderWriter {\n\treturn &HeaderWriter{\n\t\theader: header,\n\t}\n}\n\nfunc (w *HeaderWriter) Write(writer io.Writer) error {\n\tif w.header == nil {\n\t\treturn nil\n\t}\n\terr := buf.WriteAllBytes(writer, w.header.Bytes(), nil)\n\tw.header.Release()\n\tw.header = nil\n\treturn err\n}\n\ntype Conn struct {\n\tnet.Conn\n\n\treadBuffer          *buf.Buffer\n\toneTimeReader       Reader\n\toneTimeWriter       Writer\n\terrorWriter         Writer\n\terrorMismatchWriter Writer\n\terrorTooLongWriter  Writer\n\terrReason           error\n}\n\nfunc NewConn(conn net.Conn, reader Reader, writer Writer, errorWriter Writer, errorMismatchWriter Writer, errorTooLongWriter Writer) *Conn {\n\treturn &Conn{\n\t\tConn:                conn,\n\t\toneTimeReader:       reader,\n\t\toneTimeWriter:       writer,\n\t\terrorWriter:         errorWriter,\n\t\terrorMismatchWriter: errorMismatchWriter,\n\t\terrorTooLongWriter:  errorTooLongWriter,\n\t}\n}\n\nfunc (c *Conn) Read(b []byte) (int, error) {\n\tif c.oneTimeReader != nil {\n\t\tbuffer, err := c.oneTimeReader.Read(c.Conn)\n\t\tif err != nil {\n\t\t\tc.errReason = err\n\t\t\treturn 0, err\n\t\t}\n\t\tc.readBuffer = buffer\n\t\tc.oneTimeReader = nil\n\t}\n\n\tif !c.readBuffer.IsEmpty() {\n\t\tnBytes, _ := c.readBuffer.Read(b)\n\t\tif c.readBuffer.IsEmpty() {\n\t\t\tc.readBuffer.Release()\n\t\t\tc.readBuffer = nil\n\t\t}\n\t\treturn nBytes, nil\n\t}\n\n\treturn c.Conn.Read(b)\n}\n\n// Write implements io.Writer.\nfunc (c *Conn) Write(b []byte) (int, error) {\n\tif c.oneTimeWriter != nil {\n\t\terr := c.oneTimeWriter.Write(c.Conn)\n\t\tc.oneTimeWriter = nil\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\treturn c.Conn.Write(b)\n}\n\n// Close implements net.Conn.Close().\nfunc (c *Conn) Close() error {\n\tif c.oneTimeWriter != nil && c.errorWriter != nil {\n\t\t// Connection is being closed but header wasn't sent. This means the client request\n\t\t// is probably not valid. Sending back a server error header in this case.\n\n\t\t// Write response based on error reason\n\t\tswitch c.errReason {\n\t\tcase ErrHeaderMisMatch:\n\t\t\tc.errorMismatchWriter.Write(c.Conn)\n\t\tcase ErrHeaderToLong:\n\t\t\tc.errorTooLongWriter.Write(c.Conn)\n\t\tdefault:\n\t\t\tc.errorWriter.Write(c.Conn)\n\t\t}\n\t}\n\n\treturn c.Conn.Close()\n}\n\nfunc formResponseHeader(config *ResponseConfig) *HeaderWriter {\n\theader := buf.New()\n\tcommon.Must2(header.WriteString(strings.Join([]string{config.GetFullVersion(), config.GetStatusValue().Code, config.GetStatusValue().Reason}, \" \")))\n\tcommon.Must2(header.WriteString(CRLF))\n\n\theaders := config.PickHeaders()\n\tfor _, h := range headers {\n\t\tcommon.Must2(header.WriteString(h))\n\t\tcommon.Must2(header.WriteString(CRLF))\n\t}\n\tif !config.HasHeader(\"Date\") {\n\t\tcommon.Must2(header.WriteString(\"Date: \"))\n\t\tcommon.Must2(header.WriteString(time.Now().Format(http.TimeFormat)))\n\t\tcommon.Must2(header.WriteString(CRLF))\n\t}\n\tcommon.Must2(header.WriteString(CRLF))\n\treturn &HeaderWriter{\n\t\theader: header,\n\t}\n}\n\ntype Authenticator struct {\n\tconfig *Config\n}\n\nfunc (a Authenticator) GetClientWriter() *HeaderWriter {\n\theader := buf.New()\n\tconfig := a.config.Request\n\tcommon.Must2(header.WriteString(strings.Join([]string{config.GetMethodValue(), config.PickURI(), config.GetFullVersion()}, \" \")))\n\tcommon.Must2(header.WriteString(CRLF))\n\n\theaders := config.PickHeaders()\n\tfor _, h := range headers {\n\t\tcommon.Must2(header.WriteString(h))\n\t\tcommon.Must2(header.WriteString(CRLF))\n\t}\n\tcommon.Must2(header.WriteString(CRLF))\n\treturn &HeaderWriter{\n\t\theader: header,\n\t}\n}\n\nfunc (a Authenticator) GetServerWriter() *HeaderWriter {\n\treturn formResponseHeader(a.config.Response)\n}\n\nfunc (a Authenticator) Client(conn net.Conn) net.Conn {\n\tif a.config.Request == nil && a.config.Response == nil {\n\t\treturn conn\n\t}\n\tvar reader Reader = NoOpReader{}\n\tif a.config.Request != nil {\n\t\treader = new(HeaderReader)\n\t}\n\n\tvar writer Writer = NoOpWriter{}\n\tif a.config.Response != nil {\n\t\twriter = a.GetClientWriter()\n\t}\n\treturn NewConn(conn, reader, writer, NoOpWriter{}, NoOpWriter{}, NoOpWriter{})\n}\n\nfunc (a Authenticator) Server(conn net.Conn) net.Conn {\n\tif a.config.Request == nil && a.config.Response == nil {\n\t\treturn conn\n\t}\n\treturn NewConn(conn, new(HeaderReader).ExpectThisRequest(a.config.Request), a.GetServerWriter(),\n\t\tformResponseHeader(resp400),\n\t\tformResponseHeader(resp404),\n\t\tformResponseHeader(resp400))\n}\n\nfunc NewAuthenticator(ctx context.Context, config *Config) (Authenticator, error) {\n\treturn Authenticator{\n\t\tconfig: config,\n\t}, nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {\n\t\treturn NewAuthenticator(ctx, config.(*Config))\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/headers/http/http_test.go",
    "content": "package http_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t. \"github.com/xtls/xray-core/transport/internet/headers/http\"\n)\n\nfunc TestReaderWriter(t *testing.T) {\n\tcache := buf.New()\n\tb := buf.New()\n\tcommon.Must2(b.WriteString(\"abcd\" + ENDING))\n\twriter := NewHeaderWriter(b)\n\terr := writer.Write(cache)\n\tcommon.Must(err)\n\tif v := cache.Len(); v != 8 {\n\t\tt.Error(\"cache len: \", v)\n\t}\n\t_, err = cache.Write([]byte{'e', 'f', 'g'})\n\tcommon.Must(err)\n\n\treader := &HeaderReader{}\n\t_, err = reader.Read(cache)\n\tif err != nil && !strings.HasPrefix(err.Error(), \"malformed HTTP request\") {\n\t\tt.Error(\"unknown error \", err)\n\t}\n}\n\nfunc TestRequestHeader(t *testing.T) {\n\tauth, err := NewAuthenticator(context.Background(), &Config{\n\t\tRequest: &RequestConfig{\n\t\t\tUri: []string{\"/\"},\n\t\t\tHeader: []*Header{\n\t\t\t\t{\n\t\t\t\t\tName:  \"Test\",\n\t\t\t\t\tValue: []string{\"Value\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\tcache := buf.New()\n\terr = auth.GetClientWriter().Write(cache)\n\tcommon.Must(err)\n\n\tif cache.String() != \"GET / HTTP/1.1\\r\\nTest: Value\\r\\n\\r\\n\" {\n\t\tt.Error(\"cache: \", cache.String())\n\t}\n}\n\nfunc TestLongRequestHeader(t *testing.T) {\n\tpayload := make([]byte, buf.Size+2)\n\tcommon.Must2(rand.Read(payload[:buf.Size-2]))\n\tcopy(payload[buf.Size-2:], ENDING)\n\tpayload = append(payload, []byte(\"abcd\")...)\n\n\treader := HeaderReader{}\n\t_, err := reader.Read(bytes.NewReader(payload))\n\n\tif err != nil && !(strings.HasPrefix(err.Error(), \"invalid\") || strings.HasPrefix(err.Error(), \"malformed\")) {\n\t\tt.Error(\"unknown error \", err)\n\t}\n}\n\nfunc TestConnection(t *testing.T) {\n\tauth, err := NewAuthenticator(context.Background(), &Config{\n\t\tRequest: &RequestConfig{\n\t\t\tMethod: &Method{Value: \"Post\"},\n\t\t\tUri:    []string{\"/testpath\"},\n\t\t\tHeader: []*Header{\n\t\t\t\t{\n\t\t\t\t\tName:  \"Host\",\n\t\t\t\t\tValue: []string{\"www.example.com\", \"www.google.com\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"User-Agent\",\n\t\t\t\t\tValue: []string{\"Test-Agent\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tResponse: &ResponseConfig{\n\t\t\tVersion: &Version{\n\t\t\t\tValue: \"1.1\",\n\t\t\t},\n\t\t\tStatus: &Status{\n\t\t\t\tCode:   \"404\",\n\t\t\t\tReason: \"Not Found\",\n\t\t\t},\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tcommon.Must(err)\n\n\tgo func() {\n\t\tconn, err := listener.Accept()\n\t\tcommon.Must(err)\n\t\tauthConn := auth.Server(conn)\n\t\tb := make([]byte, 256)\n\t\tfor {\n\t\t\tn, err := authConn.Read(b)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t_, err = authConn.Write(b[:n])\n\t\t\tcommon.Must(err)\n\t\t}\n\t}()\n\n\tconn, err := net.DialTCP(\"tcp\", nil, listener.Addr().(*net.TCPAddr))\n\tcommon.Must(err)\n\n\tauthConn := auth.Client(conn)\n\tdefer authConn.Close()\n\n\tauthConn.Write([]byte(\"Test payload\"))\n\tauthConn.Write([]byte(\"Test payload 2\"))\n\n\texpectedResponse := \"Test payloadTest payload 2\"\n\tactualResponse := make([]byte, 256)\n\tdeadline := time.Now().Add(time.Second * 5)\n\ttotalBytes := 0\n\tfor {\n\t\tn, err := authConn.Read(actualResponse[totalBytes:])\n\t\tcommon.Must(err)\n\t\ttotalBytes += n\n\t\tif totalBytes >= len(expectedResponse) || time.Now().After(deadline) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif string(actualResponse[:totalBytes]) != expectedResponse {\n\t\tt.Error(\"response: \", string(actualResponse[:totalBytes]))\n\t}\n}\n\nfunc TestConnectionInvPath(t *testing.T) {\n\tauth, err := NewAuthenticator(context.Background(), &Config{\n\t\tRequest: &RequestConfig{\n\t\t\tMethod: &Method{Value: \"Post\"},\n\t\t\tUri:    []string{\"/testpath\"},\n\t\t\tHeader: []*Header{\n\t\t\t\t{\n\t\t\t\t\tName:  \"Host\",\n\t\t\t\t\tValue: []string{\"www.example.com\", \"www.google.com\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"User-Agent\",\n\t\t\t\t\tValue: []string{\"Test-Agent\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tResponse: &ResponseConfig{\n\t\t\tVersion: &Version{\n\t\t\t\tValue: \"1.1\",\n\t\t\t},\n\t\t\tStatus: &Status{\n\t\t\t\tCode:   \"404\",\n\t\t\t\tReason: \"Not Found\",\n\t\t\t},\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\tauthR, err := NewAuthenticator(context.Background(), &Config{\n\t\tRequest: &RequestConfig{\n\t\t\tMethod: &Method{Value: \"Post\"},\n\t\t\tUri:    []string{\"/testpathErr\"},\n\t\t\tHeader: []*Header{\n\t\t\t\t{\n\t\t\t\t\tName:  \"Host\",\n\t\t\t\t\tValue: []string{\"www.example.com\", \"www.google.com\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"User-Agent\",\n\t\t\t\t\tValue: []string{\"Test-Agent\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tResponse: &ResponseConfig{\n\t\t\tVersion: &Version{\n\t\t\t\tValue: \"1.1\",\n\t\t\t},\n\t\t\tStatus: &Status{\n\t\t\t\tCode:   \"404\",\n\t\t\t\tReason: \"Not Found\",\n\t\t\t},\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tcommon.Must(err)\n\n\tgo func() {\n\t\tconn, err := listener.Accept()\n\t\tcommon.Must(err)\n\t\tauthConn := auth.Server(conn)\n\t\tb := make([]byte, 256)\n\t\tfor {\n\t\t\tn, err := authConn.Read(b)\n\t\t\tif err != nil {\n\t\t\t\tauthConn.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t_, err = authConn.Write(b[:n])\n\t\t\tcommon.Must(err)\n\t\t}\n\t}()\n\n\tconn, err := net.DialTCP(\"tcp\", nil, listener.Addr().(*net.TCPAddr))\n\tcommon.Must(err)\n\n\tauthConn := authR.Client(conn)\n\tdefer authConn.Close()\n\n\tauthConn.Write([]byte(\"Test payload\"))\n\tauthConn.Write([]byte(\"Test payload 2\"))\n\n\texpectedResponse := \"Test payloadTest payload 2\"\n\tactualResponse := make([]byte, 256)\n\tdeadline := time.Now().Add(time.Second * 5)\n\ttotalBytes := 0\n\tfor {\n\t\tn, err := authConn.Read(actualResponse[totalBytes:])\n\t\tif err == nil {\n\t\t\tt.Error(\"Error Expected\", err)\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\ttotalBytes += n\n\t\tif totalBytes >= len(expectedResponse) || time.Now().After(deadline) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc TestConnectionInvReq(t *testing.T) {\n\tauth, err := NewAuthenticator(context.Background(), &Config{\n\t\tRequest: &RequestConfig{\n\t\t\tMethod: &Method{Value: \"Post\"},\n\t\t\tUri:    []string{\"/testpath\"},\n\t\t\tHeader: []*Header{\n\t\t\t\t{\n\t\t\t\t\tName:  \"Host\",\n\t\t\t\t\tValue: []string{\"www.example.com\", \"www.google.com\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:  \"User-Agent\",\n\t\t\t\t\tValue: []string{\"Test-Agent\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tResponse: &ResponseConfig{\n\t\t\tVersion: &Version{\n\t\t\t\tValue: \"1.1\",\n\t\t\t},\n\t\t\tStatus: &Status{\n\t\t\t\tCode:   \"404\",\n\t\t\t\tReason: \"Not Found\",\n\t\t\t},\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tcommon.Must(err)\n\n\tgo func() {\n\t\tconn, err := listener.Accept()\n\t\tcommon.Must(err)\n\t\tauthConn := auth.Server(conn)\n\t\tb := make([]byte, 256)\n\t\tfor {\n\t\t\tn, err := authConn.Read(b)\n\t\t\tif err != nil {\n\t\t\t\tauthConn.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t_, err = authConn.Write(b[:n])\n\t\t\tcommon.Must(err)\n\t\t}\n\t}()\n\n\tconn, err := net.DialTCP(\"tcp\", nil, listener.Addr().(*net.TCPAddr))\n\tcommon.Must(err)\n\n\tconn.Write([]byte(\"ABCDEFGHIJKMLN\\r\\n\\r\\n\"))\n\tl, _, err := bufio.NewReader(conn).ReadLine()\n\tcommon.Must(err)\n\tif !strings.HasPrefix(string(l), \"HTTP/1.1 400 Bad Request\") {\n\t\tt.Error(\"Resp to non http conn\", string(l))\n\t}\n}\n"
  },
  {
    "path": "transport/internet/headers/http/linkedreadRequest.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"net/http\"\n\t// required to use go:linkname\n\t_ \"unsafe\"\n)\n\n//go:linkname readRequest net/http.readRequest\nfunc readRequest(b *bufio.Reader) (req *http.Request, err error)\n"
  },
  {
    "path": "transport/internet/headers/http/resp.go",
    "content": "package http\n\nvar resp400 = &ResponseConfig{\n\tVersion: &Version{\n\t\tValue: \"1.1\",\n\t},\n\tStatus: &Status{\n\t\tCode:   \"400\",\n\t\tReason: \"Bad Request\",\n\t},\n\tHeader: []*Header{\n\t\t{\n\t\t\tName:  \"Connection\",\n\t\t\tValue: []string{\"close\"},\n\t\t},\n\t\t{\n\t\t\tName:  \"Cache-Control\",\n\t\t\tValue: []string{\"private\"},\n\t\t},\n\t\t{\n\t\t\tName:  \"Content-Length\",\n\t\t\tValue: []string{\"0\"},\n\t\t},\n\t},\n}\n\nvar resp404 = &ResponseConfig{\n\tVersion: &Version{\n\t\tValue: \"1.1\",\n\t},\n\tStatus: &Status{\n\t\tCode:   \"404\",\n\t\tReason: \"Not Found\",\n\t},\n\tHeader: []*Header{\n\t\t{\n\t\t\tName:  \"Connection\",\n\t\t\tValue: []string{\"close\"},\n\t\t},\n\t\t{\n\t\t\tName:  \"Cache-Control\",\n\t\t\tValue: []string{\"private\"},\n\t\t},\n\t\t{\n\t\t\tName:  \"Content-Length\",\n\t\t\tValue: []string{\"0\"},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "transport/internet/headers/noop/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/headers/noop/config.proto\n\npackage noop\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_headers_noop_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_noop_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_noop_config_proto_rawDescGZIP(), []int{0}\n}\n\ntype ConnectionConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ConnectionConfig) Reset() {\n\t*x = ConnectionConfig{}\n\tmi := &file_transport_internet_headers_noop_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectionConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectionConfig) ProtoMessage() {}\n\nfunc (x *ConnectionConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_headers_noop_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectionConfig.ProtoReflect.Descriptor instead.\nfunc (*ConnectionConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_headers_noop_config_proto_rawDescGZIP(), []int{1}\n}\n\nvar File_transport_internet_headers_noop_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_headers_noop_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\",transport/internet/headers/noop/config.proto\\x12$xray.transport.internet.headers.noop\\\"\\b\\n\" +\n\t\"\\x06Config\\\"\\x12\\n\" +\n\t\"\\x10ConnectionConfigB\\x8e\\x01\\n\" +\n\t\"(com.xray.transport.internet.headers.noopP\\x01Z9github.com/xtls/xray-core/transport/internet/headers/noop\\xaa\\x02$Xray.Transport.Internet.Headers.Noopb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_headers_noop_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_headers_noop_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_headers_noop_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_headers_noop_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_headers_noop_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_headers_noop_config_proto_rawDesc), len(file_transport_internet_headers_noop_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_headers_noop_config_proto_rawDescData\n}\n\nvar file_transport_internet_headers_noop_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_headers_noop_config_proto_goTypes = []any{\n\t(*Config)(nil),           // 0: xray.transport.internet.headers.noop.Config\n\t(*ConnectionConfig)(nil), // 1: xray.transport.internet.headers.noop.ConnectionConfig\n}\nvar file_transport_internet_headers_noop_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_headers_noop_config_proto_init() }\nfunc file_transport_internet_headers_noop_config_proto_init() {\n\tif File_transport_internet_headers_noop_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_headers_noop_config_proto_rawDesc), len(file_transport_internet_headers_noop_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_headers_noop_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_headers_noop_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_headers_noop_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_headers_noop_config_proto = out.File\n\tfile_transport_internet_headers_noop_config_proto_goTypes = nil\n\tfile_transport_internet_headers_noop_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/headers/noop/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.headers.noop;\noption csharp_namespace = \"Xray.Transport.Internet.Headers.Noop\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/headers/noop\";\noption java_package = \"com.xray.transport.internet.headers.noop\";\noption java_multiple_files = true;\n\nmessage Config {}\n\nmessage ConnectionConfig {}\n"
  },
  {
    "path": "transport/internet/headers/noop/noop.go",
    "content": "package noop\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/common\"\n)\n\ntype NoOpHeader struct{}\n\nfunc (NoOpHeader) Size() int32 {\n\treturn 0\n}\n\n// Serialize implements PacketHeader.\nfunc (NoOpHeader) Serialize([]byte) {}\n\nfunc NewNoOpHeader(context.Context, interface{}) (interface{}, error) {\n\treturn NoOpHeader{}, nil\n}\n\ntype NoOpConnectionHeader struct{}\n\nfunc (NoOpConnectionHeader) Client(conn net.Conn) net.Conn {\n\treturn conn\n}\n\nfunc (NoOpConnectionHeader) Server(conn net.Conn) net.Conn {\n\treturn conn\n}\n\nfunc NewNoOpConnectionHeader(context.Context, interface{}) (interface{}, error) {\n\treturn NoOpConnectionHeader{}, nil\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), NewNoOpHeader))\n\tcommon.Must(common.RegisterConfig((*ConnectionConfig)(nil), NewNoOpConnectionHeader))\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/config.go",
    "content": "package httpupgrade\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc (c *Config) GetNormalizedPath() string {\n\tpath := c.Path\n\tif path == \"\" {\n\t\treturn \"/\"\n\t}\n\tif path[0] != '/' {\n\t\treturn \"/\" + path\n\t}\n\treturn path\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/httpupgrade/config.proto\n\npackage httpupgrade\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate               protoimpl.MessageState `protogen:\"open.v1\"`\n\tHost                string                 `protobuf:\"bytes,1,opt,name=host,proto3\" json:\"host,omitempty\"`\n\tPath                string                 `protobuf:\"bytes,2,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tHeader              map[string]string      `protobuf:\"bytes,3,rep,name=header,proto3\" json:\"header,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tAcceptProxyProtocol bool                   `protobuf:\"varint,4,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3\" json:\"accept_proxy_protocol,omitempty\"`\n\tEd                  uint32                 `protobuf:\"varint,5,opt,name=ed,proto3\" json:\"ed,omitempty\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_httpupgrade_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_httpupgrade_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_httpupgrade_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetHost() string {\n\tif x != nil {\n\t\treturn x.Host\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetHeader() map[string]string {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetAcceptProxyProtocol() bool {\n\tif x != nil {\n\t\treturn x.AcceptProxyProtocol\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetEd() uint32 {\n\tif x != nil {\n\t\treturn x.Ed\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_httpupgrade_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_httpupgrade_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"+transport/internet/httpupgrade/config.proto\\x12#xray.transport.internet.httpupgrade\\\"\\x80\\x02\\n\" +\n\t\"\\x06Config\\x12\\x12\\n\" +\n\t\"\\x04host\\x18\\x01 \\x01(\\tR\\x04host\\x12\\x12\\n\" +\n\t\"\\x04path\\x18\\x02 \\x01(\\tR\\x04path\\x12O\\n\" +\n\t\"\\x06header\\x18\\x03 \\x03(\\v27.xray.transport.internet.httpupgrade.Config.HeaderEntryR\\x06header\\x122\\n\" +\n\t\"\\x15accept_proxy_protocol\\x18\\x04 \\x01(\\bR\\x13acceptProxyProtocol\\x12\\x0e\\n\" +\n\t\"\\x02ed\\x18\\x05 \\x01(\\rR\\x02ed\\x1a9\\n\" +\n\t\"\\vHeaderEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\x8b\\x01\\n\" +\n\t\"'com.xray.transport.internet.httpupgradeP\\x01Z8github.com/xtls/xray-core/transport/internet/httpupgrade\\xaa\\x02#Xray.Transport.Internet.HttpUpgradeb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_httpupgrade_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_httpupgrade_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_httpupgrade_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_httpupgrade_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_httpupgrade_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_httpupgrade_config_proto_rawDesc), len(file_transport_internet_httpupgrade_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_httpupgrade_config_proto_rawDescData\n}\n\nvar file_transport_internet_httpupgrade_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_httpupgrade_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.httpupgrade.Config\n\tnil,            // 1: xray.transport.internet.httpupgrade.Config.HeaderEntry\n}\nvar file_transport_internet_httpupgrade_config_proto_depIdxs = []int32{\n\t1, // 0: xray.transport.internet.httpupgrade.Config.header:type_name -> xray.transport.internet.httpupgrade.Config.HeaderEntry\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_httpupgrade_config_proto_init() }\nfunc file_transport_internet_httpupgrade_config_proto_init() {\n\tif File_transport_internet_httpupgrade_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_httpupgrade_config_proto_rawDesc), len(file_transport_internet_httpupgrade_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_httpupgrade_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_httpupgrade_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_httpupgrade_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_httpupgrade_config_proto = out.File\n\tfile_transport_internet_httpupgrade_config_proto_goTypes = nil\n\tfile_transport_internet_httpupgrade_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.httpupgrade;\noption csharp_namespace = \"Xray.Transport.Internet.HttpUpgrade\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/httpupgrade\";\noption java_package = \"com.xray.transport.internet.httpupgrade\";\noption java_multiple_files = true;\n\nmessage Config {\n  string host = 1;\n  string path = 2;\n  map<string, string> header = 3;\n  bool accept_proxy_protocol = 4;\n  uint32 ed = 5;\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/connection.go",
    "content": "package httpupgrade\n\nimport \"net\"\n\ntype connection struct {\n\tnet.Conn\n\tremoteAddr net.Addr\n}\n\nfunc newConnection(conn net.Conn, remoteAddr net.Addr) *connection {\n\treturn &connection{\n\t\tConn:       conn,\n\t\tremoteAddr: remoteAddr,\n\t}\n}\n\nfunc (c *connection) RemoteAddr() net.Addr {\n\treturn c.remoteAddr\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/dialer.go",
    "content": "package httpupgrade\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\ntype ConnRF struct {\n\tnet.Conn\n\tReq   *http.Request\n\tFirst bool\n}\n\nfunc (c *ConnRF) Read(b []byte) (int, error) {\n\tif c.First {\n\t\tc.First = false\n\t\t// create reader capped to size of `b`, so it can be fully drained into\n\t\t// `b` later with a single Read call\n\t\treader := bufio.NewReaderSize(c.Conn, len(b))\n\t\tresp, err := http.ReadResponse(reader, c.Req) // nolint:bodyclose\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif resp.Status != \"101 Switching Protocols\" ||\n\t\t\tstrings.ToLower(resp.Header.Get(\"Upgrade\")) != \"websocket\" ||\n\t\t\tstrings.ToLower(resp.Header.Get(\"Connection\")) != \"upgrade\" {\n\t\t\treturn 0, errors.New(\"unrecognized reply\")\n\t\t}\n\t\t// drain remaining bufreader\n\t\treturn reader.Read(b[:reader.Buffered()])\n\t}\n\treturn c.Conn.Read(b)\n}\n\nfunc dialhttpUpgrade(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {\n\ttransportConfiguration := streamSettings.ProtocolSettings.(*Config)\n\n\tpconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)\n\tif err != nil {\n\t\terrors.LogErrorInner(ctx, err, \"failed to dial to \", dest)\n\t\treturn nil, err\n\t}\n\n\tif streamSettings.TcpmaskManager != nil {\n\t\tnewConn, err := streamSettings.TcpmaskManager.WrapConnClient(pconn)\n\t\tif err != nil {\n\t\t\tpconn.Close()\n\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t}\n\t\tpconn = newConn\n\t}\n\n\tvar conn net.Conn\n\tvar requestURL url.URL\n\ttConfig := tls.ConfigFromStreamSettings(streamSettings)\n\tif tConfig != nil {\n\t\ttlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto(\"http/1.1\"))\n\t\tif fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil {\n\t\t\tconn = tls.UClient(pconn, tlsConfig, fingerprint)\n\t\t\tif err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\tconn = tls.Client(pconn, tlsConfig)\n\t\t}\n\t\trequestURL.Scheme = \"https\"\n\t} else {\n\t\tconn = pconn\n\t\trequestURL.Scheme = \"http\"\n\t}\n\n\trequestURL.Host = transportConfiguration.Host\n\tif requestURL.Host == \"\" && tConfig != nil {\n\t\trequestURL.Host = tConfig.ServerName\n\t}\n\tif requestURL.Host == \"\" {\n\t\trequestURL.Host = dest.Address.String()\n\t}\n\trequestURL.Path = transportConfiguration.GetNormalizedPath()\n\treq := &http.Request{\n\t\tMethod: http.MethodGet,\n\t\tURL:    &requestURL,\n\t\tHeader: make(http.Header),\n\t}\n\tfor key, value := range transportConfiguration.Header {\n\t\tAddHeader(req.Header, key, value)\n\t}\n\tif req.Header.Get(\"User-Agent\") == \"\" {\n\t\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\t}\n\treq.Header.Set(\"Connection\", \"Upgrade\")\n\treq.Header.Set(\"Upgrade\", \"websocket\")\n\n\terr = req.Write(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconnRF := &ConnRF{\n\t\tConn:  conn,\n\t\tReq:   req,\n\t\tFirst: true,\n\t}\n\n\tif transportConfiguration.Ed == 0 {\n\t\t_, err = connRF.Read([]byte{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn connRF, nil\n}\n\n// http.Header.Add() will convert headers to MIME header format.\n// Some people don't like this because they want to send \"Web*S*ocket\".\n// So we add a simple function to replace that method.\nfunc AddHeader(header http.Header, key, value string) {\n\theader[key] = append(header[key], value)\n}\n\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\terrors.LogInfo(ctx, \"creating connection to \", dest)\n\n\tconn, err := dialhttpUpgrade(ctx, dest, streamSettings)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to dial request to \", dest).Base(err)\n\t}\n\treturn stat.Connection(conn), nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, Dial))\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/httpupgrade.go",
    "content": "package httpupgrade\n\nconst protocolName = \"httpupgrade\"\n"
  },
  {
    "path": "transport/internet/httpupgrade/httpupgrade_test.go",
    "content": "package httpupgrade_test\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t. \"github.com/xtls/xray-core/transport/internet/httpupgrade\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nfunc Test_listenHTTPUpgradeAndDial(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenHTTPUpgrade(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"httpupgrade\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"httpupgrade\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := c.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\n\tctx := context.Background()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"httpupgrade\",\n\t\tProtocolSettings: &Config{Path: \"httpupgrade\"},\n\t}\n\tconn, err := Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, err := conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(conn.Close())\n\tconn, err = Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 2\"))\n\tcommon.Must(err)\n\tn, err = conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\tcommon.Must(conn.Close())\n\n\tcommon.Must(listen.Close())\n}\n\nfunc Test_listenHTTPUpgradeAndDialWithHeaders(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenHTTPUpgrade(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"httpupgrade\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"httpupgrade\",\n\t\t\tHeader: map[string]string{\n\t\t\t\t\"User-Agent\": \"Mozilla\",\n\t\t\t},\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := c.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\n\tctx := context.Background()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"httpupgrade\",\n\t\tProtocolSettings: &Config{Path: \"httpupgrade\"},\n\t}\n\tconn, err := Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, err := conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(conn.Close())\n\tconn, err = Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 2\"))\n\tcommon.Must(err)\n\tn, err = conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\tcommon.Must(conn.Close())\n\n\tcommon.Must(listen.Close())\n}\n\nfunc TestDialWithRemoteAddr(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenHTTPUpgrade(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"httpupgrade\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"httpupgrade\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\t_, err := c.Read(b[:])\n\t\t\t// common.Must(err)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = c.Write([]byte(c.RemoteAddr().String()))\n\t\t\tcommon.Must(err)\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\n\tconn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"httpupgrade\",\n\t\tProtocolSettings: &Config{Path: \"httpupgrade\", Header: map[string]string{\"X-Forwarded-For\": \"1.1.1.1\"}},\n\t})\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, err := conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"1.1.1.1:0\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(listen.Close())\n}\n\nfunc Test_listenHTTPUpgradeAndDial_TLS(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tif runtime.GOARCH == \"arm64\" {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"httpupgrade\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"httpupgrades\",\n\t\t},\n\t\tSecurityType: \"tls\",\n\t\tSecuritySettings: &tls.Config{\n\t\t\tCertificate:          []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t},\n\t}\n\tlisten, err := ListenHTTPUpgrade(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {\n\t\tgo func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t})\n\tcommon.Must(err)\n\tdefer listen.Close()\n\n\tconn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\t_ = conn.Close()\n\n\tend := time.Now()\n\tif !end.Before(start.Add(time.Second * 5)) {\n\t\tt.Error(\"end: \", end, \" start: \", start)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/httpupgrade/hub.go",
    "content": "package httpupgrade\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\thttp_proto \"github.com/xtls/xray-core/common/protocol/http\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\tv2tls \"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\ntype server struct {\n\tconfig         *Config\n\taddConn        internet.ConnHandler\n\tinnnerListener net.Listener\n\tsocketSettings *internet.SocketConfig\n}\n\nfunc (s *server) Close() error {\n\treturn s.innnerListener.Close()\n}\n\nfunc (s *server) Addr() net.Addr {\n\treturn nil\n}\n\nfunc (s *server) Handle(conn net.Conn) {\n\tupgradedConn, err := s.upgrade(conn)\n\tif err != nil {\n\t\tcommon.CloseIfExists(conn)\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to handle request\")\n\t\treturn\n\t}\n\ts.addConn(upgradedConn)\n}\n\n// upgrade execute a fake websocket upgrade process and return the available connection\nfunc (s *server) upgrade(conn net.Conn) (stat.Connection, error) {\n\tconnReader := bufio.NewReader(conn)\n\treq, err := http.ReadRequest(connReader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.config != nil {\n\t\thost := req.Host\n\t\tif len(s.config.Host) > 0 && !internet.IsValidHTTPHost(host, s.config.Host) {\n\t\t\treturn nil, errors.New(\"bad host: \", host)\n\t\t}\n\t\tpath := s.config.GetNormalizedPath()\n\t\tif req.URL.Path != path {\n\t\t\treturn nil, errors.New(\"bad path: \", req.URL.Path)\n\t\t}\n\t}\n\n\tconnection := strings.ToLower(req.Header.Get(\"Connection\"))\n\tupgrade := strings.ToLower(req.Header.Get(\"Upgrade\"))\n\tif connection != \"upgrade\" || upgrade != \"websocket\" {\n\t\treturn nil, errors.New(\"unrecognized request\")\n\t}\n\tresp := &http.Response{\n\t\tStatus:     \"101 Switching Protocols\",\n\t\tStatusCode: 101,\n\t\tProto:      \"HTTP/1.1\",\n\t\tProtoMajor: 1,\n\t\tProtoMinor: 1,\n\t\tHeader:     http.Header{},\n\t}\n\tresp.Header.Set(\"Connection\", \"Upgrade\")\n\tresp.Header.Set(\"Upgrade\", \"websocket\")\n\terr = resp.Write(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar forwardedAddrs []net.Address\n\tif s.socketSettings != nil && len(s.socketSettings.TrustedXForwardedFor) > 0 {\n\t\tfor _, key := range s.socketSettings.TrustedXForwardedFor {\n\t\t\tif len(req.Header.Values(key)) > 0 {\n\t\t\t\tforwardedAddrs = http_proto.ParseXForwardedFor(req.Header)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tforwardedAddrs = http_proto.ParseXForwardedFor(req.Header)\n\t}\n\tremoteAddr := conn.RemoteAddr()\n\tif len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {\n\t\tremoteAddr = &net.TCPAddr{\n\t\t\tIP:   forwardedAddrs[0].IP(),\n\t\t\tPort: int(0),\n\t\t}\n\t}\n\n\treturn stat.Connection(newConnection(conn, remoteAddr)), nil\n}\n\nfunc (s *server) keepAccepting() {\n\tfor {\n\t\tconn, err := s.innnerListener.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tgo s.Handle(conn)\n\t}\n}\n\nfunc ListenHTTPUpgrade(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {\n\ttransportConfiguration := streamSettings.ProtocolSettings.(*Config)\n\tif transportConfiguration != nil {\n\t\tif streamSettings.SocketSettings == nil {\n\t\t\tstreamSettings.SocketSettings = &internet.SocketConfig{}\n\t\t}\n\t\tstreamSettings.SocketSettings.AcceptProxyProtocol = transportConfiguration.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocol\n\t}\n\tvar listener net.Listener\n\tvar err error\n\tif port == net.Port(0) { // unix\n\t\tlistener, err = internet.ListenSystem(ctx, &net.UnixAddr{\n\t\t\tName: address.Domain(),\n\t\t\tNet:  \"unix\",\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen unix domain socket(for HttpUpgrade) on \", address).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening unix domain socket(for HttpUpgrade) on \", address)\n\t} else { // tcp\n\t\tlistener, err = internet.ListenSystem(ctx, &net.TCPAddr{\n\t\t\tIP:   address.IP(),\n\t\t\tPort: int(port),\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen TCP(for HttpUpgrade) on \", address, \":\", port).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening TCP(for HttpUpgrade) on \", address, \":\", port)\n\t}\n\n\tif streamSettings.TcpmaskManager != nil {\n\t\tlistener, _ = streamSettings.TcpmaskManager.WrapListener(listener)\n\t}\n\n\tif streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {\n\t\terrors.LogWarning(ctx, \"accepting PROXY protocol\")\n\t}\n\n\tif config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tif tlsConfig := config.GetTLSConfig(); tlsConfig != nil {\n\t\t\tlistener = tls.NewListener(listener, tlsConfig)\n\t\t}\n\t}\n\n\tserverInstance := &server{\n\t\tconfig:         transportConfiguration,\n\t\taddConn:        addConn,\n\t\tinnnerListener: listener,\n\t\tsocketSettings: streamSettings.SocketSettings,\n\t}\n\tgo serverInstance.keepAccepting()\n\treturn serverInstance, nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, ListenHTTPUpgrade))\n}\n"
  },
  {
    "path": "transport/internet/hysteria/config.go",
    "content": "package hysteria\n\nimport (\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/padding\"\n)\n\nconst (\n\tcloseErrCodeOK            = 0x100 // HTTP3 ErrCodeNoError\n\tcloseErrCodeProtocolError = 0x101 // HTTP3 ErrCodeGeneralProtocolError\n\n\tMaxDatagramFrameSize = 1200\n\n\tURLHost = \"hysteria\"\n\tURLPath = \"/auth\"\n\n\tRequestHeaderAuth        = \"Hysteria-Auth\"\n\tResponseHeaderUDPEnabled = \"Hysteria-UDP\"\n\tCommonHeaderCCRX         = \"Hysteria-CC-RX\"\n\tCommonHeaderPadding      = \"Hysteria-Padding\"\n\n\tStatusAuthOK = 233\n\n\tudpMessageChanSize = 1024\n\n\tFrameTypeTCPRequest = 0x401\n\n\tidleCleanupInterval = 1 * time.Second\n)\n\nvar (\n\tauthRequestPadding  = padding.Padding{Min: 256, Max: 2048}\n\tauthResponsePadding = padding.Padding{Min: 256, Max: 2048}\n)\n\ntype Status int\n\nconst (\n\tStatusUnknown Status = iota\n\tStatusActive\n\tStatusInactive\n)\n\nconst protocolName = \"hysteria\"\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/hysteria/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/hysteria/config.proto\n\npackage hysteria\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate                protoimpl.MessageState `protogen:\"open.v1\"`\n\tVersion              int32                  `protobuf:\"varint,1,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tAuth                 string                 `protobuf:\"bytes,2,opt,name=auth,proto3\" json:\"auth,omitempty\"`\n\tUdpIdleTimeout       int64                  `protobuf:\"varint,3,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3\" json:\"udp_idle_timeout,omitempty\"`\n\tMasqType             string                 `protobuf:\"bytes,4,opt,name=masq_type,json=masqType,proto3\" json:\"masq_type,omitempty\"`\n\tMasqFile             string                 `protobuf:\"bytes,5,opt,name=masq_file,json=masqFile,proto3\" json:\"masq_file,omitempty\"`\n\tMasqUrl              string                 `protobuf:\"bytes,6,opt,name=masq_url,json=masqUrl,proto3\" json:\"masq_url,omitempty\"`\n\tMasqUrlRewriteHost   bool                   `protobuf:\"varint,7,opt,name=masq_url_rewrite_host,json=masqUrlRewriteHost,proto3\" json:\"masq_url_rewrite_host,omitempty\"`\n\tMasqUrlInsecure      bool                   `protobuf:\"varint,8,opt,name=masq_url_insecure,json=masqUrlInsecure,proto3\" json:\"masq_url_insecure,omitempty\"`\n\tMasqString           string                 `protobuf:\"bytes,9,opt,name=masq_string,json=masqString,proto3\" json:\"masq_string,omitempty\"`\n\tMasqStringHeaders    map[string]string      `protobuf:\"bytes,10,rep,name=masq_string_headers,json=masqStringHeaders,proto3\" json:\"masq_string_headers,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tMasqStringStatusCode int32                  `protobuf:\"varint,11,opt,name=masq_string_status_code,json=masqStringStatusCode,proto3\" json:\"masq_string_status_code,omitempty\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_hysteria_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_hysteria_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_hysteria_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetVersion() int32 {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetAuth() string {\n\tif x != nil {\n\t\treturn x.Auth\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetUdpIdleTimeout() int64 {\n\tif x != nil {\n\t\treturn x.UdpIdleTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetMasqType() string {\n\tif x != nil {\n\t\treturn x.MasqType\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMasqFile() string {\n\tif x != nil {\n\t\treturn x.MasqFile\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMasqUrl() string {\n\tif x != nil {\n\t\treturn x.MasqUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMasqUrlRewriteHost() bool {\n\tif x != nil {\n\t\treturn x.MasqUrlRewriteHost\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetMasqUrlInsecure() bool {\n\tif x != nil {\n\t\treturn x.MasqUrlInsecure\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetMasqString() string {\n\tif x != nil {\n\t\treturn x.MasqString\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMasqStringHeaders() map[string]string {\n\tif x != nil {\n\t\treturn x.MasqStringHeaders\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMasqStringStatusCode() int32 {\n\tif x != nil {\n\t\treturn x.MasqStringStatusCode\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_hysteria_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"(transport/internet/hysteria/config.proto\\x12 xray.transport.internet.hysteria\\\"\\xa3\\x04\\n\" +\n\t\"\\x06Config\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x01 \\x01(\\x05R\\aversion\\x12\\x12\\n\" +\n\t\"\\x04auth\\x18\\x02 \\x01(\\tR\\x04auth\\x12(\\n\" +\n\t\"\\x10udp_idle_timeout\\x18\\x03 \\x01(\\x03R\\x0eudpIdleTimeout\\x12\\x1b\\n\" +\n\t\"\\tmasq_type\\x18\\x04 \\x01(\\tR\\bmasqType\\x12\\x1b\\n\" +\n\t\"\\tmasq_file\\x18\\x05 \\x01(\\tR\\bmasqFile\\x12\\x19\\n\" +\n\t\"\\bmasq_url\\x18\\x06 \\x01(\\tR\\amasqUrl\\x121\\n\" +\n\t\"\\x15masq_url_rewrite_host\\x18\\a \\x01(\\bR\\x12masqUrlRewriteHost\\x12*\\n\" +\n\t\"\\x11masq_url_insecure\\x18\\b \\x01(\\bR\\x0fmasqUrlInsecure\\x12\\x1f\\n\" +\n\t\"\\vmasq_string\\x18\\t \\x01(\\tR\\n\" +\n\t\"masqString\\x12o\\n\" +\n\t\"\\x13masq_string_headers\\x18\\n\" +\n\t\" \\x03(\\v2?.xray.transport.internet.hysteria.Config.MasqStringHeadersEntryR\\x11masqStringHeaders\\x125\\n\" +\n\t\"\\x17masq_string_status_code\\x18\\v \\x01(\\x05R\\x14masqStringStatusCode\\x1aD\\n\" +\n\t\"\\x16MasqStringHeadersEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\x82\\x01\\n\" +\n\t\"$com.xray.transport.internet.hysteriaP\\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\\xaa\\x02 Xray.Transport.Internet.Hysteriab\\x06proto3\"\n\nvar (\n\tfile_transport_internet_hysteria_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_hysteria_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_hysteria_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_hysteria_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_hysteria_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_hysteria_config_proto_rawDesc), len(file_transport_internet_hysteria_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_hysteria_config_proto_rawDescData\n}\n\nvar file_transport_internet_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_hysteria_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.hysteria.Config\n\tnil,            // 1: xray.transport.internet.hysteria.Config.MasqStringHeadersEntry\n}\nvar file_transport_internet_hysteria_config_proto_depIdxs = []int32{\n\t1, // 0: xray.transport.internet.hysteria.Config.masq_string_headers:type_name -> xray.transport.internet.hysteria.Config.MasqStringHeadersEntry\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_hysteria_config_proto_init() }\nfunc file_transport_internet_hysteria_config_proto_init() {\n\tif File_transport_internet_hysteria_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_hysteria_config_proto_rawDesc), len(file_transport_internet_hysteria_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_hysteria_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_hysteria_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_hysteria_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_hysteria_config_proto = out.File\n\tfile_transport_internet_hysteria_config_proto_goTypes = nil\n\tfile_transport_internet_hysteria_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/hysteria/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.hysteria;\noption csharp_namespace = \"Xray.Transport.Internet.Hysteria\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/hysteria\";\noption java_package = \"com.xray.transport.internet.hysteria\";\noption java_multiple_files = true;\n\nmessage Config {\n  int32 version = 1;\n  string auth = 2;\n\n  int64 udp_idle_timeout = 3;\n  string masq_type = 4;\n  string masq_file = 5;\n  string masq_url = 6;\n  bool masq_url_rewrite_host = 7;\n  bool masq_url_insecure = 8;\n  string masq_string = 9;\n  map<string, string> masq_string_headers = 10;\n  int32 masq_string_status_code = 11;\n}"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/bandwidth.go",
    "content": "package bbr\n\nimport (\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go/congestion\"\n)\n\nconst (\n\tinfBandwidth = Bandwidth(math.MaxUint64)\n)\n\n// Bandwidth of a connection\ntype Bandwidth uint64\n\nconst (\n\t// BitsPerSecond is 1 bit per second\n\tBitsPerSecond Bandwidth = 1\n\t// BytesPerSecond is 1 byte per second\n\tBytesPerSecond = 8 * BitsPerSecond\n)\n\n// BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta\nfunc BandwidthFromDelta(bytes congestion.ByteCount, delta time.Duration) Bandwidth {\n\treturn Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/bandwidth_sampler.go",
    "content": "package bbr\n\nimport (\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go/congestion\"\n\t\"github.com/apernet/quic-go/monotime\"\n)\n\nconst (\n\tinfRTT                             = time.Duration(math.MaxInt64)\n\tdefaultConnectionStateMapQueueSize = 256\n\tdefaultCandidatesBufferSize        = 256\n)\n\ntype roundTripCount uint64\n\n// SendTimeState is a subset of ConnectionStateOnSentPacket which is returned\n// to the caller when the packet is acked or lost.\ntype sendTimeState struct {\n\t// Whether other states in this object is valid.\n\tisValid bool\n\t// Whether the sender is app limited at the time the packet was sent.\n\t// App limited bandwidth sample might be artificially low because the sender\n\t// did not have enough data to send in order to saturate the link.\n\tisAppLimited bool\n\t// Total number of sent bytes at the time the packet was sent.\n\t// Includes the packet itself.\n\ttotalBytesSent congestion.ByteCount\n\t// Total number of acked bytes at the time the packet was sent.\n\ttotalBytesAcked congestion.ByteCount\n\t// Total number of lost bytes at the time the packet was sent.\n\ttotalBytesLost congestion.ByteCount\n\t// Total number of inflight bytes at the time the packet was sent.\n\t// Includes the packet itself.\n\t// It should be equal to |total_bytes_sent| minus the sum of\n\t// |total_bytes_acked|, |total_bytes_lost| and total neutered bytes.\n\tbytesInFlight congestion.ByteCount\n}\n\nfunc newSendTimeState(\n\tisAppLimited bool,\n\ttotalBytesSent congestion.ByteCount,\n\ttotalBytesAcked congestion.ByteCount,\n\ttotalBytesLost congestion.ByteCount,\n\tbytesInFlight congestion.ByteCount,\n) *sendTimeState {\n\treturn &sendTimeState{\n\t\tisValid:         true,\n\t\tisAppLimited:    isAppLimited,\n\t\ttotalBytesSent:  totalBytesSent,\n\t\ttotalBytesAcked: totalBytesAcked,\n\t\ttotalBytesLost:  totalBytesLost,\n\t\tbytesInFlight:   bytesInFlight,\n\t}\n}\n\ntype extraAckedEvent struct {\n\t// The excess bytes acknowlwedged in the time delta for this event.\n\textraAcked congestion.ByteCount\n\n\t// The bytes acknowledged and time delta from the event.\n\tbytesAcked congestion.ByteCount\n\ttimeDelta  time.Duration\n\t// The round trip of the event.\n\tround roundTripCount\n}\n\nfunc maxExtraAckedEventFunc(a, b extraAckedEvent) int {\n\tif a.extraAcked > b.extraAcked {\n\t\treturn 1\n\t} else if a.extraAcked < b.extraAcked {\n\t\treturn -1\n\t}\n\treturn 0\n}\n\n// BandwidthSample\ntype bandwidthSample struct {\n\t// The bandwidth at that particular sample. Zero if no valid bandwidth sample\n\t// is available.\n\tbandwidth Bandwidth\n\t// The RTT measurement at this particular sample.  Zero if no RTT sample is\n\t// available.  Does not correct for delayed ack time.\n\trtt time.Duration\n\t// |send_rate| is computed from the current packet being acked('P') and an\n\t// earlier packet that is acked before P was sent.\n\tsendRate Bandwidth\n\t// States captured when the packet was sent.\n\tstateAtSend sendTimeState\n}\n\nfunc newBandwidthSample() *bandwidthSample {\n\treturn &bandwidthSample{\n\t\tsendRate: infBandwidth,\n\t}\n}\n\n// MaxAckHeightTracker is part of the BandwidthSampler. It is called after every\n// ack event to keep track the degree of ack aggregation(a.k.a \"ack height\").\ntype maxAckHeightTracker struct {\n\t// Tracks the maximum number of bytes acked faster than the estimated\n\t// bandwidth.\n\tmaxAckHeightFilter *WindowedFilter[extraAckedEvent, roundTripCount]\n\t// The time this aggregation started and the number of bytes acked during it.\n\taggregationEpochStartTime monotime.Time\n\taggregationEpochBytes     congestion.ByteCount\n\t// The last sent packet number before the current aggregation epoch started.\n\tlastSentPacketNumberBeforeEpoch congestion.PacketNumber\n\t// The number of ack aggregation epochs ever started, including the ongoing\n\t// one. Stats only.\n\tnumAckAggregationEpochs                uint64\n\tackAggregationBandwidthThreshold       float64\n\tstartNewAggregationEpochAfterFullRound bool\n\treduceExtraAckedOnBandwidthIncrease    bool\n}\n\nfunc newMaxAckHeightTracker(windowLength roundTripCount) *maxAckHeightTracker {\n\treturn &maxAckHeightTracker{\n\t\tmaxAckHeightFilter:               NewWindowedFilter(windowLength, maxExtraAckedEventFunc),\n\t\tlastSentPacketNumberBeforeEpoch:  invalidPacketNumber,\n\t\tackAggregationBandwidthThreshold: 1.0,\n\t}\n}\n\nfunc (m *maxAckHeightTracker) Get() congestion.ByteCount {\n\treturn m.maxAckHeightFilter.GetBest().extraAcked\n}\n\nfunc (m *maxAckHeightTracker) Update(\n\tbandwidthEstimate Bandwidth,\n\tisNewMaxBandwidth bool,\n\troundTripCount roundTripCount,\n\tlastSentPacketNumber congestion.PacketNumber,\n\tlastAckedPacketNumber congestion.PacketNumber,\n\tackTime monotime.Time,\n\tbytesAcked congestion.ByteCount,\n) congestion.ByteCount {\n\tforceNewEpoch := false\n\n\tif m.reduceExtraAckedOnBandwidthIncrease && isNewMaxBandwidth {\n\t\t// Save and clear existing entries.\n\t\tbest := m.maxAckHeightFilter.GetBest()\n\t\tsecondBest := m.maxAckHeightFilter.GetSecondBest()\n\t\tthirdBest := m.maxAckHeightFilter.GetThirdBest()\n\t\tm.maxAckHeightFilter.Clear()\n\n\t\t// Reinsert the heights into the filter after recalculating.\n\t\texpectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, best.timeDelta)\n\t\tif expectedBytesAcked < best.bytesAcked {\n\t\t\tbest.extraAcked = best.bytesAcked - expectedBytesAcked\n\t\t\tm.maxAckHeightFilter.Update(best, best.round)\n\t\t}\n\t\texpectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, secondBest.timeDelta)\n\t\tif expectedBytesAcked < secondBest.bytesAcked {\n\t\t\tsecondBest.extraAcked = secondBest.bytesAcked - expectedBytesAcked\n\t\t\tm.maxAckHeightFilter.Update(secondBest, secondBest.round)\n\t\t}\n\t\texpectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, thirdBest.timeDelta)\n\t\tif expectedBytesAcked < thirdBest.bytesAcked {\n\t\t\tthirdBest.extraAcked = thirdBest.bytesAcked - expectedBytesAcked\n\t\t\tm.maxAckHeightFilter.Update(thirdBest, thirdBest.round)\n\t\t}\n\t}\n\n\t// If any packet sent after the start of the epoch has been acked, start a new\n\t// epoch.\n\tif m.startNewAggregationEpochAfterFullRound &&\n\t\tm.lastSentPacketNumberBeforeEpoch != invalidPacketNumber &&\n\t\tlastAckedPacketNumber != invalidPacketNumber &&\n\t\tlastAckedPacketNumber > m.lastSentPacketNumberBeforeEpoch {\n\t\tforceNewEpoch = true\n\t}\n\tif m.aggregationEpochStartTime.IsZero() || forceNewEpoch {\n\t\tm.aggregationEpochBytes = bytesAcked\n\t\tm.aggregationEpochStartTime = ackTime\n\t\tm.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber\n\t\tm.numAckAggregationEpochs++\n\t\treturn 0\n\t}\n\n\t// Compute how many bytes are expected to be delivered, assuming max bandwidth\n\t// is correct.\n\taggregationDelta := ackTime.Sub(m.aggregationEpochStartTime)\n\texpectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, aggregationDelta)\n\t// Reset the current aggregation epoch as soon as the ack arrival rate is less\n\t// than or equal to the max bandwidth.\n\tif m.aggregationEpochBytes <= congestion.ByteCount(m.ackAggregationBandwidthThreshold*float64(expectedBytesAcked)) {\n\t\t// Reset to start measuring a new aggregation epoch.\n\t\tm.aggregationEpochBytes = bytesAcked\n\t\tm.aggregationEpochStartTime = ackTime\n\t\tm.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber\n\t\tm.numAckAggregationEpochs++\n\t\treturn 0\n\t}\n\n\tm.aggregationEpochBytes += bytesAcked\n\n\t// Compute how many extra bytes were delivered vs max bandwidth.\n\textraBytesAcked := m.aggregationEpochBytes - expectedBytesAcked\n\tnewEvent := extraAckedEvent{\n\t\textraAcked: extraBytesAcked,\n\t\tbytesAcked: m.aggregationEpochBytes,\n\t\ttimeDelta:  aggregationDelta,\n\t}\n\tm.maxAckHeightFilter.Update(newEvent, roundTripCount)\n\treturn extraBytesAcked\n}\n\nfunc (m *maxAckHeightTracker) SetFilterWindowLength(length roundTripCount) {\n\tm.maxAckHeightFilter.SetWindowLength(length)\n}\n\nfunc (m *maxAckHeightTracker) Reset(newHeight congestion.ByteCount, newTime roundTripCount) {\n\tnewEvent := extraAckedEvent{\n\t\textraAcked: newHeight,\n\t\tround:      newTime,\n\t}\n\tm.maxAckHeightFilter.Reset(newEvent, newTime)\n}\n\nfunc (m *maxAckHeightTracker) SetAckAggregationBandwidthThreshold(threshold float64) {\n\tm.ackAggregationBandwidthThreshold = threshold\n}\n\nfunc (m *maxAckHeightTracker) SetStartNewAggregationEpochAfterFullRound(value bool) {\n\tm.startNewAggregationEpochAfterFullRound = value\n}\n\nfunc (m *maxAckHeightTracker) SetReduceExtraAckedOnBandwidthIncrease(value bool) {\n\tm.reduceExtraAckedOnBandwidthIncrease = value\n}\n\nfunc (m *maxAckHeightTracker) AckAggregationBandwidthThreshold() float64 {\n\treturn m.ackAggregationBandwidthThreshold\n}\n\nfunc (m *maxAckHeightTracker) NumAckAggregationEpochs() uint64 {\n\treturn m.numAckAggregationEpochs\n}\n\n// AckPoint represents a point on the ack line.\ntype ackPoint struct {\n\tackTime         monotime.Time\n\ttotalBytesAcked congestion.ByteCount\n}\n\n// RecentAckPoints maintains the most recent 2 ack points at distinct times.\ntype recentAckPoints struct {\n\tackPoints [2]ackPoint\n}\n\nfunc (r *recentAckPoints) Update(ackTime monotime.Time, totalBytesAcked congestion.ByteCount) {\n\tif ackTime.Before(r.ackPoints[1].ackTime) {\n\t\tr.ackPoints[1].ackTime = ackTime\n\t} else if ackTime.After(r.ackPoints[1].ackTime) {\n\t\tr.ackPoints[0] = r.ackPoints[1]\n\t\tr.ackPoints[1].ackTime = ackTime\n\t}\n\n\tr.ackPoints[1].totalBytesAcked = totalBytesAcked\n}\n\nfunc (r *recentAckPoints) Clear() {\n\tr.ackPoints[0] = ackPoint{}\n\tr.ackPoints[1] = ackPoint{}\n}\n\nfunc (r *recentAckPoints) MostRecentPoint() *ackPoint {\n\treturn &r.ackPoints[1]\n}\n\nfunc (r *recentAckPoints) LessRecentPoint() *ackPoint {\n\tif r.ackPoints[0].totalBytesAcked != 0 {\n\t\treturn &r.ackPoints[0]\n\t}\n\n\treturn &r.ackPoints[1]\n}\n\n// ConnectionStateOnSentPacket represents the information about a sent packet\n// and the state of the connection at the moment the packet was sent,\n// specifically the information about the most recently acknowledged packet at\n// that moment.\ntype connectionStateOnSentPacket struct {\n\t// Time at which the packet is sent.\n\tsentTime monotime.Time\n\t// Size of the packet.\n\tsize congestion.ByteCount\n\t// The value of |totalBytesSentAtLastAckedPacket| at the time the\n\t// packet was sent.\n\ttotalBytesSentAtLastAckedPacket congestion.ByteCount\n\t// The value of |lastAckedPacketSentTime| at the time the packet was\n\t// sent.\n\tlastAckedPacketSentTime monotime.Time\n\t// The value of |lastAckedPacketAckTime| at the time the packet was\n\t// sent.\n\tlastAckedPacketAckTime monotime.Time\n\t// Send time states that are returned to the congestion controller when the\n\t// packet is acked or lost.\n\tsendTimeState sendTimeState\n}\n\n// Snapshot constructor. Records the current state of the bandwidth\n// sampler.\n// |bytes_in_flight| is the bytes in flight right after the packet is sent.\nfunc newConnectionStateOnSentPacket(\n\tsentTime monotime.Time,\n\tsize congestion.ByteCount,\n\tbytesInFlight congestion.ByteCount,\n\tsampler *bandwidthSampler,\n) *connectionStateOnSentPacket {\n\treturn &connectionStateOnSentPacket{\n\t\tsentTime:                        sentTime,\n\t\tsize:                            size,\n\t\ttotalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket,\n\t\tlastAckedPacketSentTime:         sampler.lastAckedPacketSentTime,\n\t\tlastAckedPacketAckTime:          sampler.lastAckedPacketAckTime,\n\t\tsendTimeState: *newSendTimeState(\n\t\t\tsampler.isAppLimited,\n\t\t\tsampler.totalBytesSent,\n\t\t\tsampler.totalBytesAcked,\n\t\t\tsampler.totalBytesLost,\n\t\t\tbytesInFlight,\n\t\t),\n\t}\n}\n\n// BandwidthSampler keeps track of sent and acknowledged packets and outputs a\n// bandwidth sample for every packet acknowledged. The samples are taken for\n// individual packets, and are not filtered; the consumer has to filter the\n// bandwidth samples itself. In certain cases, the sampler will locally severely\n// underestimate the bandwidth, hence a maximum filter with a size of at least\n// one RTT is recommended.\n//\n// This class bases its samples on the slope of two curves: the number of bytes\n// sent over time, and the number of bytes acknowledged as received over time.\n// It produces a sample of both slopes for every packet that gets acknowledged,\n// based on a slope between two points on each of the corresponding curves. Note\n// that due to the packet loss, the number of bytes on each curve might get\n// further and further away from each other, meaning that it is not feasible to\n// compare byte values coming from different curves with each other.\n//\n// The obvious points for measuring slope sample are the ones corresponding to\n// the packet that was just acknowledged. Let us denote them as S_1 (point at\n// which the current packet was sent) and A_1 (point at which the current packet\n// was acknowledged). However, taking a slope requires two points on each line,\n// so estimating bandwidth requires picking a packet in the past with respect to\n// which the slope is measured.\n//\n// For that purpose, BandwidthSampler always keeps track of the most recently\n// acknowledged packet, and records it together with every outgoing packet.\n// When a packet gets acknowledged (A_1), it has not only information about when\n// it itself was sent (S_1), but also the information about the latest\n// acknowledged packet right before it was sent (S_0 and A_0).\n//\n// Based on that data, send and ack rate are estimated as:\n//\n//\tsend_rate = (bytes(S_1) - bytes(S_0)) / (time(S_1) - time(S_0))\n//\tack_rate = (bytes(A_1) - bytes(A_0)) / (time(A_1) - time(A_0))\n//\n// Here, the ack rate is intuitively the rate we want to treat as bandwidth.\n// However, in certain cases (e.g. ack compression) the ack rate at a point may\n// end up higher than the rate at which the data was originally sent, which is\n// not indicative of the real bandwidth. Hence, we use the send rate as an upper\n// bound, and the sample value is\n//\n//\trate_sample = min(send_rate, ack_rate)\n//\n// An important edge case handled by the sampler is tracking the app-limited\n// samples. There are multiple meaning of \"app-limited\" used interchangeably,\n// hence it is important to understand and to be able to distinguish between\n// them.\n//\n// Meaning 1: connection state. The connection is said to be app-limited when\n// there is no outstanding data to send. This means that certain bandwidth\n// samples in the future would not be an accurate indication of the link\n// capacity, and it is important to inform consumer about that. Whenever\n// connection becomes app-limited, the sampler is notified via OnAppLimited()\n// method.\n//\n// Meaning 2: a phase in the bandwidth sampler. As soon as the bandwidth\n// sampler becomes notified about the connection being app-limited, it enters\n// app-limited phase. In that phase, all *sent* packets are marked as\n// app-limited. Note that the connection itself does not have to be\n// app-limited during the app-limited phase, and in fact it will not be\n// (otherwise how would it send packets?). The boolean flag below indicates\n// whether the sampler is in that phase.\n//\n// Meaning 3: a flag on the sent packet and on the sample. If a sent packet is\n// sent during the app-limited phase, the resulting sample related to the\n// packet will be marked as app-limited.\n//\n// With the terminology issue out of the way, let us consider the question of\n// what kind of situation it addresses.\n//\n// Consider a scenario where we first send packets 1 to 20 at a regular\n// bandwidth, and then immediately run out of data. After a few seconds, we send\n// packets 21 to 60, and only receive ack for 21 between sending packets 40 and\n// 41. In this case, when we sample bandwidth for packets 21 to 40, the S_0/A_0\n// we use to compute the slope is going to be packet 20, a few seconds apart\n// from the current packet, hence the resulting estimate would be extremely low\n// and not indicative of anything. Only at packet 41 the S_0/A_0 will become 21,\n// meaning that the bandwidth sample would exclude the quiescence.\n//\n// Based on the analysis of that scenario, we implement the following rule: once\n// OnAppLimited() is called, all sent packets will produce app-limited samples\n// up until an ack for a packet that was sent after OnAppLimited() was called.\n// Note that while the scenario above is not the only scenario when the\n// connection is app-limited, the approach works in other cases too.\n\ntype congestionEventSample struct {\n\t// The maximum bandwidth sample from all acked packets.\n\t// QuicBandwidth::Zero() if no samples are available.\n\tsampleMaxBandwidth Bandwidth\n\t// Whether |sample_max_bandwidth| is from a app-limited sample.\n\tsampleIsAppLimited bool\n\t// The minimum rtt sample from all acked packets.\n\t// QuicTime::Delta::Infinite() if no samples are available.\n\tsampleRtt time.Duration\n\t// For each packet p in acked packets, this is the max value of INFLIGHT(p),\n\t// where INFLIGHT(p) is the number of bytes acked while p is inflight.\n\tsampleMaxInflight congestion.ByteCount\n\t// The send state of the largest packet in acked_packets, unless it is\n\t// empty. If acked_packets is empty, it's the send state of the largest\n\t// packet in lost_packets.\n\tlastPacketSendState sendTimeState\n\t// The number of extra bytes acked from this ack event, compared to what is\n\t// expected from the flow's bandwidth. Larger value means more ack\n\t// aggregation.\n\textraAcked congestion.ByteCount\n}\n\nfunc newCongestionEventSample() *congestionEventSample {\n\treturn &congestionEventSample{\n\t\tsampleRtt: infRTT,\n\t}\n}\n\ntype bandwidthSampler struct {\n\t// The total number of congestion controlled bytes sent during the connection.\n\ttotalBytesSent congestion.ByteCount\n\n\t// The total number of congestion controlled bytes which were acknowledged.\n\ttotalBytesAcked congestion.ByteCount\n\n\t// The total number of congestion controlled bytes which were lost.\n\ttotalBytesLost congestion.ByteCount\n\n\t// The total number of congestion controlled bytes which have been neutered.\n\ttotalBytesNeutered congestion.ByteCount\n\n\t// The value of |total_bytes_sent_| at the time the last acknowledged packet\n\t// was sent. Valid only when |last_acked_packet_sent_time_| is valid.\n\ttotalBytesSentAtLastAckedPacket congestion.ByteCount\n\n\t// The time at which the last acknowledged packet was sent. Set to\n\t// QuicTime::Zero() if no valid timestamp is available.\n\tlastAckedPacketSentTime monotime.Time\n\n\t// The time at which the most recent packet was acknowledged.\n\tlastAckedPacketAckTime monotime.Time\n\n\t// The most recently sent packet.\n\tlastSentPacket congestion.PacketNumber\n\n\t// The most recently acked packet.\n\tlastAckedPacket congestion.PacketNumber\n\n\t// Indicates whether the bandwidth sampler is currently in an app-limited\n\t// phase.\n\tisAppLimited bool\n\n\t// The packet that will be acknowledged after this one will cause the sampler\n\t// to exit the app-limited phase.\n\tendOfAppLimitedPhase congestion.PacketNumber\n\n\t// Record of the connection state at the point where each packet in flight was\n\t// sent, indexed by the packet number.\n\tconnectionStateMap *packetNumberIndexedQueue[connectionStateOnSentPacket]\n\n\trecentAckPoints recentAckPoints\n\ta0Candidates    RingBuffer[ackPoint]\n\n\t// Maximum number of tracked packets.\n\tmaxTrackedPackets congestion.ByteCount\n\n\tmaxAckHeightTracker              *maxAckHeightTracker\n\ttotalBytesAckedAfterLastAckEvent congestion.ByteCount\n\n\t// True if connection option 'BSAO' is set.\n\toverestimateAvoidance bool\n\n\t// True if connection option 'BBRB' is set.\n\tlimitMaxAckHeightTrackerBySendRate bool\n}\n\nfunc newBandwidthSampler(maxAckHeightTrackerWindowLength roundTripCount) *bandwidthSampler {\n\tb := &bandwidthSampler{\n\t\tmaxAckHeightTracker:  newMaxAckHeightTracker(maxAckHeightTrackerWindowLength),\n\t\tconnectionStateMap:   newPacketNumberIndexedQueue[connectionStateOnSentPacket](defaultConnectionStateMapQueueSize),\n\t\tlastSentPacket:       invalidPacketNumber,\n\t\tlastAckedPacket:      invalidPacketNumber,\n\t\tendOfAppLimitedPhase: invalidPacketNumber,\n\t}\n\n\tb.a0Candidates.Init(defaultCandidatesBufferSize)\n\n\treturn b\n}\n\nfunc (b *bandwidthSampler) MaxAckHeight() congestion.ByteCount {\n\treturn b.maxAckHeightTracker.Get()\n}\n\nfunc (b *bandwidthSampler) NumAckAggregationEpochs() uint64 {\n\treturn b.maxAckHeightTracker.NumAckAggregationEpochs()\n}\n\nfunc (b *bandwidthSampler) SetMaxAckHeightTrackerWindowLength(length roundTripCount) {\n\tb.maxAckHeightTracker.SetFilterWindowLength(length)\n}\n\nfunc (b *bandwidthSampler) ResetMaxAckHeightTracker(newHeight congestion.ByteCount, newTime roundTripCount) {\n\tb.maxAckHeightTracker.Reset(newHeight, newTime)\n}\n\nfunc (b *bandwidthSampler) SetStartNewAggregationEpochAfterFullRound(value bool) {\n\tb.maxAckHeightTracker.SetStartNewAggregationEpochAfterFullRound(value)\n}\n\nfunc (b *bandwidthSampler) SetLimitMaxAckHeightTrackerBySendRate(value bool) {\n\tb.limitMaxAckHeightTrackerBySendRate = value\n}\n\nfunc (b *bandwidthSampler) SetReduceExtraAckedOnBandwidthIncrease(value bool) {\n\tb.maxAckHeightTracker.SetReduceExtraAckedOnBandwidthIncrease(value)\n}\n\nfunc (b *bandwidthSampler) EnableOverestimateAvoidance() {\n\tif b.overestimateAvoidance {\n\t\treturn\n\t}\n\n\tb.overestimateAvoidance = true\n\tb.maxAckHeightTracker.SetAckAggregationBandwidthThreshold(2.0)\n}\n\nfunc (b *bandwidthSampler) IsOverestimateAvoidanceEnabled() bool {\n\treturn b.overestimateAvoidance\n}\n\nfunc (b *bandwidthSampler) OnPacketSent(\n\tsentTime monotime.Time,\n\tpacketNumber congestion.PacketNumber,\n\tbytes congestion.ByteCount,\n\tbytesInFlight congestion.ByteCount,\n\tisRetransmittable bool,\n) {\n\tb.lastSentPacket = packetNumber\n\n\tif !isRetransmittable {\n\t\treturn\n\t}\n\n\tb.totalBytesSent += bytes\n\n\t// If there are no packets in flight, the time at which the new transmission\n\t// opens can be treated as the A_0 point for the purpose of bandwidth\n\t// sampling. This underestimates bandwidth to some extent, and produces some\n\t// artificially low samples for most packets in flight, but it provides with\n\t// samples at important points where we would not have them otherwise, most\n\t// importantly at the beginning of the connection.\n\tif bytesInFlight == 0 {\n\t\tb.lastAckedPacketAckTime = sentTime\n\t\tif b.overestimateAvoidance {\n\t\t\tb.recentAckPoints.Clear()\n\t\t\tb.recentAckPoints.Update(sentTime, b.totalBytesAcked)\n\t\t\tb.a0Candidates.Clear()\n\t\t\tb.a0Candidates.PushBack(*b.recentAckPoints.MostRecentPoint())\n\t\t}\n\t\tb.totalBytesSentAtLastAckedPacket = b.totalBytesSent\n\n\t\t// In this situation ack compression is not a concern, set send rate to\n\t\t// effectively infinite.\n\t\tb.lastAckedPacketSentTime = sentTime\n\t}\n\n\tb.connectionStateMap.Emplace(packetNumber, newConnectionStateOnSentPacket(\n\t\tsentTime,\n\t\tbytes,\n\t\tbytesInFlight+bytes,\n\t\tb,\n\t))\n}\n\nfunc (b *bandwidthSampler) OnCongestionEvent(\n\tackTime monotime.Time,\n\tackedPackets []congestion.AckedPacketInfo,\n\tlostPackets []congestion.LostPacketInfo,\n\tmaxBandwidth Bandwidth,\n\testBandwidthUpperBound Bandwidth,\n\troundTripCount roundTripCount,\n) congestionEventSample {\n\teventSample := newCongestionEventSample()\n\n\tvar lastLostPacketSendState sendTimeState\n\n\tfor _, p := range lostPackets {\n\t\tsendState := b.OnPacketLost(p.PacketNumber, p.BytesLost)\n\t\tif sendState.isValid {\n\t\t\tlastLostPacketSendState = sendState\n\t\t}\n\t}\n\n\tif len(ackedPackets) == 0 {\n\t\t// Only populate send state for a loss-only event.\n\t\teventSample.lastPacketSendState = lastLostPacketSendState\n\t\treturn *eventSample\n\t}\n\n\tvar lastAckedPacketSendState sendTimeState\n\tvar maxSendRate Bandwidth\n\n\tfor _, p := range ackedPackets {\n\t\tsample := b.onPacketAcknowledged(ackTime, p.PacketNumber)\n\t\tif !sample.stateAtSend.isValid {\n\t\t\tcontinue\n\t\t}\n\n\t\tlastAckedPacketSendState = sample.stateAtSend\n\n\t\tif sample.rtt != 0 {\n\t\t\teventSample.sampleRtt = min(eventSample.sampleRtt, sample.rtt)\n\t\t}\n\t\tif sample.bandwidth > eventSample.sampleMaxBandwidth {\n\t\t\teventSample.sampleMaxBandwidth = sample.bandwidth\n\t\t\teventSample.sampleIsAppLimited = sample.stateAtSend.isAppLimited\n\t\t}\n\t\tif sample.sendRate != infBandwidth {\n\t\t\tmaxSendRate = max(maxSendRate, sample.sendRate)\n\t\t}\n\t\tinflightSample := b.totalBytesAcked - lastAckedPacketSendState.totalBytesAcked\n\t\tif inflightSample > eventSample.sampleMaxInflight {\n\t\t\teventSample.sampleMaxInflight = inflightSample\n\t\t}\n\t}\n\n\tif !lastLostPacketSendState.isValid {\n\t\teventSample.lastPacketSendState = lastAckedPacketSendState\n\t} else if !lastAckedPacketSendState.isValid {\n\t\teventSample.lastPacketSendState = lastLostPacketSendState\n\t} else {\n\t\t// If two packets are inflight and an alarm is armed to lose a packet and it\n\t\t// wakes up late, then the first of two in flight packets could have been\n\t\t// acknowledged before the wakeup, which re-evaluates loss detection, and\n\t\t// could declare the later of the two lost.\n\t\tif lostPackets[len(lostPackets)-1].PacketNumber > ackedPackets[len(ackedPackets)-1].PacketNumber {\n\t\t\teventSample.lastPacketSendState = lastLostPacketSendState\n\t\t} else {\n\t\t\teventSample.lastPacketSendState = lastAckedPacketSendState\n\t\t}\n\t}\n\n\tisNewMaxBandwidth := eventSample.sampleMaxBandwidth > maxBandwidth\n\tmaxBandwidth = max(maxBandwidth, eventSample.sampleMaxBandwidth)\n\tif b.limitMaxAckHeightTrackerBySendRate {\n\t\tmaxBandwidth = max(maxBandwidth, maxSendRate)\n\t}\n\n\teventSample.extraAcked = b.onAckEventEnd(min(estBandwidthUpperBound, maxBandwidth), isNewMaxBandwidth, roundTripCount)\n\n\treturn *eventSample\n}\n\nfunc (b *bandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber, bytesLost congestion.ByteCount) (s sendTimeState) {\n\tb.totalBytesLost += bytesLost\n\tif sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber); sentPacketPointer != nil {\n\t\tsentPacketToSendTimeState(sentPacketPointer, &s)\n\t}\n\treturn s\n}\n\nfunc (b *bandwidthSampler) OnPacketNeutered(packetNumber congestion.PacketNumber) {\n\tb.connectionStateMap.Remove(packetNumber, func(sentPacket connectionStateOnSentPacket) {\n\t\tb.totalBytesNeutered += sentPacket.size\n\t})\n}\n\nfunc (b *bandwidthSampler) OnAppLimited() {\n\tb.isAppLimited = true\n\tb.endOfAppLimitedPhase = b.lastSentPacket\n}\n\nfunc (b *bandwidthSampler) RemoveObsoletePackets(leastUnacked congestion.PacketNumber) {\n\t// A packet can become obsolete when it is removed from QuicUnackedPacketMap's\n\t// view of inflight before it is acked or marked as lost. For example, when\n\t// QuicSentPacketManager::RetransmitCryptoPackets retransmits a crypto packet,\n\t// the packet is removed from QuicUnackedPacketMap's inflight, but is not\n\t// marked as acked or lost in the BandwidthSampler.\n\tb.connectionStateMap.RemoveUpTo(leastUnacked)\n}\n\nfunc (b *bandwidthSampler) TotalBytesSent() congestion.ByteCount {\n\treturn b.totalBytesSent\n}\n\nfunc (b *bandwidthSampler) TotalBytesLost() congestion.ByteCount {\n\treturn b.totalBytesLost\n}\n\nfunc (b *bandwidthSampler) TotalBytesAcked() congestion.ByteCount {\n\treturn b.totalBytesAcked\n}\n\nfunc (b *bandwidthSampler) TotalBytesNeutered() congestion.ByteCount {\n\treturn b.totalBytesNeutered\n}\n\nfunc (b *bandwidthSampler) IsAppLimited() bool {\n\treturn b.isAppLimited\n}\n\nfunc (b *bandwidthSampler) EndOfAppLimitedPhase() congestion.PacketNumber {\n\treturn b.endOfAppLimitedPhase\n}\n\nfunc (b *bandwidthSampler) max_ack_height() congestion.ByteCount {\n\treturn b.maxAckHeightTracker.Get()\n}\n\nfunc (b *bandwidthSampler) chooseA0Point(totalBytesAcked congestion.ByteCount, a0 *ackPoint) bool {\n\tif b.a0Candidates.Empty() {\n\t\treturn false\n\t}\n\n\tif b.a0Candidates.Len() == 1 {\n\t\t*a0 = *b.a0Candidates.Front()\n\t\treturn true\n\t}\n\n\tfor i := 1; i < b.a0Candidates.Len(); i++ {\n\t\tif b.a0Candidates.Offset(i).totalBytesAcked > totalBytesAcked {\n\t\t\t*a0 = *b.a0Candidates.Offset(i - 1)\n\t\t\tif i > 1 {\n\t\t\t\tfor j := 0; j < i-1; j++ {\n\t\t\t\t\tb.a0Candidates.PopFront()\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\n\t*a0 = *b.a0Candidates.Back()\n\tfor k := 0; k < b.a0Candidates.Len()-1; k++ {\n\t\tb.a0Candidates.PopFront()\n\t}\n\treturn true\n}\n\nfunc (b *bandwidthSampler) onPacketAcknowledged(ackTime monotime.Time, packetNumber congestion.PacketNumber) bandwidthSample {\n\tsample := newBandwidthSample()\n\tb.lastAckedPacket = packetNumber\n\tsentPacketPointer := b.connectionStateMap.GetEntry(packetNumber)\n\tif sentPacketPointer == nil {\n\t\treturn *sample\n\t}\n\n\t// OnPacketAcknowledgedInner\n\tb.totalBytesAcked += sentPacketPointer.size\n\tb.totalBytesSentAtLastAckedPacket = sentPacketPointer.sendTimeState.totalBytesSent\n\tb.lastAckedPacketSentTime = sentPacketPointer.sentTime\n\tb.lastAckedPacketAckTime = ackTime\n\tif b.overestimateAvoidance {\n\t\tb.recentAckPoints.Update(ackTime, b.totalBytesAcked)\n\t}\n\n\tif b.isAppLimited {\n\t\t// Exit app-limited phase in two cases:\n\t\t// (1) end_of_app_limited_phase_ is not initialized, i.e., so far all\n\t\t// packets are sent while there are buffered packets or pending data.\n\t\t// (2) The current acked packet is after the sent packet marked as the end\n\t\t// of the app limit phase.\n\t\tif b.endOfAppLimitedPhase == invalidPacketNumber ||\n\t\t\tpacketNumber > b.endOfAppLimitedPhase {\n\t\t\tb.isAppLimited = false\n\t\t}\n\t}\n\n\t// There might have been no packets acknowledged at the moment when the\n\t// current packet was sent. In that case, there is no bandwidth sample to\n\t// make.\n\tif sentPacketPointer.lastAckedPacketSentTime.IsZero() {\n\t\treturn *sample\n\t}\n\n\t// Infinite rate indicates that the sampler is supposed to discard the\n\t// current send rate sample and use only the ack rate.\n\tsendRate := infBandwidth\n\tif sentPacketPointer.sentTime.After(sentPacketPointer.lastAckedPacketSentTime) {\n\t\tsendRate = BandwidthFromDelta(\n\t\t\tsentPacketPointer.sendTimeState.totalBytesSent-sentPacketPointer.totalBytesSentAtLastAckedPacket,\n\t\t\tsentPacketPointer.sentTime.Sub(sentPacketPointer.lastAckedPacketSentTime))\n\t}\n\n\tvar a0 ackPoint\n\tif b.overestimateAvoidance && b.chooseA0Point(sentPacketPointer.sendTimeState.totalBytesAcked, &a0) {\n\t} else {\n\t\ta0.ackTime = sentPacketPointer.lastAckedPacketAckTime\n\t\ta0.totalBytesAcked = sentPacketPointer.sendTimeState.totalBytesAcked\n\t}\n\n\t// During the slope calculation, ensure that ack time of the current packet is\n\t// always larger than the time of the previous packet, otherwise division by\n\t// zero or integer underflow can occur.\n\tif ackTime.Sub(a0.ackTime) <= 0 {\n\t\treturn *sample\n\t}\n\n\tackRate := BandwidthFromDelta(b.totalBytesAcked-a0.totalBytesAcked, ackTime.Sub(a0.ackTime))\n\n\tsample.bandwidth = min(sendRate, ackRate)\n\t// Note: this sample does not account for delayed acknowledgement time.  This\n\t// means that the RTT measurements here can be artificially high, especially\n\t// on low bandwidth connections.\n\tsample.rtt = ackTime.Sub(sentPacketPointer.sentTime)\n\tsample.sendRate = sendRate\n\tsentPacketToSendTimeState(sentPacketPointer, &sample.stateAtSend)\n\n\treturn *sample\n}\n\nfunc (b *bandwidthSampler) onAckEventEnd(\n\tbandwidthEstimate Bandwidth,\n\tisNewMaxBandwidth bool,\n\troundTripCount roundTripCount,\n) congestion.ByteCount {\n\tnewlyAckedBytes := b.totalBytesAcked - b.totalBytesAckedAfterLastAckEvent\n\tif newlyAckedBytes == 0 {\n\t\treturn 0\n\t}\n\tb.totalBytesAckedAfterLastAckEvent = b.totalBytesAcked\n\textraAcked := b.maxAckHeightTracker.Update(\n\t\tbandwidthEstimate,\n\t\tisNewMaxBandwidth,\n\t\troundTripCount,\n\t\tb.lastSentPacket,\n\t\tb.lastAckedPacket,\n\t\tb.lastAckedPacketAckTime,\n\t\tnewlyAckedBytes)\n\t// If |extra_acked| is zero, i.e. this ack event marks the start of a new ack\n\t// aggregation epoch, save LessRecentPoint, which is the last ack point of the\n\t// previous epoch, as a A0 candidate.\n\tif b.overestimateAvoidance && extraAcked == 0 {\n\t\tb.a0Candidates.PushBack(*b.recentAckPoints.LessRecentPoint())\n\t}\n\treturn extraAcked\n}\n\nfunc sentPacketToSendTimeState(sentPacket *connectionStateOnSentPacket, sendTimeState *sendTimeState) {\n\t*sendTimeState = sentPacket.sendTimeState\n\tsendTimeState.isValid = true\n}\n\n// BytesFromBandwidthAndTimeDelta calculates the bytes\n// from a bandwidth(bits per second) and a time delta\nfunc bytesFromBandwidthAndTimeDelta(bandwidth Bandwidth, delta time.Duration) congestion.ByteCount {\n\treturn (congestion.ByteCount(bandwidth) * congestion.ByteCount(delta)) /\n\t\t(congestion.ByteCount(time.Second) * 8)\n}\n\nfunc timeDeltaFromBytesAndBandwidth(bytes congestion.ByteCount, bandwidth Bandwidth) time.Duration {\n\treturn time.Duration(bytes*8) * time.Second / time.Duration(bandwidth)\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/bbr_sender.go",
    "content": "package bbr\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go/congestion\"\n\t\"github.com/apernet/quic-go/monotime\"\n\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion/common\"\n)\n\n// BbrSender implements BBR congestion control algorithm.  BBR aims to estimate\n// the current available Bottleneck Bandwidth and RTT (hence the name), and\n// regulates the pacing rate and the size of the congestion window based on\n// those signals.\n//\n// BBR relies on pacing in order to function properly.  Do not use BBR when\n// pacing is disabled.\n//\n\nconst (\n\tminBps = 65536 // 64 KB/s\n\n\tinvalidPacketNumber            = -1\n\tinitialCongestionWindowPackets = 32\n\n\t// Constants based on TCP defaults.\n\t// The minimum CWND to ensure delayed acks don't reduce bandwidth measurements.\n\t// Does not inflate the pacing rate.\n\tdefaultMinimumCongestionWindow = 4 * congestion.ByteCount(congestion.InitialPacketSize)\n\n\t// The gain used for the STARTUP, equal to 2/ln(2).\n\tdefaultHighGain = 2.885\n\t// The newly derived gain for STARTUP, equal to 4 * ln(2)\n\tderivedHighGain = 2.773\n\t// The newly derived CWND gain for STARTUP, 2.\n\tderivedHighCWNDGain = 2.0\n\n\tdebugEnv = \"HYSTERIA_BBR_DEBUG\"\n)\n\n// The cycle of gains used during the PROBE_BW stage.\nvar pacingGain = [...]float64{1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}\n\nconst (\n\t// The length of the gain cycle.\n\tgainCycleLength = len(pacingGain)\n\t// The size of the bandwidth filter window, in round-trips.\n\tbandwidthWindowSize = gainCycleLength + 2\n\n\t// The time after which the current min_rtt value expires.\n\tminRttExpiry = 10 * time.Second\n\t// The minimum time the connection can spend in PROBE_RTT mode.\n\tprobeRttTime = 200 * time.Millisecond\n\t// If the bandwidth does not increase by the factor of |kStartupGrowthTarget|\n\t// within |kRoundTripsWithoutGrowthBeforeExitingStartup| rounds, the connection\n\t// will exit the STARTUP mode.\n\tstartupGrowthTarget                         = 1.25\n\troundTripsWithoutGrowthBeforeExitingStartup = int64(3)\n\n\t// Flag.\n\tdefaultStartupFullLossCount  = 8\n\tquicBbr2DefaultLossThreshold = 0.02\n\tmaxBbrBurstPackets           = 10\n)\n\ntype bbrMode int\n\nconst (\n\t// Startup phase of the connection.\n\tbbrModeStartup = iota\n\t// After achieving the highest possible bandwidth during the startup, lower\n\t// the pacing rate in order to drain the queue.\n\tbbrModeDrain\n\t// Cruising mode.\n\tbbrModeProbeBw\n\t// Temporarily slow down sending in order to empty the buffer and measure\n\t// the real minimum RTT.\n\tbbrModeProbeRtt\n)\n\n// Indicates how the congestion control limits the amount of bytes in flight.\ntype bbrRecoveryState int\n\nconst (\n\t// Do not limit.\n\tbbrRecoveryStateNotInRecovery = iota\n\t// Allow an extra outstanding byte for each byte acknowledged.\n\tbbrRecoveryStateConservation\n\t// Allow two extra outstanding bytes for each byte acknowledged (slow\n\t// start).\n\tbbrRecoveryStateGrowth\n)\n\ntype bbrSender struct {\n\trttStats congestion.RTTStatsProvider\n\tclock    Clock\n\tpacer    *common.Pacer\n\n\tmode bbrMode\n\n\t// Bandwidth sampler provides BBR with the bandwidth measurements at\n\t// individual points.\n\tsampler *bandwidthSampler\n\n\t// The number of the round trips that have occurred during the connection.\n\troundTripCount roundTripCount\n\n\t// The packet number of the most recently sent packet.\n\tlastSentPacket congestion.PacketNumber\n\t// Acknowledgement of any packet after |current_round_trip_end_| will cause\n\t// the round trip counter to advance.\n\tcurrentRoundTripEnd congestion.PacketNumber\n\n\t// Number of congestion events with some losses, in the current round.\n\tnumLossEventsInRound uint64\n\n\t// Number of total bytes lost in the current round.\n\tbytesLostInRound congestion.ByteCount\n\n\t// The filter that tracks the maximum bandwidth over the multiple recent\n\t// round-trips.\n\tmaxBandwidth *WindowedFilter[Bandwidth, roundTripCount]\n\n\t// Minimum RTT estimate.  Automatically expires within 10 seconds (and\n\t// triggers PROBE_RTT mode) if no new value is sampled during that period.\n\tminRtt time.Duration\n\t// The time at which the current value of |min_rtt_| was assigned.\n\tminRttTimestamp monotime.Time\n\n\t// The maximum allowed number of bytes in flight.\n\tcongestionWindow congestion.ByteCount\n\n\t// The initial value of the |congestion_window_|.\n\tinitialCongestionWindow congestion.ByteCount\n\n\t// The largest value the |congestion_window_| can achieve.\n\tmaxCongestionWindow congestion.ByteCount\n\n\t// The smallest value the |congestion_window_| can achieve.\n\tminCongestionWindow congestion.ByteCount\n\n\t// The pacing gain applied during the STARTUP phase.\n\thighGain float64\n\n\t// The CWND gain applied during the STARTUP phase.\n\thighCwndGain float64\n\n\t// The pacing gain applied during the DRAIN phase.\n\tdrainGain float64\n\n\t// The current pacing rate of the connection.\n\tpacingRate Bandwidth\n\n\t// The gain currently applied to the pacing rate.\n\tpacingGain float64\n\t// The gain currently applied to the congestion window.\n\tcongestionWindowGain float64\n\n\t// The gain used for the congestion window during PROBE_BW.  Latched from\n\t// quic_bbr_cwnd_gain flag.\n\tcongestionWindowGainConstant float64\n\t// The number of RTTs to stay in STARTUP mode.  Defaults to 3.\n\tnumStartupRtts int64\n\n\t// Number of round-trips in PROBE_BW mode, used for determining the current\n\t// pacing gain cycle.\n\tcycleCurrentOffset int\n\t// The time at which the last pacing gain cycle was started.\n\tlastCycleStart monotime.Time\n\n\t// Indicates whether the connection has reached the full bandwidth mode.\n\tisAtFullBandwidth bool\n\t// Number of rounds during which there was no significant bandwidth increase.\n\troundsWithoutBandwidthGain int64\n\t// The bandwidth compared to which the increase is measured.\n\tbandwidthAtLastRound Bandwidth\n\n\t// Set to true upon exiting quiescence.\n\texitingQuiescence bool\n\n\t// Time at which PROBE_RTT has to be exited.  Setting it to zero indicates\n\t// that the time is yet unknown as the number of packets in flight has not\n\t// reached the required value.\n\texitProbeRttAt monotime.Time\n\t// Indicates whether a round-trip has passed since PROBE_RTT became active.\n\tprobeRttRoundPassed bool\n\n\t// Indicates whether the most recent bandwidth sample was marked as\n\t// app-limited.\n\tlastSampleIsAppLimited bool\n\t// Indicates whether any non app-limited samples have been recorded.\n\thasNoAppLimitedSample bool\n\n\t// Current state of recovery.\n\trecoveryState bbrRecoveryState\n\t// Receiving acknowledgement of a packet after |end_recovery_at_| will cause\n\t// BBR to exit the recovery mode.  A value above zero indicates at least one\n\t// loss has been detected, so it must not be set back to zero.\n\tendRecoveryAt congestion.PacketNumber\n\t// A window used to limit the number of bytes in flight during loss recovery.\n\trecoveryWindow congestion.ByteCount\n\t// If true, consider all samples in recovery app-limited.\n\tisAppLimitedRecovery bool // not used\n\n\t// When true, pace at 1.5x and disable packet conservation in STARTUP.\n\tslowerStartup bool // not used\n\t// When true, disables packet conservation in STARTUP.\n\trateBasedStartup bool // not used\n\n\t// When true, add the most recent ack aggregation measurement during STARTUP.\n\tenableAckAggregationDuringStartup bool\n\t// When true, expire the windowed ack aggregation values in STARTUP when\n\t// bandwidth increases more than 25%.\n\texpireAckAggregationInStartup bool\n\n\t// If true, will not exit low gain mode until bytes_in_flight drops below BDP\n\t// or it's time for high gain mode.\n\tdrainToTarget bool\n\n\t// If true, slow down pacing rate in STARTUP when overshooting is detected.\n\tdetectOvershooting bool\n\t// Bytes lost while detect_overshooting_ is true.\n\tbytesLostWhileDetectingOvershooting congestion.ByteCount\n\t// Slow down pacing rate if\n\t// bytes_lost_while_detecting_overshooting_ *\n\t// bytes_lost_multiplier_while_detecting_overshooting_ > IW.\n\tbytesLostMultiplierWhileDetectingOvershooting uint8\n\t// When overshooting is detected, do not drop pacing_rate_ below this value /\n\t// min_rtt.\n\tcwndToCalculateMinPacingRate congestion.ByteCount\n\n\t// Max congestion window when adjusting network parameters.\n\tmaxCongestionWindowWithNetworkParametersAdjusted congestion.ByteCount // not used\n\n\t// Params.\n\tmaxDatagramSize congestion.ByteCount\n\t// Recorded on packet sent. equivalent |unacked_packets_->bytes_in_flight()|\n\tbytesInFlight congestion.ByteCount\n\n\tdebug bool\n}\n\nvar _ congestion.CongestionControl = &bbrSender{}\n\nfunc NewBbrSender(\n\tclock Clock,\n\tinitialMaxDatagramSize congestion.ByteCount,\n) *bbrSender {\n\treturn newBbrSender(\n\t\tclock,\n\t\tinitialMaxDatagramSize,\n\t\tinitialCongestionWindowPackets*initialMaxDatagramSize,\n\t\tcongestion.MaxCongestionWindowPackets*initialMaxDatagramSize,\n\t)\n}\n\nfunc newBbrSender(\n\tclock Clock,\n\tinitialMaxDatagramSize,\n\tinitialCongestionWindow,\n\tinitialMaxCongestionWindow congestion.ByteCount,\n) *bbrSender {\n\tdebug, _ := strconv.ParseBool(os.Getenv(debugEnv))\n\tb := &bbrSender{\n\t\tclock:                        clock,\n\t\tmode:                         bbrModeStartup,\n\t\tsampler:                      newBandwidthSampler(roundTripCount(bandwidthWindowSize)),\n\t\tlastSentPacket:               invalidPacketNumber,\n\t\tcurrentRoundTripEnd:          invalidPacketNumber,\n\t\tmaxBandwidth:                 NewWindowedFilter(roundTripCount(bandwidthWindowSize), MaxFilter[Bandwidth]),\n\t\tcongestionWindow:             initialCongestionWindow,\n\t\tinitialCongestionWindow:      initialCongestionWindow,\n\t\tmaxCongestionWindow:          initialMaxCongestionWindow,\n\t\tminCongestionWindow:          defaultMinimumCongestionWindow,\n\t\thighGain:                     defaultHighGain,\n\t\thighCwndGain:                 defaultHighGain,\n\t\tdrainGain:                    1.0 / defaultHighGain,\n\t\tpacingGain:                   1.0,\n\t\tcongestionWindowGain:         1.0,\n\t\tcongestionWindowGainConstant: 2.0,\n\t\tnumStartupRtts:               roundTripsWithoutGrowthBeforeExitingStartup,\n\t\trecoveryState:                bbrRecoveryStateNotInRecovery,\n\t\tendRecoveryAt:                invalidPacketNumber,\n\t\trecoveryWindow:               initialMaxCongestionWindow,\n\t\tbytesLostMultiplierWhileDetectingOvershooting:    2,\n\t\tcwndToCalculateMinPacingRate:                     initialCongestionWindow,\n\t\tmaxCongestionWindowWithNetworkParametersAdjusted: initialMaxCongestionWindow,\n\t\tmaxDatagramSize: initialMaxDatagramSize,\n\t\tdebug:           debug,\n\t}\n\tb.pacer = common.NewPacer(b.bandwidthForPacer)\n\n\t/*\n\t\tif b.tracer != nil {\n\t\t\tb.lastState = logging.CongestionStateStartup\n\t\t\tb.tracer.UpdatedCongestionState(logging.CongestionStateStartup)\n\t\t}\n\t*/\n\n\tb.enterStartupMode(b.clock.Now())\n\tb.setHighCwndGain(derivedHighCWNDGain)\n\n\treturn b\n}\n\nfunc (b *bbrSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) {\n\tb.rttStats = provider\n}\n\n// TimeUntilSend implements the SendAlgorithm interface.\nfunc (b *bbrSender) TimeUntilSend(bytesInFlight congestion.ByteCount) monotime.Time {\n\treturn b.pacer.TimeUntilSend()\n}\n\n// HasPacingBudget implements the SendAlgorithm interface.\nfunc (b *bbrSender) HasPacingBudget(now monotime.Time) bool {\n\treturn b.pacer.Budget(now) >= b.maxDatagramSize\n}\n\n// OnPacketSent implements the SendAlgorithm interface.\nfunc (b *bbrSender) OnPacketSent(\n\tsentTime monotime.Time,\n\tbytesInFlight congestion.ByteCount,\n\tpacketNumber congestion.PacketNumber,\n\tbytes congestion.ByteCount,\n\tisRetransmittable bool,\n) {\n\tb.pacer.SentPacket(sentTime, bytes)\n\n\tb.lastSentPacket = packetNumber\n\tb.bytesInFlight = bytesInFlight\n\n\tif bytesInFlight == 0 {\n\t\tb.exitingQuiescence = true\n\t}\n\n\tb.sampler.OnPacketSent(sentTime, packetNumber, bytes, bytesInFlight, isRetransmittable)\n}\n\n// CanSend implements the SendAlgorithm interface.\nfunc (b *bbrSender) CanSend(bytesInFlight congestion.ByteCount) bool {\n\treturn bytesInFlight < b.GetCongestionWindow()\n}\n\n// MaybeExitSlowStart implements the SendAlgorithm interface.\nfunc (b *bbrSender) MaybeExitSlowStart() {\n\t// Do nothing\n}\n\n// OnPacketAcked implements the SendAlgorithm interface.\nfunc (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes, priorInFlight congestion.ByteCount, eventTime monotime.Time) {\n\t// Do nothing.\n}\n\n// OnPacketLost implements the SendAlgorithm interface.\nfunc (b *bbrSender) OnPacketLost(number congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) {\n\t// Do nothing.\n}\n\n// OnRetransmissionTimeout implements the SendAlgorithm interface.\nfunc (b *bbrSender) OnRetransmissionTimeout(packetsRetransmitted bool) {\n\t// Do nothing.\n}\n\n// SetMaxDatagramSize implements the SendAlgorithm interface.\nfunc (b *bbrSender) SetMaxDatagramSize(s congestion.ByteCount) {\n\tif s < b.maxDatagramSize {\n\t\tpanic(fmt.Sprintf(\"congestion BUG: decreased max datagram size from %d to %d\", b.maxDatagramSize, s))\n\t}\n\tcwndIsMinCwnd := b.congestionWindow == b.minCongestionWindow\n\tb.maxDatagramSize = s\n\tif cwndIsMinCwnd {\n\t\tb.congestionWindow = b.minCongestionWindow\n\t}\n\tb.pacer.SetMaxDatagramSize(s)\n}\n\n// InSlowStart implements the SendAlgorithmWithDebugInfos interface.\nfunc (b *bbrSender) InSlowStart() bool {\n\treturn b.mode == bbrModeStartup\n}\n\n// InRecovery implements the SendAlgorithmWithDebugInfos interface.\nfunc (b *bbrSender) InRecovery() bool {\n\treturn b.recoveryState != bbrRecoveryStateNotInRecovery\n}\n\n// GetCongestionWindow implements the SendAlgorithmWithDebugInfos interface.\nfunc (b *bbrSender) GetCongestionWindow() congestion.ByteCount {\n\tif b.mode == bbrModeProbeRtt {\n\t\treturn b.probeRttCongestionWindow()\n\t}\n\n\tif b.InRecovery() {\n\t\treturn min(b.congestionWindow, b.recoveryWindow)\n\t}\n\n\treturn b.congestionWindow\n}\n\nfunc (b *bbrSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) {\n\t// Do nothing.\n}\n\nfunc (b *bbrSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime monotime.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) {\n\ttotalBytesAckedBefore := b.sampler.TotalBytesAcked()\n\ttotalBytesLostBefore := b.sampler.TotalBytesLost()\n\n\tvar isRoundStart, minRttExpired bool\n\tvar excessAcked, bytesLost congestion.ByteCount\n\n\t// The send state of the largest packet in acked_packets, unless it is\n\t// empty. If acked_packets is empty, it's the send state of the largest\n\t// packet in lost_packets.\n\tvar lastPacketSendState sendTimeState\n\n\tb.maybeAppLimited(priorInFlight)\n\n\t// Update bytesInFlight\n\tb.bytesInFlight = priorInFlight\n\tfor _, p := range ackedPackets {\n\t\tb.bytesInFlight -= p.BytesAcked\n\t}\n\tfor _, p := range lostPackets {\n\t\tb.bytesInFlight -= p.BytesLost\n\t}\n\n\tif len(ackedPackets) != 0 {\n\t\tlastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber\n\t\tisRoundStart = b.updateRoundTripCounter(lastAckedPacket)\n\t\tb.updateRecoveryState(lastAckedPacket, len(lostPackets) != 0, isRoundStart)\n\t}\n\n\tsample := b.sampler.OnCongestionEvent(eventTime,\n\t\tackedPackets, lostPackets, b.maxBandwidth.GetBest(), infBandwidth, b.roundTripCount)\n\tif sample.lastPacketSendState.isValid {\n\t\tb.lastSampleIsAppLimited = sample.lastPacketSendState.isAppLimited\n\t\tb.hasNoAppLimitedSample = b.hasNoAppLimitedSample || !b.lastSampleIsAppLimited\n\t}\n\t// Avoid updating |max_bandwidth_| if a) this is a loss-only event, or b) all\n\t// packets in |acked_packets| did not generate valid samples. (e.g. ack of\n\t// ack-only packets). In both cases, sampler_.total_bytes_acked() will not\n\t// change.\n\tif totalBytesAckedBefore != b.sampler.TotalBytesAcked() {\n\t\tif !sample.sampleIsAppLimited || sample.sampleMaxBandwidth > b.maxBandwidth.GetBest() {\n\t\t\tb.maxBandwidth.Update(sample.sampleMaxBandwidth, b.roundTripCount)\n\t\t}\n\t}\n\n\tif sample.sampleRtt != infRTT {\n\t\tminRttExpired = b.maybeUpdateMinRtt(eventTime, sample.sampleRtt)\n\t}\n\tbytesLost = b.sampler.TotalBytesLost() - totalBytesLostBefore\n\n\texcessAcked = sample.extraAcked\n\tlastPacketSendState = sample.lastPacketSendState\n\n\tif len(lostPackets) != 0 {\n\t\tb.numLossEventsInRound++\n\t\tb.bytesLostInRound += bytesLost\n\t}\n\n\t// Handle logic specific to PROBE_BW mode.\n\tif b.mode == bbrModeProbeBw {\n\t\tb.updateGainCyclePhase(eventTime, priorInFlight, len(lostPackets) != 0)\n\t}\n\n\t// Handle logic specific to STARTUP and DRAIN modes.\n\tif isRoundStart && !b.isAtFullBandwidth {\n\t\tb.checkIfFullBandwidthReached(&lastPacketSendState)\n\t}\n\n\tb.maybeExitStartupOrDrain(eventTime)\n\n\t// Handle logic specific to PROBE_RTT.\n\tb.maybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired)\n\n\t// Calculate number of packets acked and lost.\n\tbytesAcked := b.sampler.TotalBytesAcked() - totalBytesAckedBefore\n\n\t// After the model is updated, recalculate the pacing rate and congestion\n\t// window.\n\tb.calculatePacingRate(bytesLost)\n\tb.calculateCongestionWindow(bytesAcked, excessAcked)\n\tb.calculateRecoveryWindow(bytesAcked, bytesLost)\n\n\t// Cleanup internal state.\n\t// This is where we clean up obsolete (acked or lost) packets from the bandwidth sampler.\n\t// The \"least unacked\" should actually be FirstOutstanding, but since we are not passing\n\t// that through OnCongestionEventEx, we will only do an estimate using acked/lost packets\n\t// for now. Because of fast retransmission, they should differ by no more than 2 packets.\n\t// (this is controlled by packetThreshold in quic-go's sentPacketHandler)\n\tvar leastUnacked congestion.PacketNumber\n\tif len(ackedPackets) != 0 {\n\t\tleastUnacked = ackedPackets[len(ackedPackets)-1].PacketNumber - 2\n\t} else {\n\t\tleastUnacked = lostPackets[len(lostPackets)-1].PacketNumber + 1\n\t}\n\tb.sampler.RemoveObsoletePackets(leastUnacked)\n\n\tif isRoundStart {\n\t\tb.numLossEventsInRound = 0\n\t\tb.bytesLostInRound = 0\n\t}\n}\n\nfunc (b *bbrSender) PacingRate() Bandwidth {\n\tif b.pacingRate == 0 {\n\t\treturn Bandwidth(b.highGain * float64(\n\t\t\tBandwidthFromDelta(b.initialCongestionWindow, b.getMinRtt())))\n\t}\n\n\treturn b.pacingRate\n}\n\nfunc (b *bbrSender) hasGoodBandwidthEstimateForResumption() bool {\n\treturn b.hasNonAppLimitedSample()\n}\n\nfunc (b *bbrSender) hasNonAppLimitedSample() bool {\n\treturn b.hasNoAppLimitedSample\n}\n\n// Sets the pacing gain used in STARTUP.  Must be greater than 1.\nfunc (b *bbrSender) setHighGain(highGain float64) {\n\tb.highGain = highGain\n\tif b.mode == bbrModeStartup {\n\t\tb.pacingGain = highGain\n\t}\n}\n\n// Sets the CWND gain used in STARTUP.  Must be greater than 1.\nfunc (b *bbrSender) setHighCwndGain(highCwndGain float64) {\n\tb.highCwndGain = highCwndGain\n\tif b.mode == bbrModeStartup {\n\t\tb.congestionWindowGain = highCwndGain\n\t}\n}\n\n// Sets the gain used in DRAIN.  Must be less than 1.\nfunc (b *bbrSender) setDrainGain(drainGain float64) {\n\tb.drainGain = drainGain\n}\n\n// Get the current bandwidth estimate. Note that Bandwidth is in bits per second.\nfunc (b *bbrSender) bandwidthEstimate() Bandwidth {\n\treturn b.maxBandwidth.GetBest()\n}\n\nfunc (b *bbrSender) bandwidthForPacer() congestion.ByteCount {\n\tbps := congestion.ByteCount(float64(b.PacingRate()) / float64(BytesPerSecond))\n\tif bps < minBps {\n\t\t// We need to make sure that the bandwidth value for pacer is never zero,\n\t\t// otherwise it will go into an edge case where HasPacingBudget = false\n\t\t// but TimeUntilSend is before, causing the quic-go send loop to go crazy and get stuck.\n\t\treturn minBps\n\t}\n\treturn bps\n}\n\n// Returns the current estimate of the RTT of the connection.  Outside of the\n// edge cases, this is minimum RTT.\nfunc (b *bbrSender) getMinRtt() time.Duration {\n\tif b.minRtt != 0 {\n\t\treturn b.minRtt\n\t}\n\t// min_rtt could be available if the handshake packet gets neutered then\n\t// gets acknowledged. This could only happen for QUIC crypto where we do not\n\t// drop keys.\n\tminRtt := b.rttStats.MinRTT()\n\tif minRtt == 0 {\n\t\treturn 100 * time.Millisecond\n\t} else {\n\t\treturn minRtt\n\t}\n}\n\n// Computes the target congestion window using the specified gain.\nfunc (b *bbrSender) getTargetCongestionWindow(gain float64) congestion.ByteCount {\n\tbdp := bdpFromRttAndBandwidth(b.getMinRtt(), b.bandwidthEstimate())\n\tcongestionWindow := congestion.ByteCount(gain * float64(bdp))\n\n\t// BDP estimate will be zero if no bandwidth samples are available yet.\n\tif congestionWindow == 0 {\n\t\tcongestionWindow = congestion.ByteCount(gain * float64(b.initialCongestionWindow))\n\t}\n\n\treturn max(congestionWindow, b.minCongestionWindow)\n}\n\n// The target congestion window during PROBE_RTT.\nfunc (b *bbrSender) probeRttCongestionWindow() congestion.ByteCount {\n\treturn b.minCongestionWindow\n}\n\nfunc (b *bbrSender) maybeUpdateMinRtt(now monotime.Time, sampleMinRtt time.Duration) bool {\n\t// Do not expire min_rtt if none was ever available.\n\tminRttExpired := b.minRtt != 0 && now.After(b.minRttTimestamp.Add(minRttExpiry))\n\tif minRttExpired || sampleMinRtt < b.minRtt || b.minRtt == 0 {\n\t\tb.minRtt = sampleMinRtt\n\t\tb.minRttTimestamp = now\n\t}\n\n\treturn minRttExpired\n}\n\n// Enters the STARTUP mode.\nfunc (b *bbrSender) enterStartupMode(now monotime.Time) {\n\tb.mode = bbrModeStartup\n\t// b.maybeTraceStateChange(logging.CongestionStateStartup)\n\tb.pacingGain = b.highGain\n\tb.congestionWindowGain = b.highCwndGain\n\n\tif b.debug {\n\t\tb.debugPrint(\"Phase: STARTUP\")\n\t}\n}\n\n// Enters the PROBE_BW mode.\nfunc (b *bbrSender) enterProbeBandwidthMode(now monotime.Time) {\n\tb.mode = bbrModeProbeBw\n\t// b.maybeTraceStateChange(logging.CongestionStateProbeBw)\n\tb.congestionWindowGain = b.congestionWindowGainConstant\n\n\t// Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is\n\t// excluded because in that case increased gain and decreased gain would not\n\t// follow each other.\n\tb.cycleCurrentOffset = int(rand.Int31n(congestion.PacketsPerConnectionID)) % (gainCycleLength - 1)\n\tif b.cycleCurrentOffset >= 1 {\n\t\tb.cycleCurrentOffset += 1\n\t}\n\n\tb.lastCycleStart = now\n\tb.pacingGain = pacingGain[b.cycleCurrentOffset]\n\n\tif b.debug {\n\t\tb.debugPrint(\"Phase: PROBE_BW\")\n\t}\n}\n\n// Updates the round-trip counter if a round-trip has passed.  Returns true if\n// the counter has been advanced.\nfunc (b *bbrSender) updateRoundTripCounter(lastAckedPacket congestion.PacketNumber) bool {\n\tif b.currentRoundTripEnd == invalidPacketNumber || lastAckedPacket > b.currentRoundTripEnd {\n\t\tb.roundTripCount++\n\t\tb.currentRoundTripEnd = b.lastSentPacket\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Updates the current gain used in PROBE_BW mode.\nfunc (b *bbrSender) updateGainCyclePhase(now monotime.Time, priorInFlight congestion.ByteCount, hasLosses bool) {\n\t// In most cases, the cycle is advanced after an RTT passes.\n\tshouldAdvanceGainCycling := now.After(b.lastCycleStart.Add(b.getMinRtt()))\n\t// If the pacing gain is above 1.0, the connection is trying to probe the\n\t// bandwidth by increasing the number of bytes in flight to at least\n\t// pacing_gain * BDP.  Make sure that it actually reaches the target, as long\n\t// as there are no losses suggesting that the buffers are not able to hold\n\t// that much.\n\tif b.pacingGain > 1.0 && !hasLosses && priorInFlight < b.getTargetCongestionWindow(b.pacingGain) {\n\t\tshouldAdvanceGainCycling = false\n\t}\n\n\t// If pacing gain is below 1.0, the connection is trying to drain the extra\n\t// queue which could have been incurred by probing prior to it.  If the number\n\t// of bytes in flight falls down to the estimated BDP value earlier, conclude\n\t// that the queue has been successfully drained and exit this cycle early.\n\tif b.pacingGain < 1.0 && b.bytesInFlight <= b.getTargetCongestionWindow(1) {\n\t\tshouldAdvanceGainCycling = true\n\t}\n\n\tif shouldAdvanceGainCycling {\n\t\tb.cycleCurrentOffset = (b.cycleCurrentOffset + 1) % gainCycleLength\n\t\tb.lastCycleStart = now\n\t\t// Stay in low gain mode until the target BDP is hit.\n\t\t// Low gain mode will be exited immediately when the target BDP is achieved.\n\t\tif b.drainToTarget && b.pacingGain < 1 &&\n\t\t\tpacingGain[b.cycleCurrentOffset] == 1 &&\n\t\t\tb.bytesInFlight > b.getTargetCongestionWindow(1) {\n\t\t\treturn\n\t\t}\n\t\tb.pacingGain = pacingGain[b.cycleCurrentOffset]\n\t}\n}\n\n// Tracks for how many round-trips the bandwidth has not increased\n// significantly.\nfunc (b *bbrSender) checkIfFullBandwidthReached(lastPacketSendState *sendTimeState) {\n\tif b.lastSampleIsAppLimited {\n\t\treturn\n\t}\n\n\ttarget := Bandwidth(float64(b.bandwidthAtLastRound) * startupGrowthTarget)\n\tif b.bandwidthEstimate() >= target {\n\t\tb.bandwidthAtLastRound = b.bandwidthEstimate()\n\t\tb.roundsWithoutBandwidthGain = 0\n\t\tif b.expireAckAggregationInStartup {\n\t\t\t// Expire old excess delivery measurements now that bandwidth increased.\n\t\t\tb.sampler.ResetMaxAckHeightTracker(0, b.roundTripCount)\n\t\t}\n\t\treturn\n\t}\n\n\tb.roundsWithoutBandwidthGain++\n\tif b.roundsWithoutBandwidthGain >= b.numStartupRtts ||\n\t\tb.shouldExitStartupDueToLoss(lastPacketSendState) {\n\t\tb.isAtFullBandwidth = true\n\t}\n}\n\nfunc (b *bbrSender) maybeAppLimited(bytesInFlight congestion.ByteCount) {\n\tif bytesInFlight < b.getTargetCongestionWindow(1) {\n\t\tb.sampler.OnAppLimited()\n\t}\n}\n\n// Transitions from STARTUP to DRAIN and from DRAIN to PROBE_BW if\n// appropriate.\nfunc (b *bbrSender) maybeExitStartupOrDrain(now monotime.Time) {\n\tif b.mode == bbrModeStartup && b.isAtFullBandwidth {\n\t\tb.mode = bbrModeDrain\n\t\t// b.maybeTraceStateChange(logging.CongestionStateDrain)\n\t\tb.pacingGain = b.drainGain\n\t\tb.congestionWindowGain = b.highCwndGain\n\n\t\tif b.debug {\n\t\t\tb.debugPrint(\"Phase: DRAIN\")\n\t\t}\n\t}\n\tif b.mode == bbrModeDrain && b.bytesInFlight <= b.getTargetCongestionWindow(1) {\n\t\tb.enterProbeBandwidthMode(now)\n\t}\n}\n\n// Decides whether to enter or exit PROBE_RTT.\nfunc (b *bbrSender) maybeEnterOrExitProbeRtt(now monotime.Time, isRoundStart, minRttExpired bool) {\n\tif minRttExpired && !b.exitingQuiescence && b.mode != bbrModeProbeRtt {\n\t\tb.mode = bbrModeProbeRtt\n\t\t// b.maybeTraceStateChange(logging.CongestionStateProbRtt)\n\t\tb.pacingGain = 1.0\n\t\t// Do not decide on the time to exit PROBE_RTT until the |bytes_in_flight|\n\t\t// is at the target small value.\n\t\tb.exitProbeRttAt = 0\n\n\t\tif b.debug {\n\t\t\tb.debugPrint(\"BandwidthEstimate: %s, CongestionWindowGain: %.2f, PacingGain: %.2f, PacingRate: %s\",\n\t\t\t\tformatSpeed(b.bandwidthEstimate()), b.congestionWindowGain, b.pacingGain, formatSpeed(b.PacingRate()))\n\t\t\tb.debugPrint(\"Phase: PROBE_RTT\")\n\t\t}\n\t}\n\n\tif b.mode == bbrModeProbeRtt {\n\t\tb.sampler.OnAppLimited()\n\t\t// b.maybeTraceStateChange(logging.CongestionStateApplicationLimited)\n\n\t\tif b.exitProbeRttAt.IsZero() {\n\t\t\t// If the window has reached the appropriate size, schedule exiting\n\t\t\t// PROBE_RTT.  The CWND during PROBE_RTT is kMinimumCongestionWindow, but\n\t\t\t// we allow an extra packet since QUIC checks CWND before sending a\n\t\t\t// packet.\n\t\t\tif b.bytesInFlight < b.probeRttCongestionWindow()+congestion.MaxPacketBufferSize {\n\t\t\t\tb.exitProbeRttAt = now.Add(probeRttTime)\n\t\t\t\tb.probeRttRoundPassed = false\n\t\t\t}\n\t\t} else {\n\t\t\tif isRoundStart {\n\t\t\t\tb.probeRttRoundPassed = true\n\t\t\t}\n\t\t\tif now.Sub(b.exitProbeRttAt) >= 0 && b.probeRttRoundPassed {\n\t\t\t\tb.minRttTimestamp = now\n\t\t\t\tif b.debug {\n\t\t\t\t\tb.debugPrint(\"MinRTT: %s\", b.getMinRtt())\n\t\t\t\t}\n\t\t\t\tif !b.isAtFullBandwidth {\n\t\t\t\t\tb.enterStartupMode(now)\n\t\t\t\t} else {\n\t\t\t\t\tb.enterProbeBandwidthMode(now)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tb.exitingQuiescence = false\n}\n\n// Determines whether BBR needs to enter, exit or advance state of the\n// recovery.\nfunc (b *bbrSender) updateRecoveryState(lastAckedPacket congestion.PacketNumber, hasLosses, isRoundStart bool) {\n\t// Disable recovery in startup, if loss-based exit is enabled.\n\tif !b.isAtFullBandwidth {\n\t\treturn\n\t}\n\n\t// Exit recovery when there are no losses for a round.\n\tif hasLosses {\n\t\tb.endRecoveryAt = b.lastSentPacket\n\t}\n\n\tswitch b.recoveryState {\n\tcase bbrRecoveryStateNotInRecovery:\n\t\tif hasLosses {\n\t\t\tb.recoveryState = bbrRecoveryStateConservation\n\t\t\t// This will cause the |recovery_window_| to be set to the correct\n\t\t\t// value in CalculateRecoveryWindow().\n\t\t\tb.recoveryWindow = 0\n\t\t\t// Since the conservation phase is meant to be lasting for a whole\n\t\t\t// round, extend the current round as if it were started right now.\n\t\t\tb.currentRoundTripEnd = b.lastSentPacket\n\t\t}\n\tcase bbrRecoveryStateConservation:\n\t\tif isRoundStart {\n\t\t\tb.recoveryState = bbrRecoveryStateGrowth\n\t\t}\n\t\tfallthrough\n\tcase bbrRecoveryStateGrowth:\n\t\t// Exit recovery if appropriate.\n\t\tif !hasLosses && lastAckedPacket > b.endRecoveryAt {\n\t\t\tb.recoveryState = bbrRecoveryStateNotInRecovery\n\t\t}\n\t}\n}\n\n// Determines the appropriate pacing rate for the connection.\nfunc (b *bbrSender) calculatePacingRate(bytesLost congestion.ByteCount) {\n\tif b.bandwidthEstimate() == 0 {\n\t\treturn\n\t}\n\n\ttargetRate := Bandwidth(b.pacingGain * float64(b.bandwidthEstimate()))\n\tif b.isAtFullBandwidth {\n\t\tb.pacingRate = targetRate\n\t\treturn\n\t}\n\n\t// Pace at the rate of initial_window / RTT as soon as RTT measurements are\n\t// available.\n\tif b.pacingRate == 0 && b.rttStats.MinRTT() != 0 {\n\t\tb.pacingRate = BandwidthFromDelta(b.initialCongestionWindow, b.rttStats.MinRTT())\n\t\treturn\n\t}\n\n\tif b.detectOvershooting {\n\t\tb.bytesLostWhileDetectingOvershooting += bytesLost\n\t\t// Check for overshooting with network parameters adjusted when pacing rate\n\t\t// > target_rate and loss has been detected.\n\t\tif b.pacingRate > targetRate && b.bytesLostWhileDetectingOvershooting > 0 {\n\t\t\tif b.hasNoAppLimitedSample ||\n\t\t\t\tb.bytesLostWhileDetectingOvershooting*congestion.ByteCount(b.bytesLostMultiplierWhileDetectingOvershooting) > b.initialCongestionWindow {\n\t\t\t\t// We are fairly sure overshoot happens if 1) there is at least one\n\t\t\t\t// non app-limited bw sample or 2) half of IW gets lost. Slow pacing\n\t\t\t\t// rate.\n\t\t\t\tb.pacingRate = max(targetRate, BandwidthFromDelta(b.cwndToCalculateMinPacingRate, b.rttStats.MinRTT()))\n\t\t\t\tb.bytesLostWhileDetectingOvershooting = 0\n\t\t\t\tb.detectOvershooting = false\n\t\t\t}\n\t\t}\n\t}\n\n\t// Do not decrease the pacing rate during startup.\n\tb.pacingRate = max(b.pacingRate, targetRate)\n}\n\n// Determines the appropriate congestion window for the connection.\nfunc (b *bbrSender) calculateCongestionWindow(bytesAcked, excessAcked congestion.ByteCount) {\n\tif b.mode == bbrModeProbeRtt {\n\t\treturn\n\t}\n\n\ttargetWindow := b.getTargetCongestionWindow(b.congestionWindowGain)\n\tif b.isAtFullBandwidth {\n\t\t// Add the max recently measured ack aggregation to CWND.\n\t\ttargetWindow += b.sampler.MaxAckHeight()\n\t} else if b.enableAckAggregationDuringStartup {\n\t\t// Add the most recent excess acked.  Because CWND never decreases in\n\t\t// STARTUP, this will automatically create a very localized max filter.\n\t\ttargetWindow += excessAcked\n\t}\n\n\t// Instead of immediately setting the target CWND as the new one, BBR grows\n\t// the CWND towards |target_window| by only increasing it |bytes_acked| at a\n\t// time.\n\tif b.isAtFullBandwidth {\n\t\tb.congestionWindow = min(targetWindow, b.congestionWindow+bytesAcked)\n\t} else if b.congestionWindow < targetWindow ||\n\t\tb.sampler.TotalBytesAcked() < b.initialCongestionWindow {\n\t\t// If the connection is not yet out of startup phase, do not decrease the\n\t\t// window.\n\t\tb.congestionWindow += bytesAcked\n\t}\n\n\t// Enforce the limits on the congestion window.\n\tb.congestionWindow = max(b.congestionWindow, b.minCongestionWindow)\n\tb.congestionWindow = min(b.congestionWindow, b.maxCongestionWindow)\n}\n\n// Determines the appropriate window that constrains the in-flight during recovery.\nfunc (b *bbrSender) calculateRecoveryWindow(bytesAcked, bytesLost congestion.ByteCount) {\n\tif b.recoveryState == bbrRecoveryStateNotInRecovery {\n\t\treturn\n\t}\n\n\t// Set up the initial recovery window.\n\tif b.recoveryWindow == 0 {\n\t\tb.recoveryWindow = b.bytesInFlight + bytesAcked\n\t\tb.recoveryWindow = max(b.minCongestionWindow, b.recoveryWindow)\n\t\treturn\n\t}\n\n\t// Remove losses from the recovery window, while accounting for a potential\n\t// integer underflow.\n\tif b.recoveryWindow >= bytesLost {\n\t\tb.recoveryWindow = b.recoveryWindow - bytesLost\n\t} else {\n\t\tb.recoveryWindow = b.maxDatagramSize\n\t}\n\n\t// In CONSERVATION mode, just subtracting losses is sufficient.  In GROWTH,\n\t// release additional |bytes_acked| to achieve a slow-start-like behavior.\n\tif b.recoveryState == bbrRecoveryStateGrowth {\n\t\tb.recoveryWindow += bytesAcked\n\t}\n\n\t// Always allow sending at least |bytes_acked| in response.\n\tb.recoveryWindow = max(b.recoveryWindow, b.bytesInFlight+bytesAcked)\n\tb.recoveryWindow = max(b.minCongestionWindow, b.recoveryWindow)\n}\n\n// Return whether we should exit STARTUP due to excessive loss.\nfunc (b *bbrSender) shouldExitStartupDueToLoss(lastPacketSendState *sendTimeState) bool {\n\tif b.numLossEventsInRound < defaultStartupFullLossCount || !lastPacketSendState.isValid {\n\t\treturn false\n\t}\n\n\tinflightAtSend := lastPacketSendState.bytesInFlight\n\n\tif inflightAtSend > 0 && b.bytesLostInRound > 0 {\n\t\tif b.bytesLostInRound > congestion.ByteCount(float64(inflightAtSend)*quicBbr2DefaultLossThreshold) {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\treturn false\n}\n\nfunc (b *bbrSender) debugPrint(format string, a ...any) {\n\tfmt.Printf(\"[BBRSender] [%s] %s\\n\",\n\t\ttime.Now().Format(\"15:04:05\"),\n\t\tfmt.Sprintf(format, a...))\n}\n\nfunc bdpFromRttAndBandwidth(rtt time.Duration, bandwidth Bandwidth) congestion.ByteCount {\n\treturn congestion.ByteCount(rtt) * congestion.ByteCount(bandwidth) / congestion.ByteCount(BytesPerSecond) / congestion.ByteCount(time.Second)\n}\n\nfunc GetInitialPacketSize(addr net.Addr) congestion.ByteCount {\n\t// If this is not a UDP address, we don't know anything about the MTU.\n\t// Use the minimum size of an Initial packet as the max packet size.\n\tif _, ok := addr.(*net.UDPAddr); ok {\n\t\treturn congestion.InitialPacketSize\n\t} else {\n\t\treturn congestion.MinInitialPacketSize\n\t}\n}\n\nfunc formatSpeed(bw Bandwidth) string {\n\tbwf := float64(bw)\n\tunits := []string{\"bps\", \"Kbps\", \"Mbps\", \"Gbps\"}\n\tunitIndex := 0\n\tfor bwf > 1000 && unitIndex < len(units)-1 {\n\t\tbwf /= 1000\n\t\tunitIndex++\n\t}\n\treturn fmt.Sprintf(\"%.2f %s\", bwf, units[unitIndex])\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/clock.go",
    "content": "package bbr\n\nimport \"github.com/apernet/quic-go/monotime\"\n\n// A Clock returns the current time\ntype Clock interface {\n\tNow() monotime.Time\n}\n\n// DefaultClock implements the Clock interface using the Go stdlib clock.\ntype DefaultClock struct{}\n\nvar _ Clock = DefaultClock{}\n\n// Now gets the current time\nfunc (DefaultClock) Now() monotime.Time {\n\treturn monotime.Now()\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/packet_number_indexed_queue.go",
    "content": "package bbr\n\nimport (\n\t\"github.com/apernet/quic-go/congestion\"\n)\n\n// packetNumberIndexedQueue is a queue of mostly continuous numbered entries\n// which supports the following operations:\n// - adding elements to the end of the queue, or at some point past the end\n// - removing elements in any order\n// - retrieving elements\n// If all elements are inserted in order, all of the operations above are\n// amortized O(1) time.\n//\n// Internally, the data structure is a deque where each element is marked as\n// present or not.  The deque starts at the lowest present index.  Whenever an\n// element is removed, it's marked as not present, and the front of the deque is\n// cleared of elements that are not present.\n//\n// The tail of the queue is not cleared due to the assumption of entries being\n// inserted in order, though removing all elements of the queue will return it\n// to its initial state.\n//\n// Note that this data structure is inherently hazardous, since an addition of\n// just two entries will cause it to consume all of the memory available.\n// Because of that, it is not a general-purpose container and should not be used\n// as one.\n\ntype entryWrapper[T any] struct {\n\tpresent bool\n\tentry   T\n}\n\ntype packetNumberIndexedQueue[T any] struct {\n\tentries                RingBuffer[entryWrapper[T]]\n\tnumberOfPresentEntries int\n\tfirstPacket            congestion.PacketNumber\n}\n\nfunc newPacketNumberIndexedQueue[T any](size int) *packetNumberIndexedQueue[T] {\n\tq := &packetNumberIndexedQueue[T]{\n\t\tfirstPacket: invalidPacketNumber,\n\t}\n\n\tq.entries.Init(size)\n\n\treturn q\n}\n\n// Emplace inserts data associated |packet_number| into (or past) the end of the\n// queue, filling up the missing intermediate entries as necessary.  Returns\n// true if the element has been inserted successfully, false if it was already\n// in the queue or inserted out of order.\nfunc (p *packetNumberIndexedQueue[T]) Emplace(packetNumber congestion.PacketNumber, entry *T) bool {\n\tif packetNumber == invalidPacketNumber || entry == nil {\n\t\treturn false\n\t}\n\n\tif p.IsEmpty() {\n\t\tp.entries.PushBack(entryWrapper[T]{\n\t\t\tpresent: true,\n\t\t\tentry:   *entry,\n\t\t})\n\t\tp.numberOfPresentEntries = 1\n\t\tp.firstPacket = packetNumber\n\t\treturn true\n\t}\n\n\t// Do not allow insertion out-of-order.\n\tif packetNumber <= p.LastPacket() {\n\t\treturn false\n\t}\n\n\t// Handle potentially missing elements.\n\toffset := int(packetNumber - p.FirstPacket())\n\tif gap := offset - p.entries.Len(); gap > 0 {\n\t\tfor i := 0; i < gap; i++ {\n\t\t\tp.entries.PushBack(entryWrapper[T]{})\n\t\t}\n\t}\n\n\tp.entries.PushBack(entryWrapper[T]{\n\t\tpresent: true,\n\t\tentry:   *entry,\n\t})\n\tp.numberOfPresentEntries++\n\treturn true\n}\n\n// GetEntry Retrieve the entry associated with the packet number.  Returns the pointer\n// to the entry in case of success, or nullptr if the entry does not exist.\nfunc (p *packetNumberIndexedQueue[T]) GetEntry(packetNumber congestion.PacketNumber) *T {\n\tew := p.getEntryWraper(packetNumber)\n\tif ew == nil {\n\t\treturn nil\n\t}\n\n\treturn &ew.entry\n}\n\n// Remove, Same as above, but if an entry is present in the queue, also call f(entry)\n// before removing it.\nfunc (p *packetNumberIndexedQueue[T]) Remove(packetNumber congestion.PacketNumber, f func(T)) bool {\n\tew := p.getEntryWraper(packetNumber)\n\tif ew == nil {\n\t\treturn false\n\t}\n\tif f != nil {\n\t\tf(ew.entry)\n\t}\n\tew.present = false\n\tp.numberOfPresentEntries--\n\n\tif packetNumber == p.FirstPacket() {\n\t\tp.clearup()\n\t}\n\n\treturn true\n}\n\n// RemoveUpTo, but not including |packet_number|.\n// Unused slots in the front are also removed, which means when the function\n// returns, |first_packet()| can be larger than |packet_number|.\nfunc (p *packetNumberIndexedQueue[T]) RemoveUpTo(packetNumber congestion.PacketNumber) {\n\tfor !p.entries.Empty() &&\n\t\tp.firstPacket != invalidPacketNumber &&\n\t\tp.firstPacket < packetNumber {\n\t\tif p.entries.Front().present {\n\t\t\tp.numberOfPresentEntries--\n\t\t}\n\t\tp.entries.PopFront()\n\t\tp.firstPacket++\n\t}\n\tp.clearup()\n\n\treturn\n}\n\n// IsEmpty return if queue is empty.\nfunc (p *packetNumberIndexedQueue[T]) IsEmpty() bool {\n\treturn p.numberOfPresentEntries == 0\n}\n\n// NumberOfPresentEntries returns the number of entries in the queue.\nfunc (p *packetNumberIndexedQueue[T]) NumberOfPresentEntries() int {\n\treturn p.numberOfPresentEntries\n}\n\n// EntrySlotsUsed returns the number of entries allocated in the underlying deque.  This is\n// proportional to the memory usage of the queue.\nfunc (p *packetNumberIndexedQueue[T]) EntrySlotsUsed() int {\n\treturn p.entries.Len()\n}\n\n// FirstPacket returns packet number of the first entry in the queue.\nfunc (p *packetNumberIndexedQueue[T]) FirstPacket() (packetNumber congestion.PacketNumber) {\n\treturn p.firstPacket\n}\n\n// LastPacket returns packet number of the last entry ever inserted in the queue.  Note that the\n// entry in question may have already been removed.  Zero if the queue is\n// empty.\nfunc (p *packetNumberIndexedQueue[T]) LastPacket() (packetNumber congestion.PacketNumber) {\n\tif p.IsEmpty() {\n\t\treturn invalidPacketNumber\n\t}\n\n\treturn p.firstPacket + congestion.PacketNumber(p.entries.Len()-1)\n}\n\nfunc (p *packetNumberIndexedQueue[T]) clearup() {\n\tfor !p.entries.Empty() && !p.entries.Front().present {\n\t\tp.entries.PopFront()\n\t\tp.firstPacket++\n\t}\n\tif p.entries.Empty() {\n\t\tp.firstPacket = invalidPacketNumber\n\t}\n}\n\nfunc (p *packetNumberIndexedQueue[T]) getEntryWraper(packetNumber congestion.PacketNumber) *entryWrapper[T] {\n\tif packetNumber == invalidPacketNumber ||\n\t\tp.IsEmpty() ||\n\t\tpacketNumber < p.firstPacket {\n\t\treturn nil\n\t}\n\n\toffset := int(packetNumber - p.firstPacket)\n\tif offset >= p.entries.Len() {\n\t\treturn nil\n\t}\n\n\tew := p.entries.Offset(offset)\n\tif ew == nil || !ew.present {\n\t\treturn nil\n\t}\n\n\treturn ew\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/ringbuffer.go",
    "content": "package bbr\n\n// A RingBuffer is a ring buffer.\n// It acts as a heap that doesn't cause any allocations.\ntype RingBuffer[T any] struct {\n\tring             []T\n\theadPos, tailPos int\n\tfull             bool\n}\n\n// Init preallocs a buffer with a certain size.\nfunc (r *RingBuffer[T]) Init(size int) {\n\tr.ring = make([]T, size)\n}\n\n// Len returns the number of elements in the ring buffer.\nfunc (r *RingBuffer[T]) Len() int {\n\tif r.full {\n\t\treturn len(r.ring)\n\t}\n\tif r.tailPos >= r.headPos {\n\t\treturn r.tailPos - r.headPos\n\t}\n\treturn r.tailPos - r.headPos + len(r.ring)\n}\n\n// Empty says if the ring buffer is empty.\nfunc (r *RingBuffer[T]) Empty() bool {\n\treturn !r.full && r.headPos == r.tailPos\n}\n\n// PushBack adds a new element.\n// If the ring buffer is full, its capacity is increased first.\nfunc (r *RingBuffer[T]) PushBack(t T) {\n\tif r.full || len(r.ring) == 0 {\n\t\tr.grow()\n\t}\n\tr.ring[r.tailPos] = t\n\tr.tailPos++\n\tif r.tailPos == len(r.ring) {\n\t\tr.tailPos = 0\n\t}\n\tif r.tailPos == r.headPos {\n\t\tr.full = true\n\t}\n}\n\n// PopFront returns the next element.\n// It must not be called when the buffer is empty, that means that\n// callers might need to check if there are elements in the buffer first.\nfunc (r *RingBuffer[T]) PopFront() T {\n\tif r.Empty() {\n\t\tpanic(\"github.com/quic-go/quic-go/internal/utils/ringbuffer: pop from an empty queue\")\n\t}\n\tr.full = false\n\tt := r.ring[r.headPos]\n\tr.ring[r.headPos] = *new(T)\n\tr.headPos++\n\tif r.headPos == len(r.ring) {\n\t\tr.headPos = 0\n\t}\n\treturn t\n}\n\n// Offset returns the offset element.\n// It must not be called when the buffer is empty, that means that\n// callers might need to check if there are elements in the buffer first\n// and check if the index larger than buffer length.\nfunc (r *RingBuffer[T]) Offset(index int) *T {\n\tif r.Empty() || index >= r.Len() {\n\t\tpanic(\"github.com/quic-go/quic-go/internal/utils/ringbuffer: offset from invalid index\")\n\t}\n\toffset := (r.headPos + index) % len(r.ring)\n\treturn &r.ring[offset]\n}\n\n// Front returns the front element.\n// It must not be called when the buffer is empty, that means that\n// callers might need to check if there are elements in the buffer first.\nfunc (r *RingBuffer[T]) Front() *T {\n\tif r.Empty() {\n\t\tpanic(\"github.com/quic-go/quic-go/internal/utils/ringbuffer: front from an empty queue\")\n\t}\n\treturn &r.ring[r.headPos]\n}\n\n// Back returns the back element.\n// It must not be called when the buffer is empty, that means that\n// callers might need to check if there are elements in the buffer first.\nfunc (r *RingBuffer[T]) Back() *T {\n\tif r.Empty() {\n\t\tpanic(\"github.com/quic-go/quic-go/internal/utils/ringbuffer: back from an empty queue\")\n\t}\n\treturn r.Offset(r.Len() - 1)\n}\n\n// Grow the maximum size of the queue.\n// This method assume the queue is full.\nfunc (r *RingBuffer[T]) grow() {\n\toldRing := r.ring\n\tnewSize := len(oldRing) * 2\n\tif newSize == 0 {\n\t\tnewSize = 1\n\t}\n\tr.ring = make([]T, newSize)\n\theadLen := copy(r.ring, oldRing[r.headPos:])\n\tcopy(r.ring[headLen:], oldRing[:r.headPos])\n\tr.headPos, r.tailPos, r.full = 0, len(oldRing), false\n}\n\n// Clear removes all elements.\nfunc (r *RingBuffer[T]) Clear() {\n\tvar zeroValue T\n\tfor i := range r.ring {\n\t\tr.ring[i] = zeroValue\n\t}\n\tr.headPos, r.tailPos, r.full = 0, 0, false\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/bbr/windowed_filter.go",
    "content": "package bbr\n\nimport (\n\t\"golang.org/x/exp/constraints\"\n)\n\n// Implements Kathleen Nichols' algorithm for tracking the minimum (or maximum)\n// estimate of a stream of samples over some fixed time interval. (E.g.,\n// the minimum RTT over the past five minutes.) The algorithm keeps track of\n// the best, second best, and third best min (or max) estimates, maintaining an\n// invariant that the measurement time of the n'th best >= n-1'th best.\n\n// The algorithm works as follows. On a reset, all three estimates are set to\n// the same sample. The second best estimate is then recorded in the second\n// quarter of the window, and a third best estimate is recorded in the second\n// half of the window, bounding the worst case error when the true min is\n// monotonically increasing (or true max is monotonically decreasing) over the\n// window.\n//\n// A new best sample replaces all three estimates, since the new best is lower\n// (or higher) than everything else in the window and it is the most recent.\n// The window thus effectively gets reset on every new min. The same property\n// holds true for second best and third best estimates. Specifically, when a\n// sample arrives that is better than the second best but not better than the\n// best, it replaces the second and third best estimates but not the best\n// estimate. Similarly, a sample that is better than the third best estimate\n// but not the other estimates replaces only the third best estimate.\n//\n// Finally, when the best expires, it is replaced by the second best, which in\n// turn is replaced by the third best. The newest sample replaces the third\n// best.\n\ntype WindowedFilterValue interface {\n\tany\n}\n\ntype WindowedFilterTime interface {\n\tconstraints.Integer | constraints.Float\n}\n\ntype WindowedFilter[V WindowedFilterValue, T WindowedFilterTime] struct {\n\t// Time length of window.\n\twindowLength T\n\testimates    []entry[V, T]\n\tcomparator   func(V, V) int\n}\n\ntype entry[V WindowedFilterValue, T WindowedFilterTime] struct {\n\tsample V\n\ttime   T\n}\n\n// Compares two values and returns true if the first is greater than or equal\n// to the second.\nfunc MaxFilter[O constraints.Ordered](a, b O) int {\n\tif a > b {\n\t\treturn 1\n\t} else if a < b {\n\t\treturn -1\n\t}\n\treturn 0\n}\n\n// Compares two values and returns true if the first is less than or equal\n// to the second.\nfunc MinFilter[O constraints.Ordered](a, b O) int {\n\tif a < b {\n\t\treturn 1\n\t} else if a > b {\n\t\treturn -1\n\t}\n\treturn 0\n}\n\nfunc NewWindowedFilter[V WindowedFilterValue, T WindowedFilterTime](windowLength T, comparator func(V, V) int) *WindowedFilter[V, T] {\n\treturn &WindowedFilter[V, T]{\n\t\twindowLength: windowLength,\n\t\testimates:    make([]entry[V, T], 3, 3),\n\t\tcomparator:   comparator,\n\t}\n}\n\n// Changes the window length.  Does not update any current samples.\nfunc (f *WindowedFilter[V, T]) SetWindowLength(windowLength T) {\n\tf.windowLength = windowLength\n}\n\nfunc (f *WindowedFilter[V, T]) GetBest() V {\n\treturn f.estimates[0].sample\n}\n\nfunc (f *WindowedFilter[V, T]) GetSecondBest() V {\n\treturn f.estimates[1].sample\n}\n\nfunc (f *WindowedFilter[V, T]) GetThirdBest() V {\n\treturn f.estimates[2].sample\n}\n\n// Updates best estimates with |sample|, and expires and updates best\n// estimates as necessary.\nfunc (f *WindowedFilter[V, T]) Update(newSample V, newTime T) {\n\t// Reset all estimates if they have not yet been initialized, if new sample\n\t// is a new best, or if the newest recorded estimate is too old.\n\tif f.comparator(f.estimates[0].sample, *new(V)) == 0 ||\n\t\tf.comparator(newSample, f.estimates[0].sample) >= 0 ||\n\t\tnewTime-f.estimates[2].time > f.windowLength {\n\t\tf.Reset(newSample, newTime)\n\t\treturn\n\t}\n\n\tif f.comparator(newSample, f.estimates[1].sample) >= 0 {\n\t\tf.estimates[1] = entry[V, T]{newSample, newTime}\n\t\tf.estimates[2] = f.estimates[1]\n\t} else if f.comparator(newSample, f.estimates[2].sample) >= 0 {\n\t\tf.estimates[2] = entry[V, T]{newSample, newTime}\n\t}\n\n\t// Expire and update estimates as necessary.\n\tif newTime-f.estimates[0].time > f.windowLength {\n\t\t// The best estimate hasn't been updated for an entire window, so promote\n\t\t// second and third best estimates.\n\t\tf.estimates[0] = f.estimates[1]\n\t\tf.estimates[1] = f.estimates[2]\n\t\tf.estimates[2] = entry[V, T]{newSample, newTime}\n\t\t// Need to iterate one more time. Check if the new best estimate is\n\t\t// outside the window as well, since it may also have been recorded a\n\t\t// long time ago. Don't need to iterate once more since we cover that\n\t\t// case at the beginning of the method.\n\t\tif newTime-f.estimates[0].time > f.windowLength {\n\t\t\tf.estimates[0] = f.estimates[1]\n\t\t\tf.estimates[1] = f.estimates[2]\n\t\t}\n\t\treturn\n\t}\n\tif f.comparator(f.estimates[1].sample, f.estimates[0].sample) == 0 &&\n\t\tnewTime-f.estimates[1].time > f.windowLength/4 {\n\t\t// A quarter of the window has passed without a better sample, so the\n\t\t// second-best estimate is taken from the second quarter of the window.\n\t\tf.estimates[1] = entry[V, T]{newSample, newTime}\n\t\tf.estimates[2] = f.estimates[1]\n\t\treturn\n\t}\n\n\tif f.comparator(f.estimates[2].sample, f.estimates[1].sample) == 0 &&\n\t\tnewTime-f.estimates[2].time > f.windowLength/2 {\n\t\t// We've passed a half of the window without a better estimate, so take\n\t\t// a third-best estimate from the second half of the window.\n\t\tf.estimates[2] = entry[V, T]{newSample, newTime}\n\t}\n}\n\n// Resets all estimates to new sample.\nfunc (f *WindowedFilter[V, T]) Reset(newSample V, newTime T) {\n\tf.estimates[2] = entry[V, T]{newSample, newTime}\n\tf.estimates[1] = f.estimates[2]\n\tf.estimates[0] = f.estimates[1]\n}\n\nfunc (f *WindowedFilter[V, T]) Clear() {\n\tf.estimates = make([]entry[V, T], 3, 3)\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/brutal/brutal.go",
    "content": "package brutal\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion/common\"\n\n\t\"github.com/apernet/quic-go/congestion\"\n\t\"github.com/apernet/quic-go/monotime\"\n)\n\nconst (\n\tpktInfoSlotCount           = 5 // slot index is based on seconds, so this is basically how many seconds we sample\n\tminSampleCount             = 50\n\tminAckRate                 = 0.8\n\tcongestionWindowMultiplier = 2\n\n\tdebugEnv           = \"HYSTERIA_BRUTAL_DEBUG\"\n\tdebugPrintInterval = 2\n)\n\nvar _ congestion.CongestionControl = &BrutalSender{}\n\ntype BrutalSender struct {\n\trttStats        congestion.RTTStatsProvider\n\tbps             congestion.ByteCount\n\tmaxDatagramSize congestion.ByteCount\n\tpacer           *common.Pacer\n\n\tpktInfoSlots [pktInfoSlotCount]pktInfo\n\tackRate      float64\n\n\tdebug                 bool\n\tlastAckPrintTimestamp int64\n}\n\ntype pktInfo struct {\n\tTimestamp int64\n\tAckCount  uint64\n\tLossCount uint64\n}\n\nfunc NewBrutalSender(bps uint64) *BrutalSender {\n\tdebug, _ := strconv.ParseBool(os.Getenv(debugEnv))\n\tbs := &BrutalSender{\n\t\tbps:             congestion.ByteCount(bps),\n\t\tmaxDatagramSize: congestion.InitialPacketSize,\n\t\tackRate:         1,\n\t\tdebug:           debug,\n\t}\n\tbs.pacer = common.NewPacer(func() congestion.ByteCount {\n\t\treturn congestion.ByteCount(float64(bs.bps) / bs.ackRate)\n\t})\n\treturn bs\n}\n\nfunc (b *BrutalSender) SetRTTStatsProvider(rttStats congestion.RTTStatsProvider) {\n\tb.rttStats = rttStats\n}\n\nfunc (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) monotime.Time {\n\treturn b.pacer.TimeUntilSend()\n}\n\nfunc (b *BrutalSender) HasPacingBudget(now monotime.Time) bool {\n\treturn b.pacer.Budget(now) >= b.maxDatagramSize\n}\n\nfunc (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool {\n\treturn bytesInFlight <= b.GetCongestionWindow()\n}\n\nfunc (b *BrutalSender) GetCongestionWindow() congestion.ByteCount {\n\trtt := b.rttStats.SmoothedRTT()\n\tif rtt <= 0 {\n\t\treturn 10240\n\t}\n\tcwnd := congestion.ByteCount(float64(b.bps) * rtt.Seconds() * congestionWindowMultiplier / b.ackRate)\n\tif cwnd < b.maxDatagramSize {\n\t\tcwnd = b.maxDatagramSize\n\t}\n\treturn cwnd\n}\n\nfunc (b *BrutalSender) OnPacketSent(sentTime monotime.Time, bytesInFlight congestion.ByteCount,\n\tpacketNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool,\n) {\n\tb.pacer.SentPacket(sentTime, bytes)\n}\n\nfunc (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount,\n\tpriorInFlight congestion.ByteCount, eventTime monotime.Time,\n) {\n\t// Stub\n}\n\nfunc (b *BrutalSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes congestion.ByteCount,\n\tpriorInFlight congestion.ByteCount,\n) {\n\t// Stub\n}\n\nfunc (b *BrutalSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime monotime.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) {\n\tcurrentTimestamp := int64(time.Duration(eventTime) / time.Second)\n\tslot := currentTimestamp % pktInfoSlotCount\n\tif b.pktInfoSlots[slot].Timestamp == currentTimestamp {\n\t\tb.pktInfoSlots[slot].LossCount += uint64(len(lostPackets))\n\t\tb.pktInfoSlots[slot].AckCount += uint64(len(ackedPackets))\n\t} else {\n\t\t// uninitialized slot or too old, reset\n\t\tb.pktInfoSlots[slot].Timestamp = currentTimestamp\n\t\tb.pktInfoSlots[slot].AckCount = uint64(len(ackedPackets))\n\t\tb.pktInfoSlots[slot].LossCount = uint64(len(lostPackets))\n\t}\n\tb.updateAckRate(currentTimestamp)\n}\n\nfunc (b *BrutalSender) SetMaxDatagramSize(size congestion.ByteCount) {\n\tb.maxDatagramSize = size\n\tb.pacer.SetMaxDatagramSize(size)\n\tif b.debug {\n\t\tb.debugPrint(\"SetMaxDatagramSize: %d\", size)\n\t}\n}\n\nfunc (b *BrutalSender) updateAckRate(currentTimestamp int64) {\n\tminTimestamp := currentTimestamp - pktInfoSlotCount\n\tvar ackCount, lossCount uint64\n\tfor _, info := range b.pktInfoSlots {\n\t\tif info.Timestamp < minTimestamp {\n\t\t\tcontinue\n\t\t}\n\t\tackCount += info.AckCount\n\t\tlossCount += info.LossCount\n\t}\n\tif ackCount+lossCount < minSampleCount {\n\t\tb.ackRate = 1\n\t\tif b.canPrintAckRate(currentTimestamp) {\n\t\t\tb.lastAckPrintTimestamp = currentTimestamp\n\t\t\tb.debugPrint(\"Not enough samples (total=%d, ack=%d, loss=%d, rtt=%d)\",\n\t\t\t\tackCount+lossCount, ackCount, lossCount, b.rttStats.SmoothedRTT().Milliseconds())\n\t\t}\n\t\treturn\n\t}\n\trate := float64(ackCount) / float64(ackCount+lossCount)\n\tif rate < minAckRate {\n\t\tb.ackRate = minAckRate\n\t\tif b.canPrintAckRate(currentTimestamp) {\n\t\t\tb.lastAckPrintTimestamp = currentTimestamp\n\t\t\tb.debugPrint(\"ACK rate too low: %.2f, clamped to %.2f (total=%d, ack=%d, loss=%d, rtt=%d)\",\n\t\t\t\trate, minAckRate, ackCount+lossCount, ackCount, lossCount, b.rttStats.SmoothedRTT().Milliseconds())\n\t\t}\n\t\treturn\n\t}\n\tb.ackRate = rate\n\tif b.canPrintAckRate(currentTimestamp) {\n\t\tb.lastAckPrintTimestamp = currentTimestamp\n\t\tb.debugPrint(\"ACK rate: %.2f (total=%d, ack=%d, loss=%d, rtt=%d)\",\n\t\t\trate, ackCount+lossCount, ackCount, lossCount, b.rttStats.SmoothedRTT().Milliseconds())\n\t}\n}\n\nfunc (b *BrutalSender) InSlowStart() bool {\n\treturn false\n}\n\nfunc (b *BrutalSender) InRecovery() bool {\n\treturn false\n}\n\nfunc (b *BrutalSender) MaybeExitSlowStart() {}\n\nfunc (b *BrutalSender) OnRetransmissionTimeout(packetsRetransmitted bool) {}\n\nfunc (b *BrutalSender) canPrintAckRate(currentTimestamp int64) bool {\n\treturn b.debug && currentTimestamp-b.lastAckPrintTimestamp >= debugPrintInterval\n}\n\nfunc (b *BrutalSender) debugPrint(format string, a ...any) {\n\tfmt.Printf(\"[BrutalSender] [%s] %s\\n\",\n\t\ttime.Now().Format(\"15:04:05\"),\n\t\tfmt.Sprintf(format, a...))\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/common/pacer.go",
    "content": "package common\n\nimport (\n\t\"time\"\n\n\t\"github.com/apernet/quic-go/congestion\"\n\t\"github.com/apernet/quic-go/monotime\"\n)\n\nconst (\n\tmaxBurstPackets               = 10\n\tmaxBurstPacingDelayMultiplier = 4\n)\n\n// Pacer implements a token bucket pacing algorithm.\ntype Pacer struct {\n\tbudgetAtLastSent congestion.ByteCount\n\tmaxDatagramSize  congestion.ByteCount\n\tlastSentTime     monotime.Time\n\tgetBandwidth     func() congestion.ByteCount // in bytes/s\n}\n\nfunc NewPacer(getBandwidth func() congestion.ByteCount) *Pacer {\n\tp := &Pacer{\n\t\tbudgetAtLastSent: maxBurstPackets * congestion.InitialPacketSize,\n\t\tmaxDatagramSize:  congestion.InitialPacketSize,\n\t\tgetBandwidth:     getBandwidth,\n\t}\n\treturn p\n}\n\nfunc (p *Pacer) SentPacket(sendTime monotime.Time, size congestion.ByteCount) {\n\tbudget := p.Budget(sendTime)\n\tif size > budget {\n\t\tp.budgetAtLastSent = 0\n\t} else {\n\t\tp.budgetAtLastSent = budget - size\n\t}\n\tp.lastSentTime = sendTime\n}\n\nfunc (p *Pacer) Budget(now monotime.Time) congestion.ByteCount {\n\tif p.lastSentTime.IsZero() {\n\t\treturn p.maxBurstSize()\n\t}\n\tbudget := p.budgetAtLastSent + (p.getBandwidth()*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9\n\tif budget < 0 { // protect against overflows\n\t\tbudget = congestion.ByteCount(1<<62 - 1)\n\t}\n\treturn min(p.maxBurstSize(), budget)\n}\n\nfunc (p *Pacer) maxBurstSize() congestion.ByteCount {\n\treturn max(\n\t\tcongestion.ByteCount((maxBurstPacingDelayMultiplier*congestion.MinPacingDelay).Nanoseconds())*p.getBandwidth()/1e9,\n\t\tmaxBurstPackets*p.maxDatagramSize,\n\t)\n}\n\n// TimeUntilSend returns when the next packet should be sent.\n// It returns the zero value if a packet can be sent immediately.\nfunc (p *Pacer) TimeUntilSend() monotime.Time {\n\tif p.budgetAtLastSent >= p.maxDatagramSize {\n\t\treturn 0\n\t}\n\tdiff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)\n\tbw := uint64(p.getBandwidth())\n\t// We might need to round up this value.\n\t// Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires.\n\td := diff / bw\n\t// this is effectively a math.Ceil, but using only integer math\n\tif diff%bw > 0 {\n\t\td++\n\t}\n\treturn p.lastSentTime.Add(max(congestion.MinPacingDelay, time.Duration(d)*time.Nanosecond))\n}\n\nfunc (p *Pacer) SetMaxDatagramSize(s congestion.ByteCount) {\n\tp.maxDatagramSize = s\n}\n"
  },
  {
    "path": "transport/internet/hysteria/congestion/utils.go",
    "content": "package congestion\n\nimport (\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion/brutal\"\n)\n\nfunc UseBBR(conn *quic.Conn) {\n\tconn.SetCongestionControl(bbr.NewBbrSender(\n\t\tbbr.DefaultClock{},\n\t\tbbr.GetInitialPacketSize(conn.RemoteAddr()),\n\t))\n}\n\nfunc UseBrutal(conn *quic.Conn, tx uint64) {\n\tconn.SetCongestionControl(brutal.NewBrutalSender(tx))\n}\n"
  },
  {
    "path": "transport/internet/hysteria/conn.go",
    "content": "package hysteria\n\nimport (\n\t\"encoding/binary\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/apernet/quic-go/quicvarint\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n)\n\ntype interConn struct {\n\tstream *quic.Stream\n\tlocal  net.Addr\n\tremote net.Addr\n\n\tclient bool\n\tmutex  sync.Mutex\n\n\tuser *protocol.MemoryUser\n}\n\nfunc (i *interConn) User() *protocol.MemoryUser {\n\treturn i.user\n}\n\nfunc (i *interConn) Read(b []byte) (int, error) {\n\treturn i.stream.Read(b)\n}\n\nfunc (i *interConn) Write(b []byte) (int, error) {\n\tif i.client {\n\t\ti.mutex.Lock()\n\t\tdefer i.mutex.Unlock()\n\t\tif i.client {\n\t\t\tbuf := make([]byte, 0, quicvarint.Len(FrameTypeTCPRequest)+len(b))\n\t\t\tbuf = quicvarint.Append(buf, FrameTypeTCPRequest)\n\t\t\tbuf = append(buf, b...)\n\t\t\t_, err := i.stream.Write(buf)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\ti.client = false\n\t\t\treturn len(b), nil\n\t\t}\n\t}\n\n\treturn i.stream.Write(b)\n}\n\nfunc (i *interConn) Close() error {\n\ti.stream.CancelRead(0)\n\treturn i.stream.Close()\n}\n\nfunc (i *interConn) LocalAddr() net.Addr {\n\treturn i.local\n}\n\nfunc (i *interConn) RemoteAddr() net.Addr {\n\treturn i.remote\n}\n\nfunc (i *interConn) SetDeadline(t time.Time) error {\n\treturn i.stream.SetDeadline(t)\n}\n\nfunc (i *interConn) SetReadDeadline(t time.Time) error {\n\treturn i.stream.SetReadDeadline(t)\n}\n\nfunc (i *interConn) SetWriteDeadline(t time.Time) error {\n\treturn i.stream.SetWriteDeadline(t)\n}\n\ntype InterUdpConn struct {\n\tconn   *quic.Conn\n\tlocal  net.Addr\n\tremote net.Addr\n\n\tid uint32\n\tch chan []byte\n\n\tclosed    bool\n\tcloseFunc func()\n\n\tlast  time.Time\n\tmutex sync.Mutex\n\n\tuser *protocol.MemoryUser\n}\n\nfunc (i *InterUdpConn) User() *protocol.MemoryUser {\n\treturn i.user\n}\n\nfunc (i *InterUdpConn) SetLast() {\n\ti.mutex.Lock()\n\tdefer i.mutex.Unlock()\n\n\ti.last = time.Now()\n}\n\nfunc (i *InterUdpConn) GetLast() time.Time {\n\ti.mutex.Lock()\n\tdefer i.mutex.Unlock()\n\n\treturn i.last\n}\n\nfunc (i *InterUdpConn) Read(p []byte) (int, error) {\n\tb, ok := <-i.ch\n\tif !ok {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(p, b)\n\tif n != len(b) {\n\t\treturn 0, io.ErrShortBuffer\n\t}\n\n\ti.SetLast()\n\treturn n, nil\n}\n\nfunc (i *InterUdpConn) Write(p []byte) (int, error) {\n\ti.SetLast()\n\n\tbinary.BigEndian.PutUint32(p, i.id)\n\tif err := i.conn.SendDatagram(p); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(p), nil\n}\n\nfunc (i *InterUdpConn) Close() error {\n\ti.closeFunc()\n\treturn nil\n}\n\nfunc (i *InterUdpConn) LocalAddr() net.Addr {\n\treturn i.local\n}\n\nfunc (i *InterUdpConn) RemoteAddr() net.Addr {\n\treturn i.remote\n}\n\nfunc (i *InterUdpConn) SetDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (i *InterUdpConn) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (i *InterUdpConn) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/hysteria/dialer.go",
    "content": "package hysteria\n\nimport (\n\t\"context\"\n\tgo_tls \"crypto/tls\"\n\t\"encoding/binary\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/apernet/quic-go/http3\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/task\"\n\thyCtx \"github.com/xtls/xray-core/proxy/hysteria/ctx\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/udphop\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\ntype udpSessionManagerClient struct {\n\tconn   *quic.Conn\n\tm      map[uint32]*InterUdpConn\n\tnext   uint32\n\tclosed bool\n\tmutex  sync.RWMutex\n}\n\nfunc (m *udpSessionManagerClient) close(udpConn *InterUdpConn) {\n\tif !udpConn.closed {\n\t\tudpConn.closed = true\n\t\tclose(udpConn.ch)\n\t\tdelete(m.m, udpConn.id)\n\t}\n}\n\nfunc (m *udpSessionManagerClient) run() {\n\tfor {\n\t\td, err := m.conn.ReceiveDatagram(context.Background())\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif len(d) < 4 {\n\t\t\tcontinue\n\t\t}\n\t\tid := binary.BigEndian.Uint32(d[:4])\n\n\t\tm.feed(id, d)\n\t}\n\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tm.closed = true\n\n\tfor _, udpConn := range m.m {\n\t\tm.close(udpConn)\n\t}\n}\n\nfunc (m *udpSessionManagerClient) udp() (*InterUdpConn, error) {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tif m.closed {\n\t\treturn nil, errors.New(\"closed\")\n\t}\n\n\tudpConn := &InterUdpConn{\n\t\tconn:   m.conn,\n\t\tlocal:  m.conn.LocalAddr(),\n\t\tremote: m.conn.RemoteAddr(),\n\n\t\tid: m.next,\n\t\tch: make(chan []byte, udpMessageChanSize),\n\t}\n\tudpConn.closeFunc = func() {\n\t\tm.mutex.Lock()\n\t\tdefer m.mutex.Unlock()\n\t\tm.close(udpConn)\n\t}\n\tm.m[m.next] = udpConn\n\tm.next++\n\n\treturn udpConn, nil\n}\n\nfunc (m *udpSessionManagerClient) feed(id uint32, d []byte) {\n\tm.mutex.RLock()\n\tdefer m.mutex.RUnlock()\n\n\tudpConn, ok := m.m[id]\n\tif !ok {\n\t\treturn\n\t}\n\n\tselect {\n\tcase udpConn.ch <- d:\n\tdefault:\n\t}\n}\n\ntype client struct {\n\tctx            context.Context\n\tdest           net.Destination\n\tpktConn        net.PacketConn\n\tconn           *quic.Conn\n\tconfig         *Config\n\ttlsConfig      *go_tls.Config\n\tsocketConfig   *internet.SocketConfig\n\tudpmaskManager *finalmask.UdpmaskManager\n\tquicParams     *internet.QuicParams\n\n\tudpSM *udpSessionManagerClient\n\tmutex sync.Mutex\n}\n\nfunc (c *client) status() Status {\n\tif c.conn == nil {\n\t\treturn StatusUnknown\n\t}\n\tselect {\n\tcase <-c.conn.Context().Done():\n\t\treturn StatusInactive\n\tdefault:\n\t\treturn StatusActive\n\t}\n}\n\nfunc (c *client) close() {\n\t_ = c.conn.CloseWithError(closeErrCodeOK, \"\")\n\t_ = c.pktConn.Close()\n\tc.pktConn = nil\n\tc.conn = nil\n\tc.udpSM = nil\n}\n\nfunc (c *client) dial() error {\n\tstatus := c.status()\n\tif status == StatusActive {\n\t\treturn nil\n\t}\n\tif status == StatusInactive {\n\t\tc.close()\n\t}\n\n\tquicParams := c.quicParams\n\tif quicParams == nil {\n\t\tquicParams = &internet.QuicParams{}\n\t}\n\tif quicParams.UdpHop == nil {\n\t\tquicParams.UdpHop = &internet.UdpHop{}\n\t}\n\n\tvar index int\n\tif len(quicParams.UdpHop.Ports) > 0 {\n\t\tindex = rand.Intn(len(quicParams.UdpHop.Ports))\n\t\tc.dest.Port = net.Port(quicParams.UdpHop.Ports[index])\n\t}\n\n\traw, err := internet.DialSystem(c.ctx, c.dest, c.socketConfig)\n\tif err != nil {\n\t\treturn errors.New(\"failed to dial to dest\").Base(err)\n\t}\n\n\tvar pktConn net.PacketConn\n\tvar remote *net.UDPAddr\n\n\tswitch conn := raw.(type) {\n\tcase *internet.PacketConnWrapper:\n\t\tpktConn = conn.PacketConn\n\t\tremote = conn.RemoteAddr().(*net.UDPAddr)\n\tcase *net.UDPConn:\n\t\tpktConn = conn\n\t\tremote = conn.RemoteAddr().(*net.UDPAddr)\n\tcase *cnc.Connection:\n\t\tfakeConn := &internet.FakePacketConn{Conn: conn}\n\t\tpktConn = fakeConn\n\t\tremote = fakeConn.RemoteAddr().(*net.UDPAddr)\n\n\t\tif len(quicParams.UdpHop.Ports) > 0 {\n\t\t\traw.Close()\n\t\t\treturn errors.New(\"udphop requires being at the outermost level\")\n\t\t}\n\tdefault:\n\t\traw.Close()\n\t\treturn errors.New(\"unknown conn \", reflect.TypeOf(conn))\n\t}\n\n\tif len(quicParams.UdpHop.Ports) > 0 {\n\t\taddr := &udphop.UDPHopAddr{\n\t\t\tIP:    remote.IP,\n\t\t\tPorts: quicParams.UdpHop.Ports,\n\t\t}\n\t\tpktConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, c.udphopDialer, pktConn)\n\t\tif err != nil {\n\t\t\traw.Close()\n\t\t\treturn errors.New(\"udphop err\").Base(err)\n\t\t}\n\t}\n\n\tif c.udpmaskManager != nil {\n\t\tpktConn, err = c.udpmaskManager.WrapPacketConnClient(pktConn)\n\t\tif err != nil {\n\t\t\traw.Close()\n\t\t\treturn errors.New(\"mask err\").Base(err)\n\t\t}\n\t}\n\n\tquicConfig := &quic.Config{\n\t\tInitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,\n\t\tMaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,\n\t\tInitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,\n\t\tMaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,\n\t\tMaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,\n\t\tKeepAlivePeriod:                time.Duration(quicParams.KeepAlivePeriod) * time.Second,\n\t\tDisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,\n\t\tEnableDatagrams:                true,\n\t\tMaxDatagramFrameSize:           MaxDatagramFrameSize,\n\t\tDisablePathManager:             true,\n\t}\n\tif quicParams.InitStreamReceiveWindow == 0 {\n\t\tquicConfig.InitialStreamReceiveWindow = 8388608\n\t}\n\tif quicParams.MaxStreamReceiveWindow == 0 {\n\t\tquicConfig.MaxStreamReceiveWindow = 8388608\n\t}\n\tif quicParams.InitConnReceiveWindow == 0 {\n\t\tquicConfig.InitialConnectionReceiveWindow = 8388608 * 5 / 2\n\t}\n\tif quicParams.MaxConnReceiveWindow == 0 {\n\t\tquicConfig.MaxConnectionReceiveWindow = 8388608 * 5 / 2\n\t}\n\tif quicParams.MaxIdleTimeout == 0 {\n\t\tquicConfig.MaxIdleTimeout = 30 * time.Second\n\t}\n\t// if quicParams.KeepAlivePeriod == 0 {\n\t// \tquicConfig.KeepAlivePeriod = 10 * time.Second\n\t// }\n\n\tvar quicConn *quic.Conn\n\trt := &http3.Transport{\n\t\tTLSClientConfig: c.tlsConfig,\n\t\tQUICConfig:      quicConfig,\n\t\tDial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\tqc, err := quic.DialEarly(ctx, pktConn, remote, tlsCfg, cfg)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tquicConn = qc\n\t\t\treturn qc, nil\n\t\t},\n\t}\n\treq := &http.Request{\n\t\tMethod: http.MethodPost,\n\t\tURL: &url.URL{\n\t\t\tScheme: \"https\",\n\t\t\tHost:   URLHost,\n\t\t\tPath:   URLPath,\n\t\t},\n\t\tHeader: http.Header{\n\t\t\tRequestHeaderAuth:   []string{c.config.Auth},\n\t\t\tCommonHeaderCCRX:    []string{strconv.FormatUint(quicParams.BrutalDown, 10)},\n\t\t\tCommonHeaderPadding: []string{authRequestPadding.String()},\n\t\t},\n\t}\n\tresp, err := rt.RoundTrip(req)\n\tif err != nil {\n\t\tif quicConn != nil {\n\t\t\t_ = quicConn.CloseWithError(closeErrCodeProtocolError, \"\")\n\t\t}\n\t\t_ = pktConn.Close()\n\t\treturn errors.New(\"RoundTrip err\").Base(err)\n\t}\n\tif resp.StatusCode != StatusAuthOK {\n\t\t_ = quicConn.CloseWithError(closeErrCodeProtocolError, \"\")\n\t\t_ = pktConn.Close()\n\t\treturn errors.New(\"auth failed\")\n\t}\n\t_ = resp.Body.Close()\n\n\tserverUdp, _ := strconv.ParseBool(resp.Header.Get(ResponseHeaderUDPEnabled))\n\tserverAuto := resp.Header.Get(CommonHeaderCCRX)\n\tserverDown, _ := strconv.ParseUint(serverAuto, 10, 64)\n\n\tswitch quicParams.Congestion {\n\tcase \"reno\":\n\t\terrors.LogDebug(c.ctx, \"congestion reno\")\n\tcase \"bbr\":\n\t\terrors.LogDebug(c.ctx, \"congestion bbr\")\n\t\tcongestion.UseBBR(quicConn)\n\tcase \"brutal\", \"\":\n\t\tif serverAuto == \"auto\" || quicParams.BrutalUp == 0 || serverDown == 0 {\n\t\t\terrors.LogDebug(c.ctx, \"congestion bbr\")\n\t\t\tcongestion.UseBBR(quicConn)\n\t\t} else {\n\t\t\terrors.LogDebug(c.ctx, \"congestion brutal bytes per second \", min(quicParams.BrutalUp, serverDown))\n\t\t\tcongestion.UseBrutal(quicConn, min(quicParams.BrutalUp, serverDown))\n\t\t}\n\tcase \"force-brutal\":\n\t\terrors.LogDebug(c.ctx, \"congestion brutal bytes per second \", quicParams.BrutalUp)\n\t\tcongestion.UseBrutal(quicConn, quicParams.BrutalUp)\n\tdefault:\n\t\terrors.LogDebug(c.ctx, \"congestion reno\")\n\t}\n\n\tc.pktConn = pktConn\n\tc.conn = quicConn\n\tif serverUdp {\n\t\tc.udpSM = &udpSessionManagerClient{\n\t\t\tconn: quicConn,\n\t\t\tm:    make(map[uint32]*InterUdpConn),\n\t\t\tnext: 1,\n\t\t}\n\t\tgo c.udpSM.run()\n\t}\n\n\treturn nil\n}\n\nfunc (c *client) clean() {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tif c.status() == StatusInactive {\n\t\tc.close()\n\t}\n}\n\nfunc (c *client) tcp() (stat.Connection, error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\terr := c.dial()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstream, err := c.conn.OpenStream()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &interConn{\n\t\tstream: stream,\n\t\tlocal:  c.conn.LocalAddr(),\n\t\tremote: c.conn.RemoteAddr(),\n\n\t\tclient: true,\n\t}, nil\n}\n\nfunc (c *client) udp() (stat.Connection, error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\terr := c.dial()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.udpSM == nil {\n\t\treturn nil, errors.New(\"server does not support udp\")\n\t}\n\n\treturn c.udpSM.udp()\n}\n\nfunc (c *client) setCtx(ctx context.Context) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.ctx = ctx\n}\n\nfunc (c *client) udphopDialer(addr *net.UDPAddr) (net.PacketConn, error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tif c.status() != StatusActive {\n\t\terrors.LogDebug(context.Background(), \"skip hop: disconnected QUIC\")\n\t\treturn nil, errors.New()\n\t}\n\n\traw, err := internet.DialSystem(c.ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)\n\tif err != nil {\n\t\terrors.LogDebug(context.Background(), \"skip hop: failed to dial to dest\")\n\t\traw.Close()\n\t\treturn nil, errors.New()\n\t}\n\n\tvar pktConn net.PacketConn\n\n\tswitch conn := raw.(type) {\n\tcase *internet.PacketConnWrapper:\n\t\tpktConn = conn.PacketConn\n\tcase *net.UDPConn:\n\t\tpktConn = conn\n\tcase *cnc.Connection:\n\t\terrors.LogDebug(context.Background(), \"skip hop: udphop requires being at the outermost level\")\n\t\traw.Close()\n\t\treturn nil, errors.New()\n\tdefault:\n\t\terrors.LogDebug(context.Background(), \"skip hop: unknown conn \", reflect.TypeOf(conn))\n\t\traw.Close()\n\t\treturn nil, errors.New()\n\t}\n\n\treturn pktConn, nil\n}\n\ntype clientManager struct {\n\tm     map[string]*client\n\tmutex sync.Mutex\n}\n\nfunc (m *clientManager) clean() {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tfor _, c := range m.m {\n\t\tc.clean()\n\t}\n}\n\nvar manger *clientManager\n\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\ttlsConfig := tls.ConfigFromStreamSettings(streamSettings)\n\tif tlsConfig == nil {\n\t\treturn nil, errors.New(\"tls config is nil\")\n\t}\n\n\trequireDatagram := hyCtx.RequireDatagramFromContext(ctx)\n\taddr := dest.NetAddr()\n\tconfig := streamSettings.ProtocolSettings.(*Config)\n\n\tmanger.mutex.Lock()\n\tc, ok := manger.m[addr]\n\tif !ok {\n\t\tdest.Network = net.Network_UDP\n\t\tc = &client{\n\t\t\tctx:            ctx,\n\t\t\tdest:           dest,\n\t\t\tconfig:         config,\n\t\t\ttlsConfig:      tlsConfig.GetTLSConfig(),\n\t\t\tsocketConfig:   streamSettings.SocketSettings,\n\t\t\tudpmaskManager: streamSettings.UdpmaskManager,\n\t\t\tquicParams:     streamSettings.QuicParams,\n\t\t}\n\t\tmanger.m[addr] = c\n\t}\n\tc.setCtx(ctx)\n\tmanger.mutex.Unlock()\n\n\tif requireDatagram {\n\t\treturn c.udp()\n\t}\n\treturn c.tcp()\n}\n\nfunc init() {\n\tmanger = &clientManager{\n\t\tm: make(map[string]*client),\n\t}\n\t(&task.Periodic{\n\t\tInterval: 30 * time.Second,\n\t\tExecute: func() error {\n\t\t\tmanger.clean()\n\t\t\treturn nil\n\t\t},\n\t}).Start()\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, Dial))\n}\n"
  },
  {
    "path": "transport/internet/hysteria/hub.go",
    "content": "package hysteria\n\nimport (\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"encoding/binary\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/apernet/quic-go/http3\"\n\t\"github.com/apernet/quic-go/quicvarint\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol\"\n\t\"github.com/xtls/xray-core/proxy/hysteria/account\"\n\thyCtx \"github.com/xtls/xray-core/proxy/hysteria/ctx\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\ntype udpSessionManagerServer struct {\n\tconn           *quic.Conn\n\tm              map[uint32]*InterUdpConn\n\taddConn        internet.ConnHandler\n\tstopCh         chan struct{}\n\tudpIdleTimeout time.Duration\n\tmutex          sync.RWMutex\n\n\tuser *protocol.MemoryUser\n}\n\nfunc (m *udpSessionManagerServer) close(udpConn *InterUdpConn) {\n\tif !udpConn.closed {\n\t\tudpConn.closed = true\n\t\tclose(udpConn.ch)\n\t\tdelete(m.m, udpConn.id)\n\t}\n}\n\nfunc (m *udpSessionManagerServer) clean() {\n\tticker := time.NewTicker(idleCleanupInterval)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tm.mutex.RLock()\n\t\t\tnow := time.Now()\n\t\t\ttimeoutConn := make([]*InterUdpConn, 0, len(m.m))\n\t\t\tfor _, udpConn := range m.m {\n\t\t\t\tif now.Sub(udpConn.GetLast()) > m.udpIdleTimeout {\n\t\t\t\t\ttimeoutConn = append(timeoutConn, udpConn)\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.mutex.RUnlock()\n\n\t\t\tfor _, udpConn := range timeoutConn {\n\t\t\t\tm.mutex.Lock()\n\t\t\t\tm.close(udpConn)\n\t\t\t\tm.mutex.Unlock()\n\t\t\t}\n\t\tcase <-m.stopCh:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (m *udpSessionManagerServer) run() {\n\tfor {\n\t\td, err := m.conn.ReceiveDatagram(context.Background())\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif len(d) < 4 {\n\t\t\tcontinue\n\t\t}\n\t\tid := binary.BigEndian.Uint32(d[:4])\n\n\t\tm.feed(id, d)\n\t}\n\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tclose(m.stopCh)\n\n\tfor _, udpConn := range m.m {\n\t\tm.close(udpConn)\n\t}\n}\n\nfunc (m *udpSessionManagerServer) feed(id uint32, d []byte) {\n\tm.mutex.RLock()\n\tudpConn, ok := m.m[id]\n\tm.mutex.RUnlock()\n\n\tif !ok {\n\t\tm.mutex.Lock()\n\t\tudpConn, ok = m.m[id]\n\t\tif !ok {\n\t\t\tudpConn = &InterUdpConn{\n\t\t\t\tconn:   m.conn,\n\t\t\t\tlocal:  m.conn.LocalAddr(),\n\t\t\t\tremote: m.conn.RemoteAddr(),\n\n\t\t\t\tid:   id,\n\t\t\t\tch:   make(chan []byte, udpMessageChanSize),\n\t\t\t\tlast: time.Now(),\n\n\t\t\t\tuser: m.user,\n\t\t\t}\n\t\t\tudpConn.closeFunc = func() {\n\t\t\t\tm.mutex.Lock()\n\t\t\t\tdefer m.mutex.Unlock()\n\t\t\t\tm.close(udpConn)\n\t\t\t}\n\t\t\tm.m[id] = udpConn\n\t\t\tm.addConn(udpConn)\n\t\t}\n\t\tm.mutex.Unlock()\n\t}\n\n\tselect {\n\tcase udpConn.ch <- d:\n\tdefault:\n\t}\n}\n\ntype httpHandler struct {\n\tctx     context.Context\n\tconn    *quic.Conn\n\taddConn internet.ConnHandler\n\n\tconfig      *Config\n\tquicParams  *internet.QuicParams\n\tvalidator   *account.Validator\n\tmasqHandler http.Handler\n\n\tauth  bool\n\tmutex sync.Mutex\n\tuser  *protocol.MemoryUser\n}\n\nfunc (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif r.Method == http.MethodPost && r.Host == URLHost && r.URL.Path == URLPath {\n\t\th.mutex.Lock()\n\t\tdefer h.mutex.Unlock()\n\n\t\tif h.auth {\n\t\t\tw.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))\n\t\t\tw.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))\n\t\t\tw.Header().Set(CommonHeaderPadding, authResponsePadding.String())\n\t\t\tw.WriteHeader(StatusAuthOK)\n\t\t\treturn\n\t\t}\n\n\t\tauth := r.Header.Get(RequestHeaderAuth)\n\t\tclientDown, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)\n\n\t\tvar user *protocol.MemoryUser\n\t\tvar ok bool\n\t\tif h.validator != nil {\n\t\t\tuser = h.validator.Get(auth)\n\t\t} else if auth == h.config.Auth {\n\t\t\tok = true\n\t\t}\n\n\t\tif user != nil || ok {\n\t\t\th.auth = true\n\t\t\th.user = user\n\n\t\t\tswitch h.quicParams.Congestion {\n\t\t\tcase \"reno\":\n\t\t\t\terrors.LogDebug(context.Background(), h.conn.RemoteAddr(), \" \", \"congestion reno\")\n\t\t\tcase \"bbr\":\n\t\t\t\terrors.LogDebug(context.Background(), h.conn.RemoteAddr(), \" \", \"congestion bbr\")\n\t\t\t\tcongestion.UseBBR(h.conn)\n\t\t\tcase \"brutal\", \"\":\n\t\t\t\tif h.quicParams.BrutalUp == 0 || clientDown == 0 {\n\t\t\t\t\terrors.LogDebug(context.Background(), h.conn.RemoteAddr(), \" \", \"congestion bbr\")\n\t\t\t\t\tcongestion.UseBBR(h.conn)\n\t\t\t\t} else {\n\t\t\t\t\terrors.LogDebug(context.Background(), h.conn.RemoteAddr(), \" \", \"congestion brutal bytes per second \", min(h.quicParams.BrutalUp, clientDown))\n\t\t\t\t\tcongestion.UseBrutal(h.conn, min(h.quicParams.BrutalUp, clientDown))\n\t\t\t\t}\n\t\t\tcase \"force-brutal\":\n\t\t\t\terrors.LogDebug(context.Background(), h.conn.RemoteAddr(), \" \", \"congestion brutal bytes per second \", h.quicParams.BrutalUp)\n\t\t\t\tcongestion.UseBrutal(h.conn, h.quicParams.BrutalUp)\n\t\t\tdefault:\n\t\t\t\terrors.LogDebug(context.Background(), h.conn.RemoteAddr(), \" \", \"congestion reno\")\n\t\t\t}\n\n\t\t\tif hyCtx.RequireDatagramFromContext(h.ctx) {\n\t\t\t\tudpSM := &udpSessionManagerServer{\n\t\t\t\t\tconn:           h.conn,\n\t\t\t\t\tm:              make(map[uint32]*InterUdpConn),\n\t\t\t\t\taddConn:        h.addConn,\n\t\t\t\t\tstopCh:         make(chan struct{}),\n\t\t\t\t\tudpIdleTimeout: time.Duration(h.config.UdpIdleTimeout) * time.Second,\n\n\t\t\t\t\tuser: h.user,\n\t\t\t\t}\n\t\t\t\tgo udpSM.clean()\n\t\t\t\tgo udpSM.run()\n\t\t\t}\n\n\t\t\tw.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))\n\t\t\tw.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))\n\t\t\tw.Header().Set(CommonHeaderPadding, authResponsePadding.String())\n\t\t\tw.WriteHeader(StatusAuthOK)\n\t\t\treturn\n\t\t}\n\t}\n\n\th.masqHandler.ServeHTTP(w, r)\n}\n\nfunc (h *httpHandler) StreamDispatcher(ft http3.FrameType, stream *quic.Stream, err error) (bool, error) {\n\tif err != nil || !h.auth {\n\t\treturn false, nil\n\t}\n\n\tswitch ft {\n\tcase FrameTypeTCPRequest:\n\t\tif _, err := quicvarint.Read(quicvarint.NewReader(stream)); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\th.addConn(&interConn{\n\t\t\tstream: stream,\n\t\t\tlocal:  h.conn.LocalAddr(),\n\t\t\tremote: h.conn.RemoteAddr(),\n\n\t\t\tuser: h.user,\n\t\t})\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, nil\n\t}\n}\n\ntype Listener struct {\n\tctx      context.Context\n\tpktConn  net.PacketConn\n\tlistener *quic.Listener\n\taddConn  internet.ConnHandler\n\n\tconfig      *Config\n\tquicParams  *internet.QuicParams\n\tvalidator   *account.Validator\n\tmasqHandler http.Handler\n}\n\nfunc (l *Listener) handleClient(conn *quic.Conn) {\n\thandler := &httpHandler{\n\t\tctx:     l.ctx,\n\t\tconn:    conn,\n\t\taddConn: l.addConn,\n\n\t\tconfig:      l.config,\n\t\tquicParams:  l.quicParams,\n\t\tvalidator:   l.validator,\n\t\tmasqHandler: l.masqHandler,\n\t}\n\th3 := http3.Server{\n\t\tHandler:          handler,\n\t\tStreamDispatcher: handler.StreamDispatcher,\n\t}\n\terr := h3.ServeQUICConn(conn)\n\t_ = conn.CloseWithError(closeErrCodeOK, \"\")\n\terrors.LogDebug(context.Background(), conn.RemoteAddr(), \" disconnected with err \", err)\n}\n\nfunc (l *Listener) keepAccepting() {\n\tfor {\n\t\tconn, err := l.listener.Accept(context.Background())\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to accept QUIC connection\")\n\t\t\tbreak\n\t\t}\n\t\tgo l.handleClient(conn)\n\t}\n}\n\nfunc (l *Listener) Addr() net.Addr {\n\treturn l.listener.Addr()\n}\n\nfunc (l *Listener) Close() error {\n\terr := l.listener.Close()\n\t_ = l.pktConn.Close()\n\treturn err\n}\n\nfunc Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {\n\tif address.Family().IsDomain() {\n\t\treturn nil, errors.New(\"address is domain\")\n\t}\n\n\ttlsConfig := tls.ConfigFromStreamSettings(streamSettings)\n\tif tlsConfig == nil {\n\t\treturn nil, errors.New(\"tls config is nil\")\n\t}\n\n\tconfig := streamSettings.ProtocolSettings.(*Config)\n\n\tvalidator := hyCtx.ValidatorFromContext(ctx)\n\n\tif config.Auth == \"\" && validator == nil {\n\t\treturn nil, errors.New(\"validator is nil\")\n\t}\n\n\tvar masqHandler http.Handler\n\tswitch strings.ToLower(config.MasqType) {\n\tcase \"\", \"404\":\n\t\tmasqHandler = http.NotFoundHandler()\n\tcase \"file\":\n\t\tmasqHandler = http.FileServer(http.Dir(config.MasqFile))\n\tcase \"proxy\":\n\t\tu, err := url.Parse(config.MasqUrl)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttransport := http.DefaultTransport.(*http.Transport)\n\t\tif config.MasqUrlInsecure {\n\t\t\ttransport = transport.Clone()\n\t\t\ttransport.TLSClientConfig = &gotls.Config{\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t}\n\t\t}\n\t\tmasqHandler = &httputil.ReverseProxy{\n\t\t\tRewrite: func(pr *httputil.ProxyRequest) {\n\t\t\t\tpr.SetURL(u)\n\t\t\t\tif !config.MasqUrlRewriteHost {\n\t\t\t\t\tpr.Out.Host = pr.In.Host\n\t\t\t\t}\n\t\t\t},\n\t\t\tTransport: transport,\n\t\t\tErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {\n\t\t\t\tw.WriteHeader(http.StatusBadGateway)\n\t\t\t},\n\t\t}\n\tcase \"string\":\n\t\tmasqHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tfor k, v := range config.MasqStringHeaders {\n\t\t\t\tw.Header().Set(k, v)\n\t\t\t}\n\t\t\tif config.MasqStringStatusCode != 0 {\n\t\t\t\tw.WriteHeader(int(config.MasqStringStatusCode))\n\t\t\t} else {\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t}\n\t\t\t_, _ = w.Write([]byte(config.MasqString))\n\t\t})\n\tdefault:\n\t\treturn nil, errors.New(\"unknown masq type\")\n\t}\n\n\traw, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar pktConn net.PacketConn\n\tpktConn = raw\n\n\tif streamSettings.UdpmaskManager != nil {\n\t\tpktConn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)\n\t\tif err != nil {\n\t\t\traw.Close()\n\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t}\n\t}\n\n\tquicParams := streamSettings.QuicParams\n\tif quicParams == nil {\n\t\tquicParams = &internet.QuicParams{}\n\t}\n\n\tquicConfig := &quic.Config{\n\t\tInitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,\n\t\tMaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,\n\t\tInitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,\n\t\tMaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,\n\t\tMaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,\n\t\tMaxIncomingStreams:             quicParams.MaxIncomingStreams,\n\t\tDisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,\n\t\tEnableDatagrams:                true,\n\t\tMaxDatagramFrameSize:           MaxDatagramFrameSize,\n\t\tDisablePathManager:             true,\n\t}\n\tif quicParams.InitStreamReceiveWindow == 0 {\n\t\tquicConfig.InitialStreamReceiveWindow = 8388608\n\t}\n\tif quicParams.MaxStreamReceiveWindow == 0 {\n\t\tquicConfig.MaxStreamReceiveWindow = 8388608\n\t}\n\tif quicParams.InitConnReceiveWindow == 0 {\n\t\tquicConfig.InitialConnectionReceiveWindow = 8388608 * 5 / 2\n\t}\n\tif quicParams.MaxConnReceiveWindow == 0 {\n\t\tquicConfig.MaxConnectionReceiveWindow = 8388608 * 5 / 2\n\t}\n\tif quicParams.MaxIdleTimeout == 0 {\n\t\tquicConfig.MaxIdleTimeout = 30 * time.Second\n\t}\n\tif quicParams.MaxIncomingStreams == 0 {\n\t\tquicConfig.MaxIncomingStreams = 1024\n\t}\n\n\tqListener, err := quic.Listen(pktConn, tlsConfig.GetTLSConfig(), quicConfig)\n\tif err != nil {\n\t\t_ = pktConn.Close()\n\t\treturn nil, err\n\t}\n\n\tlistener := &Listener{\n\t\tctx:      ctx,\n\t\tpktConn:  pktConn,\n\t\tlistener: qListener,\n\t\taddConn:  handler,\n\n\t\tconfig:      config,\n\t\tquicParams:  quicParams,\n\t\tvalidator:   validator,\n\t\tmasqHandler: masqHandler,\n\t}\n\n\tgo listener.keepAccepting()\n\n\treturn listener, nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, Listen))\n}\n"
  },
  {
    "path": "transport/internet/hysteria/padding/padding.go",
    "content": "package padding\n\nimport (\n\t\"math/rand\"\n)\n\nconst (\n\tpaddingChars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n)\n\n// padding specifies a half-open range [Min, Max).\ntype Padding struct {\n\tMin int\n\tMax int\n}\n\nfunc (p Padding) String() string {\n\tn := p.Min + rand.Intn(p.Max-p.Min)\n\tbs := make([]byte, n)\n\tfor i := range bs {\n\t\tbs[i] = paddingChars[rand.Intn(len(paddingChars))]\n\t}\n\treturn string(bs)\n}\n"
  },
  {
    "path": "transport/internet/hysteria/udphop/addr.go",
    "content": "package udphop\n\nimport (\n\t\"fmt\"\n\t\"net\"\n)\n\ntype InvalidPortError struct {\n\tPortStr string\n}\n\nfunc (e InvalidPortError) Error() string {\n\treturn fmt.Sprintf(\"%s is not a valid port number or range\", e.PortStr)\n}\n\n// UDPHopAddr contains an IP address and a list of ports.\ntype UDPHopAddr struct {\n\tIP      net.IP\n\tPorts   []uint32\n\tPortStr string\n}\n\nfunc (a *UDPHopAddr) Network() string {\n\treturn \"udphop\"\n}\n\nfunc (a *UDPHopAddr) String() string {\n\treturn net.JoinHostPort(a.IP.String(), a.PortStr)\n}\n\n// addrs returns a list of net.Addr's, one for each port.\nfunc (a *UDPHopAddr) addrs() ([]net.Addr, error) {\n\tvar addrs []net.Addr\n\tfor _, port := range a.Ports {\n\t\taddr := &net.UDPAddr{\n\t\t\tIP:   a.IP,\n\t\t\tPort: int(port),\n\t\t}\n\t\taddrs = append(addrs, addr)\n\t}\n\treturn addrs, nil\n}\n\n// func ResolveUDPHopAddr(addr string) (*UDPHopAddr, error) {\n// \thost, portStr, err := net.SplitHostPort(addr)\n// \tif err != nil {\n// \t\treturn nil, err\n// \t}\n// \tip, err := net.ResolveIPAddr(\"ip\", host)\n// \tif err != nil {\n// \t\treturn nil, err\n// \t}\n// \tresult := &UDPHopAddr{\n// \t\tIP:      ip.IP,\n// \t\tPortStr: portStr,\n// \t}\n\n// \tpu := utils.ParsePortUnion(portStr)\n// \tif pu == nil {\n// \t\treturn nil, InvalidPortError{portStr}\n// \t}\n// \tresult.Ports = pu.Ports()\n\n// \treturn result, nil\n// }\n"
  },
  {
    "path": "transport/internet/hysteria/udphop/conn.go",
    "content": "package udphop\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"net\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\nconst (\n\tpacketQueueSize = 1024\n\tudpBufferSize   = finalmask.UDPSize\n\n\tdefaultHopInterval = 30 * time.Second\n)\n\ntype UdpHopPacketConn struct {\n\tAddr           net.Addr\n\tAddrs          []net.Addr\n\tHopIntervalMin int64\n\tHopIntervalMax int64\n\tListenUDPFunc  ListenUDPFunc\n\n\tconnMutex   sync.RWMutex\n\tprevConn    net.PacketConn\n\tcurrentConn net.PacketConn\n\taddrIndex   int\n\n\treadBufferSize  int\n\twriteBufferSize int\n\n\trecvQueue chan *udpPacket\n\tcloseChan chan struct{}\n\tclosed    bool\n\n\tbufPool sync.Pool\n}\n\ntype udpPacket struct {\n\tBuf  []byte\n\tN    int\n\tAddr net.Addr\n\tErr  error\n}\n\ntype ListenUDPFunc = func(*net.UDPAddr) (net.PacketConn, error)\n\nfunc NewUDPHopPacketConn(addr *UDPHopAddr, index int, intervalMin int64, intervalMax int64, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn) (net.PacketConn, error) {\n\tif intervalMin == 0 || intervalMax == 0 {\n\t\tintervalMin = int64(defaultHopInterval)\n\t\tintervalMax = int64(defaultHopInterval)\n\t}\n\tif intervalMin < 5 || intervalMax < 5 {\n\t\treturn nil, errors.New(\"hop interval must be at least 5 seconds\")\n\t}\n\t// if listenUDPFunc == nil {\n\t// \tlistenUDPFunc = func() (net.PacketConn, error) {\n\t// \t\treturn net.ListenUDP(\"udp\", nil)\n\t// \t}\n\t// }\n\tif listenUDPFunc == nil {\n\t\treturn nil, errors.New(\"nil listenUDPFunc\")\n\t}\n\taddrs, err := addr.addrs()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// curConn, err := listenUDPFunc()\n\t// if err != nil {\n\t// \treturn nil, err\n\t// }\n\thConn := &UdpHopPacketConn{\n\t\tAddr:           addr,\n\t\tAddrs:          addrs,\n\t\tHopIntervalMin: intervalMin,\n\t\tHopIntervalMax: intervalMax,\n\t\tListenUDPFunc:  listenUDPFunc,\n\t\tprevConn:       nil,\n\t\tcurrentConn:    pktConn,\n\t\taddrIndex:      index,\n\t\trecvQueue:      make(chan *udpPacket, packetQueueSize),\n\t\tcloseChan:      make(chan struct{}),\n\t\tbufPool: sync.Pool{\n\t\t\tNew: func() interface{} {\n\t\t\t\treturn make([]byte, udpBufferSize)\n\t\t\t},\n\t\t},\n\t}\n\tgo hConn.recvLoop(pktConn)\n\tgo hConn.hopLoop()\n\treturn hConn, nil\n}\n\nfunc (u *UdpHopPacketConn) recvLoop(conn net.PacketConn) {\n\tfor {\n\t\tbuf := u.bufPool.Get().([]byte)\n\t\tn, addr, err := conn.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\tu.bufPool.Put(buf)\n\t\t\tvar netErr net.Error\n\t\t\tif errors.As(err, &netErr) && netErr.Timeout() {\n\t\t\t\t// Only pass through timeout errors here, not permanent errors\n\t\t\t\t// like connection closed. Connection close is normal as we close\n\t\t\t\t// the old connection to exit this loop every time we hop.\n\t\t\t\tu.recvQueue <- &udpPacket{nil, 0, nil, netErr}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tselect {\n\t\tcase u.recvQueue <- &udpPacket{buf, n, addr, nil}:\n\t\t\t// Packet successfully queued\n\t\tdefault:\n\t\t\t// Queue is full, drop the packet\n\t\t\tu.bufPool.Put(buf)\n\t\t}\n\t}\n}\n\nfunc (u *UdpHopPacketConn) hopLoop() {\n\tticker := time.NewTicker(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tu.hop()\n\t\t\tticker.Reset(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second)\n\t\tcase <-u.closeChan:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (u *UdpHopPacketConn) hop() {\n\tu.connMutex.Lock()\n\tdefer u.connMutex.Unlock()\n\tif u.closed {\n\t\treturn\n\t}\n\t// Update addrIndex to a new random value\n\tu.addrIndex = rand.Intn(len(u.Addrs))\n\tnewConn, err := u.ListenUDPFunc(u.Addrs[u.addrIndex].(*net.UDPAddr))\n\tif err != nil {\n\t\t// Could be temporary, just skip this hop\n\t\treturn\n\t}\n\t// We need to keep receiving packets from the previous connection,\n\t// because otherwise there will be packet loss due to the time gap\n\t// between we hop to a new port and the server acknowledges this change.\n\t// So we do the following:\n\t// Close prevConn,\n\t// move currentConn to prevConn,\n\t// set newConn as currentConn,\n\t// start recvLoop on newConn.\n\tif u.prevConn != nil {\n\t\t_ = u.prevConn.Close() // recvLoop for this conn will exit\n\t}\n\tu.prevConn = u.currentConn\n\tu.currentConn = newConn\n\t// Set buffer sizes if previously set\n\tif u.readBufferSize > 0 {\n\t\t_ = trySetReadBuffer(u.currentConn, u.readBufferSize)\n\t}\n\tif u.writeBufferSize > 0 {\n\t\t_ = trySetWriteBuffer(u.currentConn, u.writeBufferSize)\n\t}\n\tgo u.recvLoop(newConn)\n}\n\nfunc (u *UdpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {\n\tfor {\n\t\tselect {\n\t\tcase p := <-u.recvQueue:\n\t\t\tif p.Err != nil {\n\t\t\t\treturn 0, nil, p.Err\n\t\t\t}\n\t\t\t// Currently we do not check whether the packet is from\n\t\t\t// the server or not due to performance reasons.\n\t\t\tn := copy(b, p.Buf[:p.N])\n\t\t\tu.bufPool.Put(p.Buf)\n\t\t\treturn n, u.Addr, nil\n\t\tcase <-u.closeChan:\n\t\t\treturn 0, nil, net.ErrClosed\n\t\t}\n\t}\n}\n\nfunc (u *UdpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {\n\tu.connMutex.RLock()\n\tdefer u.connMutex.RUnlock()\n\tif u.closed {\n\t\treturn 0, net.ErrClosed\n\t}\n\t// Skip the check for now, always write to the server,\n\t// for the same reason as in ReadFrom.\n\treturn u.currentConn.WriteTo(b, u.Addrs[u.addrIndex])\n}\n\nfunc (u *UdpHopPacketConn) Close() error {\n\tu.connMutex.Lock()\n\tdefer u.connMutex.Unlock()\n\tif u.closed {\n\t\treturn nil\n\t}\n\t// Close prevConn and currentConn\n\t// Close closeChan to unblock ReadFrom & hopLoop\n\t// Set closed flag to true to prevent double close\n\tif u.prevConn != nil {\n\t\t_ = u.prevConn.Close()\n\t}\n\terr := u.currentConn.Close()\n\tclose(u.closeChan)\n\tu.closed = true\n\tu.Addrs = nil // For GC\n\treturn err\n}\n\nfunc (u *UdpHopPacketConn) LocalAddr() net.Addr {\n\tu.connMutex.RLock()\n\tdefer u.connMutex.RUnlock()\n\treturn u.currentConn.LocalAddr()\n}\n\nfunc (u *UdpHopPacketConn) SetDeadline(t time.Time) error {\n\tu.connMutex.RLock()\n\tdefer u.connMutex.RUnlock()\n\tif u.prevConn != nil {\n\t\t_ = u.prevConn.SetDeadline(t)\n\t}\n\treturn u.currentConn.SetDeadline(t)\n}\n\nfunc (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {\n\tu.connMutex.RLock()\n\tdefer u.connMutex.RUnlock()\n\tif u.prevConn != nil {\n\t\t_ = u.prevConn.SetReadDeadline(t)\n\t}\n\treturn u.currentConn.SetReadDeadline(t)\n}\n\nfunc (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {\n\tu.connMutex.RLock()\n\tdefer u.connMutex.RUnlock()\n\tif u.prevConn != nil {\n\t\t_ = u.prevConn.SetWriteDeadline(t)\n\t}\n\treturn u.currentConn.SetWriteDeadline(t)\n}\n\n// UDP-specific methods below\n\nfunc (u *UdpHopPacketConn) SetReadBuffer(bytes int) error {\n\tu.connMutex.Lock()\n\tdefer u.connMutex.Unlock()\n\tu.readBufferSize = bytes\n\tif u.prevConn != nil {\n\t\t_ = trySetReadBuffer(u.prevConn, bytes)\n\t}\n\treturn trySetReadBuffer(u.currentConn, bytes)\n}\n\nfunc (u *UdpHopPacketConn) SetWriteBuffer(bytes int) error {\n\tu.connMutex.Lock()\n\tdefer u.connMutex.Unlock()\n\tu.writeBufferSize = bytes\n\tif u.prevConn != nil {\n\t\t_ = trySetWriteBuffer(u.prevConn, bytes)\n\t}\n\treturn trySetWriteBuffer(u.currentConn, bytes)\n}\n\nfunc (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {\n\tu.connMutex.RLock()\n\tdefer u.connMutex.RUnlock()\n\tsc, ok := u.currentConn.(syscall.Conn)\n\tif !ok {\n\t\treturn nil, errors.New(\"not supported\")\n\t}\n\treturn sc.SyscallConn()\n}\n\nfunc trySetReadBuffer(pc net.PacketConn, bytes int) error {\n\tsc, ok := pc.(interface {\n\t\tSetReadBuffer(bytes int) error\n\t})\n\tif ok {\n\t\treturn sc.SetReadBuffer(bytes)\n\t}\n\treturn nil\n}\n\nfunc trySetWriteBuffer(pc net.PacketConn, bytes int) error {\n\tsc, ok := pc.(interface {\n\t\tSetWriteBuffer(bytes int) error\n\t})\n\tif ok {\n\t\treturn sc.SetWriteBuffer(bytes)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/internet.go",
    "content": "package internet\n\nimport (\n\t\"net\"\n\t\"strings\"\n)\n\nfunc IsValidHTTPHost(request string, config string) bool {\n\tr := strings.ToLower(request)\n\tc := strings.ToLower(config)\n\tif strings.Contains(r, \":\") {\n\t\th, _, _ := net.SplitHostPort(r)\n\t\treturn h == c\n\t}\n\treturn r == c\n}\n"
  },
  {
    "path": "transport/internet/kcp/config.go",
    "content": "package kcp\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// GetMTUValue returns the value of MTU settings.\nfunc (c *Config) GetMTUValue() uint32 {\n\tif c == nil || c.Mtu == nil {\n\t\treturn 1350\n\t}\n\treturn c.Mtu.Value\n}\n\n// GetTTIValue returns the value of TTI settings.\nfunc (c *Config) GetTTIValue() uint32 {\n\tif c == nil || c.Tti == nil {\n\t\treturn 50\n\t}\n\treturn c.Tti.Value\n}\n\n// GetUplinkCapacityValue returns the value of UplinkCapacity settings.\nfunc (c *Config) GetUplinkCapacityValue() uint32 {\n\tif c == nil || c.UplinkCapacity == nil {\n\t\treturn 5\n\t}\n\treturn c.UplinkCapacity.Value\n}\n\n// GetDownlinkCapacityValue returns the value of DownlinkCapacity settings.\nfunc (c *Config) GetDownlinkCapacityValue() uint32 {\n\tif c == nil || c.DownlinkCapacity == nil {\n\t\treturn 20\n\t}\n\treturn c.DownlinkCapacity.Value\n}\n\n// GetWriteBufferSize returns the size of WriterBuffer in bytes.\nfunc (c *Config) GetWriteBufferSize() uint32 {\n\tif c == nil || c.WriteBuffer == nil {\n\t\treturn 2 * 1024 * 1024\n\t}\n\treturn c.WriteBuffer.Size\n}\n\n// GetReadBufferSize returns the size of ReadBuffer in bytes.\n// func (c *Config) GetReadBufferSize() uint32 {\n// \tif c == nil || c.ReadBuffer == nil {\n// \t\treturn 2 * 1024 * 1024\n// \t}\n// \treturn c.ReadBuffer.Size\n// }\n\nfunc (c *Config) GetSendingInFlightSize() uint32 {\n\tsize := c.GetUplinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue())\n\tif size < 8 {\n\t\tsize = 8\n\t}\n\treturn size\n}\n\nfunc (c *Config) GetSendingBufferSize() uint32 {\n\treturn c.GetWriteBufferSize() / c.GetMTUValue()\n}\n\nfunc (c *Config) GetReceivingInFlightSize() uint32 {\n\tsize := c.GetDownlinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue())\n\tif size < 8 {\n\t\tsize = 8\n\t}\n\treturn size\n}\n\n// func (c *Config) GetReceivingBufferSize() uint32 {\n// \treturn c.GetReadBufferSize() / c.GetMTUValue()\n// }\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/kcp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/kcp/config.proto\n\npackage kcp\n\nimport (\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Maximum Transmission Unit, in bytes.\ntype MTU struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         uint32                 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *MTU) Reset() {\n\t*x = MTU{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *MTU) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MTU) ProtoMessage() {}\n\nfunc (x *MTU) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MTU.ProtoReflect.Descriptor instead.\nfunc (*MTU) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *MTU) GetValue() uint32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\n// Transmission Time Interview, in milli-sec.\ntype TTI struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         uint32                 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TTI) Reset() {\n\t*x = TTI{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TTI) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TTI) ProtoMessage() {}\n\nfunc (x *TTI) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TTI.ProtoReflect.Descriptor instead.\nfunc (*TTI) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *TTI) GetValue() uint32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\n// Uplink capacity, in MB.\ntype UplinkCapacity struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         uint32                 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UplinkCapacity) Reset() {\n\t*x = UplinkCapacity{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UplinkCapacity) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UplinkCapacity) ProtoMessage() {}\n\nfunc (x *UplinkCapacity) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UplinkCapacity.ProtoReflect.Descriptor instead.\nfunc (*UplinkCapacity) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *UplinkCapacity) GetValue() uint32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\n// Downlink capacity, in MB.\ntype DownlinkCapacity struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tValue         uint32                 `protobuf:\"varint,1,opt,name=value,proto3\" json:\"value,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DownlinkCapacity) Reset() {\n\t*x = DownlinkCapacity{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DownlinkCapacity) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DownlinkCapacity) ProtoMessage() {}\n\nfunc (x *DownlinkCapacity) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DownlinkCapacity.ProtoReflect.Descriptor instead.\nfunc (*DownlinkCapacity) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *DownlinkCapacity) GetValue() uint32 {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn 0\n}\n\ntype WriteBuffer struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Buffer size in bytes.\n\tSize          uint32 `protobuf:\"varint,1,opt,name=size,proto3\" json:\"size,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WriteBuffer) Reset() {\n\t*x = WriteBuffer{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WriteBuffer) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WriteBuffer) ProtoMessage() {}\n\nfunc (x *WriteBuffer) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WriteBuffer.ProtoReflect.Descriptor instead.\nfunc (*WriteBuffer) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *WriteBuffer) GetSize() uint32 {\n\tif x != nil {\n\t\treturn x.Size\n\t}\n\treturn 0\n}\n\ntype ReadBuffer struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Buffer size in bytes.\n\tSize          uint32 `protobuf:\"varint,1,opt,name=size,proto3\" json:\"size,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReadBuffer) Reset() {\n\t*x = ReadBuffer{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReadBuffer) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReadBuffer) ProtoMessage() {}\n\nfunc (x *ReadBuffer) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReadBuffer.ProtoReflect.Descriptor instead.\nfunc (*ReadBuffer) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ReadBuffer) GetSize() uint32 {\n\tif x != nil {\n\t\treturn x.Size\n\t}\n\treturn 0\n}\n\ntype ConnectionReuse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEnable        bool                   `protobuf:\"varint,1,opt,name=enable,proto3\" json:\"enable,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ConnectionReuse) Reset() {\n\t*x = ConnectionReuse{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectionReuse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectionReuse) ProtoMessage() {}\n\nfunc (x *ConnectionReuse) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectionReuse.ProtoReflect.Descriptor instead.\nfunc (*ConnectionReuse) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ConnectionReuse) GetEnable() bool {\n\tif x != nil {\n\t\treturn x.Enable\n\t}\n\treturn false\n}\n\n// Pre-shared secret between client and server. It is used for traffic obfuscation.\n// Note that if seed is absent in the config, the traffic will still be obfuscated,\n// but by a predefined algorithm.\ntype EncryptionSeed struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSeed          string                 `protobuf:\"bytes,1,opt,name=seed,proto3\" json:\"seed,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *EncryptionSeed) Reset() {\n\t*x = EncryptionSeed{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *EncryptionSeed) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EncryptionSeed) ProtoMessage() {}\n\nfunc (x *EncryptionSeed) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EncryptionSeed.ProtoReflect.Descriptor instead.\nfunc (*EncryptionSeed) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *EncryptionSeed) GetSeed() string {\n\tif x != nil {\n\t\treturn x.Seed\n\t}\n\treturn \"\"\n}\n\ntype Config struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tMtu              *MTU                   `protobuf:\"bytes,1,opt,name=mtu,proto3\" json:\"mtu,omitempty\"`\n\tTti              *TTI                   `protobuf:\"bytes,2,opt,name=tti,proto3\" json:\"tti,omitempty\"`\n\tUplinkCapacity   *UplinkCapacity        `protobuf:\"bytes,3,opt,name=uplink_capacity,json=uplinkCapacity,proto3\" json:\"uplink_capacity,omitempty\"`\n\tDownlinkCapacity *DownlinkCapacity      `protobuf:\"bytes,4,opt,name=downlink_capacity,json=downlinkCapacity,proto3\" json:\"downlink_capacity,omitempty\"`\n\tCongestion       bool                   `protobuf:\"varint,5,opt,name=congestion,proto3\" json:\"congestion,omitempty\"`\n\tWriteBuffer      *WriteBuffer           `protobuf:\"bytes,6,opt,name=write_buffer,json=writeBuffer,proto3\" json:\"write_buffer,omitempty\"`\n\tReadBuffer       *ReadBuffer            `protobuf:\"bytes,7,opt,name=read_buffer,json=readBuffer,proto3\" json:\"read_buffer,omitempty\"`\n\tHeaderConfig     *serial.TypedMessage   `protobuf:\"bytes,8,opt,name=header_config,json=headerConfig,proto3\" json:\"header_config,omitempty\"`\n\tSeed             *EncryptionSeed        `protobuf:\"bytes,10,opt,name=seed,proto3\" json:\"seed,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_kcp_config_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *Config) GetMtu() *MTU {\n\tif x != nil {\n\t\treturn x.Mtu\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetTti() *TTI {\n\tif x != nil {\n\t\treturn x.Tti\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetUplinkCapacity() *UplinkCapacity {\n\tif x != nil {\n\t\treturn x.UplinkCapacity\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetDownlinkCapacity() *DownlinkCapacity {\n\tif x != nil {\n\t\treturn x.DownlinkCapacity\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetCongestion() bool {\n\tif x != nil {\n\t\treturn x.Congestion\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetWriteBuffer() *WriteBuffer {\n\tif x != nil {\n\t\treturn x.WriteBuffer\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetReadBuffer() *ReadBuffer {\n\tif x != nil {\n\t\treturn x.ReadBuffer\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetHeaderConfig() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.HeaderConfig\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetSeed() *EncryptionSeed {\n\tif x != nil {\n\t\treturn x.Seed\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_kcp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_kcp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#transport/internet/kcp/config.proto\\x12\\x1bxray.transport.internet.kcp\\x1a!common/serial/typed_message.proto\\\"\\x1b\\n\" +\n\t\"\\x03MTU\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\rR\\x05value\\\"\\x1b\\n\" +\n\t\"\\x03TTI\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\rR\\x05value\\\"&\\n\" +\n\t\"\\x0eUplinkCapacity\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\rR\\x05value\\\"(\\n\" +\n\t\"\\x10DownlinkCapacity\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x01 \\x01(\\rR\\x05value\\\"!\\n\" +\n\t\"\\vWriteBuffer\\x12\\x12\\n\" +\n\t\"\\x04size\\x18\\x01 \\x01(\\rR\\x04size\\\" \\n\" +\n\t\"\\n\" +\n\t\"ReadBuffer\\x12\\x12\\n\" +\n\t\"\\x04size\\x18\\x01 \\x01(\\rR\\x04size\\\")\\n\" +\n\t\"\\x0fConnectionReuse\\x12\\x16\\n\" +\n\t\"\\x06enable\\x18\\x01 \\x01(\\bR\\x06enable\\\"$\\n\" +\n\t\"\\x0eEncryptionSeed\\x12\\x12\\n\" +\n\t\"\\x04seed\\x18\\x01 \\x01(\\tR\\x04seed\\\"\\xe7\\x04\\n\" +\n\t\"\\x06Config\\x122\\n\" +\n\t\"\\x03mtu\\x18\\x01 \\x01(\\v2 .xray.transport.internet.kcp.MTUR\\x03mtu\\x122\\n\" +\n\t\"\\x03tti\\x18\\x02 \\x01(\\v2 .xray.transport.internet.kcp.TTIR\\x03tti\\x12T\\n\" +\n\t\"\\x0fuplink_capacity\\x18\\x03 \\x01(\\v2+.xray.transport.internet.kcp.UplinkCapacityR\\x0euplinkCapacity\\x12Z\\n\" +\n\t\"\\x11downlink_capacity\\x18\\x04 \\x01(\\v2-.xray.transport.internet.kcp.DownlinkCapacityR\\x10downlinkCapacity\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"congestion\\x18\\x05 \\x01(\\bR\\n\" +\n\t\"congestion\\x12K\\n\" +\n\t\"\\fwrite_buffer\\x18\\x06 \\x01(\\v2(.xray.transport.internet.kcp.WriteBufferR\\vwriteBuffer\\x12H\\n\" +\n\t\"\\vread_buffer\\x18\\a \\x01(\\v2'.xray.transport.internet.kcp.ReadBufferR\\n\" +\n\t\"readBuffer\\x12E\\n\" +\n\t\"\\rheader_config\\x18\\b \\x01(\\v2 .xray.common.serial.TypedMessageR\\fheaderConfig\\x12?\\n\" +\n\t\"\\x04seed\\x18\\n\" +\n\t\" \\x01(\\v2+.xray.transport.internet.kcp.EncryptionSeedR\\x04seedJ\\x04\\b\\t\\x10\\n\" +\n\t\"Bs\\n\" +\n\t\"\\x1fcom.xray.transport.internet.kcpP\\x01Z0github.com/xtls/xray-core/transport/internet/kcp\\xaa\\x02\\x1bXray.Transport.Internet.Kcpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_kcp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_kcp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_kcp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_kcp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_kcp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_kcp_config_proto_rawDesc), len(file_transport_internet_kcp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_kcp_config_proto_rawDescData\n}\n\nvar file_transport_internet_kcp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9)\nvar file_transport_internet_kcp_config_proto_goTypes = []any{\n\t(*MTU)(nil),                 // 0: xray.transport.internet.kcp.MTU\n\t(*TTI)(nil),                 // 1: xray.transport.internet.kcp.TTI\n\t(*UplinkCapacity)(nil),      // 2: xray.transport.internet.kcp.UplinkCapacity\n\t(*DownlinkCapacity)(nil),    // 3: xray.transport.internet.kcp.DownlinkCapacity\n\t(*WriteBuffer)(nil),         // 4: xray.transport.internet.kcp.WriteBuffer\n\t(*ReadBuffer)(nil),          // 5: xray.transport.internet.kcp.ReadBuffer\n\t(*ConnectionReuse)(nil),     // 6: xray.transport.internet.kcp.ConnectionReuse\n\t(*EncryptionSeed)(nil),      // 7: xray.transport.internet.kcp.EncryptionSeed\n\t(*Config)(nil),              // 8: xray.transport.internet.kcp.Config\n\t(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage\n}\nvar file_transport_internet_kcp_config_proto_depIdxs = []int32{\n\t0, // 0: xray.transport.internet.kcp.Config.mtu:type_name -> xray.transport.internet.kcp.MTU\n\t1, // 1: xray.transport.internet.kcp.Config.tti:type_name -> xray.transport.internet.kcp.TTI\n\t2, // 2: xray.transport.internet.kcp.Config.uplink_capacity:type_name -> xray.transport.internet.kcp.UplinkCapacity\n\t3, // 3: xray.transport.internet.kcp.Config.downlink_capacity:type_name -> xray.transport.internet.kcp.DownlinkCapacity\n\t4, // 4: xray.transport.internet.kcp.Config.write_buffer:type_name -> xray.transport.internet.kcp.WriteBuffer\n\t5, // 5: xray.transport.internet.kcp.Config.read_buffer:type_name -> xray.transport.internet.kcp.ReadBuffer\n\t9, // 6: xray.transport.internet.kcp.Config.header_config:type_name -> xray.common.serial.TypedMessage\n\t7, // 7: xray.transport.internet.kcp.Config.seed:type_name -> xray.transport.internet.kcp.EncryptionSeed\n\t8, // [8:8] is the sub-list for method output_type\n\t8, // [8:8] is the sub-list for method input_type\n\t8, // [8:8] is the sub-list for extension type_name\n\t8, // [8:8] is the sub-list for extension extendee\n\t0, // [0:8] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_kcp_config_proto_init() }\nfunc file_transport_internet_kcp_config_proto_init() {\n\tif File_transport_internet_kcp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_kcp_config_proto_rawDesc), len(file_transport_internet_kcp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   9,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_kcp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_kcp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_kcp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_kcp_config_proto = out.File\n\tfile_transport_internet_kcp_config_proto_goTypes = nil\n\tfile_transport_internet_kcp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/kcp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.kcp;\noption csharp_namespace = \"Xray.Transport.Internet.Kcp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/kcp\";\noption java_package = \"com.xray.transport.internet.kcp\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\n\n// Maximum Transmission Unit, in bytes.\nmessage MTU {\n  uint32 value = 1;\n}\n\n// Transmission Time Interview, in milli-sec.\nmessage TTI {\n  uint32 value = 1;\n}\n\n// Uplink capacity, in MB.\nmessage UplinkCapacity {\n  uint32 value = 1;\n}\n\n// Downlink capacity, in MB.\nmessage DownlinkCapacity {\n  uint32 value = 1;\n}\n\nmessage WriteBuffer {\n  // Buffer size in bytes.\n  uint32 size = 1;\n}\n\nmessage ReadBuffer {\n  // Buffer size in bytes.\n  uint32 size = 1;\n}\n\nmessage ConnectionReuse {\n  bool enable = 1;\n}\n\n// Pre-shared secret between client and server. It is used for traffic obfuscation.\n// Note that if seed is absent in the config, the traffic will still be obfuscated,\n// but by a predefined algorithm.\nmessage EncryptionSeed {\n  string seed = 1;\n}\n\nmessage Config {\n  MTU mtu = 1;\n  TTI tti = 2;\n  UplinkCapacity uplink_capacity = 3;\n  DownlinkCapacity downlink_capacity = 4;\n  bool congestion = 5;\n  WriteBuffer write_buffer = 6;\n  ReadBuffer read_buffer = 7;\n  xray.common.serial.TypedMessage header_config = 8;\n  reserved 9;\n  EncryptionSeed seed = 10;\n}\n"
  },
  {
    "path": "transport/internet/kcp/connection.go",
    "content": "package kcp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/signal/semaphore\"\n)\n\nvar (\n\tErrIOTimeout        = errors.New(\"Read/Write timeout\")\n\tErrClosedListener   = errors.New(\"Listener closed.\")\n\tErrClosedConnection = errors.New(\"Connection closed.\")\n)\n\n// State of the connection\ntype State int32\n\n// Is returns true if current State is one of the candidates.\nfunc (s State) Is(states ...State) bool {\n\tfor _, state := range states {\n\t\tif s == state {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nconst (\n\tStateActive          State = 0 // Connection is active\n\tStateReadyToClose    State = 1 // Connection is closed locally\n\tStatePeerClosed      State = 2 // Connection is closed on remote\n\tStateTerminating     State = 3 // Connection is ready to be destroyed locally\n\tStatePeerTerminating State = 4 // Connection is ready to be destroyed on remote\n\tStateTerminated      State = 5 // Connection is destroyed.\n)\n\nfunc nowMillisec() int64 {\n\tnow := time.Now()\n\treturn now.Unix()*1000 + int64(now.Nanosecond()/1000000)\n}\n\ntype RoundTripInfo struct {\n\tsync.RWMutex\n\tvariation        uint32\n\tsrtt             uint32\n\trto              uint32\n\tminRtt           uint32\n\tupdatedTimestamp uint32\n}\n\nfunc (info *RoundTripInfo) UpdatePeerRTO(rto uint32, current uint32) {\n\tinfo.Lock()\n\tdefer info.Unlock()\n\n\tif current-info.updatedTimestamp < 3000 {\n\t\treturn\n\t}\n\n\tinfo.updatedTimestamp = current\n\tinfo.rto = rto\n}\n\nfunc (info *RoundTripInfo) Update(rtt uint32, current uint32) {\n\tif rtt > 0x7FFFFFFF {\n\t\treturn\n\t}\n\tinfo.Lock()\n\tdefer info.Unlock()\n\n\t// https://tools.ietf.org/html/rfc6298\n\tif info.srtt == 0 {\n\t\tinfo.srtt = rtt\n\t\tinfo.variation = rtt / 2\n\t} else {\n\t\tdelta := rtt - info.srtt\n\t\tif info.srtt > rtt {\n\t\t\tdelta = info.srtt - rtt\n\t\t}\n\t\tinfo.variation = (3*info.variation + delta) / 4\n\t\tinfo.srtt = (7*info.srtt + rtt) / 8\n\t\tif info.srtt < info.minRtt {\n\t\t\tinfo.srtt = info.minRtt\n\t\t}\n\t}\n\tvar rto uint32\n\tif info.minRtt < 4*info.variation {\n\t\trto = info.srtt + 4*info.variation\n\t} else {\n\t\trto = info.srtt + info.variation\n\t}\n\n\tif rto > 10000 {\n\t\trto = 10000\n\t}\n\tinfo.rto = rto * 5 / 4\n\tinfo.updatedTimestamp = current\n}\n\nfunc (info *RoundTripInfo) Timeout() uint32 {\n\tinfo.RLock()\n\tdefer info.RUnlock()\n\n\treturn info.rto\n}\n\nfunc (info *RoundTripInfo) SmoothedTime() uint32 {\n\tinfo.RLock()\n\tdefer info.RUnlock()\n\n\treturn info.srtt\n}\n\ntype Updater struct {\n\tinterval        int64\n\tshouldContinue  func() bool\n\tshouldTerminate func() bool\n\tupdateFunc      func()\n\tnotifier        *semaphore.Instance\n}\n\nfunc NewUpdater(interval uint32, shouldContinue func() bool, shouldTerminate func() bool, updateFunc func()) *Updater {\n\tu := &Updater{\n\t\tinterval:        int64(time.Duration(interval) * time.Millisecond),\n\t\tshouldContinue:  shouldContinue,\n\t\tshouldTerminate: shouldTerminate,\n\t\tupdateFunc:      updateFunc,\n\t\tnotifier:        semaphore.New(1),\n\t}\n\treturn u\n}\n\nfunc (u *Updater) WakeUp() {\n\tselect {\n\tcase <-u.notifier.Wait():\n\t\tgo u.run()\n\tdefault:\n\t}\n}\n\nfunc (u *Updater) run() {\n\tdefer u.notifier.Signal()\n\n\tif u.shouldTerminate() {\n\t\treturn\n\t}\n\tticker := time.NewTicker(u.Interval())\n\tfor u.shouldContinue() {\n\t\tu.updateFunc()\n\t\t<-ticker.C\n\t}\n\tticker.Stop()\n}\n\nfunc (u *Updater) Interval() time.Duration {\n\treturn time.Duration(atomic.LoadInt64(&u.interval))\n}\n\nfunc (u *Updater) SetInterval(d time.Duration) {\n\tatomic.StoreInt64(&u.interval, int64(d))\n}\n\ntype ConnMetadata struct {\n\tLocalAddr    net.Addr\n\tRemoteAddr   net.Addr\n\tConversation uint16\n}\n\n// Connection is a KCP connection over UDP.\ntype Connection struct {\n\tmeta       ConnMetadata\n\tcloser     io.Closer\n\trd         time.Time\n\twd         time.Time // write deadline\n\tsince      int64\n\tdataInput  *signal.Notifier\n\tdataOutput *signal.Notifier\n\tConfig     *Config\n\n\tstate            State\n\tstateBeginTime   uint32\n\tlastIncomingTime uint32\n\tlastPingTime     uint32\n\n\tmss       uint32\n\troundTrip *RoundTripInfo\n\n\treceivingWorker *ReceivingWorker\n\tsendingWorker   *SendingWorker\n\n\toutput SegmentWriter\n\n\tdataUpdater *Updater\n\tpingUpdater *Updater\n}\n\n// NewConnection create a new KCP connection between local and remote.\nfunc NewConnection(meta ConnMetadata, writer io.Writer, closer io.Closer, config *Config) *Connection {\n\terrors.LogInfo(context.Background(), \"#\", meta.Conversation, \" creating connection to \", meta.RemoteAddr)\n\n\tconn := &Connection{\n\t\tmeta:       meta,\n\t\tcloser:     closer,\n\t\tsince:      nowMillisec(),\n\t\tdataInput:  signal.NewNotifier(),\n\t\tdataOutput: signal.NewNotifier(),\n\t\tConfig:     config,\n\t\toutput:     NewRetryableWriter(NewSegmentWriter(writer)),\n\t\tmss:        config.GetMTUValue() - DataSegmentOverhead,\n\t\troundTrip: &RoundTripInfo{\n\t\t\trto:    100,\n\t\t\tminRtt: config.GetTTIValue(),\n\t\t},\n\t}\n\n\tconn.receivingWorker = NewReceivingWorker(conn)\n\tconn.sendingWorker = NewSendingWorker(conn)\n\n\tisTerminating := func() bool {\n\t\treturn conn.State().Is(StateTerminating, StateTerminated)\n\t}\n\tisTerminated := func() bool {\n\t\treturn conn.State() == StateTerminated\n\t}\n\tconn.dataUpdater = NewUpdater(\n\t\tconfig.GetTTIValue(),\n\t\tfunc() bool {\n\t\t\treturn !isTerminating() && (conn.sendingWorker.UpdateNecessary() || conn.receivingWorker.UpdateNecessary())\n\t\t},\n\t\tisTerminating,\n\t\tconn.updateTask)\n\tconn.pingUpdater = NewUpdater(\n\t\t5000, // 5 seconds\n\t\tfunc() bool { return !isTerminated() },\n\t\tisTerminated,\n\t\tconn.updateTask)\n\tconn.pingUpdater.WakeUp()\n\n\treturn conn\n}\n\nfunc (c *Connection) Elapsed() uint32 {\n\treturn uint32(nowMillisec() - c.since)\n}\n\n// ReadMultiBuffer implements buf.Reader.\nfunc (c *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tif c == nil {\n\t\treturn nil, io.EOF\n\t}\n\n\tfor {\n\t\tif c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) {\n\t\t\treturn nil, io.EOF\n\t\t}\n\t\tmb := c.receivingWorker.ReadMultiBuffer()\n\t\tif !mb.IsEmpty() {\n\t\t\tc.dataUpdater.WakeUp()\n\t\t\treturn mb, nil\n\t\t}\n\n\t\tif c.State() == StatePeerTerminating {\n\t\t\treturn nil, io.EOF\n\t\t}\n\n\t\tif err := c.waitForDataInput(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n}\n\nfunc (c *Connection) waitForDataInput() error {\n\tfor i := 0; i < 16; i++ {\n\t\tselect {\n\t\tcase <-c.dataInput.Wait():\n\t\t\treturn nil\n\t\tdefault:\n\t\t\truntime.Gosched()\n\t\t}\n\t}\n\n\tduration := time.Second * 16\n\tif !c.rd.IsZero() {\n\t\tduration = time.Until(c.rd)\n\t\tif duration < 0 {\n\t\t\treturn ErrIOTimeout\n\t\t}\n\t}\n\n\ttimeout := time.NewTimer(duration)\n\tdefer timeout.Stop()\n\n\tselect {\n\tcase <-c.dataInput.Wait():\n\tcase <-timeout.C:\n\t\tif !c.rd.IsZero() && c.rd.Before(time.Now()) {\n\t\t\treturn ErrIOTimeout\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Read implements the Conn Read method.\nfunc (c *Connection) Read(b []byte) (int, error) {\n\tif c == nil {\n\t\treturn 0, io.EOF\n\t}\n\n\tfor {\n\t\tif c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\tnBytes := c.receivingWorker.Read(b)\n\t\tif nBytes > 0 {\n\t\t\tc.dataUpdater.WakeUp()\n\t\t\treturn nBytes, nil\n\t\t}\n\n\t\tif err := c.waitForDataInput(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n}\n\nfunc (c *Connection) waitForDataOutput() error {\n\tfor i := 0; i < 16; i++ {\n\t\tselect {\n\t\tcase <-c.dataOutput.Wait():\n\t\t\treturn nil\n\t\tdefault:\n\t\t\truntime.Gosched()\n\t\t}\n\t}\n\n\tduration := time.Second * 16\n\tif !c.wd.IsZero() {\n\t\tduration = time.Until(c.wd)\n\t\tif duration < 0 {\n\t\t\treturn ErrIOTimeout\n\t\t}\n\t}\n\n\ttimeout := time.NewTimer(duration)\n\tdefer timeout.Stop()\n\n\tselect {\n\tcase <-c.dataOutput.Wait():\n\tcase <-timeout.C:\n\t\tif !c.wd.IsZero() && c.wd.Before(time.Now()) {\n\t\t\treturn ErrIOTimeout\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Write implements io.Writer.\nfunc (c *Connection) Write(b []byte) (int, error) {\n\treader := bytes.NewReader(b)\n\tif err := c.writeMultiBufferInternal(reader); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(b), nil\n}\n\n// WriteMultiBuffer implements buf.Writer.\nfunc (c *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\treader := &buf.MultiBufferContainer{\n\t\tMultiBuffer: mb,\n\t}\n\tdefer reader.Close()\n\n\treturn c.writeMultiBufferInternal(reader)\n}\n\nfunc (c *Connection) writeMultiBufferInternal(reader io.Reader) error {\n\tupdatePending := false\n\tdefer func() {\n\t\tif updatePending {\n\t\t\tc.dataUpdater.WakeUp()\n\t\t}\n\t}()\n\n\tvar b *buf.Buffer\n\tdefer b.Release()\n\n\tfor {\n\t\tfor {\n\t\t\tif c == nil || c.State() != StateActive {\n\t\t\t\treturn io.ErrClosedPipe\n\t\t\t}\n\n\t\t\tif b == nil {\n\t\t\t\tb = buf.New()\n\t\t\t\t_, err := b.ReadFrom(io.LimitReader(reader, int64(c.mss)))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !c.sendingWorker.Push(b) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tupdatePending = true\n\t\t\tb = nil\n\t\t}\n\n\t\tif updatePending {\n\t\t\tc.dataUpdater.WakeUp()\n\t\t\tupdatePending = false\n\t\t}\n\n\t\tif err := c.waitForDataOutput(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (c *Connection) SetState(state State) {\n\tcurrent := c.Elapsed()\n\tatomic.StoreInt32((*int32)(&c.state), int32(state))\n\tatomic.StoreUint32(&c.stateBeginTime, current)\n\terrors.LogDebug(context.Background(), \"#\", c.meta.Conversation, \" entering state \", state, \" at \", current)\n\n\tswitch state {\n\tcase StateReadyToClose:\n\t\tc.receivingWorker.CloseRead()\n\tcase StatePeerClosed:\n\t\tc.sendingWorker.CloseWrite()\n\tcase StateTerminating:\n\t\tc.receivingWorker.CloseRead()\n\t\tc.sendingWorker.CloseWrite()\n\t\tc.pingUpdater.SetInterval(time.Second)\n\tcase StatePeerTerminating:\n\t\tc.sendingWorker.CloseWrite()\n\t\tc.pingUpdater.SetInterval(time.Second)\n\tcase StateTerminated:\n\t\tc.receivingWorker.CloseRead()\n\t\tc.sendingWorker.CloseWrite()\n\t\tc.pingUpdater.SetInterval(time.Second)\n\t\tc.dataUpdater.WakeUp()\n\t\tc.pingUpdater.WakeUp()\n\t\tgo c.Terminate()\n\t}\n}\n\n// Close closes the connection.\nfunc (c *Connection) Close() error {\n\tif c == nil {\n\t\treturn ErrClosedConnection\n\t}\n\n\tc.dataInput.Signal()\n\tc.dataOutput.Signal()\n\n\tswitch c.State() {\n\tcase StateReadyToClose, StateTerminating, StateTerminated:\n\t\treturn ErrClosedConnection\n\tcase StateActive:\n\t\tc.SetState(StateReadyToClose)\n\tcase StatePeerClosed:\n\t\tc.SetState(StateTerminating)\n\tcase StatePeerTerminating:\n\t\tc.SetState(StateTerminated)\n\t}\n\n\terrors.LogInfo(context.Background(), \"#\", c.meta.Conversation, \" closing connection to \", c.meta.RemoteAddr)\n\n\treturn nil\n}\n\n// LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it.\nfunc (c *Connection) LocalAddr() net.Addr {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.meta.LocalAddr\n}\n\n// RemoteAddr returns the remote network address. The Addr returned is shared by all invocations of RemoteAddr, so do not modify it.\nfunc (c *Connection) RemoteAddr() net.Addr {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.meta.RemoteAddr\n}\n\n// SetDeadline sets the deadline associated with the listener. A zero time value disables the deadline.\nfunc (c *Connection) SetDeadline(t time.Time) error {\n\tif err := c.SetReadDeadline(t); err != nil {\n\t\treturn err\n\t}\n\treturn c.SetWriteDeadline(t)\n}\n\n// SetReadDeadline implements the Conn SetReadDeadline method.\nfunc (c *Connection) SetReadDeadline(t time.Time) error {\n\tif c == nil || c.State() != StateActive {\n\t\treturn ErrClosedConnection\n\t}\n\tc.rd = t\n\treturn nil\n}\n\n// SetWriteDeadline implements the Conn SetWriteDeadline method.\nfunc (c *Connection) SetWriteDeadline(t time.Time) error {\n\tif c == nil || c.State() != StateActive {\n\t\treturn ErrClosedConnection\n\t}\n\tc.wd = t\n\treturn nil\n}\n\n// kcp update, input loop\nfunc (c *Connection) updateTask() {\n\tc.flush()\n}\n\nfunc (c *Connection) Terminate() {\n\tif c == nil {\n\t\treturn\n\t}\n\terrors.LogInfo(context.Background(), \"#\", c.meta.Conversation, \" terminating connection to \", c.RemoteAddr())\n\n\t// v.SetState(StateTerminated)\n\tc.dataInput.Signal()\n\tc.dataOutput.Signal()\n\n\tc.closer.Close()\n\tc.sendingWorker.Release()\n\tc.receivingWorker.Release()\n}\n\nfunc (c *Connection) HandleOption(opt SegmentOption) {\n\tif (opt & SegmentOptionClose) == SegmentOptionClose {\n\t\tc.OnPeerClosed()\n\t}\n}\n\nfunc (c *Connection) OnPeerClosed() {\n\tswitch c.State() {\n\tcase StateReadyToClose:\n\t\tc.SetState(StateTerminating)\n\tcase StateActive:\n\t\tc.SetState(StatePeerClosed)\n\t}\n}\n\n// Input when you received a low level packet (eg. UDP packet), call it\nfunc (c *Connection) Input(segments []Segment) {\n\tcurrent := c.Elapsed()\n\tatomic.StoreUint32(&c.lastIncomingTime, current)\n\n\tfor _, seg := range segments {\n\t\tif seg.Conversation() != c.meta.Conversation {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch seg := seg.(type) {\n\t\tcase *DataSegment:\n\t\t\tc.HandleOption(seg.Option)\n\t\t\tc.receivingWorker.ProcessSegment(seg)\n\t\t\tif c.receivingWorker.IsDataAvailable() {\n\t\t\t\tc.dataInput.Signal()\n\t\t\t}\n\t\t\tc.dataUpdater.WakeUp()\n\t\tcase *AckSegment:\n\t\t\tc.HandleOption(seg.Option)\n\t\t\tc.sendingWorker.ProcessSegment(current, seg, c.roundTrip.Timeout())\n\t\t\tc.dataOutput.Signal()\n\t\t\tc.dataUpdater.WakeUp()\n\t\tcase *CmdOnlySegment:\n\t\t\tc.HandleOption(seg.Option)\n\t\t\tif seg.Command() == CommandTerminate {\n\t\t\t\tswitch c.State() {\n\t\t\t\tcase StateActive, StatePeerClosed:\n\t\t\t\t\tc.SetState(StatePeerTerminating)\n\t\t\t\tcase StateReadyToClose:\n\t\t\t\t\tc.SetState(StateTerminating)\n\t\t\t\tcase StateTerminating:\n\t\t\t\t\tc.SetState(StateTerminated)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif seg.Option == SegmentOptionClose || seg.Command() == CommandTerminate {\n\t\t\t\tc.dataInput.Signal()\n\t\t\t\tc.dataOutput.Signal()\n\t\t\t}\n\t\t\tc.sendingWorker.ProcessReceivingNext(seg.ReceivingNext)\n\t\t\tc.receivingWorker.ProcessSendingNext(seg.SendingNext)\n\t\t\tc.roundTrip.UpdatePeerRTO(seg.PeerRTO, current)\n\t\t\tseg.Release()\n\t\tdefault:\n\t\t}\n\t}\n}\n\nfunc (c *Connection) flush() {\n\tcurrent := c.Elapsed()\n\n\tif c.State() == StateTerminated {\n\t\treturn\n\t}\n\tif c.State() == StateActive && current-atomic.LoadUint32(&c.lastIncomingTime) >= 30000 {\n\t\tc.Close()\n\t}\n\tif c.State() == StateReadyToClose && c.sendingWorker.IsEmpty() {\n\t\tc.SetState(StateTerminating)\n\t}\n\n\tif c.State() == StateTerminating {\n\t\terrors.LogDebug(context.Background(), \"#\", c.meta.Conversation, \" sending terminating cmd.\")\n\t\tc.Ping(current, CommandTerminate)\n\n\t\tif current-atomic.LoadUint32(&c.stateBeginTime) > 8000 {\n\t\t\tc.SetState(StateTerminated)\n\t\t}\n\t\treturn\n\t}\n\tif c.State() == StatePeerTerminating && current-atomic.LoadUint32(&c.stateBeginTime) > 4000 {\n\t\tc.SetState(StateTerminating)\n\t}\n\n\tif c.State() == StateReadyToClose && current-atomic.LoadUint32(&c.stateBeginTime) > 15000 {\n\t\tc.SetState(StateTerminating)\n\t}\n\n\t// flush acknowledges\n\tc.receivingWorker.Flush(current)\n\tc.sendingWorker.Flush(current)\n\n\tif current-atomic.LoadUint32(&c.lastPingTime) >= 3000 {\n\t\tc.Ping(current, CommandPing)\n\t}\n}\n\nfunc (c *Connection) State() State {\n\treturn State(atomic.LoadInt32((*int32)(&c.state)))\n}\n\nfunc (c *Connection) Ping(current uint32, cmd Command) {\n\tseg := NewCmdOnlySegment()\n\tseg.Conv = c.meta.Conversation\n\tseg.Cmd = cmd\n\tseg.ReceivingNext = c.receivingWorker.NextNumber()\n\tseg.SendingNext = c.sendingWorker.FirstUnacknowledged()\n\tseg.PeerRTO = c.roundTrip.Timeout()\n\tif c.State() == StateReadyToClose {\n\t\tseg.Option = SegmentOptionClose\n\t}\n\tc.output.Write(seg)\n\tatomic.StoreUint32(&c.lastPingTime, current)\n\tseg.Release()\n}\n"
  },
  {
    "path": "transport/internet/kcp/connection_test.go",
    "content": "package kcp_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/transport/internet/kcp\"\n)\n\ntype NoOpCloser int\n\nfunc (NoOpCloser) Close() error {\n\treturn nil\n}\n\nfunc TestConnectionReadTimeout(t *testing.T) {\n\tconn := NewConnection(ConnMetadata{Conversation: 1}, buf.DiscardBytes, NoOpCloser(0), &Config{})\n\tconn.SetReadDeadline(time.Now().Add(time.Second))\n\n\tb := make([]byte, 1024)\n\tnBytes, err := conn.Read(b)\n\tif nBytes != 0 || err == nil {\n\t\tt.Error(\"unexpected read: \", nBytes, err)\n\t}\n\n\tconn.Terminate()\n}\n\nfunc TestConnectionInterface(t *testing.T) {\n\t_ = (io.Writer)(new(Connection))\n\t_ = (io.Reader)(new(Connection))\n\t_ = (buf.Reader)(new(Connection))\n\t_ = (buf.Writer)(new(Connection))\n}\n"
  },
  {
    "path": "transport/internet/kcp/dialer.go",
    "content": "package kcp\n\nimport (\n\t\"context\"\n\t\"io\"\n\treflect \"reflect\"\n\t\"sync/atomic\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/dice\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nvar globalConv = uint32(dice.RollUint16())\n\nfunc fetchInput(_ context.Context, input io.Reader, reader PacketReader, conn *Connection) {\n\tcache := make(chan *buf.Buffer, 1024)\n\tgo func() {\n\t\tfor {\n\t\t\tpayload := buf.New()\n\t\t\tif _, err := payload.ReadFrom(input); err != nil {\n\t\t\t\tpayload.Release()\n\t\t\t\tclose(cache)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase cache <- payload:\n\t\t\tdefault:\n\t\t\t\tpayload.Release()\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor payload := range cache {\n\t\tsegments := reader.Read(payload.Bytes())\n\t\tpayload.Release()\n\t\tif len(segments) > 0 {\n\t\t\tconn.Input(segments)\n\t\t}\n\t}\n}\n\n// DialKCP dials a new KCP connections to the specific destination.\nfunc DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\tdest.Network = net.Network_UDP\n\terrors.LogInfo(ctx, \"dialing mKCP to \", dest)\n\n\tconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to dial to dest: \", err).AtWarning().Base(err)\n\t}\n\n\tif streamSettings.UdpmaskManager != nil {\n\t\tswitch c := conn.(type) {\n\t\tcase *internet.PacketConnWrapper:\n\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c.PacketConn)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t}\n\t\t\tc.PacketConn = pktConn\n\t\tcase *net.UDPConn:\n\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t}\n\t\t\tconn = &internet.PacketConnWrapper{\n\t\t\t\tPacketConn: pktConn,\n\t\t\t\tDest:       c.RemoteAddr().(*net.UDPAddr),\n\t\t\t}\n\t\tcase *cnc.Connection:\n\t\t\tfakeConn := &internet.FakePacketConn{Conn: c}\n\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t}\n\t\t\tconn = &internet.PacketConnWrapper{\n\t\t\t\tPacketConn: pktConn,\n\t\t\t\tDest: &net.UDPAddr{\n\t\t\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\t\t\tPort: 0,\n\t\t\t\t},\n\t\t\t}\n\t\tdefault:\n\t\t\tconn.Close()\n\t\t\treturn nil, errors.New(\"unknown conn \", reflect.TypeOf(c))\n\t\t}\n\t}\n\n\tkcpSettings := streamSettings.ProtocolSettings.(*Config)\n\n\treader := &KCPPacketReader{}\n\n\tconv := uint16(atomic.AddUint32(&globalConv, 1))\n\tsession := NewConnection(ConnMetadata{\n\t\tLocalAddr:    conn.LocalAddr(),\n\t\tRemoteAddr:   conn.RemoteAddr(),\n\t\tConversation: conv,\n\t}, conn, conn, kcpSettings)\n\n\tgo fetchInput(ctx, conn, reader, session)\n\n\tvar iConn stat.Connection = session\n\n\tif config := tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tiConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest)))\n\t}\n\n\treturn iConn, nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, DialKCP))\n}\n"
  },
  {
    "path": "transport/internet/kcp/io.go",
    "content": "package kcp\n\ntype PacketReader interface {\n\tRead([]byte) []Segment\n}\n\ntype KCPPacketReader struct{}\n\nfunc (r *KCPPacketReader) Read(b []byte) []Segment {\n\tvar result []Segment\n\tfor len(b) > 0 {\n\t\tseg, x := ReadSegment(b)\n\t\tif seg == nil {\n\t\t\tbreak\n\t\t}\n\t\tresult = append(result, seg)\n\t\tb = x\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "transport/internet/kcp/io_test.go",
    "content": "package kcp_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/transport/internet/kcp\"\n)\n\nfunc TestKCPPacketReader(t *testing.T) {\n\treader := KCPPacketReader{}\n\n\ttestCases := []struct {\n\t\tInput  []byte\n\t\tOutput []Segment\n\t}{\n\t\t{\n\t\t\tInput:  []byte{},\n\t\t\tOutput: nil,\n\t\t},\n\t\t{\n\t\t\tInput:  []byte{1},\n\t\t\tOutput: nil,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tseg := reader.Read(testCase.Input)\n\t\tif testCase.Output == nil && seg != nil {\n\t\t\tt.Errorf(\"Expect nothing returned, but actually %v\", seg)\n\t\t} else if testCase.Output != nil && seg == nil {\n\t\t\tt.Errorf(\"Expect some output, but got nil\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "transport/internet/kcp/kcp.go",
    "content": "// Package kcp - A Fast and Reliable ARQ Protocol\n//\n// Acknowledgement:\n//\n//\tskywind3000@github for inventing the KCP protocol\n//\txtaci@github for translating to Golang\npackage kcp\n\nconst protocolName = \"mkcp\"\n"
  },
  {
    "path": "transport/internet/kcp/kcp_test.go",
    "content": "package kcp_test\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t. \"github.com/xtls/xray-core/transport/internet/kcp\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"golang.org/x/sync/errgroup\"\n)\n\nfunc TestDialAndListen(t *testing.T) {\n\tlisterner, err := NewListener(context.Background(), net.LocalHostIP, net.Port(0), &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"mkcp\",\n\t\tProtocolSettings: &Config{},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tpayload := make([]byte, 4096)\n\t\t\tfor {\n\t\t\t\tnBytes, err := c.Read(payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tfor idx, b := range payload[:nBytes] {\n\t\t\t\t\tpayload[idx] = b ^ 'c'\n\t\t\t\t}\n\t\t\t\tc.Write(payload[:nBytes])\n\t\t\t}\n\t\t\tc.Close()\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\tdefer listerner.Close()\n\n\tport := net.Port(listerner.Addr().(*net.UDPAddr).Port)\n\n\tvar errg errgroup.Group\n\tfor i := 0; i < 10; i++ {\n\t\terrg.Go(func() error {\n\t\t\tclientConn, err := DialKCP(context.Background(), net.UDPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{\n\t\t\t\tProtocolName:     \"mkcp\",\n\t\t\t\tProtocolSettings: &Config{},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer clientConn.Close()\n\n\t\t\tclientSend := make([]byte, 1024*1024)\n\t\t\trand.Read(clientSend)\n\t\t\tgo clientConn.Write(clientSend)\n\n\t\t\tclientReceived := make([]byte, 1024*1024)\n\t\t\tcommon.Must2(io.ReadFull(clientConn, clientReceived))\n\n\t\t\tclientExpected := make([]byte, 1024*1024)\n\t\t\tfor idx, b := range clientSend {\n\t\t\t\tclientExpected[idx] = b ^ 'c'\n\t\t\t}\n\t\t\tif r := cmp.Diff(clientReceived, clientExpected); r != \"\" {\n\t\t\t\treturn errors.New(r)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif err := errg.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 60 && listerner.ActiveConnections() > 0; i++ {\n\t\ttime.Sleep(500 * time.Millisecond)\n\t}\n\tif v := listerner.ActiveConnections(); v != 0 {\n\t\tt.Error(\"active connections: \", v)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/kcp/listener.go",
    "content": "package kcp\n\nimport (\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/internet/udp\"\n)\n\ntype ConnectionID struct {\n\tRemote net.Address\n\tPort   net.Port\n\tConv   uint16\n}\n\n// Listener defines a server listening for connections\ntype Listener struct {\n\tsync.Mutex\n\tsessions  map[ConnectionID]*Connection\n\thub       *udp.Hub\n\ttlsConfig *gotls.Config\n\tconfig    *Config\n\treader    PacketReader\n\taddConn   internet.ConnHandler\n}\n\nfunc NewListener(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (*Listener, error) {\n\tkcpSettings := streamSettings.ProtocolSettings.(*Config)\n\n\tl := &Listener{\n\t\treader:   &KCPPacketReader{},\n\t\tsessions: make(map[ConnectionID]*Connection),\n\t\tconfig:   kcpSettings,\n\t\taddConn:  addConn,\n\t}\n\n\tif config := tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tl.tlsConfig = config.GetTLSConfig()\n\t}\n\n\thub, err := udp.ListenUDP(ctx, address, port, streamSettings, udp.HubCapacity(1024))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tl.Lock()\n\tl.hub = hub\n\tl.Unlock()\n\terrors.LogInfo(ctx, \"listening on \", address, \":\", port)\n\n\tgo l.handlePackets()\n\n\treturn l, nil\n}\n\nfunc (l *Listener) handlePackets() {\n\treceive := l.hub.Receive()\n\tfor payload := range receive {\n\t\tl.OnReceive(payload.Payload, payload.Source)\n\t}\n}\n\nfunc (l *Listener) OnReceive(payload *buf.Buffer, src net.Destination) {\n\tsegments := l.reader.Read(payload.Bytes())\n\tpayload.Release()\n\n\tif len(segments) == 0 {\n\t\terrors.LogInfo(context.Background(), \"discarding invalid payload from \", src)\n\t\treturn\n\t}\n\n\tconv := segments[0].Conversation()\n\tcmd := segments[0].Command()\n\n\tid := ConnectionID{\n\t\tRemote: src.Address,\n\t\tPort:   src.Port,\n\t\tConv:   conv,\n\t}\n\n\tl.Lock()\n\tdefer l.Unlock()\n\n\tconn, found := l.sessions[id]\n\n\tif !found {\n\t\tif cmd == CommandTerminate {\n\t\t\treturn\n\t\t}\n\t\twriter := &Writer{\n\t\t\tid:       id,\n\t\t\thub:      l.hub,\n\t\t\tdest:     src,\n\t\t\tlistener: l,\n\t\t}\n\t\tremoteAddr := &net.UDPAddr{\n\t\t\tIP:   src.Address.IP(),\n\t\t\tPort: int(src.Port),\n\t\t}\n\t\tlocalAddr := l.hub.Addr()\n\t\tconn = NewConnection(ConnMetadata{\n\t\t\tLocalAddr:    localAddr,\n\t\t\tRemoteAddr:   remoteAddr,\n\t\t\tConversation: conv,\n\t\t}, writer, writer, l.config)\n\t\tvar netConn stat.Connection = conn\n\t\tif l.tlsConfig != nil {\n\t\t\tnetConn = tls.Server(conn, l.tlsConfig)\n\t\t}\n\n\t\tl.addConn(netConn)\n\t\tl.sessions[id] = conn\n\t}\n\tconn.Input(segments)\n}\n\nfunc (l *Listener) Remove(id ConnectionID) {\n\tl.Lock()\n\tdelete(l.sessions, id)\n\tl.Unlock()\n}\n\n// Close stops listening on the UDP address. Already Accepted connections are not closed.\nfunc (l *Listener) Close() error {\n\tl.hub.Close()\n\n\tl.Lock()\n\tdefer l.Unlock()\n\n\tfor _, conn := range l.sessions {\n\t\tgo conn.Terminate()\n\t}\n\n\treturn nil\n}\n\nfunc (l *Listener) ActiveConnections() int {\n\tl.Lock()\n\tdefer l.Unlock()\n\n\treturn len(l.sessions)\n}\n\n// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.\nfunc (l *Listener) Addr() net.Addr {\n\treturn l.hub.Addr()\n}\n\ntype Writer struct {\n\tid       ConnectionID\n\tdest     net.Destination\n\thub      *udp.Hub\n\tlistener *Listener\n}\n\nfunc (w *Writer) Write(payload []byte) (int, error) {\n\treturn w.hub.WriteTo(payload, w.dest)\n}\n\nfunc (w *Writer) Close() error {\n\tw.listener.Remove(w.id)\n\treturn nil\n}\n\nfunc ListenKCP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {\n\treturn NewListener(ctx, address, port, streamSettings, addConn)\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, ListenKCP))\n}\n"
  },
  {
    "path": "transport/internet/kcp/output.go",
    "content": "package kcp\n\nimport (\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/retry\"\n)\n\ntype SegmentWriter interface {\n\tWrite(seg Segment) error\n}\n\ntype SimpleSegmentWriter struct {\n\tsync.Mutex\n\tbuffer *buf.Buffer\n\twriter io.Writer\n}\n\nfunc NewSegmentWriter(writer io.Writer) SegmentWriter {\n\treturn &SimpleSegmentWriter{\n\t\twriter: writer,\n\t\tbuffer: buf.New(),\n\t}\n}\n\nfunc (w *SimpleSegmentWriter) Write(seg Segment) error {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tw.buffer.Clear()\n\trawBytes := w.buffer.Extend(seg.ByteSize())\n\tseg.Serialize(rawBytes)\n\t_, err := w.writer.Write(w.buffer.Bytes())\n\treturn err\n}\n\ntype RetryableWriter struct {\n\twriter SegmentWriter\n}\n\nfunc NewRetryableWriter(writer SegmentWriter) SegmentWriter {\n\treturn &RetryableWriter{\n\t\twriter: writer,\n\t}\n}\n\nfunc (w *RetryableWriter) Write(seg Segment) error {\n\treturn retry.Timed(5, 100).On(func() error {\n\t\treturn w.writer.Write(seg)\n\t})\n}\n"
  },
  {
    "path": "transport/internet/kcp/receiving.go",
    "content": "package kcp\n\nimport (\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\ntype ReceivingWindow struct {\n\tcache map[uint32]*DataSegment\n}\n\nfunc NewReceivingWindow() *ReceivingWindow {\n\treturn &ReceivingWindow{\n\t\tcache: make(map[uint32]*DataSegment),\n\t}\n}\n\nfunc (w *ReceivingWindow) Set(id uint32, value *DataSegment) bool {\n\t_, f := w.cache[id]\n\tif f {\n\t\treturn false\n\t}\n\tw.cache[id] = value\n\treturn true\n}\n\nfunc (w *ReceivingWindow) Has(id uint32) bool {\n\t_, f := w.cache[id]\n\treturn f\n}\n\nfunc (w *ReceivingWindow) Remove(id uint32) *DataSegment {\n\tv, f := w.cache[id]\n\tif !f {\n\t\treturn nil\n\t}\n\tdelete(w.cache, id)\n\treturn v\n}\n\ntype AckList struct {\n\twriter     SegmentWriter\n\ttimestamps []uint32\n\tnumbers    []uint32\n\tnextFlush  []uint32\n\n\tflushCandidates []uint32\n\tdirty           bool\n\n\tmss uint32\n}\n\nfunc NewAckList(writer SegmentWriter, mss uint32) *AckList {\n\treturn &AckList{\n\t\twriter:          writer,\n\t\ttimestamps:      make([]uint32, 0, 128),\n\t\tnumbers:         make([]uint32, 0, 128),\n\t\tnextFlush:       make([]uint32, 0, 128),\n\t\tflushCandidates: make([]uint32, 0, 128),\n\t\tmss:             mss,\n\t}\n}\n\nfunc (l *AckList) Add(number uint32, timestamp uint32) {\n\tl.timestamps = append(l.timestamps, timestamp)\n\tl.numbers = append(l.numbers, number)\n\tl.nextFlush = append(l.nextFlush, 0)\n\tl.dirty = true\n}\n\nfunc (l *AckList) Clear(una uint32) {\n\tcount := 0\n\tfor i := 0; i < len(l.numbers); i++ {\n\t\tif l.numbers[i] < una {\n\t\t\tcontinue\n\t\t}\n\t\tif i != count {\n\t\t\tl.numbers[count] = l.numbers[i]\n\t\t\tl.timestamps[count] = l.timestamps[i]\n\t\t\tl.nextFlush[count] = l.nextFlush[i]\n\t\t}\n\t\tcount++\n\t}\n\tif count < len(l.numbers) {\n\t\tl.numbers = l.numbers[:count]\n\t\tl.timestamps = l.timestamps[:count]\n\t\tl.nextFlush = l.nextFlush[:count]\n\t\tl.dirty = true\n\t}\n}\n\nfunc (l *AckList) Flush(current uint32, rto uint32) {\n\tl.flushCandidates = l.flushCandidates[:0]\n\n\tseg := NewAckSegment((int(l.mss) - 17) / 4)\n\tfor i := 0; i < len(l.numbers); i++ {\n\t\tif l.nextFlush[i] > current {\n\t\t\tif len(l.flushCandidates) < cap(l.flushCandidates) {\n\t\t\t\tl.flushCandidates = append(l.flushCandidates, l.numbers[i])\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tseg.PutNumber(l.numbers[i])\n\t\tseg.PutTimestamp(l.timestamps[i])\n\t\ttimeout := rto / 2\n\t\tif timeout < 20 {\n\t\t\ttimeout = 20\n\t\t}\n\t\tl.nextFlush[i] = current + timeout\n\n\t\tif seg.IsFull() {\n\t\t\tl.writer.Write(seg)\n\t\t\tseg.Release()\n\t\t\tseg = NewAckSegment((int(l.mss) - 17) / 4)\n\t\t\tl.dirty = false\n\t\t}\n\t}\n\n\tif l.dirty || !seg.IsEmpty() {\n\t\tfor _, number := range l.flushCandidates {\n\t\t\tif seg.IsFull() {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tseg.PutNumber(number)\n\t\t}\n\t\tl.writer.Write(seg)\n\t\tl.dirty = false\n\t}\n\n\tseg.Release()\n}\n\ntype ReceivingWorker struct {\n\tsync.RWMutex\n\tconn       *Connection\n\tleftOver   buf.MultiBuffer\n\twindow     *ReceivingWindow\n\tacklist    *AckList\n\tnextNumber uint32\n\twindowSize uint32\n}\n\nfunc NewReceivingWorker(kcp *Connection) *ReceivingWorker {\n\tworker := &ReceivingWorker{\n\t\tconn:       kcp,\n\t\twindow:     NewReceivingWindow(),\n\t\twindowSize: kcp.Config.GetReceivingInFlightSize(),\n\t}\n\tworker.acklist = NewAckList(worker, kcp.mss+DataSegmentOverhead)\n\treturn worker\n}\n\nfunc (w *ReceivingWorker) Release() {\n\tw.Lock()\n\tbuf.ReleaseMulti(w.leftOver)\n\tw.leftOver = nil\n\tw.Unlock()\n}\n\nfunc (w *ReceivingWorker) ProcessSendingNext(number uint32) {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tw.acklist.Clear(number)\n}\n\nfunc (w *ReceivingWorker) ProcessSegment(seg *DataSegment) {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tnumber := seg.Number\n\tidx := number - w.nextNumber\n\tif idx >= w.windowSize {\n\t\treturn\n\t}\n\tw.acklist.Clear(seg.SendingNext)\n\tw.acklist.Add(number, seg.Timestamp)\n\n\tif !w.window.Set(seg.Number, seg) {\n\t\tseg.Release()\n\t}\n}\n\nfunc (w *ReceivingWorker) ReadMultiBuffer() buf.MultiBuffer {\n\tif w.leftOver != nil {\n\t\tmb := w.leftOver\n\t\tw.leftOver = nil\n\t\treturn mb\n\t}\n\n\tmb := make(buf.MultiBuffer, 0, 32)\n\n\tw.Lock()\n\tdefer w.Unlock()\n\tfor {\n\t\tseg := w.window.Remove(w.nextNumber)\n\t\tif seg == nil {\n\t\t\tbreak\n\t\t}\n\t\tw.nextNumber++\n\t\tmb = append(mb, seg.Detach())\n\t\tseg.Release()\n\t}\n\n\treturn mb\n}\n\nfunc (w *ReceivingWorker) Read(b []byte) int {\n\tmb := w.ReadMultiBuffer()\n\tif mb.IsEmpty() {\n\t\treturn 0\n\t}\n\tmb, nBytes := buf.SplitBytes(mb, b)\n\tif !mb.IsEmpty() {\n\t\tw.leftOver = mb\n\t}\n\treturn nBytes\n}\n\nfunc (w *ReceivingWorker) IsDataAvailable() bool {\n\tw.RLock()\n\tdefer w.RUnlock()\n\treturn w.window.Has(w.nextNumber)\n}\n\nfunc (w *ReceivingWorker) NextNumber() uint32 {\n\tw.RLock()\n\tdefer w.RUnlock()\n\n\treturn w.nextNumber\n}\n\nfunc (w *ReceivingWorker) Flush(current uint32) {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tw.acklist.Flush(current, w.conn.roundTrip.Timeout())\n}\n\nfunc (w *ReceivingWorker) Write(seg Segment) error {\n\tackSeg := seg.(*AckSegment)\n\tackSeg.Conv = w.conn.meta.Conversation\n\tackSeg.ReceivingNext = w.nextNumber\n\tackSeg.ReceivingWindow = w.nextNumber + w.windowSize\n\tackSeg.Option = 0\n\tif w.conn.State() == StateReadyToClose {\n\t\tackSeg.Option = SegmentOptionClose\n\t}\n\treturn w.conn.output.Write(ackSeg)\n}\n\nfunc (*ReceivingWorker) CloseRead() {\n}\n\nfunc (w *ReceivingWorker) UpdateNecessary() bool {\n\tw.RLock()\n\tdefer w.RUnlock()\n\n\treturn len(w.acklist.numbers) > 0\n}\n"
  },
  {
    "path": "transport/internet/kcp/segment.go",
    "content": "package kcp\n\nimport (\n\t\"encoding/binary\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\n// Command is a KCP command that indicate the purpose of a Segment.\ntype Command byte\n\nconst (\n\t// CommandACK indicates an AckSegment.\n\tCommandACK Command = 0\n\t// CommandData indicates a DataSegment.\n\tCommandData Command = 1\n\t// CommandTerminate indicates that peer terminates the connection.\n\tCommandTerminate Command = 2\n\t// CommandPing indicates a ping.\n\tCommandPing Command = 3\n)\n\ntype SegmentOption byte\n\nconst (\n\tSegmentOptionClose SegmentOption = 1\n)\n\ntype Segment interface {\n\tRelease()\n\tConversation() uint16\n\tCommand() Command\n\tByteSize() int32\n\tSerialize([]byte)\n\tparse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte)\n}\n\nconst (\n\tDataSegmentOverhead = 18\n)\n\ntype DataSegment struct {\n\tConv        uint16\n\tOption      SegmentOption\n\tTimestamp   uint32\n\tNumber      uint32\n\tSendingNext uint32\n\n\tpayload  *buf.Buffer\n\ttimeout  uint32\n\ttransmit uint32\n}\n\nfunc NewDataSegment() *DataSegment {\n\treturn new(DataSegment)\n}\n\nfunc (s *DataSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {\n\ts.Conv = conv\n\ts.Option = opt\n\tif len(buf) < 15 {\n\t\treturn false, nil\n\t}\n\ts.Timestamp = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\ts.Number = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\ts.SendingNext = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\tdataLen := int(binary.BigEndian.Uint16(buf))\n\tbuf = buf[2:]\n\n\tif len(buf) < dataLen {\n\t\treturn false, nil\n\t}\n\ts.Data().Clear()\n\ts.Data().Write(buf[:dataLen])\n\tbuf = buf[dataLen:]\n\n\treturn true, buf\n}\n\nfunc (s *DataSegment) Conversation() uint16 {\n\treturn s.Conv\n}\n\nfunc (*DataSegment) Command() Command {\n\treturn CommandData\n}\n\nfunc (s *DataSegment) Detach() *buf.Buffer {\n\tr := s.payload\n\ts.payload = nil\n\treturn r\n}\n\nfunc (s *DataSegment) Data() *buf.Buffer {\n\tif s.payload == nil {\n\t\ts.payload = buf.New()\n\t}\n\treturn s.payload\n}\n\nfunc (s *DataSegment) Serialize(b []byte) {\n\tbinary.BigEndian.PutUint16(b, s.Conv)\n\tb[2] = byte(CommandData)\n\tb[3] = byte(s.Option)\n\tbinary.BigEndian.PutUint32(b[4:], s.Timestamp)\n\tbinary.BigEndian.PutUint32(b[8:], s.Number)\n\tbinary.BigEndian.PutUint32(b[12:], s.SendingNext)\n\tbinary.BigEndian.PutUint16(b[16:], uint16(s.payload.Len()))\n\tcopy(b[18:], s.payload.Bytes())\n}\n\nfunc (s *DataSegment) ByteSize() int32 {\n\treturn 2 + 1 + 1 + 4 + 4 + 4 + 2 + s.payload.Len()\n}\n\nfunc (s *DataSegment) Release() {\n\ts.payload.Release()\n\ts.payload = nil\n}\n\ntype AckSegment struct {\n\tConv            uint16\n\tOption          SegmentOption\n\tReceivingWindow uint32\n\tReceivingNext   uint32\n\tTimestamp       uint32\n\tNumberList      []uint32\n\n\tLimit int\n}\n\nconst ackNumberLimit = 128\n\nfunc NewAckSegment(limit int) *AckSegment {\n\tif limit <= 0 {\n\t\tlimit = 1\n\t}\n\tif limit > ackNumberLimit {\n\t\tlimit = ackNumberLimit\n\t}\n\treturn &AckSegment{\n\t\tLimit: limit,\n\t}\n}\n\nfunc (s *AckSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {\n\ts.Conv = conv\n\ts.Option = opt\n\tif len(buf) < 13 {\n\t\treturn false, nil\n\t}\n\n\ts.ReceivingWindow = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\ts.ReceivingNext = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\ts.Timestamp = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\tcount := int(buf[0])\n\tbuf = buf[1:]\n\n\tif len(buf) < count*4 {\n\t\treturn false, nil\n\t}\n\tfor i := 0; i < count; i++ {\n\t\ts.PutNumber(binary.BigEndian.Uint32(buf))\n\t\tbuf = buf[4:]\n\t}\n\n\treturn true, buf\n}\n\nfunc (s *AckSegment) Conversation() uint16 {\n\treturn s.Conv\n}\n\nfunc (*AckSegment) Command() Command {\n\treturn CommandACK\n}\n\nfunc (s *AckSegment) PutTimestamp(timestamp uint32) {\n\tif timestamp-s.Timestamp < 0x7FFFFFFF {\n\t\ts.Timestamp = timestamp\n\t}\n}\n\nfunc (s *AckSegment) PutNumber(number uint32) {\n\ts.NumberList = append(s.NumberList, number)\n}\n\nfunc (s *AckSegment) IsFull() bool {\n\treturn len(s.NumberList) == s.Limit\n}\n\nfunc (s *AckSegment) IsEmpty() bool {\n\treturn len(s.NumberList) == 0\n}\n\nfunc (s *AckSegment) ByteSize() int32 {\n\treturn 2 + 1 + 1 + 4 + 4 + 4 + 1 + int32(len(s.NumberList)*4)\n}\n\nfunc (s *AckSegment) Serialize(b []byte) {\n\tbinary.BigEndian.PutUint16(b, s.Conv)\n\tb[2] = byte(CommandACK)\n\tb[3] = byte(s.Option)\n\tbinary.BigEndian.PutUint32(b[4:], s.ReceivingWindow)\n\tbinary.BigEndian.PutUint32(b[8:], s.ReceivingNext)\n\tbinary.BigEndian.PutUint32(b[12:], s.Timestamp)\n\tb[16] = byte(len(s.NumberList))\n\tn := 17\n\tfor _, number := range s.NumberList {\n\t\tbinary.BigEndian.PutUint32(b[n:], number)\n\t\tn += 4\n\t}\n}\n\nfunc (s *AckSegment) Release() {}\n\ntype CmdOnlySegment struct {\n\tConv          uint16\n\tCmd           Command\n\tOption        SegmentOption\n\tSendingNext   uint32\n\tReceivingNext uint32\n\tPeerRTO       uint32\n}\n\nfunc NewCmdOnlySegment() *CmdOnlySegment {\n\treturn new(CmdOnlySegment)\n}\n\nfunc (s *CmdOnlySegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {\n\ts.Conv = conv\n\ts.Cmd = cmd\n\ts.Option = opt\n\n\tif len(buf) < 12 {\n\t\treturn false, nil\n\t}\n\n\ts.SendingNext = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\ts.ReceivingNext = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\ts.PeerRTO = binary.BigEndian.Uint32(buf)\n\tbuf = buf[4:]\n\n\treturn true, buf\n}\n\nfunc (s *CmdOnlySegment) Conversation() uint16 {\n\treturn s.Conv\n}\n\nfunc (s *CmdOnlySegment) Command() Command {\n\treturn s.Cmd\n}\n\nfunc (*CmdOnlySegment) ByteSize() int32 {\n\treturn 2 + 1 + 1 + 4 + 4 + 4\n}\n\nfunc (s *CmdOnlySegment) Serialize(b []byte) {\n\tbinary.BigEndian.PutUint16(b, s.Conv)\n\tb[2] = byte(s.Cmd)\n\tb[3] = byte(s.Option)\n\tbinary.BigEndian.PutUint32(b[4:], s.SendingNext)\n\tbinary.BigEndian.PutUint32(b[8:], s.ReceivingNext)\n\tbinary.BigEndian.PutUint32(b[12:], s.PeerRTO)\n}\n\nfunc (*CmdOnlySegment) Release() {}\n\nfunc ReadSegment(buf []byte) (Segment, []byte) {\n\tif len(buf) < 4 {\n\t\treturn nil, nil\n\t}\n\n\tconv := binary.BigEndian.Uint16(buf)\n\tbuf = buf[2:]\n\n\tcmd := Command(buf[0])\n\topt := SegmentOption(buf[1])\n\tbuf = buf[2:]\n\n\tvar seg Segment\n\tswitch cmd {\n\tcase CommandData:\n\t\tseg = NewDataSegment()\n\tcase CommandACK:\n\t\tseg = NewAckSegment(128)\n\tdefault:\n\t\tseg = NewCmdOnlySegment()\n\t}\n\n\tvalid, extra := seg.parse(conv, cmd, opt, buf)\n\tif !valid {\n\t\treturn nil, nil\n\t}\n\treturn seg, extra\n}\n"
  },
  {
    "path": "transport/internet/kcp/segment_test.go",
    "content": "package kcp_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t. \"github.com/xtls/xray-core/transport/internet/kcp\"\n)\n\nfunc TestBadSegment(t *testing.T) {\n\tseg, buf := ReadSegment(nil)\n\tif seg != nil {\n\t\tt.Error(\"non-nil seg\")\n\t}\n\tif len(buf) != 0 {\n\t\tt.Error(\"buf len: \", len(buf))\n\t}\n}\n\nfunc TestDataSegment(t *testing.T) {\n\tseg := &DataSegment{\n\t\tConv:        1,\n\t\tTimestamp:   3,\n\t\tNumber:      4,\n\t\tSendingNext: 5,\n\t}\n\tseg.Data().Write([]byte{'a', 'b', 'c', 'd'})\n\n\tnBytes := seg.ByteSize()\n\tbytes := make([]byte, nBytes)\n\tseg.Serialize(bytes)\n\n\tiseg, _ := ReadSegment(bytes)\n\tseg2 := iseg.(*DataSegment)\n\tif r := cmp.Diff(seg2, seg, cmpopts.IgnoreUnexported(DataSegment{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\tif r := cmp.Diff(seg2.Data().Bytes(), seg.Data().Bytes()); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc Test1ByteDataSegment(t *testing.T) {\n\tseg := &DataSegment{\n\t\tConv:        1,\n\t\tTimestamp:   3,\n\t\tNumber:      4,\n\t\tSendingNext: 5,\n\t}\n\tseg.Data().WriteByte('a')\n\n\tnBytes := seg.ByteSize()\n\tbytes := make([]byte, nBytes)\n\tseg.Serialize(bytes)\n\n\tiseg, _ := ReadSegment(bytes)\n\tseg2 := iseg.(*DataSegment)\n\tif r := cmp.Diff(seg2, seg, cmpopts.IgnoreUnexported(DataSegment{})); r != \"\" {\n\t\tt.Error(r)\n\t}\n\tif r := cmp.Diff(seg2.Data().Bytes(), seg.Data().Bytes()); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestACKSegment(t *testing.T) {\n\tseg := &AckSegment{\n\t\tConv:            1,\n\t\tReceivingWindow: 2,\n\t\tReceivingNext:   3,\n\t\tTimestamp:       10,\n\t\tNumberList:      []uint32{1, 3, 5, 7, 9},\n\t\tLimit:           128,\n\t}\n\n\tnBytes := seg.ByteSize()\n\tbytes := make([]byte, nBytes)\n\tseg.Serialize(bytes)\n\n\tiseg, _ := ReadSegment(bytes)\n\tseg2 := iseg.(*AckSegment)\n\tif r := cmp.Diff(seg2, seg); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestCmdSegment(t *testing.T) {\n\tseg := &CmdOnlySegment{\n\t\tConv:          1,\n\t\tCmd:           CommandPing,\n\t\tOption:        SegmentOptionClose,\n\t\tSendingNext:   11,\n\t\tReceivingNext: 13,\n\t\tPeerRTO:       15,\n\t}\n\n\tnBytes := seg.ByteSize()\n\tbytes := make([]byte, nBytes)\n\tseg.Serialize(bytes)\n\n\tiseg, _ := ReadSegment(bytes)\n\tseg2 := iseg.(*CmdOnlySegment)\n\tif r := cmp.Diff(seg2, seg); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/kcp/sending.go",
    "content": "package kcp\n\nimport (\n\t\"container/list\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\ntype SendingWindow struct {\n\tcache             *list.List\n\ttotalInFlightSize uint32\n\twriter            SegmentWriter\n\tonPacketLoss      func(uint32)\n}\n\nfunc NewSendingWindow(writer SegmentWriter, onPacketLoss func(uint32)) *SendingWindow {\n\twindow := &SendingWindow{\n\t\tcache:        list.New(),\n\t\twriter:       writer,\n\t\tonPacketLoss: onPacketLoss,\n\t}\n\treturn window\n}\n\nfunc (sw *SendingWindow) Release() {\n\tif sw == nil {\n\t\treturn\n\t}\n\tfor sw.cache.Len() > 0 {\n\t\tseg := sw.cache.Front().Value.(*DataSegment)\n\t\tseg.Release()\n\t\tsw.cache.Remove(sw.cache.Front())\n\t}\n}\n\nfunc (sw *SendingWindow) Len() uint32 {\n\treturn uint32(sw.cache.Len())\n}\n\nfunc (sw *SendingWindow) IsEmpty() bool {\n\treturn sw.cache.Len() == 0\n}\n\nfunc (sw *SendingWindow) Push(number uint32, b *buf.Buffer) {\n\tseg := NewDataSegment()\n\tseg.Number = number\n\tseg.payload = b\n\n\tsw.cache.PushBack(seg)\n}\n\nfunc (sw *SendingWindow) FirstNumber() uint32 {\n\treturn sw.cache.Front().Value.(*DataSegment).Number\n}\n\nfunc (sw *SendingWindow) Clear(una uint32) {\n\tfor !sw.IsEmpty() {\n\t\tseg := sw.cache.Front().Value.(*DataSegment)\n\t\tif seg.Number >= una {\n\t\t\tbreak\n\t\t}\n\t\tseg.Release()\n\t\tsw.cache.Remove(sw.cache.Front())\n\t}\n}\n\nfunc (sw *SendingWindow) HandleFastAck(number uint32, rto uint32) {\n\tif sw.IsEmpty() {\n\t\treturn\n\t}\n\n\tsw.Visit(func(seg *DataSegment) bool {\n\t\tif number == seg.Number || number-seg.Number > 0x7FFFFFFF {\n\t\t\treturn false\n\t\t}\n\n\t\tif seg.transmit > 0 && seg.timeout > rto/3 {\n\t\t\tseg.timeout -= rto / 3\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc (sw *SendingWindow) Visit(visitor func(seg *DataSegment) bool) {\n\tif sw.IsEmpty() {\n\t\treturn\n\t}\n\n\tfor e := sw.cache.Front(); e != nil; e = e.Next() {\n\t\tseg := e.Value.(*DataSegment)\n\t\tif !visitor(seg) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (sw *SendingWindow) Flush(current uint32, rto uint32, maxInFlightSize uint32) {\n\tif sw.IsEmpty() {\n\t\treturn\n\t}\n\n\tvar lost uint32\n\tvar inFlightSize uint32\n\n\tsw.Visit(func(segment *DataSegment) bool {\n\t\tif current-segment.timeout >= 0x7FFFFFFF {\n\t\t\treturn true\n\t\t}\n\t\tif segment.transmit == 0 {\n\t\t\t// First time\n\t\t\tsw.totalInFlightSize++\n\t\t} else {\n\t\t\tlost++\n\t\t}\n\t\tsegment.timeout = current + rto\n\n\t\tsegment.Timestamp = current\n\t\tsegment.transmit++\n\t\tsw.writer.Write(segment)\n\t\tinFlightSize++\n\t\treturn inFlightSize < maxInFlightSize\n\t})\n\n\tif sw.onPacketLoss != nil && inFlightSize > 0 && sw.totalInFlightSize != 0 {\n\t\trate := lost * 100 / sw.totalInFlightSize\n\t\tsw.onPacketLoss(rate)\n\t}\n}\n\nfunc (sw *SendingWindow) Remove(number uint32) bool {\n\tif sw.IsEmpty() {\n\t\treturn false\n\t}\n\n\tfor e := sw.cache.Front(); e != nil; e = e.Next() {\n\t\tseg := e.Value.(*DataSegment)\n\t\tif seg.Number > number {\n\t\t\treturn false\n\t\t} else if seg.Number == number {\n\t\t\tif sw.totalInFlightSize > 0 {\n\t\t\t\tsw.totalInFlightSize--\n\t\t\t}\n\t\t\tseg.Release()\n\t\t\tsw.cache.Remove(e)\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\ntype SendingWorker struct {\n\tsync.RWMutex\n\tconn                       *Connection\n\twindow                     *SendingWindow\n\tfirstUnacknowledged        uint32\n\tnextNumber                 uint32\n\tremoteNextNumber           uint32\n\tcontrolWindow              uint32\n\tfastResend                 uint32\n\twindowSize                 uint32\n\tfirstUnacknowledgedUpdated bool\n\tclosed                     bool\n}\n\nfunc NewSendingWorker(kcp *Connection) *SendingWorker {\n\tworker := &SendingWorker{\n\t\tconn:             kcp,\n\t\tfastResend:       2,\n\t\tremoteNextNumber: 32,\n\t\tcontrolWindow:    kcp.Config.GetSendingInFlightSize(),\n\t\twindowSize:       kcp.Config.GetSendingBufferSize(),\n\t}\n\tworker.window = NewSendingWindow(worker, worker.OnPacketLoss)\n\treturn worker\n}\n\nfunc (w *SendingWorker) Release() {\n\tw.Lock()\n\tw.window.Release()\n\tw.closed = true\n\tw.Unlock()\n}\n\nfunc (w *SendingWorker) ProcessReceivingNext(nextNumber uint32) {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tw.ProcessReceivingNextWithoutLock(nextNumber)\n}\n\nfunc (w *SendingWorker) ProcessReceivingNextWithoutLock(nextNumber uint32) {\n\tw.window.Clear(nextNumber)\n\tw.FindFirstUnacknowledged()\n}\n\nfunc (w *SendingWorker) FindFirstUnacknowledged() {\n\tfirst := w.firstUnacknowledged\n\tif !w.window.IsEmpty() {\n\t\tw.firstUnacknowledged = w.window.FirstNumber()\n\t} else {\n\t\tw.firstUnacknowledged = w.nextNumber\n\t}\n\tif first != w.firstUnacknowledged {\n\t\tw.firstUnacknowledgedUpdated = true\n\t}\n}\n\nfunc (w *SendingWorker) processAck(number uint32) bool {\n\t// number < v.firstUnacknowledged || number >= v.nextNumber\n\tif number-w.firstUnacknowledged > 0x7FFFFFFF || number-w.nextNumber < 0x7FFFFFFF {\n\t\treturn false\n\t}\n\n\tremoved := w.window.Remove(number)\n\tif removed {\n\t\tw.FindFirstUnacknowledged()\n\t}\n\treturn removed\n}\n\nfunc (w *SendingWorker) ProcessSegment(current uint32, seg *AckSegment, rto uint32) {\n\tdefer seg.Release()\n\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tif w.closed {\n\t\treturn\n\t}\n\n\tif w.remoteNextNumber < seg.ReceivingWindow {\n\t\tw.remoteNextNumber = seg.ReceivingWindow\n\t}\n\tw.ProcessReceivingNextWithoutLock(seg.ReceivingNext)\n\n\tif seg.IsEmpty() {\n\t\treturn\n\t}\n\n\tvar maxack uint32\n\tvar maxackRemoved bool\n\tfor _, number := range seg.NumberList {\n\t\tremoved := w.processAck(number)\n\t\tif maxack < number {\n\t\t\tmaxack = number\n\t\t\tmaxackRemoved = removed\n\t\t}\n\t}\n\n\tif maxackRemoved {\n\t\tw.window.HandleFastAck(maxack, rto)\n\t\tif current-seg.Timestamp < 10000 {\n\t\t\tw.conn.roundTrip.Update(current-seg.Timestamp, current)\n\t\t}\n\t}\n}\n\nfunc (w *SendingWorker) Push(b *buf.Buffer) bool {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tif w.closed {\n\t\treturn false\n\t}\n\n\tif w.window.Len() > w.windowSize {\n\t\treturn false\n\t}\n\n\tw.window.Push(w.nextNumber, b)\n\tw.nextNumber++\n\treturn true\n}\n\nfunc (w *SendingWorker) Write(seg Segment) error {\n\tdataSeg := seg.(*DataSegment)\n\n\tdataSeg.Conv = w.conn.meta.Conversation\n\tdataSeg.SendingNext = w.firstUnacknowledged\n\tdataSeg.Option = 0\n\tif w.conn.State() == StateReadyToClose {\n\t\tdataSeg.Option = SegmentOptionClose\n\t}\n\n\treturn w.conn.output.Write(dataSeg)\n}\n\nfunc (w *SendingWorker) OnPacketLoss(lossRate uint32) {\n\tif !w.conn.Config.Congestion || w.conn.roundTrip.Timeout() == 0 {\n\t\treturn\n\t}\n\n\tif lossRate >= 15 {\n\t\tw.controlWindow = 3 * w.controlWindow / 4\n\t} else if lossRate <= 5 {\n\t\tw.controlWindow += w.controlWindow / 4\n\t}\n\tif w.controlWindow < 16 {\n\t\tw.controlWindow = 16\n\t}\n\tif w.controlWindow > 2*w.conn.Config.GetSendingInFlightSize() {\n\t\tw.controlWindow = 2 * w.conn.Config.GetSendingInFlightSize()\n\t}\n}\n\nfunc (w *SendingWorker) Flush(current uint32) {\n\tw.Lock()\n\n\tif w.closed {\n\t\tw.Unlock()\n\t\treturn\n\t}\n\n\tcwnd := w.conn.Config.GetSendingInFlightSize()\n\tif cwnd > w.remoteNextNumber-w.firstUnacknowledged {\n\t\tcwnd = w.remoteNextNumber - w.firstUnacknowledged\n\t}\n\tif w.conn.Config.Congestion && cwnd > w.controlWindow {\n\t\tcwnd = w.controlWindow\n\t}\n\n\tcwnd *= 20 // magic\n\n\tif !w.window.IsEmpty() {\n\t\tw.window.Flush(current, w.conn.roundTrip.Timeout(), cwnd)\n\t\tw.firstUnacknowledgedUpdated = false\n\t}\n\n\tupdated := w.firstUnacknowledgedUpdated\n\tw.firstUnacknowledgedUpdated = false\n\n\tw.Unlock()\n\n\tif updated {\n\t\tw.conn.Ping(current, CommandPing)\n\t}\n}\n\nfunc (w *SendingWorker) CloseWrite() {\n\tw.Lock()\n\tdefer w.Unlock()\n\n\tw.window.Clear(0xFFFFFFFF)\n}\n\nfunc (w *SendingWorker) IsEmpty() bool {\n\tw.RLock()\n\tdefer w.RUnlock()\n\n\treturn w.window.IsEmpty()\n}\n\nfunc (w *SendingWorker) UpdateNecessary() bool {\n\treturn !w.IsEmpty()\n}\n\nfunc (w *SendingWorker) FirstUnacknowledged() uint32 {\n\tw.RLock()\n\tdefer w.RUnlock()\n\n\treturn w.firstUnacknowledged\n}\n"
  },
  {
    "path": "transport/internet/memory_settings.go",
    "content": "package internet\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet/finalmask\"\n)\n\n// MemoryStreamConfig is a parsed form of StreamConfig. It is used to reduce the number of Protobuf parses.\ntype MemoryStreamConfig struct {\n\tDestination      *net.Destination\n\tProtocolName     string\n\tProtocolSettings interface{}\n\tSecurityType     string\n\tSecuritySettings interface{}\n\tTcpmaskManager   *finalmask.TcpmaskManager\n\tUdpmaskManager   *finalmask.UdpmaskManager\n\tQuicParams       *QuicParams\n\tSocketSettings   *SocketConfig\n\tDownloadSettings *MemoryStreamConfig\n}\n\n// ToMemoryStreamConfig converts a StreamConfig to MemoryStreamConfig. It returns a default non-nil MemoryStreamConfig for nil input.\nfunc ToMemoryStreamConfig(s *StreamConfig) (*MemoryStreamConfig, error) {\n\tets, err := s.GetEffectiveTransportSettings()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmss := &MemoryStreamConfig{\n\t\tProtocolName:     s.GetEffectiveProtocol(),\n\t\tProtocolSettings: ets,\n\t}\n\n\tif s != nil {\n\t\tif s.Address != nil {\n\t\t\tmss.Destination = &net.Destination{\n\t\t\t\tAddress: s.Address.AsAddress(),\n\t\t\t\tPort:    net.Port(s.Port),\n\t\t\t\tNetwork: net.Network_TCP,\n\t\t\t}\n\t\t}\n\t\tmss.SocketSettings = s.SocketSettings\n\t}\n\n\tif s != nil && s.HasSecuritySettings() {\n\t\tess, err := s.GetEffectiveSecuritySettings()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmss.SecurityType = s.SecurityType\n\t\tmss.SecuritySettings = ess\n\t}\n\n\tif s != nil && len(s.Tcpmasks) > 0 {\n\t\tvar masks []finalmask.Tcpmask\n\t\tfor _, msg := range s.Tcpmasks {\n\t\t\tinstance, err := msg.GetInstance()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tmasks = append(masks, instance.(finalmask.Tcpmask))\n\t\t}\n\t\tmss.TcpmaskManager = finalmask.NewTcpmaskManager(masks)\n\t}\n\n\tif s != nil && s.QuicParams != nil {\n\t\tmss.QuicParams = s.QuicParams\n\t}\n\n\tif s != nil && len(s.Udpmasks) > 0 {\n\t\tvar masks []finalmask.Udpmask\n\t\tfor _, msg := range s.Udpmasks {\n\t\t\tinstance, err := msg.GetInstance()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tmasks = append(masks, instance.(finalmask.Udpmask))\n\t\t}\n\t\tmss.UdpmaskManager = finalmask.NewUdpmaskManager(masks)\n\t}\n\n\treturn mss, nil\n}\n"
  },
  {
    "path": "transport/internet/reality/config.go",
    "content": "package reality\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/cloudflare/circl/sign/mldsa/mldsa65\"\n\t\"github.com/xtls/reality\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc (c *Config) GetREALITYConfig() *reality.Config {\n\tvar dialer net.Dialer\n\tconfig := &reality.Config{\n\t\tDialContext: dialer.DialContext,\n\n\t\tShow: c.Show,\n\t\tType: c.Type,\n\t\tDest: c.Dest,\n\t\tXver: byte(c.Xver),\n\n\t\tPrivateKey:   c.PrivateKey,\n\t\tMinClientVer: c.MinClientVer,\n\t\tMaxClientVer: c.MaxClientVer,\n\t\tMaxTimeDiff:  time.Duration(c.MaxTimeDiff) * time.Millisecond,\n\n\t\tNextProtos:             nil, // should be nil\n\t\tSessionTicketsDisabled: true,\n\n\t\tKeyLogWriter: KeyLogWriterFromConfig(c),\n\t}\n\tif c.Mldsa65Seed != nil {\n\t\t_, key := mldsa65.NewKeyFromSeed((*[32]byte)(c.Mldsa65Seed))\n\t\tconfig.Mldsa65Key = key.Bytes()\n\t}\n\tif c.LimitFallbackUpload != nil {\n\t\tconfig.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes\n\t\tconfig.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec\n\t\tconfig.LimitFallbackUpload.BurstBytesPerSec = c.LimitFallbackUpload.BurstBytesPerSec\n\t}\n\tif c.LimitFallbackDownload != nil {\n\t\tconfig.LimitFallbackDownload.AfterBytes = c.LimitFallbackDownload.AfterBytes\n\t\tconfig.LimitFallbackDownload.BytesPerSec = c.LimitFallbackDownload.BytesPerSec\n\t\tconfig.LimitFallbackDownload.BurstBytesPerSec = c.LimitFallbackDownload.BurstBytesPerSec\n\t}\n\tconfig.ServerNames = make(map[string]bool)\n\tfor _, serverName := range c.ServerNames {\n\t\tconfig.ServerNames[serverName] = true\n\t}\n\tconfig.ShortIds = make(map[[8]byte]bool)\n\tfor _, shortId := range c.ShortIds {\n\t\tconfig.ShortIds[*(*[8]byte)(shortId)] = true\n\t}\n\treturn config\n}\n\nfunc KeyLogWriterFromConfig(c *Config) io.Writer {\n\tif len(c.MasterKeyLog) <= 0 || c.MasterKeyLog == \"none\" {\n\t\treturn nil\n\t}\n\n\twriter, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)\n\tif err != nil {\n\t\terrors.LogErrorInner(context.Background(), err, \"failed to open \", c.MasterKeyLog, \" as master key log\")\n\t}\n\n\treturn writer\n}\n\nfunc ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {\n\tif settings == nil {\n\t\treturn nil\n\t}\n\tconfig, ok := settings.SecuritySettings.(*Config)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn config\n}\n"
  },
  {
    "path": "transport/internet/reality/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/reality/config.proto\n\npackage reality\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate                 protoimpl.MessageState `protogen:\"open.v1\"`\n\tShow                  bool                   `protobuf:\"varint,1,opt,name=show,proto3\" json:\"show,omitempty\"`\n\tDest                  string                 `protobuf:\"bytes,2,opt,name=dest,proto3\" json:\"dest,omitempty\"`\n\tType                  string                 `protobuf:\"bytes,3,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tXver                  uint64                 `protobuf:\"varint,4,opt,name=xver,proto3\" json:\"xver,omitempty\"`\n\tServerNames           []string               `protobuf:\"bytes,5,rep,name=server_names,json=serverNames,proto3\" json:\"server_names,omitempty\"`\n\tPrivateKey            []byte                 `protobuf:\"bytes,6,opt,name=private_key,json=privateKey,proto3\" json:\"private_key,omitempty\"`\n\tMinClientVer          []byte                 `protobuf:\"bytes,7,opt,name=min_client_ver,json=minClientVer,proto3\" json:\"min_client_ver,omitempty\"`\n\tMaxClientVer          []byte                 `protobuf:\"bytes,8,opt,name=max_client_ver,json=maxClientVer,proto3\" json:\"max_client_ver,omitempty\"`\n\tMaxTimeDiff           uint64                 `protobuf:\"varint,9,opt,name=max_time_diff,json=maxTimeDiff,proto3\" json:\"max_time_diff,omitempty\"`\n\tShortIds              [][]byte               `protobuf:\"bytes,10,rep,name=short_ids,json=shortIds,proto3\" json:\"short_ids,omitempty\"`\n\tMldsa65Seed           []byte                 `protobuf:\"bytes,11,opt,name=mldsa65_seed,json=mldsa65Seed,proto3\" json:\"mldsa65_seed,omitempty\"`\n\tLimitFallbackUpload   *LimitFallback         `protobuf:\"bytes,12,opt,name=limit_fallback_upload,json=limitFallbackUpload,proto3\" json:\"limit_fallback_upload,omitempty\"`\n\tLimitFallbackDownload *LimitFallback         `protobuf:\"bytes,13,opt,name=limit_fallback_download,json=limitFallbackDownload,proto3\" json:\"limit_fallback_download,omitempty\"`\n\tFingerprint           string                 `protobuf:\"bytes,21,opt,name=Fingerprint,proto3\" json:\"Fingerprint,omitempty\"`\n\tServerName            string                 `protobuf:\"bytes,22,opt,name=server_name,json=serverName,proto3\" json:\"server_name,omitempty\"`\n\tPublicKey             []byte                 `protobuf:\"bytes,23,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\"`\n\tShortId               []byte                 `protobuf:\"bytes,24,opt,name=short_id,json=shortId,proto3\" json:\"short_id,omitempty\"`\n\tMldsa65Verify         []byte                 `protobuf:\"bytes,25,opt,name=mldsa65_verify,json=mldsa65Verify,proto3\" json:\"mldsa65_verify,omitempty\"`\n\tSpiderX               string                 `protobuf:\"bytes,26,opt,name=spider_x,json=spiderX,proto3\" json:\"spider_x,omitempty\"`\n\tSpiderY               []int64                `protobuf:\"varint,27,rep,packed,name=spider_y,json=spiderY,proto3\" json:\"spider_y,omitempty\"`\n\tMasterKeyLog          string                 `protobuf:\"bytes,31,opt,name=master_key_log,json=masterKeyLog,proto3\" json:\"master_key_log,omitempty\"`\n\tunknownFields         protoimpl.UnknownFields\n\tsizeCache             protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_reality_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_reality_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_reality_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetShow() bool {\n\tif x != nil {\n\t\treturn x.Show\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetDest() string {\n\tif x != nil {\n\t\treturn x.Dest\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetXver() uint64 {\n\tif x != nil {\n\t\treturn x.Xver\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetServerNames() []string {\n\tif x != nil {\n\t\treturn x.ServerNames\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetPrivateKey() []byte {\n\tif x != nil {\n\t\treturn x.PrivateKey\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMinClientVer() []byte {\n\tif x != nil {\n\t\treturn x.MinClientVer\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMaxClientVer() []byte {\n\tif x != nil {\n\t\treturn x.MaxClientVer\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMaxTimeDiff() uint64 {\n\tif x != nil {\n\t\treturn x.MaxTimeDiff\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetShortIds() [][]byte {\n\tif x != nil {\n\t\treturn x.ShortIds\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMldsa65Seed() []byte {\n\tif x != nil {\n\t\treturn x.Mldsa65Seed\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetLimitFallbackUpload() *LimitFallback {\n\tif x != nil {\n\t\treturn x.LimitFallbackUpload\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetLimitFallbackDownload() *LimitFallback {\n\tif x != nil {\n\t\treturn x.LimitFallbackDownload\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetFingerprint() string {\n\tif x != nil {\n\t\treturn x.Fingerprint\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetServerName() string {\n\tif x != nil {\n\t\treturn x.ServerName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetPublicKey() []byte {\n\tif x != nil {\n\t\treturn x.PublicKey\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetShortId() []byte {\n\tif x != nil {\n\t\treturn x.ShortId\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMldsa65Verify() []byte {\n\tif x != nil {\n\t\treturn x.Mldsa65Verify\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetSpiderX() string {\n\tif x != nil {\n\t\treturn x.SpiderX\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetSpiderY() []int64 {\n\tif x != nil {\n\t\treturn x.SpiderY\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetMasterKeyLog() string {\n\tif x != nil {\n\t\treturn x.MasterKeyLog\n\t}\n\treturn \"\"\n}\n\ntype LimitFallback struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tAfterBytes       uint64                 `protobuf:\"varint,1,opt,name=after_bytes,json=afterBytes,proto3\" json:\"after_bytes,omitempty\"`\n\tBytesPerSec      uint64                 `protobuf:\"varint,2,opt,name=bytes_per_sec,json=bytesPerSec,proto3\" json:\"bytes_per_sec,omitempty\"`\n\tBurstBytesPerSec uint64                 `protobuf:\"varint,3,opt,name=burst_bytes_per_sec,json=burstBytesPerSec,proto3\" json:\"burst_bytes_per_sec,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *LimitFallback) Reset() {\n\t*x = LimitFallback{}\n\tmi := &file_transport_internet_reality_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LimitFallback) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LimitFallback) ProtoMessage() {}\n\nfunc (x *LimitFallback) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_reality_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LimitFallback.ProtoReflect.Descriptor instead.\nfunc (*LimitFallback) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_reality_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *LimitFallback) GetAfterBytes() uint64 {\n\tif x != nil {\n\t\treturn x.AfterBytes\n\t}\n\treturn 0\n}\n\nfunc (x *LimitFallback) GetBytesPerSec() uint64 {\n\tif x != nil {\n\t\treturn x.BytesPerSec\n\t}\n\treturn 0\n}\n\nfunc (x *LimitFallback) GetBurstBytesPerSec() uint64 {\n\tif x != nil {\n\t\treturn x.BurstBytesPerSec\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_reality_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_reality_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"'transport/internet/reality/config.proto\\x12\\x1fxray.transport.internet.reality\\\"\\x98\\x06\\n\" +\n\t\"\\x06Config\\x12\\x12\\n\" +\n\t\"\\x04show\\x18\\x01 \\x01(\\bR\\x04show\\x12\\x12\\n\" +\n\t\"\\x04dest\\x18\\x02 \\x01(\\tR\\x04dest\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x03 \\x01(\\tR\\x04type\\x12\\x12\\n\" +\n\t\"\\x04xver\\x18\\x04 \\x01(\\x04R\\x04xver\\x12!\\n\" +\n\t\"\\fserver_names\\x18\\x05 \\x03(\\tR\\vserverNames\\x12\\x1f\\n\" +\n\t\"\\vprivate_key\\x18\\x06 \\x01(\\fR\\n\" +\n\t\"privateKey\\x12$\\n\" +\n\t\"\\x0emin_client_ver\\x18\\a \\x01(\\fR\\fminClientVer\\x12$\\n\" +\n\t\"\\x0emax_client_ver\\x18\\b \\x01(\\fR\\fmaxClientVer\\x12\\\"\\n\" +\n\t\"\\rmax_time_diff\\x18\\t \\x01(\\x04R\\vmaxTimeDiff\\x12\\x1b\\n\" +\n\t\"\\tshort_ids\\x18\\n\" +\n\t\" \\x03(\\fR\\bshortIds\\x12!\\n\" +\n\t\"\\fmldsa65_seed\\x18\\v \\x01(\\fR\\vmldsa65Seed\\x12b\\n\" +\n\t\"\\x15limit_fallback_upload\\x18\\f \\x01(\\v2..xray.transport.internet.reality.LimitFallbackR\\x13limitFallbackUpload\\x12f\\n\" +\n\t\"\\x17limit_fallback_download\\x18\\r \\x01(\\v2..xray.transport.internet.reality.LimitFallbackR\\x15limitFallbackDownload\\x12 \\n\" +\n\t\"\\vFingerprint\\x18\\x15 \\x01(\\tR\\vFingerprint\\x12\\x1f\\n\" +\n\t\"\\vserver_name\\x18\\x16 \\x01(\\tR\\n\" +\n\t\"serverName\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"public_key\\x18\\x17 \\x01(\\fR\\tpublicKey\\x12\\x19\\n\" +\n\t\"\\bshort_id\\x18\\x18 \\x01(\\fR\\ashortId\\x12%\\n\" +\n\t\"\\x0emldsa65_verify\\x18\\x19 \\x01(\\fR\\rmldsa65Verify\\x12\\x19\\n\" +\n\t\"\\bspider_x\\x18\\x1a \\x01(\\tR\\aspiderX\\x12\\x19\\n\" +\n\t\"\\bspider_y\\x18\\x1b \\x03(\\x03R\\aspiderY\\x12$\\n\" +\n\t\"\\x0emaster_key_log\\x18\\x1f \\x01(\\tR\\fmasterKeyLog\\\"\\x83\\x01\\n\" +\n\t\"\\rLimitFallback\\x12\\x1f\\n\" +\n\t\"\\vafter_bytes\\x18\\x01 \\x01(\\x04R\\n\" +\n\t\"afterBytes\\x12\\\"\\n\" +\n\t\"\\rbytes_per_sec\\x18\\x02 \\x01(\\x04R\\vbytesPerSec\\x12-\\n\" +\n\t\"\\x13burst_bytes_per_sec\\x18\\x03 \\x01(\\x04R\\x10burstBytesPerSecB\\x7f\\n\" +\n\t\"#com.xray.transport.internet.realityP\\x01Z4github.com/xtls/xray-core/transport/internet/reality\\xaa\\x02\\x1fXray.Transport.Internet.Realityb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_reality_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_reality_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_reality_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_reality_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_reality_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_reality_config_proto_rawDesc), len(file_transport_internet_reality_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_reality_config_proto_rawDescData\n}\n\nvar file_transport_internet_reality_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_reality_config_proto_goTypes = []any{\n\t(*Config)(nil),        // 0: xray.transport.internet.reality.Config\n\t(*LimitFallback)(nil), // 1: xray.transport.internet.reality.LimitFallback\n}\nvar file_transport_internet_reality_config_proto_depIdxs = []int32{\n\t1, // 0: xray.transport.internet.reality.Config.limit_fallback_upload:type_name -> xray.transport.internet.reality.LimitFallback\n\t1, // 1: xray.transport.internet.reality.Config.limit_fallback_download:type_name -> xray.transport.internet.reality.LimitFallback\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_reality_config_proto_init() }\nfunc file_transport_internet_reality_config_proto_init() {\n\tif File_transport_internet_reality_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_reality_config_proto_rawDesc), len(file_transport_internet_reality_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_reality_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_reality_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_reality_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_reality_config_proto = out.File\n\tfile_transport_internet_reality_config_proto_goTypes = nil\n\tfile_transport_internet_reality_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/reality/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.reality;\noption csharp_namespace = \"Xray.Transport.Internet.Reality\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/reality\";\noption java_package = \"com.xray.transport.internet.reality\";\noption java_multiple_files = true;\n\nmessage Config {\n  bool show = 1;\n  string dest = 2;\n  string type = 3;\n  uint64 xver = 4;\n  repeated string server_names = 5;\n  bytes private_key = 6;\n  bytes min_client_ver = 7;\n  bytes max_client_ver = 8;\n  uint64 max_time_diff = 9;\n  repeated bytes short_ids = 10;\n\n  bytes mldsa65_seed = 11;\n  LimitFallback limit_fallback_upload = 12;\n  LimitFallback limit_fallback_download = 13;\n\n  string Fingerprint = 21;\n  string server_name = 22;\n  bytes public_key = 23;\n  bytes short_id = 24;\n  bytes mldsa65_verify = 25;\n  string spider_x = 26;\n  repeated int64 spider_y = 27;\n\n  string master_key_log = 31;\n}\n\nmessage LimitFallback {\n  uint64 after_bytes = 1;\n  uint64 bytes_per_sec = 2;\n  uint64 burst_bytes_per_sec = 3;\n}\n"
  },
  {
    "path": "transport/internet/reality/reality.go",
    "content": "package reality\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ecdh\"\n\t\"crypto/ed25519\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\tgotls \"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/cloudflare/circl/sign/mldsa/mldsa65\"\n\tutls \"github.com/refraction-networking/utls\"\n\t\"github.com/xtls/reality\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"golang.org/x/crypto/hkdf\"\n\t\"golang.org/x/net/http2\"\n)\n\ntype Conn struct {\n\t*reality.Conn\n}\n\nfunc (c *Conn) HandshakeAddress() net.Address {\n\tif err := c.Handshake(); err != nil {\n\t\treturn nil\n\t}\n\tstate := c.ConnectionState()\n\tif state.ServerName == \"\" {\n\t\treturn nil\n\t}\n\treturn net.ParseAddress(state.ServerName)\n}\n\nfunc Server(c net.Conn, config *reality.Config) (net.Conn, error) {\n\trealityConn, err := reality.Server(context.Background(), c, config)\n\treturn &Conn{Conn: realityConn}, err\n}\n\ntype UConn struct {\n\t*utls.UConn\n\tConfig     *Config\n\tServerName string\n\tAuthKey    []byte\n\tVerified   bool\n}\n\nfunc (c *UConn) HandshakeAddress() net.Address {\n\tif err := c.Handshake(); err != nil {\n\t\treturn nil\n\t}\n\tstate := c.ConnectionState()\n\tif state.ServerName == \"\" {\n\t\treturn nil\n\t}\n\treturn net.ParseAddress(state.ServerName)\n}\n\nfunc (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\tif c.Config.Show {\n\t\tlocalAddr := c.LocalAddr().String()\n\t\tfmt.Printf(\"REALITY localAddr: %v\\tis using X25519MLKEM768 for TLS' communication: %v\\n\", localAddr, c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768)\n\t\tfmt.Printf(\"REALITY localAddr: %v\\tis using ML-DSA-65 for cert's extra verification: %v\\n\", localAddr, len(c.Config.Mldsa65Verify) > 0)\n\t}\n\tp, _ := reflect.TypeOf(c.Conn).Elem().FieldByName(\"peerCertificates\")\n\tcerts := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))\n\tif pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {\n\t\th := hmac.New(sha512.New, c.AuthKey)\n\t\th.Write(pub)\n\t\tif bytes.Equal(h.Sum(nil), certs[0].Signature) {\n\t\t\tif len(c.Config.Mldsa65Verify) > 0 {\n\t\t\t\tif len(certs[0].Extensions) > 0 {\n\t\t\t\t\th.Write(c.HandshakeState.Hello.Raw)\n\t\t\t\t\th.Write(c.HandshakeState.ServerHello.Raw)\n\t\t\t\t\tverify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify)\n\t\t\t\t\tif mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) {\n\t\t\t\t\t\tc.Verified = true\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tc.Verified = true\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\topts := x509.VerifyOptions{\n\t\tDNSName:       c.ServerName,\n\t\tIntermediates: x509.NewCertPool(),\n\t}\n\tfor _, cert := range certs[1:] {\n\t\topts.Intermediates.AddCert(cert)\n\t}\n\tif _, err := certs[0].Verify(opts); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) {\n\tlocalAddr := c.LocalAddr().String()\n\tuConn := &UConn{\n\t\tConfig: config,\n\t}\n\tutlsConfig := &utls.Config{\n\t\tVerifyPeerCertificate:  uConn.VerifyPeerCertificate,\n\t\tServerName:             config.ServerName,\n\t\tInsecureSkipVerify:     true,\n\t\tSessionTicketsDisabled: true,\n\t\tKeyLogWriter:           KeyLogWriterFromConfig(config),\n\t}\n\tif utlsConfig.ServerName == \"\" {\n\t\tutlsConfig.ServerName = dest.Address.String()\n\t}\n\tuConn.ServerName = utlsConfig.ServerName\n\tfingerprint := tls.GetFingerprint(config.Fingerprint)\n\tif fingerprint == nil {\n\t\treturn nil, errors.New(\"REALITY: failed to get fingerprint\").AtError()\n\t}\n\tuConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)\n\t{\n\t\tuConn.BuildHandshakeState()\n\t\thello := uConn.HandshakeState.Hello\n\t\thello.SessionId = make([]byte, 32)\n\t\tcopy(hello.Raw[39:], hello.SessionId) // the fixed location of `Session ID`\n\t\thello.SessionId[0] = core.Version_x\n\t\thello.SessionId[1] = core.Version_y\n\t\thello.SessionId[2] = core.Version_z\n\t\thello.SessionId[3] = 0 // reserved\n\t\tbinary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))\n\t\tcopy(hello.SessionId[8:], config.ShortId)\n\t\tif config.Show {\n\t\t\tfmt.Printf(\"REALITY localAddr: %v\\thello.SessionId[:16]: %v\\n\", localAddr, hello.SessionId[:16])\n\t\t}\n\t\tpublicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"REALITY: publicKey == nil\")\n\t\t}\n\t\tecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe\n\t\tif ecdhe == nil {\n\t\t\tecdhe = uConn.HandshakeState.State13.KeyShareKeys.MlkemEcdhe\n\t\t}\n\t\tif ecdhe == nil {\n\t\t\treturn nil, errors.New(\"Current fingerprint \", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, \" does not support TLS 1.3, REALITY handshake cannot establish.\")\n\t\t}\n\t\tuConn.AuthKey, _ = ecdhe.ECDH(publicKey)\n\t\tif uConn.AuthKey == nil {\n\t\t\treturn nil, errors.New(\"REALITY: SharedKey == nil\")\n\t\t}\n\t\tif _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte(\"REALITY\")).Read(uConn.AuthKey); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\taead := crypto.NewAesGcm(uConn.AuthKey)\n\t\tif config.Show {\n\t\t\tfmt.Printf(\"REALITY localAddr: %v\\tuConn.AuthKey[:16]: %v\\tAEAD: %T\\n\", localAddr, uConn.AuthKey[:16], aead)\n\t\t}\n\t\taead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)\n\t\tcopy(hello.Raw[39:], hello.SessionId)\n\t}\n\tif err := uConn.HandshakeContext(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tif config.Show {\n\t\tfmt.Printf(\"REALITY localAddr: %v\\tuConn.Verified: %v\\n\", localAddr, uConn.Verified)\n\t}\n\tif !uConn.Verified {\n\t\terrors.LogError(ctx, \"REALITY: received real certificate (potential MITM or redirection)\")\n\t\tgo func() {\n\t\t\tclient := &http.Client{\n\t\t\t\tTransport: &http2.Transport{\n\t\t\t\t\tDialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) {\n\t\t\t\t\t\tif config.Show {\n\t\t\t\t\t\t\tfmt.Printf(\"REALITY localAddr: %v\\tDialTLSContext\\n\", localAddr)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn uConn, nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tprefix := []byte(\"https://\" + uConn.ServerName)\n\t\t\tmaps.Lock()\n\t\t\tif maps.maps == nil {\n\t\t\t\tmaps.maps = make(map[string]map[string]struct{})\n\t\t\t}\n\t\t\tpaths := maps.maps[uConn.ServerName]\n\t\t\tif paths == nil {\n\t\t\t\tpaths = make(map[string]struct{})\n\t\t\t\tpaths[config.SpiderX] = struct{}{}\n\t\t\t\tmaps.maps[uConn.ServerName] = paths\n\t\t\t}\n\t\t\tfirstURL := string(prefix) + getPathLocked(paths)\n\t\t\tmaps.Unlock()\n\t\t\tget := func(first bool) {\n\t\t\t\tvar (\n\t\t\t\t\treq  *http.Request\n\t\t\t\t\tresp *http.Response\n\t\t\t\t\terr  error\n\t\t\t\t\tbody []byte\n\t\t\t\t)\n\t\t\t\tif first {\n\t\t\t\t\treq, _ = http.NewRequest(\"GET\", firstURL, nil)\n\t\t\t\t} else {\n\t\t\t\t\tmaps.Lock()\n\t\t\t\t\treq, _ = http.NewRequest(\"GET\", string(prefix)+getPathLocked(paths), nil)\n\t\t\t\t\tmaps.Unlock()\n\t\t\t\t}\n\t\t\t\tif req == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\t\t\t\tif first && config.Show {\n\t\t\t\t\tfmt.Printf(\"REALITY localAddr: %v\\treq.UserAgent(): %v\\n\", localAddr, req.UserAgent())\n\t\t\t\t}\n\t\t\t\ttimes := 1\n\t\t\t\tif !first {\n\t\t\t\t\ttimes = int(crypto.RandBetween(config.SpiderY[4], config.SpiderY[5]))\n\t\t\t\t}\n\t\t\t\tfor j := 0; j < times; j++ {\n\t\t\t\t\tif !first && j == 0 {\n\t\t\t\t\t\treq.Header.Set(\"Referer\", firstURL)\n\t\t\t\t\t}\n\t\t\t\t\treq.AddCookie(&http.Cookie{Name: \"padding\", Value: strings.Repeat(\"0\", int(crypto.RandBetween(config.SpiderY[0], config.SpiderY[1])))})\n\t\t\t\t\tif resp, err = client.Do(req); err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\treq.Header.Set(\"Referer\", req.URL.String())\n\t\t\t\t\tif body, err = io.ReadAll(resp.Body); err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tmaps.Lock()\n\t\t\t\t\tfor _, m := range href.FindAllSubmatch(body, -1) {\n\t\t\t\t\t\tm[1] = bytes.TrimPrefix(m[1], prefix)\n\t\t\t\t\t\tif !bytes.Contains(m[1], dot) {\n\t\t\t\t\t\t\tpaths[string(m[1])] = struct{}{}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treq.URL.Path = getPathLocked(paths)\n\t\t\t\t\tif config.Show {\n\t\t\t\t\t\tfmt.Printf(\"REALITY localAddr: %v\\treq.Referer(): %v\\n\", localAddr, req.Referer())\n\t\t\t\t\t\tfmt.Printf(\"REALITY localAddr: %v\\tlen(body): %v\\n\", localAddr, len(body))\n\t\t\t\t\t\tfmt.Printf(\"REALITY localAddr: %v\\tlen(paths): %v\\n\", localAddr, len(paths))\n\t\t\t\t\t}\n\t\t\t\t\tmaps.Unlock()\n\t\t\t\t\tif !first {\n\t\t\t\t\t\ttime.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tget(true)\n\t\t\tconcurrency := int(crypto.RandBetween(config.SpiderY[2], config.SpiderY[3]))\n\t\t\tfor i := 0; i < concurrency; i++ {\n\t\t\t\tgo get(false)\n\t\t\t}\n\t\t\t// Do not close the connection\n\t\t}()\n\t\ttime.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return\n\t\treturn nil, errors.New(\"REALITY: processed invalid connection\").AtWarning()\n\t}\n\treturn uConn, nil\n}\n\nvar (\n\thref = regexp.MustCompile(`href=\"([/h].*?)\"`)\n\tdot  = []byte(\".\")\n)\n\nvar maps struct {\n\tsync.Mutex\n\tmaps map[string]map[string]struct{}\n}\n\nfunc getPathLocked(paths map[string]struct{}) string {\n\tstopAt := int(crypto.RandBetween(0, int64(len(paths)-1)))\n\ti := 0\n\tfor s := range paths {\n\t\tif i == stopAt {\n\t\t\treturn s\n\t\t}\n\t\ti++\n\t}\n\treturn \"/\"\n}\n"
  },
  {
    "path": "transport/internet/sockopt.go",
    "content": "package internet\n\nfunc isTCPSocket(network string) bool {\n\tswitch network {\n\tcase \"tcp\", \"tcp4\", \"tcp6\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc isUDPSocket(network string) bool {\n\tswitch network {\n\tcase \"udp\", \"udp4\", \"udp6\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (v *SocketConfig) ParseTFOValue() int {\n\tif v.Tfo == 0 {\n\t\treturn -1\n\t}\n\ttfo := int(v.Tfo)\n\tif tfo < 0 {\n\t\ttfo = 0\n\t}\n\treturn tfo\n}\n"
  },
  {
    "path": "transport/internet/sockopt_darwin.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\t// TCP_FASTOPEN_SERVER is the value to enable TCP fast open on darwin for server connections.\n\tTCP_FASTOPEN_SERVER = 0x01\n\t// TCP_FASTOPEN_CLIENT is the value to enable TCP fast open on darwin for client connections.\n\tTCP_FASTOPEN_CLIENT = 0x02 // nolint: revive,stylecheck\n\t// syscall.TCP_KEEPINTVL is missing on some darwin architectures.\n\tsysTCP_KEEPINTVL = 0x101 // nolint: revive,stylecheck\n)\n\nconst (\n\tPfOut       = 2\n\tIOCOut      = 0x40000000\n\tIOCIn       = 0x80000000\n\tIOCInOut    = IOCIn | IOCOut\n\tIOCPARMMask = 0x1FFF\n\tLEN         = 4*16 + 4*4 + 4*1\n\t// #define\t_IOC(inout,group,num,len) (inout | ((len & IOCPARMMask) << 16) | ((group) << 8) | (num))\n\t// #define\t_IOWR(g,n,t)\t_IOC(IOCInOut,\t(g), (n), sizeof(t))\n\t// #define DIOCNATLOOK\t\t_IOWR('D', 23, struct pfioc_natlook)\n\tDIOCNATLOOK = IOCInOut | ((LEN & IOCPARMMask) << 16) | ('D' << 8) | 23\n)\n\n// OriginalDst uses ioctl to read original destination from /dev/pf\nfunc OriginalDst(la, ra net.Addr) (net.IP, int, error) {\n\tf, err := os.Open(\"/dev/pf\")\n\tif err != nil {\n\t\treturn net.IP{}, -1, errors.New(\"failed to open device /dev/pf\").Base(err)\n\t}\n\tdefer f.Close()\n\tfd := f.Fd()\n\tnl := struct { // struct pfioc_natlook\n\t\tsaddr, daddr, rsaddr, rdaddr       [16]byte\n\t\tsxport, dxport, rsxport, rdxport   [4]byte\n\t\taf, proto, protoVariant, direction uint8\n\t}{\n\t\taf:        syscall.AF_INET,\n\t\tproto:     syscall.IPPROTO_TCP,\n\t\tdirection: PfOut,\n\t}\n\tvar raIP, laIP net.IP\n\tvar raPort, laPort int\n\tswitch la.(type) {\n\tcase *net.TCPAddr:\n\t\traIP = ra.(*net.TCPAddr).IP\n\t\tlaIP = la.(*net.TCPAddr).IP\n\t\traPort = ra.(*net.TCPAddr).Port\n\t\tlaPort = la.(*net.TCPAddr).Port\n\tcase *net.UDPAddr:\n\t\traIP = ra.(*net.UDPAddr).IP\n\t\tlaIP = la.(*net.UDPAddr).IP\n\t\traPort = ra.(*net.UDPAddr).Port\n\t\tlaPort = la.(*net.UDPAddr).Port\n\t}\n\tif raIP.To4() != nil {\n\t\tif laIP.IsUnspecified() {\n\t\t\tlaIP = net.ParseIP(\"127.0.0.1\")\n\t\t}\n\t\tcopy(nl.saddr[:net.IPv4len], raIP.To4())\n\t\tcopy(nl.daddr[:net.IPv4len], laIP.To4())\n\t}\n\tif raIP.To16() != nil && raIP.To4() == nil {\n\t\tif laIP.IsUnspecified() {\n\t\t\tlaIP = net.ParseIP(\"::1\")\n\t\t}\n\t\tcopy(nl.saddr[:], raIP)\n\t\tcopy(nl.daddr[:], laIP)\n\t}\n\tnl.sxport[0], nl.sxport[1] = byte(raPort>>8), byte(raPort)\n\tnl.dxport[0], nl.dxport[1] = byte(laPort>>8), byte(laPort)\n\tif _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 {\n\t\treturn net.IP{}, -1, os.NewSyscallError(\"ioctl\", err)\n\t}\n\n\todPort := nl.rdxport\n\tvar odIP net.IP\n\tswitch nl.af {\n\tcase syscall.AF_INET:\n\t\todIP = make(net.IP, net.IPv4len)\n\t\tcopy(odIP, nl.rdaddr[:net.IPv4len])\n\tcase syscall.AF_INET6:\n\t\todIP = make(net.IP, net.IPv6len)\n\t\tcopy(odIP, nl.rdaddr[:])\n\t}\n\treturn odIP, int(net.PortFromBytes(odPort[:2])), nil\n}\n\nfunc applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {\n\tif isTCPSocket(network) {\n\t\ttfo := config.ParseTFOValue()\n\t\tif tfo > 0 {\n\t\t\ttfo = TCP_FASTOPEN_CLIENT\n\t\t}\n\t\tif tfo >= 0 {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {\n\t\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_KEEPALIVE, int(config.TcpKeepAliveInterval)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPINTVL\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif config.TcpKeepAliveInterval > 0 {\n\t\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, sysTCP_KEEPINTVL, int(config.TcpKeepAliveIdle)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPIDLE\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.Interface != \"\" {\n\t\tiface, err := net.InterfaceByName(config.Interface)\n\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to get interface \", config.Interface).Base(err)\n\t\t}\n\t\tif network == \"tcp6\" || network == \"udp6\" {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index); err != nil {\n\t\t\t\treturn errors.New(\"failed to set IPV6_BOUND_IF\").Base(err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index); err != nil {\n\t\t\t\treturn errors.New(\"failed to set IP_BOUND_IF\").Base(err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(config.CustomSockopt) > 0 {\n\t\tfor _, custom := range config.CustomSockopt {\n\t\t\tif custom.System != \"\" && custom.System != runtime.GOOS {\n\t\t\t\terrors.LogDebug(context.Background(), \"CustomSockopt system not match: \", \"want \", custom.System, \" got \", runtime.GOOS)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip unwanted network type\n\t\t\t// network might be tcp4 or tcp6\n\t\t\t// use HasPrefix so that \"tcp\" can match tcp4/6 with \"tcp\" if user want to control all tcp (udp is also the same)\n\t\t\t// if it is empty, strings.HasPrefix will always return true to make it apply for all networks\n\t\t\tif !strings.HasPrefix(network, custom.Network) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar level = 0x6 // default TCP\n\t\t\tvar opt int\n\t\t\tif len(custom.Opt) == 0 {\n\t\t\t\treturn errors.New(\"No opt!\")\n\t\t\t} else {\n\t\t\t\topt, _ = strconv.Atoi(custom.Opt)\n\t\t\t}\n\t\t\tif custom.Level != \"\" {\n\t\t\t\tlevel, _ = strconv.Atoi(custom.Level)\n\t\t\t}\n\t\t\tif custom.Type == \"int\" {\n\t\t\t\tvalue, _ := strconv.Atoi(custom.Value)\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptInt\", opt, value, err)\n\t\t\t\t}\n\t\t\t} else if custom.Type == \"str\" {\n\t\t\t\tif err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptString\", opt, custom.Value, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"unknown CustomSockopt type:\", custom.Type)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {\n\tif isTCPSocket(network) {\n\t\ttfo := config.ParseTFOValue()\n\t\tif tfo > 0 {\n\t\t\ttfo = TCP_FASTOPEN_SERVER\n\t\t}\n\t\tif tfo >= 0 {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {\n\t\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_KEEPALIVE, int(config.TcpKeepAliveInterval)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPINTVL\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif config.TcpKeepAliveInterval > 0 {\n\t\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, sysTCP_KEEPINTVL, int(config.TcpKeepAliveIdle)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPIDLE\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.Interface != \"\" {\n\t\tiface, err := net.InterfaceByName(config.Interface)\n\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to get interface \", config.Interface).Base(err)\n\t\t}\n\t\tif network == \"tcp6\" || network == \"udp6\" {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index); err != nil {\n\t\t\t\treturn errors.New(\"failed to set IPV6_BOUND_IF\").Base(err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index); err != nil {\n\t\t\t\treturn errors.New(\"failed to set IP_BOUND_IF\").Base(err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.V6Only {\n\t\tif err := unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1); err != nil {\n\t\t\treturn errors.New(\"failed to set IPV6_V6ONLY\").Base(err)\n\t\t}\n\t}\n\n\tif len(config.CustomSockopt) > 0 {\n\t\tfor _, custom := range config.CustomSockopt {\n\t\t\tif custom.System != \"\" && custom.System != runtime.GOOS {\n\t\t\t\terrors.LogDebug(context.Background(), \"CustomSockopt system not match: \", \"want \", custom.System, \" got \", runtime.GOOS)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip unwanted network type\n\t\t\t// network might be tcp4 or tcp6\n\t\t\t// use HasPrefix so that \"tcp\" can match tcp4/6 with \"tcp\" if user want to control all tcp (udp is also the same)\n\t\t\t// if it is empty, strings.HasPrefix will always return true to make it apply for all networks\n\t\t\tif !strings.HasPrefix(network, custom.Network) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar level = 0x6 // default TCP\n\t\t\tvar opt int\n\t\t\tif len(custom.Opt) == 0 {\n\t\t\t\treturn errors.New(\"No opt!\")\n\t\t\t} else {\n\t\t\t\topt, _ = strconv.Atoi(custom.Opt)\n\t\t\t}\n\t\t\tif custom.Level != \"\" {\n\t\t\t\tlevel, _ = strconv.Atoi(custom.Level)\n\t\t\t}\n\t\t\tif custom.Type == \"int\" {\n\t\t\t\tvalue, _ := strconv.Atoi(custom.Value)\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptInt\", opt, value, err)\n\t\t\t\t}\n\t\t\t} else if custom.Type == \"str\" {\n\t\t\t\tif err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptString\", opt, custom.Value, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"unknown CustomSockopt type:\", custom.Type)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc bindAddr(fd uintptr, address []byte, port uint32) error {\n\tsetReuseAddr(fd)\n\tsetReusePort(fd)\n\n\tvar sockaddr unix.Sockaddr\n\n\tswitch len(address) {\n\tcase net.IPv4len:\n\t\ta4 := &unix.SockaddrInet4{\n\t\t\tPort: int(port),\n\t\t}\n\t\tcopy(a4.Addr[:], address)\n\t\tsockaddr = a4\n\tcase net.IPv6len:\n\t\ta6 := &unix.SockaddrInet6{\n\t\t\tPort: int(port),\n\t\t}\n\t\tcopy(a6.Addr[:], address)\n\t\tsockaddr = a6\n\tdefault:\n\t\treturn errors.New(\"unexpected length of ip\")\n\t}\n\n\treturn unix.Bind(int(fd), sockaddr)\n}\n\nfunc setReuseAddr(fd uintptr) error {\n\tif err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil {\n\t\treturn errors.New(\"failed to set SO_REUSEADDR\").Base(err).AtWarning()\n\t}\n\treturn nil\n}\n\nfunc setReusePort(fd uintptr) error {\n\tif err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\treturn errors.New(\"failed to set SO_REUSEPORT\").Base(err).AtWarning()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/sockopt_freebsd.go",
    "content": "package internet\n\nimport (\n\t\"encoding/binary\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\tsysPFINOUT     = 0x0\n\tsysPFIN        = 0x1\n\tsysPFOUT       = 0x2\n\tsysPFFWD       = 0x3\n\tsysDIOCNATLOOK = 0xc04c4417\n)\n\ntype pfiocNatlook struct {\n\tSaddr     [16]byte /* pf_addr */\n\tDaddr     [16]byte /* pf_addr */\n\tRsaddr    [16]byte /* pf_addr */\n\tRdaddr    [16]byte /* pf_addr */\n\tSport     uint16\n\tDport     uint16\n\tRsport    uint16\n\tRdport    uint16\n\tAf        uint8\n\tProto     uint8\n\tDirection uint8\n\tPad       [1]byte\n}\n\nconst (\n\tsizeofPfiocNatlook = 0x4c\n\tsoReUsePort        = 0x00000200\n\tsoReUsePortLB      = 0x00010000\n)\n\nfunc ioctl(s uintptr, ioc int, b []byte) error {\n\tif _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, s, uintptr(ioc), uintptr(unsafe.Pointer(&b[0]))); errno != 0 {\n\t\treturn error(errno)\n\t}\n\treturn nil\n}\n\nfunc (nl *pfiocNatlook) rdPort() int {\n\treturn int(binary.BigEndian.Uint16((*[2]byte)(unsafe.Pointer(&nl.Rdport))[:]))\n}\n\nfunc (nl *pfiocNatlook) setPort(remote, local int) {\n\tbinary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&nl.Sport))[:], uint16(remote))\n\tbinary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&nl.Dport))[:], uint16(local))\n}\n\n// OriginalDst uses ioctl to read original destination from /dev/pf\nfunc OriginalDst(la, ra net.Addr) (net.IP, int, error) {\n\tf, err := os.Open(\"/dev/pf\")\n\tif err != nil {\n\t\treturn net.IP{}, -1, errors.New(\"failed to open device /dev/pf\").Base(err)\n\t}\n\tdefer f.Close()\n\tfd := f.Fd()\n\tb := make([]byte, sizeofPfiocNatlook)\n\tnl := (*pfiocNatlook)(unsafe.Pointer(&b[0]))\n\tvar raIP, laIP net.IP\n\tvar raPort, laPort int\n\tswitch la.(type) {\n\tcase *net.TCPAddr:\n\t\traIP = ra.(*net.TCPAddr).IP\n\t\tlaIP = la.(*net.TCPAddr).IP\n\t\traPort = ra.(*net.TCPAddr).Port\n\t\tlaPort = la.(*net.TCPAddr).Port\n\t\tnl.Proto = syscall.IPPROTO_TCP\n\tcase *net.UDPAddr:\n\t\traIP = ra.(*net.UDPAddr).IP\n\t\tlaIP = la.(*net.UDPAddr).IP\n\t\traPort = ra.(*net.UDPAddr).Port\n\t\tlaPort = la.(*net.UDPAddr).Port\n\t\tnl.Proto = syscall.IPPROTO_UDP\n\t}\n\tif raIP.To4() != nil {\n\t\tif laIP.IsUnspecified() {\n\t\t\tlaIP = net.ParseIP(\"127.0.0.1\")\n\t\t}\n\t\tcopy(nl.Saddr[:net.IPv4len], raIP.To4())\n\t\tcopy(nl.Daddr[:net.IPv4len], laIP.To4())\n\t\tnl.Af = syscall.AF_INET\n\t}\n\tif raIP.To16() != nil && raIP.To4() == nil {\n\t\tif laIP.IsUnspecified() {\n\t\t\tlaIP = net.ParseIP(\"::1\")\n\t\t}\n\t\tcopy(nl.Saddr[:], raIP)\n\t\tcopy(nl.Daddr[:], laIP)\n\t\tnl.Af = syscall.AF_INET6\n\t}\n\tnl.setPort(raPort, laPort)\n\tioc := uintptr(sysDIOCNATLOOK)\n\tfor _, dir := range []byte{sysPFOUT, sysPFIN} {\n\t\tnl.Direction = dir\n\t\terr = ioctl(fd, int(ioc), b)\n\t\tif err == nil || err != syscall.ENOENT {\n\t\t\tbreak\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn net.IP{}, -1, os.NewSyscallError(\"ioctl\", err)\n\t}\n\n\todPort := nl.rdPort()\n\tvar odIP net.IP\n\tswitch nl.Af {\n\tcase syscall.AF_INET:\n\t\todIP = make(net.IP, net.IPv4len)\n\t\tcopy(odIP, nl.Rdaddr[:net.IPv4len])\n\tcase syscall.AF_INET6:\n\t\todIP = make(net.IP, net.IPv6len)\n\t\tcopy(odIP, nl.Rdaddr[:])\n\t}\n\treturn odIP, odPort, nil\n}\n\nfunc applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {\n\tif config.Mark != 0 {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_USER_COOKIE, int(config.Mark)); err != nil {\n\t\t\treturn errors.New(\"failed to set SO_USER_COOKIE\").Base(err)\n\t\t}\n\t}\n\n\tif isTCPSocket(network) {\n\t\ttfo := config.ParseTFOValue()\n\t\tif tfo > 0 {\n\t\t\ttfo = 1\n\t\t}\n\t\tif tfo >= 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_FASTOPEN_CONNECT=\", tfo).Base(err)\n\t\t\t}\n\t\t}\n\t\tif config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {\n\t\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, int(config.TcpKeepAliveIdle)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPIDLE\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif config.TcpKeepAliveInterval > 0 {\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, int(config.TcpKeepAliveInterval)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPINTVL\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.Tproxy.IsEnabled() {\n\t\tip, _, _ := net.SplitHostPort(address)\n\t\tif net.ParseIP(ip).To4() != nil {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set outbound IP_BINDANY\").Base(err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set outbound IPV6_BINDANY\").Base(err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {\n\tif config.Mark != 0 {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_USER_COOKIE, int(config.Mark)); err != nil {\n\t\t\treturn errors.New(\"failed to set SO_USER_COOKIE\").Base(err)\n\t\t}\n\t}\n\tif isTCPSocket(network) {\n\t\ttfo := config.ParseTFOValue()\n\t\tif tfo >= 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_FASTOPEN, tfo); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_FASTOPEN=\", tfo).Base(err)\n\t\t\t}\n\t\t}\n\t\tif config.TcpKeepAliveIdle > 0 || config.TcpKeepAliveInterval > 0 {\n\t\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, int(config.TcpKeepAliveIdle)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPIDLE\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif config.TcpKeepAliveInterval > 0 {\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, int(config.TcpKeepAliveInterval)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPINTVL\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.Tproxy.IsEnabled() {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BINDANY, 1); err != nil {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BINDANY, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set inbound IP_BINDANY\").Base(err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc bindAddr(fd uintptr, ip []byte, port uint32) error {\n\tsetReuseAddr(fd)\n\tsetReusePort(fd)\n\n\tvar sockaddr syscall.Sockaddr\n\n\tswitch len(ip) {\n\tcase net.IPv4len:\n\t\ta4 := &syscall.SockaddrInet4{\n\t\t\tPort: int(port),\n\t\t}\n\t\tcopy(a4.Addr[:], ip)\n\t\tsockaddr = a4\n\tcase net.IPv6len:\n\t\ta6 := &syscall.SockaddrInet6{\n\t\t\tPort: int(port),\n\t\t}\n\t\tcopy(a6.Addr[:], ip)\n\t\tsockaddr = a6\n\tdefault:\n\t\treturn errors.New(\"unexpected length of ip\")\n\t}\n\n\treturn syscall.Bind(int(fd), sockaddr)\n}\n\nfunc setReuseAddr(fd uintptr) error {\n\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {\n\t\treturn errors.New(\"failed to set SO_REUSEADDR\").Base(err).AtWarning()\n\t}\n\treturn nil\n}\n\nfunc setReusePort(fd uintptr) error {\n\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePortLB, 1); err != nil {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, soReUsePort, 1); err != nil {\n\t\t\treturn errors.New(\"failed to set SO_REUSEPORT\").Base(err).AtWarning()\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/sockopt_linux.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc bindAddr(fd uintptr, ip []byte, port uint32) error {\n\tsetReuseAddr(fd)\n\tsetReusePort(fd)\n\n\tvar sockaddr syscall.Sockaddr\n\n\tswitch len(ip) {\n\tcase net.IPv4len:\n\t\ta4 := &syscall.SockaddrInet4{\n\t\t\tPort: int(port),\n\t\t}\n\t\tcopy(a4.Addr[:], ip)\n\t\tsockaddr = a4\n\tcase net.IPv6len:\n\t\ta6 := &syscall.SockaddrInet6{\n\t\t\tPort: int(port),\n\t\t}\n\t\tcopy(a6.Addr[:], ip)\n\t\tsockaddr = a6\n\tdefault:\n\t\treturn errors.New(\"unexpected length of ip\")\n\t}\n\n\treturn syscall.Bind(int(fd), sockaddr)\n}\n\n// applyOutboundSocketOptions applies socket options for outbound connection.\n// note that unlike other part of Xray, this function needs network with speified network stack(tcp4/tcp6/udp4/udp6)\nfunc applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {\n\tif config.Mark != 0 {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil {\n\t\t\treturn errors.New(\"failed to set SO_MARK\").Base(err)\n\t\t}\n\t}\n\n\tif config.Interface != \"\" {\n\t\tif err := syscall.BindToDevice(int(fd), config.Interface); err != nil {\n\t\t\treturn errors.New(\"failed to set Interface\").Base(err)\n\t\t}\n\t}\n\n\tif isTCPSocket(network) {\n\t\ttfo := config.ParseTFOValue()\n\t\tif tfo > 0 {\n\t\t\ttfo = 1\n\t\t}\n\t\tif tfo >= 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN_CONNECT, tfo); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_FASTOPEN_CONNECT\", tfo).Base(err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpCongestion != \"\" {\n\t\t\tif err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_CONGESTION\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpWindowClamp > 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_WINDOW_CLAMP, int(config.TcpWindowClamp)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_WINDOW_CLAMP\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpUserTimeout > 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(config.TcpUserTimeout)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_USER_TIMEOUT\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpMaxSeg > 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_MAXSEG, int(config.TcpMaxSeg)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_MAXSEG\", err)\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif len(config.CustomSockopt) > 0 {\n\t\tfor _, custom := range config.CustomSockopt {\n\t\t\tif custom.System != \"\" && custom.System != runtime.GOOS {\n\t\t\t\terrors.LogDebug(context.Background(), \"CustomSockopt system not match: \", \"want \", custom.System, \" got \", runtime.GOOS)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip unwanted network type\n\t\t\t// network might be tcp4 or tcp6\n\t\t\t// use HasPrefix so that \"tcp\" can match tcp4/6 with \"tcp\" if user want to control all tcp (udp is also the same)\n\t\t\t// if it is empty, strings.HasPrefix will always return true to make it apply for all networks\n\t\t\tif !strings.HasPrefix(network, custom.Network) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar level = 0x6 // default TCP\n\t\t\tvar opt int\n\t\t\tif len(custom.Opt) == 0 {\n\t\t\t\treturn errors.New(\"No opt!\")\n\t\t\t} else {\n\t\t\t\topt, _ = strconv.Atoi(custom.Opt)\n\t\t\t}\n\t\t\tif custom.Level != \"\" {\n\t\t\t\tlevel, _ = strconv.Atoi(custom.Level)\n\t\t\t}\n\t\t\tif custom.Type == \"int\" {\n\t\t\t\tvalue, _ := strconv.Atoi(custom.Value)\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptInt\", opt, value, err)\n\t\t\t\t}\n\t\t\t} else if custom.Type == \"str\" {\n\t\t\t\tif err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptString\", opt, custom.Value, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"unknown CustomSockopt type:\", custom.Type)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.Tproxy.IsEnabled() {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {\n\t\t\treturn errors.New(\"failed to set IP_TRANSPARENT\").Base(err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// applyInboundSocketOptions applies socket options for inbound listener.\n// note that unlike other part of Xray, this function needs network with speified network stack(tcp4/tcp6/udp4/udp6)\nfunc applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {\n\tif config.Mark != 0 {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)); err != nil {\n\t\t\treturn errors.New(\"failed to set SO_MARK\").Base(err)\n\t\t}\n\t}\n\tif isTCPSocket(network) {\n\t\ttfo := config.ParseTFOValue()\n\t\tif tfo >= 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_TCP, unix.TCP_FASTOPEN, tfo); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_FASTOPEN\", tfo).Base(err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpKeepAliveInterval > 0 || config.TcpKeepAliveIdle > 0 {\n\t\t\tif config.TcpKeepAliveInterval > 0 {\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, int(config.TcpKeepAliveInterval)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPINTVL\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, int(config.TcpKeepAliveIdle)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set TCP_KEEPIDLE\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpCongestion != \"\" {\n\t\t\tif err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_CONGESTION\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpWindowClamp > 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_WINDOW_CLAMP, int(config.TcpWindowClamp)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_WINDOW_CLAMP\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpUserTimeout > 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(config.TcpUserTimeout)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_USER_TIMEOUT\", err)\n\t\t\t}\n\t\t}\n\n\t\tif config.TcpMaxSeg > 0 {\n\t\t\tif err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_MAXSEG, int(config.TcpMaxSeg)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set TCP_MAXSEG\", err)\n\t\t\t}\n\t\t}\n\t\tif len(config.CustomSockopt) > 0 {\n\t\t\tfor _, custom := range config.CustomSockopt {\n\t\t\t\tif custom.System != \"\" && custom.System != runtime.GOOS {\n\t\t\t\t\terrors.LogDebug(context.Background(), \"CustomSockopt system not match: \", \"want \", custom.System, \" got \", runtime.GOOS)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Skip unwanted network type\n\t\t\t\t// network might be tcp4 or tcp6\n\t\t\t\t// use HasPrefix so that \"tcp\" can match tcp4/6 with \"tcp\" if user want to control all tcp (udp is also the same)\n\t\t\t\t// if it is empty, strings.HasPrefix will always return true to make it apply for all networks\n\t\t\t\tif !strings.HasPrefix(network, custom.Network) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar level = 0x6 // default TCP\n\t\t\t\tvar opt int\n\t\t\t\tif len(custom.Opt) == 0 {\n\t\t\t\t\treturn errors.New(\"No opt!\")\n\t\t\t\t} else {\n\t\t\t\t\topt, _ = strconv.Atoi(custom.Opt)\n\t\t\t\t}\n\t\t\t\tif custom.Level != \"\" {\n\t\t\t\t\tlevel, _ = strconv.Atoi(custom.Level)\n\t\t\t\t}\n\t\t\t\tif custom.Type == \"int\" {\n\t\t\t\t\tvalue, _ := strconv.Atoi(custom.Value)\n\t\t\t\t\tif err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil {\n\t\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptInt\", opt, value, err)\n\t\t\t\t\t}\n\t\t\t\t} else if custom.Type == \"str\" {\n\t\t\t\t\tif err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil {\n\t\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptString\", opt, custom.Value, err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn errors.New(\"unknown CustomSockopt type:\", custom.Type)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.Tproxy.IsEnabled() {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {\n\t\t\treturn errors.New(\"failed to set IP_TRANSPARENT\").Base(err)\n\t\t}\n\t}\n\n\tif config.ReceiveOriginalDestAddress && isUDPSocket(network) {\n\t\terr1 := syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)\n\t\terr2 := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)\n\t\tif err1 != nil && err2 != nil {\n\t\t\treturn err1\n\t\t}\n\t}\n\n\tif config.V6Only {\n\t\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, syscall.IPV6_V6ONLY, 1); err != nil {\n\t\t\treturn errors.New(\"failed to set IPV6_V6ONLY\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc setReuseAddr(fd uintptr) error {\n\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {\n\t\treturn errors.New(\"failed to set SO_REUSEADDR\").Base(err).AtWarning()\n\t}\n\treturn nil\n}\n\nfunc setReusePort(fd uintptr) error {\n\tif err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {\n\t\treturn errors.New(\"failed to set SO_REUSEPORT\").Base(err).AtWarning()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/sockopt_linux_test.go",
    "content": "package internet_test\n\nimport (\n\t\"context\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t. \"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc TestSockOptMark(t *testing.T) {\n\tt.Skip(\"requires CAP_NET_ADMIN\")\n\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: func(b []byte) []byte {\n\t\t\treturn b\n\t\t},\n\t}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tconst mark = 1\n\tdialer := DefaultSystemDialer{}\n\tconn, err := dialer.Dial(context.Background(), nil, dest, &SocketConfig{Mark: mark})\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\trawConn, err := conn.(*net.TCPConn).SyscallConn()\n\tcommon.Must(err)\n\terr = rawConn.Control(func(fd uintptr) {\n\t\tm, err := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK)\n\t\tcommon.Must(err)\n\t\tif mark != m {\n\t\t\tt.Fatal(\"unexpected connection mark\", m, \" want \", mark)\n\t\t}\n\t})\n\tcommon.Must(err)\n}\n"
  },
  {
    "path": "transport/internet/sockopt_other.go",
    "content": "//go:build js || netbsd || openbsd || solaris\n// +build js netbsd openbsd solaris\n\npackage internet\n\nfunc applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {\n\treturn nil\n}\n\nfunc applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {\n\treturn nil\n}\n\nfunc bindAddr(fd uintptr, ip []byte, port uint32) error {\n\treturn nil\n}\n\nfunc setReuseAddr(fd uintptr) error {\n\treturn nil\n}\n\nfunc setReusePort(fd uintptr) error {\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/sockopt_test.go",
    "content": "package internet_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t. \"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc TestTCPFastOpen(t *testing.T) {\n\ttcpServer := tcp.Server{\n\t\tMsgProcessor: func(b []byte) []byte {\n\t\t\treturn b\n\t\t},\n\t}\n\tdest, err := tcpServer.StartContext(context.Background(), &SocketConfig{Tfo: 256})\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tctx := context.Background()\n\tdialer := DefaultSystemDialer{}\n\tconn, err := dialer.Dial(ctx, nil, dest, &SocketConfig{\n\t\tTfo: 1,\n\t})\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\t_, err = conn.Write([]byte(\"abcd\"))\n\tcommon.Must(err)\n\n\tb := buf.New()\n\tcommon.Must2(b.ReadFrom(conn))\n\tif r := cmp.Diff(b.Bytes(), []byte(\"abcd\")); r != \"\" {\n\t\tt.Fatal(r)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/sockopt_windows.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"net\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\nconst (\n\tTCP_FASTOPEN    = 15\n\tIP_UNICAST_IF   = 31\n\tIPV6_UNICAST_IF = 31\n)\n\nfunc setTFO(fd syscall.Handle, tfo int) error {\n\tif tfo > 0 {\n\t\ttfo = 1\n\t}\n\tif tfo >= 0 {\n\t\tif err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_FASTOPEN, tfo); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc applyOutboundSocketOptions(network string, address string, fd uintptr, config *SocketConfig) error {\n\tif config.Interface != \"\" {\n\t\tinf, err := net.InterfaceByName(config.Interface)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to find the interface\").Base(err)\n\t\t}\n\t\t// easy way to check if the address is ipv4\n\t\tisV4 := strings.Contains(address, \".\")\n\t\t// note: DO NOT trust the passed network variable, it can be udp6 even if the address is ipv4\n\t\t// because operating system might(always) use ipv6 socket to process ipv4\n\t\thost, _, err := net.SplitHostPort(address)\n\t\tif isV4 {\n\t\t\tvar bytes [4]byte\n\t\t\tbinary.BigEndian.PutUint32(bytes[:], uint32(inf.Index))\n\t\t\tidx := *(*uint32)(unsafe.Pointer(&bytes[0]))\n\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)); err != nil {\n\t\t\t\treturn errors.New(\"failed to set IP_UNICAST_IF\").Base(err)\n\t\t\t}\n\t\t\tif ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {\n\t\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, int(idx)); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set IP_MULTICAST_IF\").Base(err)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, inf.Index); err != nil {\n\t\t\t\treturn errors.New(\"failed to set IPV6_UNICAST_IF\").Base(err)\n\t\t\t}\n\t\t\tif ip := net.ParseIP(host); ip != nil && ip.IsMulticast() && isUDPSocket(network) {\n\t\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, inf.Index); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set IPV6_MULTICAST_IF\").Base(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif isTCPSocket(network) {\n\t\tif err := setTFO(syscall.Handle(fd), config.ParseTFOValue()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(config.CustomSockopt) > 0 {\n\t\tfor _, custom := range config.CustomSockopt {\n\t\t\tif custom.System != \"\" && custom.System != runtime.GOOS {\n\t\t\t\terrors.LogDebug(context.Background(), \"CustomSockopt system not match: \", \"want \", custom.System, \" got \", runtime.GOOS)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip unwanted network type\n\t\t\t// network might be tcp4 or tcp6\n\t\t\t// use HasPrefix so that \"tcp\" can match tcp4/6 with \"tcp\" if user want to control all tcp (udp is also the same)\n\t\t\t// if it is empty, strings.HasPrefix will always return true to make it apply for all networks\n\t\t\tif !strings.HasPrefix(network, custom.Network) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar level = 0x6 // default TCP\n\t\t\tvar opt int\n\t\t\tif len(custom.Opt) == 0 {\n\t\t\t\treturn errors.New(\"No opt!\")\n\t\t\t} else {\n\t\t\t\topt, _ = strconv.Atoi(custom.Opt)\n\t\t\t}\n\t\t\tif custom.Level != \"\" {\n\t\t\t\tlevel, _ = strconv.Atoi(custom.Level)\n\t\t\t}\n\t\t\tif custom.Type == \"int\" {\n\t\t\t\tvalue, _ := strconv.Atoi(custom.Value)\n\t\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptInt\", opt, value, err)\n\t\t\t\t}\n\t\t\t} else if custom.Type == \"str\" {\n\t\t\t\treturn errors.New(\"failed to set CustomSockoptString: Str type does not supported on windows\")\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"unknown CustomSockopt type:\", custom.Type)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) error {\n\tif isTCPSocket(network) {\n\t\tif err := setTFO(syscall.Handle(fd), config.ParseTFOValue()); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif config.TcpKeepAliveIdle > 0 {\n\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {\n\t\t\t\treturn errors.New(\"failed to set SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t} else if config.TcpKeepAliveIdle < 0 {\n\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {\n\t\t\t\treturn errors.New(\"failed to unset SO_KEEPALIVE\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.V6Only {\n\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1); err != nil {\n\t\t\treturn errors.New(\"failed to set IPV6_V6ONLY\").Base(err)\n\t\t}\n\t}\n\n\tif len(config.CustomSockopt) > 0 {\n\t\tfor _, custom := range config.CustomSockopt {\n\t\t\tif custom.System != \"\" && custom.System != runtime.GOOS {\n\t\t\t\terrors.LogDebug(context.Background(), \"CustomSockopt system not match: \", \"want \", custom.System, \" got \", runtime.GOOS)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip unwanted network type\n\t\t\t// network might be tcp4 or tcp6\n\t\t\t// use HasPrefix so that \"tcp\" can match tcp4/6 with \"tcp\" if user want to control all tcp (udp is also the same)\n\t\t\t// if it is empty, strings.HasPrefix will always return true to make it apply for all networks\n\t\t\tif !strings.HasPrefix(network, custom.Network) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar level = 0x6 // default TCP\n\t\t\tvar opt int\n\t\t\tif len(custom.Opt) == 0 {\n\t\t\t\treturn errors.New(\"No opt!\")\n\t\t\t} else {\n\t\t\t\topt, _ = strconv.Atoi(custom.Opt)\n\t\t\t}\n\t\t\tif custom.Level != \"\" {\n\t\t\t\tlevel, _ = strconv.Atoi(custom.Level)\n\t\t\t}\n\t\t\tif custom.Type == \"int\" {\n\t\t\t\tvalue, _ := strconv.Atoi(custom.Value)\n\t\t\t\tif err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil {\n\t\t\t\t\treturn errors.New(\"failed to set CustomSockoptInt\", opt, value, err)\n\t\t\t\t}\n\t\t\t} else if custom.Type == \"str\" {\n\t\t\t\treturn errors.New(\"failed to set CustomSockoptString: Str type does not supported on windows\")\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"unknown CustomSockopt type:\", custom.Type)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc bindAddr(fd uintptr, ip []byte, port uint32) error {\n\treturn nil\n}\n\nfunc setReuseAddr(fd uintptr) error {\n\treturn nil\n}\n\nfunc setReusePort(fd uintptr) error {\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/splithttp/browser_client.go",
    "content": "package splithttp\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet/browser_dialer\"\n\t\"github.com/xtls/xray-core/transport/internet/websocket\"\n)\n\n// BrowserDialerClient implements splithttp.DialerClient in terms of browser dialer\ntype BrowserDialerClient struct {\n\ttransportConfig *Config\n}\n\nfunc (c *BrowserDialerClient) IsClosed() bool {\n\tpanic(\"not implemented yet\")\n}\n\nfunc (c *BrowserDialerClient) OpenStream(ctx context.Context, url string, sessionId string, body io.Reader, uploadOnly bool) (io.ReadCloser, net.Addr, net.Addr, error) {\n\tif body != nil {\n\t\treturn nil, nil, nil, errors.New(\"bidirectional streaming for browser dialer not implemented yet\")\n\t}\n\n\trequest, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tc.transportConfig.FillStreamRequest(request, sessionId, \"\")\n\n\tconn, err := browser_dialer.DialGet(request.URL.String(), request.Header, request.Cookies())\n\tdummyAddr := &net.IPAddr{}\n\tif err != nil {\n\t\treturn nil, dummyAddr, dummyAddr, err\n\t}\n\n\treturn websocket.NewConnection(conn, dummyAddr, nil, 0), conn.RemoteAddr(), conn.LocalAddr(), nil\n}\n\nfunc (c *BrowserDialerClient) PostPacket(ctx context.Context, url string, sessionId string, seqStr string, body io.Reader, contentLength int64) error {\n\tmethod := c.transportConfig.GetNormalizedUplinkHTTPMethod()\n\trequest, err := http.NewRequest(method, url, body)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trequest.ContentLength = contentLength\n\terr = c.transportConfig.FillPacketRequest(request, sessionId, seqStr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar bytes []byte\n\tif (request.Body != nil) {\n\t\tbytes, err = io.ReadAll(request.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = browser_dialer.DialPacket(method, request.URL.String(), request.Header, request.Cookies(), bytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/splithttp/client.go",
    "content": "package splithttp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptrace\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n)\n\n// interface to abstract between use of browser dialer, vs net/http\ntype DialerClient interface {\n\tIsClosed() bool\n\n\t// ctx, url, sessionId, body, uploadOnly\n\tOpenStream(context.Context, string, string, io.Reader, bool) (io.ReadCloser, net.Addr, net.Addr, error)\n\n\t// ctx, url, sessionId, seqStr, body, contentLength\n\tPostPacket(context.Context, string, string, string, io.Reader, int64) error\n}\n\n// implements splithttp.DialerClient in terms of direct network connections\ntype DefaultDialerClient struct {\n\ttransportConfig *Config\n\tclient          *http.Client\n\tclosed          bool\n\thttpVersion     string\n\t// pool of net.Conn, created using dialUploadConn\n\tuploadRawPool  *sync.Pool\n\tdialUploadConn func(ctxInner context.Context) (net.Conn, error)\n}\n\nfunc (c *DefaultDialerClient) IsClosed() bool {\n\treturn c.closed\n}\n\nfunc (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessionId string, body io.Reader, uploadOnly bool) (wrc io.ReadCloser, remoteAddr, localAddr net.Addr, err error) {\n\t// this is done when the TCP/UDP connection to the server was established,\n\t// and we can unblock the Dial function and print correct net addresses in\n\t// logs\n\tgotConn := done.New()\n\tctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{\n\t\tGotConn: func(connInfo httptrace.GotConnInfo) {\n\t\t\tremoteAddr = connInfo.Conn.RemoteAddr()\n\t\t\tlocalAddr = connInfo.Conn.LocalAddr()\n\t\t\tgotConn.Close()\n\t\t},\n\t})\n\n\tmethod := \"GET\" // stream-down\n\tif body != nil {\n\t\tmethod = c.transportConfig.GetNormalizedUplinkHTTPMethod() // stream-up/one\n\t}\n\treq, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)\n\tc.transportConfig.FillStreamRequest(req, sessionId, \"\")\n\n\twrc = &WaitReadCloser{Wait: make(chan struct{})}\n\tgo func() {\n\t\tresp, err := c.client.Do(req)\n\t\tif err != nil {\n\t\t\tif !uploadOnly { // stream-down is enough\n\t\t\t\tc.closed = true\n\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to \"+method+\" \"+url)\n\t\t\t}\n\t\t\tgotConn.Close()\n\t\t\twrc.Close()\n\t\t\treturn\n\t\t}\n\t\tif resp.StatusCode != 200 && !uploadOnly {\n\t\t\terrors.LogInfo(ctx, \"unexpected status \", resp.StatusCode)\n\t\t}\n\t\tif resp.StatusCode != 200 || uploadOnly { // stream-up\n\t\t\tio.Copy(io.Discard, resp.Body)\n\t\t\tresp.Body.Close() // if it is called immediately, the upload will be interrupted also\n\t\t\twrc.Close()\n\t\t\treturn\n\t\t}\n\t\twrc.(*WaitReadCloser).Set(resp.Body)\n\t}()\n\n\t<-gotConn.Wait()\n\treturn\n}\n\nfunc (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessionId string, seqStr string, body io.Reader, contentLength int64) error {\n\tmethod := c.transportConfig.GetNormalizedUplinkHTTPMethod()\n\treq, err := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.ContentLength = contentLength\n\tc.transportConfig.FillPacketRequest(req, sessionId, seqStr)\n\n\tif c.httpVersion != \"1.1\" {\n\t\tresp, err := c.client.Do(req)\n\t\tif err != nil {\n\t\t\tc.closed = true\n\t\t\treturn err\n\t\t}\n\n\t\tio.Copy(io.Discard, resp.Body)\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != 200 {\n\t\t\treturn errors.New(\"bad status code:\", resp.Status)\n\t\t}\n\t} else {\n\t\t// stringify the entire HTTP/1.1 request so it can be\n\t\t// safely retried. if instead req.Write is called multiple\n\t\t// times, the body is already drained after the first\n\t\t// request\n\t\trequestBuff := new(bytes.Buffer)\n\t\tcommon.Must(req.Write(requestBuff))\n\n\t\tvar uploadConn any\n\t\tvar h1UploadConn *H1Conn\n\n\t\tfor {\n\t\t\tuploadConn = c.uploadRawPool.Get()\n\t\t\tnewConnection := uploadConn == nil\n\t\t\tif newConnection {\n\t\t\t\tnewConn, err := c.dialUploadConn(context.WithoutCancel(ctx))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\th1UploadConn = NewH1Conn(newConn)\n\t\t\t\tuploadConn = h1UploadConn\n\t\t\t} else {\n\t\t\t\th1UploadConn = uploadConn.(*H1Conn)\n\n\t\t\t\t// TODO: Replace 0 here with a config value later\n\t\t\t\t// Or add some other condition for optimization purposes\n\t\t\t\tif h1UploadConn.UnreadedResponsesCount > 0 {\n\t\t\t\t\tresp, err := http.ReadResponse(h1UploadConn.RespBufReader, req)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tc.closed = true\n\t\t\t\t\t\treturn fmt.Errorf(\"error while reading response: %s\", err.Error())\n\t\t\t\t\t}\n\t\t\t\t\tio.Copy(io.Discard, resp.Body)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tif resp.StatusCode != 200 {\n\t\t\t\t\t\treturn fmt.Errorf(\"got non-200 error response code: %d\", resp.StatusCode)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, err := h1UploadConn.Write(requestBuff.Bytes())\n\t\t\t// if the write failed, we try another connection from\n\t\t\t// the pool, until the write on a new connection fails.\n\t\t\t// failed writes to a pooled connection are normal when\n\t\t\t// the connection has been closed in the meantime.\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t} else if newConnection {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tc.uploadRawPool.Put(uploadConn)\n\t}\n\n\treturn nil\n}\n\ntype WaitReadCloser struct {\n\tWait chan struct{}\n\tio.ReadCloser\n}\n\nfunc (w *WaitReadCloser) Set(rc io.ReadCloser) {\n\tw.ReadCloser = rc\n\tdefer func() {\n\t\tif recover() != nil {\n\t\t\trc.Close()\n\t\t}\n\t}()\n\tclose(w.Wait)\n}\n\nfunc (w *WaitReadCloser) Read(b []byte) (int, error) {\n\tif w.ReadCloser == nil {\n\t\tif <-w.Wait; w.ReadCloser == nil {\n\t\t\treturn 0, io.ErrClosedPipe\n\t\t}\n\t}\n\treturn w.ReadCloser.Read(b)\n}\n\nfunc (w *WaitReadCloser) Close() error {\n\tif w.ReadCloser != nil {\n\t\treturn w.ReadCloser.Close()\n\t}\n\tdefer func() {\n\t\tif recover() != nil && w.ReadCloser != nil {\n\t\t\tw.ReadCloser.Close()\n\t\t}\n\t}()\n\tclose(w.Wait)\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/splithttp/common.go",
    "content": "package splithttp\n\nconst (\n\tPlacementQueryInHeader = \"queryInHeader\"\n\tPlacementCookie        = \"cookie\"\n\tPlacementHeader        = \"header\"\n\tPlacementQuery         = \"query\"\n\tPlacementPath          = \"path\"\n\tPlacementBody          = \"body\"\n\tPlacementAuto          = \"auto\"\n)\n"
  },
  {
    "path": "transport/internet/splithttp/config.go",
    "content": "package splithttp\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc (c *Config) GetNormalizedPath() string {\n\tpathAndQuery := strings.SplitN(c.Path, \"?\", 2)\n\tpath := pathAndQuery[0]\n\n\tif path == \"\" || path[0] != '/' {\n\t\tpath = \"/\" + path\n\t}\n\n\tif path[len(path)-1] != '/' {\n\t\tpath = path + \"/\"\n\t}\n\n\treturn path\n}\n\nfunc (c *Config) GetNormalizedQuery() string {\n\tpathAndQuery := strings.SplitN(c.Path, \"?\", 2)\n\tquery := \"\"\n\n\tif len(pathAndQuery) > 1 {\n\t\tquery = pathAndQuery[1]\n\t}\n\n\t/*\n\t\tif query != \"\" {\n\t\t\tquery += \"&\"\n\t\t}\n\t\tquery += \"x_version=\" + core.Version()\n\t*/\n\n\treturn query\n}\n\nfunc (c *Config) GetRequestHeader() http.Header {\n\theader := http.Header{}\n\tfor k, v := range c.Headers {\n\t\theader.Add(k, v)\n\t}\n\tif header.Get(\"User-Agent\") == \"\" {\n\t\theader.Set(\"User-Agent\", utils.ChromeUA)\n\t}\n\treturn header\n}\n\n\nfunc (c *Config) GetRequestHeaderWithPayload(payload []byte) http.Header {\n\theader := c.GetRequestHeader()\n\n\tkey := c.UplinkDataKey\n\tencodedData := base64.RawURLEncoding.EncodeToString(payload)\n\n\tfor i := 0; len(encodedData) > 0; i++ {\n\t\tchunkSize := min(int(c.GetNormalizedUplinkChunkSize().rand()), len(encodedData))\n\t\tchunk := encodedData[:chunkSize]\n\t\tencodedData = encodedData[chunkSize:]\n\t\theaderKey := fmt.Sprintf(\"%s-%d\", key, i)\n\t\theader.Set(headerKey, chunk)\n\t}\n\n\treturn header\n}\n\nfunc (c *Config) GetRequestCookiesWithPayload(payload []byte) []*http.Cookie {\n\tcookies := []*http.Cookie{}\n\n\tkey := c.UplinkDataKey\n\tencodedData := base64.RawURLEncoding.EncodeToString(payload)\n\n\tfor i := 0; len(encodedData) > 0; i++ {\n\t\tchunkSize := min(int(c.GetNormalizedUplinkChunkSize().rand()), len(encodedData))\n\t\tchunk := encodedData[:chunkSize]\n\t\tencodedData = encodedData[chunkSize:]\n\t\tcookieName := fmt.Sprintf(\"%s_%d\", key, i)\n\t\tcookies = append(cookies, &http.Cookie{Name: cookieName, Value: chunk})\n\t}\n\n\treturn cookies\n}\n\nfunc (c *Config) WriteResponseHeader(writer http.ResponseWriter, requestMethod string, requestHeader http.Header) {\n\t// CORS headers for the browser dialer\n\tif origin := requestHeader.Get(\"Origin\"); origin == \"\" {\n\t\twriter.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t} else {\n\t\t// Chrome says: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.\n\t\twriter.Header().Set(\"Access-Control-Allow-Origin\", origin)\n\t}\n\n\tif c.GetNormalizedSessionPlacement() == PlacementCookie ||\n\t   c.GetNormalizedSeqPlacement() == PlacementCookie ||\n\t   c.XPaddingPlacement == PlacementCookie ||\n\t   c.GetNormalizedUplinkDataPlacement() == PlacementCookie {\n\t\twriter.Header().Set(\"Access-Control-Allow-Credentials\", \"true\")\n\t}\n\n\tif requestMethod == \"OPTIONS\" {\n\t\trequestedMethod := requestHeader.Get(\"Access-Control-Request-Method\")\n\t\tif requestedMethod != \"\" {\n\t\t\twriter.Header().Set(\"Access-Control-Allow-Methods\", requestedMethod)\n\t\t} else {\n\t\t\twriter.Header().Set(\"Access-Control-Allow-Methods\", \"*\")\n\t\t}\n\n\t\trequestedHeaders := requestHeader.Get(\"Access-Control-Request-Headers\")\n\t\tif requestedHeaders == \"\" {\n\t\t\twriter.Header().Set(\"Access-Control-Allow-Headers\", \"*\")\n\t\t} else {\n\t\t\twriter.Header().Set(\"Access-Control-Allow-Headers\", requestedHeaders)\n\t\t}\n\t}\n}\n\nfunc (c *Config) GetNormalizedUplinkHTTPMethod() string {\n\tif c.UplinkHTTPMethod == \"\" {\n\t\treturn \"POST\"\n\t}\n\n\treturn c.UplinkHTTPMethod\n}\n\nfunc (c *Config) GetNormalizedScMaxEachPostBytes() RangeConfig {\n\tif c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {\n\t\treturn RangeConfig{\n\t\t\tFrom: 1000000,\n\t\t\tTo:   1000000,\n\t\t}\n\t}\n\n\treturn *c.ScMaxEachPostBytes\n}\n\nfunc (c *Config) GetNormalizedScMinPostsIntervalMs() RangeConfig {\n\tif c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {\n\t\treturn RangeConfig{\n\t\t\tFrom: 30,\n\t\t\tTo:   30,\n\t\t}\n\t}\n\n\treturn *c.ScMinPostsIntervalMs\n}\n\nfunc (c *Config) GetNormalizedScMaxBufferedPosts() int {\n\tif c.ScMaxBufferedPosts == 0 {\n\t\treturn 30\n\t}\n\n\treturn int(c.ScMaxBufferedPosts)\n}\n\nfunc (c *Config) GetNormalizedScStreamUpServerSecs() RangeConfig {\n\tif c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 {\n\t\treturn RangeConfig{\n\t\t\tFrom: 20,\n\t\t\tTo:   80,\n\t\t}\n\t}\n\n\treturn *c.ScStreamUpServerSecs\n}\n\nfunc (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {\n\tif c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 {\n\t\tswitch c.UplinkDataPlacement {\n\t\tcase PlacementCookie:\n\t\t\treturn RangeConfig{\n\t\t\t\tFrom: 2 * 1024, // 2 KiB\n\t\t\t\tTo:   3 * 1024, // 3 KiB\n\t\t\t}\n\t\tcase PlacementHeader:\n\t\t\treturn RangeConfig{\n\t\t\t\tFrom: 3 * 1000, // 3 KB\n\t\t\t\tTo:   4 * 1000, // 4 KB\n\t\t\t}\n\t\tdefault:\n\t\t\treturn c.GetNormalizedScMaxEachPostBytes()\n\t\t}\n\t} else if c.UplinkChunkSize.From < 64 {\n\t\treturn RangeConfig{\n\t\t\tFrom: 64,\n\t\t\tTo:   max(64, c.UplinkChunkSize.To),\n\t\t}\n\t}\n\n\treturn *c.UplinkChunkSize\n}\n\nfunc (c *Config) GetNormalizedServerMaxHeaderBytes() int {\n\tif c.ServerMaxHeaderBytes <= 0 {\n\t\treturn 8192\n\t} else {\n\t\treturn int(c.ServerMaxHeaderBytes)\n\t}\n}\n\nfunc (c *Config) GetNormalizedSessionPlacement() string {\n\tif c.SessionPlacement == \"\" {\n\t\treturn PlacementPath\n\t}\n\treturn c.SessionPlacement\n}\n\nfunc (c *Config) GetNormalizedSeqPlacement() string {\n\tif c.SeqPlacement == \"\" {\n\t\treturn PlacementPath\n\t}\n\treturn c.SeqPlacement\n}\n\nfunc (c *Config) GetNormalizedUplinkDataPlacement() string {\n\tif c.UplinkDataPlacement == \"\" {\n\t\treturn PlacementBody\n\t}\n\treturn c.UplinkDataPlacement\n}\n\nfunc (c *Config) GetNormalizedSessionKey() string {\n\tif c.SessionKey != \"\" {\n\t\treturn c.SessionKey\n\t}\n\tswitch c.GetNormalizedSessionPlacement() {\n\tcase PlacementHeader:\n\t\treturn \"X-Session\"\n\tcase PlacementCookie, PlacementQuery:\n\t\treturn \"x_session\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc (c *Config) GetNormalizedSeqKey() string {\n\tif c.SeqKey != \"\" {\n\t\treturn c.SeqKey\n\t}\n\tswitch c.GetNormalizedSeqPlacement() {\n\tcase PlacementHeader:\n\t\treturn \"X-Seq\"\n\tcase PlacementCookie, PlacementQuery:\n\t\treturn \"x_seq\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc (c *Config) ApplyMetaToRequest(req *http.Request, sessionId string, seqStr string) {\n\tsessionPlacement := c.GetNormalizedSessionPlacement()\n\tseqPlacement := c.GetNormalizedSeqPlacement()\n\tsessionKey := c.GetNormalizedSessionKey()\n\tseqKey := c.GetNormalizedSeqKey()\n\n\tif sessionId != \"\" {\n\t\tswitch sessionPlacement {\n\t\tcase PlacementPath:\n\t\t\treq.URL.Path = appendToPath(req.URL.Path, sessionId)\n\t\tcase PlacementQuery:\n\t\t\tq := req.URL.Query()\n\t\t\tq.Set(sessionKey, sessionId)\n\t\t\treq.URL.RawQuery = q.Encode()\n\t\tcase PlacementHeader:\n\t\t\treq.Header.Set(sessionKey, sessionId)\n\t\tcase PlacementCookie:\n\t\t\treq.AddCookie(&http.Cookie{Name: sessionKey, Value: sessionId})\n\t\t}\n\t}\n\n\tif seqStr != \"\" {\n\t\tswitch seqPlacement {\n\t\tcase PlacementPath:\n\t\t\treq.URL.Path = appendToPath(req.URL.Path, seqStr)\n\t\tcase PlacementQuery:\n\t\t\tq := req.URL.Query()\n\t\t\tq.Set(seqKey, seqStr)\n\t\t\treq.URL.RawQuery = q.Encode()\n\t\tcase PlacementHeader:\n\t\t\treq.Header.Set(seqKey, seqStr)\n\t\tcase PlacementCookie:\n\t\t\treq.AddCookie(&http.Cookie{Name: seqKey, Value: seqStr})\n\t\t}\n\t}\n}\n\nfunc (c *Config) FillStreamRequest(request *http.Request, sessionId string, seqStr string) {\n\trequest.Header = c.GetRequestHeader()\n\tlength := int(c.GetNormalizedXPaddingBytes().rand())\n\tconfig := XPaddingConfig{Length: length}\n\n\tif c.XPaddingObfsMode {\n\t\tconfig.Placement = XPaddingPlacement{\n\t\t\tPlacement: c.XPaddingPlacement,\n\t\t\tKey:       c.XPaddingKey,\n\t\t\tHeader:    c.XPaddingHeader,\n\t\t\tRawURL:    request.URL.String(),\n\t\t}\n\t\tconfig.Method = PaddingMethod(c.XPaddingMethod)\n\t} else {\n\t\tconfig.Placement = XPaddingPlacement{\n\t\t\tPlacement: PlacementQueryInHeader,\n\t\t\tKey:       \"x_padding\",\n\t\t\tHeader:    \"Referer\",\n\t\t\tRawURL:    request.URL.String(),\n\t\t}\n\t}\n\n\tc.ApplyXPaddingToRequest(request, config)\n\tc.ApplyMetaToRequest(request, sessionId, \"\")\n\n\tif request.Body != nil && !c.NoGRPCHeader { // stream-up/one\n\t\trequest.Header.Set(\"Content-Type\", \"application/grpc\")\n\t}\n}\n\nfunc (c *Config) FillPacketRequest(request *http.Request, sessionId string, seqStr string) error {\n\tdataPlacement := c.GetNormalizedUplinkDataPlacement()\n\n\tif dataPlacement == PlacementBody || dataPlacement == PlacementAuto {\n\t\trequest.Header = c.GetRequestHeader()\n\t} else {\n\t\tvar data []byte\n\t\tvar err error\n\t\tif request.Body != nil {\n\t\t\tdata, err = io.ReadAll(request.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\trequest.Body = nil\n\t\trequest.ContentLength = 0\n\t\tswitch dataPlacement {\n\t\tcase PlacementHeader:\n\t\t\trequest.Header = c.GetRequestHeaderWithPayload(data)\n\t\tcase PlacementCookie:\n\t\t\trequest.Header = c.GetRequestHeader()\n\t\t\tfor _, cookie := range c.GetRequestCookiesWithPayload(data) {\n\t\t\t\trequest.AddCookie(cookie)\n\t\t\t}\n\t\t}\n\t}\n\n\tlength := int(c.GetNormalizedXPaddingBytes().rand())\n\tconfig := XPaddingConfig{Length: length}\n\n\tif c.XPaddingObfsMode {\n\t\tconfig.Placement = XPaddingPlacement{\n\t\t\tPlacement: c.XPaddingPlacement,\n\t\t\tKey:       c.XPaddingKey,\n\t\t\tHeader:    c.XPaddingHeader,\n\t\t\tRawURL:    request.URL.String(),\n\t\t}\n\t\tconfig.Method = PaddingMethod(c.XPaddingMethod)\n\t} else {\n\t\tconfig.Placement = XPaddingPlacement{\n\t\t\tPlacement: PlacementQueryInHeader,\n\t\t\tKey:       \"x_padding\",\n\t\t\tHeader:    \"Referer\",\n\t\t\tRawURL:    request.URL.String(),\n\t\t}\n\t}\n\n\tc.ApplyXPaddingToRequest(request, config)\n\tc.ApplyMetaToRequest(request, sessionId, seqStr)\n\n\treturn nil\n}\n\nfunc (c *Config) ExtractMetaFromRequest(req *http.Request, path string) (sessionId string, seqStr string) {\n\tsessionPlacement := c.GetNormalizedSessionPlacement()\n\tseqPlacement := c.GetNormalizedSeqPlacement()\n\tsessionKey := c.GetNormalizedSessionKey()\n\tseqKey := c.GetNormalizedSeqKey()\n\n\tvar subpath []string\n\tpathPart := 0\n\tif sessionPlacement == PlacementPath || seqPlacement == PlacementPath {\n\t\tsubpath = strings.Split(req.URL.Path[len(path):], \"/\")\n\t}\n\n\tswitch sessionPlacement {\n\tcase PlacementPath:\n\t\tif len(subpath) > pathPart {\n\t\t\tsessionId = subpath[pathPart]\n\t\t\tpathPart += 1\n\t\t}\n\tcase PlacementQuery:\n\t\tsessionId = req.URL.Query().Get(sessionKey)\n\tcase PlacementHeader:\n\t\tsessionId = req.Header.Get(sessionKey)\n\tcase PlacementCookie:\n\t\tif cookie, e := req.Cookie(sessionKey); e == nil {\n\t\t\tsessionId = cookie.Value\n\t\t}\n\t}\n\n\tswitch seqPlacement {\n\tcase PlacementPath:\n\t\tif len(subpath) > pathPart {\n\t\t\tseqStr = subpath[pathPart]\n\t\t\tpathPart += 1\n\t\t}\n\tcase PlacementQuery:\n\t\tseqStr = req.URL.Query().Get(seqKey)\n\tcase PlacementHeader:\n\t\tseqStr = req.Header.Get(seqKey)\n\tcase PlacementCookie:\n\t\tif cookie, e := req.Cookie(seqKey); e == nil {\n\t\t\tseqStr = cookie.Value\n\t\t}\n\t}\n\n\treturn sessionId, seqStr\n}\n\nfunc (m *XmuxConfig) GetNormalizedMaxConcurrency() RangeConfig {\n\tif m.MaxConcurrency == nil {\n\t\treturn RangeConfig{\n\t\t\tFrom: 0,\n\t\t\tTo:   0,\n\t\t}\n\t}\n\n\treturn *m.MaxConcurrency\n}\n\nfunc (m *XmuxConfig) GetNormalizedMaxConnections() RangeConfig {\n\tif m.MaxConnections == nil {\n\t\treturn RangeConfig{\n\t\t\tFrom: 0,\n\t\t\tTo:   0,\n\t\t}\n\t}\n\n\treturn *m.MaxConnections\n}\n\nfunc (m *XmuxConfig) GetNormalizedCMaxReuseTimes() RangeConfig {\n\tif m.CMaxReuseTimes == nil {\n\t\treturn RangeConfig{\n\t\t\tFrom: 0,\n\t\t\tTo:   0,\n\t\t}\n\t}\n\n\treturn *m.CMaxReuseTimes\n}\n\nfunc (m *XmuxConfig) GetNormalizedHMaxRequestTimes() RangeConfig {\n\tif m.HMaxRequestTimes == nil {\n\t\treturn RangeConfig{\n\t\t\tFrom: 0,\n\t\t\tTo:   0,\n\t\t}\n\t}\n\n\treturn *m.HMaxRequestTimes\n}\n\nfunc (m *XmuxConfig) GetNormalizedHMaxReusableSecs() RangeConfig {\n\tif m.HMaxReusableSecs == nil {\n\t\treturn RangeConfig{\n\t\t\tFrom: 0,\n\t\t\tTo:   0,\n\t\t}\n\t}\n\n\treturn *m.HMaxReusableSecs\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n\nfunc (c RangeConfig) rand() int32 {\n\treturn int32(crypto.RandBetween(int64(c.From), int64(c.To)))\n}\n\nfunc appendToPath(path, value string) string {\n\tif strings.HasSuffix(path, \"/\") {\n\t\treturn path + value\n\t}\n\treturn path + \"/\" + value\n}\n"
  },
  {
    "path": "transport/internet/splithttp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/splithttp/config.proto\n\npackage splithttp\n\nimport (\n\tinternet \"github.com/xtls/xray-core/transport/internet\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype RangeConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tFrom          int32                  `protobuf:\"varint,1,opt,name=from,proto3\" json:\"from,omitempty\"`\n\tTo            int32                  `protobuf:\"varint,2,opt,name=to,proto3\" json:\"to,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RangeConfig) Reset() {\n\t*x = RangeConfig{}\n\tmi := &file_transport_internet_splithttp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RangeConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RangeConfig) ProtoMessage() {}\n\nfunc (x *RangeConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_splithttp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RangeConfig.ProtoReflect.Descriptor instead.\nfunc (*RangeConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *RangeConfig) GetFrom() int32 {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn 0\n}\n\nfunc (x *RangeConfig) GetTo() int32 {\n\tif x != nil {\n\t\treturn x.To\n\t}\n\treturn 0\n}\n\ntype XmuxConfig struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tMaxConcurrency   *RangeConfig           `protobuf:\"bytes,1,opt,name=maxConcurrency,proto3\" json:\"maxConcurrency,omitempty\"`\n\tMaxConnections   *RangeConfig           `protobuf:\"bytes,2,opt,name=maxConnections,proto3\" json:\"maxConnections,omitempty\"`\n\tCMaxReuseTimes   *RangeConfig           `protobuf:\"bytes,3,opt,name=cMaxReuseTimes,proto3\" json:\"cMaxReuseTimes,omitempty\"`\n\tHMaxRequestTimes *RangeConfig           `protobuf:\"bytes,4,opt,name=hMaxRequestTimes,proto3\" json:\"hMaxRequestTimes,omitempty\"`\n\tHMaxReusableSecs *RangeConfig           `protobuf:\"bytes,5,opt,name=hMaxReusableSecs,proto3\" json:\"hMaxReusableSecs,omitempty\"`\n\tHKeepAlivePeriod int64                  `protobuf:\"varint,6,opt,name=hKeepAlivePeriod,proto3\" json:\"hKeepAlivePeriod,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *XmuxConfig) Reset() {\n\t*x = XmuxConfig{}\n\tmi := &file_transport_internet_splithttp_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *XmuxConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*XmuxConfig) ProtoMessage() {}\n\nfunc (x *XmuxConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_splithttp_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use XmuxConfig.ProtoReflect.Descriptor instead.\nfunc (*XmuxConfig) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *XmuxConfig) GetMaxConcurrency() *RangeConfig {\n\tif x != nil {\n\t\treturn x.MaxConcurrency\n\t}\n\treturn nil\n}\n\nfunc (x *XmuxConfig) GetMaxConnections() *RangeConfig {\n\tif x != nil {\n\t\treturn x.MaxConnections\n\t}\n\treturn nil\n}\n\nfunc (x *XmuxConfig) GetCMaxReuseTimes() *RangeConfig {\n\tif x != nil {\n\t\treturn x.CMaxReuseTimes\n\t}\n\treturn nil\n}\n\nfunc (x *XmuxConfig) GetHMaxRequestTimes() *RangeConfig {\n\tif x != nil {\n\t\treturn x.HMaxRequestTimes\n\t}\n\treturn nil\n}\n\nfunc (x *XmuxConfig) GetHMaxReusableSecs() *RangeConfig {\n\tif x != nil {\n\t\treturn x.HMaxReusableSecs\n\t}\n\treturn nil\n}\n\nfunc (x *XmuxConfig) GetHKeepAlivePeriod() int64 {\n\tif x != nil {\n\t\treturn x.HKeepAlivePeriod\n\t}\n\treturn 0\n}\n\ntype Config struct {\n\tstate                protoimpl.MessageState `protogen:\"open.v1\"`\n\tHost                 string                 `protobuf:\"bytes,1,opt,name=host,proto3\" json:\"host,omitempty\"`\n\tPath                 string                 `protobuf:\"bytes,2,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tMode                 string                 `protobuf:\"bytes,3,opt,name=mode,proto3\" json:\"mode,omitempty\"`\n\tHeaders              map[string]string      `protobuf:\"bytes,4,rep,name=headers,proto3\" json:\"headers,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tXPaddingBytes        *RangeConfig           `protobuf:\"bytes,5,opt,name=xPaddingBytes,proto3\" json:\"xPaddingBytes,omitempty\"`\n\tNoGRPCHeader         bool                   `protobuf:\"varint,6,opt,name=noGRPCHeader,proto3\" json:\"noGRPCHeader,omitempty\"`\n\tNoSSEHeader          bool                   `protobuf:\"varint,7,opt,name=noSSEHeader,proto3\" json:\"noSSEHeader,omitempty\"`\n\tScMaxEachPostBytes   *RangeConfig           `protobuf:\"bytes,8,opt,name=scMaxEachPostBytes,proto3\" json:\"scMaxEachPostBytes,omitempty\"`\n\tScMinPostsIntervalMs *RangeConfig           `protobuf:\"bytes,9,opt,name=scMinPostsIntervalMs,proto3\" json:\"scMinPostsIntervalMs,omitempty\"`\n\tScMaxBufferedPosts   int64                  `protobuf:\"varint,10,opt,name=scMaxBufferedPosts,proto3\" json:\"scMaxBufferedPosts,omitempty\"`\n\tScStreamUpServerSecs *RangeConfig           `protobuf:\"bytes,11,opt,name=scStreamUpServerSecs,proto3\" json:\"scStreamUpServerSecs,omitempty\"`\n\tXmux                 *XmuxConfig            `protobuf:\"bytes,12,opt,name=xmux,proto3\" json:\"xmux,omitempty\"`\n\tDownloadSettings     *internet.StreamConfig `protobuf:\"bytes,13,opt,name=downloadSettings,proto3\" json:\"downloadSettings,omitempty\"`\n\tXPaddingObfsMode     bool                   `protobuf:\"varint,14,opt,name=xPaddingObfsMode,proto3\" json:\"xPaddingObfsMode,omitempty\"`\n\tXPaddingKey          string                 `protobuf:\"bytes,15,opt,name=xPaddingKey,proto3\" json:\"xPaddingKey,omitempty\"`\n\tXPaddingHeader       string                 `protobuf:\"bytes,16,opt,name=xPaddingHeader,proto3\" json:\"xPaddingHeader,omitempty\"`\n\tXPaddingPlacement    string                 `protobuf:\"bytes,17,opt,name=xPaddingPlacement,proto3\" json:\"xPaddingPlacement,omitempty\"`\n\tXPaddingMethod       string                 `protobuf:\"bytes,18,opt,name=xPaddingMethod,proto3\" json:\"xPaddingMethod,omitempty\"`\n\tUplinkHTTPMethod     string                 `protobuf:\"bytes,19,opt,name=uplinkHTTPMethod,proto3\" json:\"uplinkHTTPMethod,omitempty\"`\n\tSessionPlacement     string                 `protobuf:\"bytes,20,opt,name=sessionPlacement,proto3\" json:\"sessionPlacement,omitempty\"`\n\tSessionKey           string                 `protobuf:\"bytes,21,opt,name=sessionKey,proto3\" json:\"sessionKey,omitempty\"`\n\tSeqPlacement         string                 `protobuf:\"bytes,22,opt,name=seqPlacement,proto3\" json:\"seqPlacement,omitempty\"`\n\tSeqKey               string                 `protobuf:\"bytes,23,opt,name=seqKey,proto3\" json:\"seqKey,omitempty\"`\n\tUplinkDataPlacement  string                 `protobuf:\"bytes,24,opt,name=uplinkDataPlacement,proto3\" json:\"uplinkDataPlacement,omitempty\"`\n\tUplinkDataKey        string                 `protobuf:\"bytes,25,opt,name=uplinkDataKey,proto3\" json:\"uplinkDataKey,omitempty\"`\n\tUplinkChunkSize      *RangeConfig           `protobuf:\"bytes,26,opt,name=uplinkChunkSize,proto3\" json:\"uplinkChunkSize,omitempty\"`\n\tServerMaxHeaderBytes int32                  `protobuf:\"varint,27,opt,name=serverMaxHeaderBytes,proto3\" json:\"serverMaxHeaderBytes,omitempty\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_splithttp_config_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_splithttp_config_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_splithttp_config_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Config) GetHost() string {\n\tif x != nil {\n\t\treturn x.Host\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMode() string {\n\tif x != nil {\n\t\treturn x.Mode\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetHeaders() map[string]string {\n\tif x != nil {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetXPaddingBytes() *RangeConfig {\n\tif x != nil {\n\t\treturn x.XPaddingBytes\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetNoGRPCHeader() bool {\n\tif x != nil {\n\t\treturn x.NoGRPCHeader\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetNoSSEHeader() bool {\n\tif x != nil {\n\t\treturn x.NoSSEHeader\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetScMaxEachPostBytes() *RangeConfig {\n\tif x != nil {\n\t\treturn x.ScMaxEachPostBytes\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetScMinPostsIntervalMs() *RangeConfig {\n\tif x != nil {\n\t\treturn x.ScMinPostsIntervalMs\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetScMaxBufferedPosts() int64 {\n\tif x != nil {\n\t\treturn x.ScMaxBufferedPosts\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetScStreamUpServerSecs() *RangeConfig {\n\tif x != nil {\n\t\treturn x.ScStreamUpServerSecs\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetXmux() *XmuxConfig {\n\tif x != nil {\n\t\treturn x.Xmux\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetDownloadSettings() *internet.StreamConfig {\n\tif x != nil {\n\t\treturn x.DownloadSettings\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetXPaddingObfsMode() bool {\n\tif x != nil {\n\t\treturn x.XPaddingObfsMode\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetXPaddingKey() string {\n\tif x != nil {\n\t\treturn x.XPaddingKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetXPaddingHeader() string {\n\tif x != nil {\n\t\treturn x.XPaddingHeader\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetXPaddingPlacement() string {\n\tif x != nil {\n\t\treturn x.XPaddingPlacement\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetXPaddingMethod() string {\n\tif x != nil {\n\t\treturn x.XPaddingMethod\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetUplinkHTTPMethod() string {\n\tif x != nil {\n\t\treturn x.UplinkHTTPMethod\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetSessionPlacement() string {\n\tif x != nil {\n\t\treturn x.SessionPlacement\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetSessionKey() string {\n\tif x != nil {\n\t\treturn x.SessionKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetSeqPlacement() string {\n\tif x != nil {\n\t\treturn x.SeqPlacement\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetSeqKey() string {\n\tif x != nil {\n\t\treturn x.SeqKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetUplinkDataPlacement() string {\n\tif x != nil {\n\t\treturn x.UplinkDataPlacement\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetUplinkDataKey() string {\n\tif x != nil {\n\t\treturn x.UplinkDataKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetUplinkChunkSize() *RangeConfig {\n\tif x != nil {\n\t\treturn x.UplinkChunkSize\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetServerMaxHeaderBytes() int32 {\n\tif x != nil {\n\t\treturn x.ServerMaxHeaderBytes\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_splithttp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\")transport/internet/splithttp/config.proto\\x12!xray.transport.internet.splithttp\\x1a\\x1ftransport/internet/config.proto\\\"1\\n\" +\n\t\"\\vRangeConfig\\x12\\x12\\n\" +\n\t\"\\x04from\\x18\\x01 \\x01(\\x05R\\x04from\\x12\\x0e\\n\" +\n\t\"\\x02to\\x18\\x02 \\x01(\\x05R\\x02to\\\"\\xf8\\x03\\n\" +\n\t\"\\n\" +\n\t\"XmuxConfig\\x12V\\n\" +\n\t\"\\x0emaxConcurrency\\x18\\x01 \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x0emaxConcurrency\\x12V\\n\" +\n\t\"\\x0emaxConnections\\x18\\x02 \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x0emaxConnections\\x12V\\n\" +\n\t\"\\x0ecMaxReuseTimes\\x18\\x03 \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x0ecMaxReuseTimes\\x12Z\\n\" +\n\t\"\\x10hMaxRequestTimes\\x18\\x04 \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x10hMaxRequestTimes\\x12Z\\n\" +\n\t\"\\x10hMaxReusableSecs\\x18\\x05 \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x10hMaxReusableSecs\\x12*\\n\" +\n\t\"\\x10hKeepAlivePeriod\\x18\\x06 \\x01(\\x03R\\x10hKeepAlivePeriod\\\"\\xc2\\v\\n\" +\n\t\"\\x06Config\\x12\\x12\\n\" +\n\t\"\\x04host\\x18\\x01 \\x01(\\tR\\x04host\\x12\\x12\\n\" +\n\t\"\\x04path\\x18\\x02 \\x01(\\tR\\x04path\\x12\\x12\\n\" +\n\t\"\\x04mode\\x18\\x03 \\x01(\\tR\\x04mode\\x12P\\n\" +\n\t\"\\aheaders\\x18\\x04 \\x03(\\v26.xray.transport.internet.splithttp.Config.HeadersEntryR\\aheaders\\x12T\\n\" +\n\t\"\\rxPaddingBytes\\x18\\x05 \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\rxPaddingBytes\\x12\\\"\\n\" +\n\t\"\\fnoGRPCHeader\\x18\\x06 \\x01(\\bR\\fnoGRPCHeader\\x12 \\n\" +\n\t\"\\vnoSSEHeader\\x18\\a \\x01(\\bR\\vnoSSEHeader\\x12^\\n\" +\n\t\"\\x12scMaxEachPostBytes\\x18\\b \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x12scMaxEachPostBytes\\x12b\\n\" +\n\t\"\\x14scMinPostsIntervalMs\\x18\\t \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x14scMinPostsIntervalMs\\x12.\\n\" +\n\t\"\\x12scMaxBufferedPosts\\x18\\n\" +\n\t\" \\x01(\\x03R\\x12scMaxBufferedPosts\\x12b\\n\" +\n\t\"\\x14scStreamUpServerSecs\\x18\\v \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x14scStreamUpServerSecs\\x12A\\n\" +\n\t\"\\x04xmux\\x18\\f \\x01(\\v2-.xray.transport.internet.splithttp.XmuxConfigR\\x04xmux\\x12Q\\n\" +\n\t\"\\x10downloadSettings\\x18\\r \\x01(\\v2%.xray.transport.internet.StreamConfigR\\x10downloadSettings\\x12*\\n\" +\n\t\"\\x10xPaddingObfsMode\\x18\\x0e \\x01(\\bR\\x10xPaddingObfsMode\\x12 \\n\" +\n\t\"\\vxPaddingKey\\x18\\x0f \\x01(\\tR\\vxPaddingKey\\x12&\\n\" +\n\t\"\\x0exPaddingHeader\\x18\\x10 \\x01(\\tR\\x0exPaddingHeader\\x12,\\n\" +\n\t\"\\x11xPaddingPlacement\\x18\\x11 \\x01(\\tR\\x11xPaddingPlacement\\x12&\\n\" +\n\t\"\\x0exPaddingMethod\\x18\\x12 \\x01(\\tR\\x0exPaddingMethod\\x12*\\n\" +\n\t\"\\x10uplinkHTTPMethod\\x18\\x13 \\x01(\\tR\\x10uplinkHTTPMethod\\x12*\\n\" +\n\t\"\\x10sessionPlacement\\x18\\x14 \\x01(\\tR\\x10sessionPlacement\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"sessionKey\\x18\\x15 \\x01(\\tR\\n\" +\n\t\"sessionKey\\x12\\\"\\n\" +\n\t\"\\fseqPlacement\\x18\\x16 \\x01(\\tR\\fseqPlacement\\x12\\x16\\n\" +\n\t\"\\x06seqKey\\x18\\x17 \\x01(\\tR\\x06seqKey\\x120\\n\" +\n\t\"\\x13uplinkDataPlacement\\x18\\x18 \\x01(\\tR\\x13uplinkDataPlacement\\x12$\\n\" +\n\t\"\\ruplinkDataKey\\x18\\x19 \\x01(\\tR\\ruplinkDataKey\\x12X\\n\" +\n\t\"\\x0fuplinkChunkSize\\x18\\x1a \\x01(\\v2..xray.transport.internet.splithttp.RangeConfigR\\x0fuplinkChunkSize\\x122\\n\" +\n\t\"\\x14serverMaxHeaderBytes\\x18\\x1b \\x01(\\x05R\\x14serverMaxHeaderBytes\\x1a:\\n\" +\n\t\"\\fHeadersEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\x85\\x01\\n\" +\n\t\"%com.xray.transport.internet.splithttpP\\x01Z6github.com/xtls/xray-core/transport/internet/splithttp\\xaa\\x02!Xray.Transport.Internet.SplitHttpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_splithttp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_splithttp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_splithttp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_splithttp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_splithttp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_splithttp_config_proto_rawDesc), len(file_transport_internet_splithttp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_splithttp_config_proto_rawDescData\n}\n\nvar file_transport_internet_splithttp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4)\nvar file_transport_internet_splithttp_config_proto_goTypes = []any{\n\t(*RangeConfig)(nil),           // 0: xray.transport.internet.splithttp.RangeConfig\n\t(*XmuxConfig)(nil),            // 1: xray.transport.internet.splithttp.XmuxConfig\n\t(*Config)(nil),                // 2: xray.transport.internet.splithttp.Config\n\tnil,                           // 3: xray.transport.internet.splithttp.Config.HeadersEntry\n\t(*internet.StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig\n}\nvar file_transport_internet_splithttp_config_proto_depIdxs = []int32{\n\t0,  // 0: xray.transport.internet.splithttp.XmuxConfig.maxConcurrency:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 1: xray.transport.internet.splithttp.XmuxConfig.maxConnections:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 2: xray.transport.internet.splithttp.XmuxConfig.cMaxReuseTimes:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 3: xray.transport.internet.splithttp.XmuxConfig.hMaxRequestTimes:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 4: xray.transport.internet.splithttp.XmuxConfig.hMaxReusableSecs:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t3,  // 5: xray.transport.internet.splithttp.Config.headers:type_name -> xray.transport.internet.splithttp.Config.HeadersEntry\n\t0,  // 6: xray.transport.internet.splithttp.Config.xPaddingBytes:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 7: xray.transport.internet.splithttp.Config.scMaxEachPostBytes:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 8: xray.transport.internet.splithttp.Config.scMinPostsIntervalMs:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t0,  // 9: xray.transport.internet.splithttp.Config.scStreamUpServerSecs:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t1,  // 10: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig\n\t4,  // 11: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig\n\t0,  // 12: xray.transport.internet.splithttp.Config.uplinkChunkSize:type_name -> xray.transport.internet.splithttp.RangeConfig\n\t13, // [13:13] is the sub-list for method output_type\n\t13, // [13:13] is the sub-list for method input_type\n\t13, // [13:13] is the sub-list for extension type_name\n\t13, // [13:13] is the sub-list for extension extendee\n\t0,  // [0:13] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_splithttp_config_proto_init() }\nfunc file_transport_internet_splithttp_config_proto_init() {\n\tif File_transport_internet_splithttp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_splithttp_config_proto_rawDesc), len(file_transport_internet_splithttp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   4,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_splithttp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_splithttp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_splithttp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_splithttp_config_proto = out.File\n\tfile_transport_internet_splithttp_config_proto_goTypes = nil\n\tfile_transport_internet_splithttp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/splithttp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.splithttp;\noption csharp_namespace = \"Xray.Transport.Internet.SplitHttp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/splithttp\";\noption java_package = \"com.xray.transport.internet.splithttp\";\noption java_multiple_files = true;\n\nimport \"transport/internet/config.proto\";\n\nmessage RangeConfig {\n  int32 from = 1;\n  int32 to = 2;\n}\n\nmessage XmuxConfig {\n  RangeConfig maxConcurrency = 1;\n  RangeConfig maxConnections = 2;\n  RangeConfig cMaxReuseTimes = 3;\n  RangeConfig hMaxRequestTimes = 4;\n  RangeConfig hMaxReusableSecs = 5;\n  int64 hKeepAlivePeriod = 6;\n}\n\nmessage Config {\n  string host = 1;\n  string path = 2;\n  string mode = 3;\n  map<string, string> headers = 4;\n  RangeConfig xPaddingBytes = 5;\n  bool noGRPCHeader = 6;\n  bool noSSEHeader = 7;\n  RangeConfig scMaxEachPostBytes = 8;\n  RangeConfig scMinPostsIntervalMs = 9;\n  int64 scMaxBufferedPosts = 10;\n  RangeConfig scStreamUpServerSecs = 11;\n  XmuxConfig xmux = 12;\n  xray.transport.internet.StreamConfig downloadSettings = 13;\n  bool xPaddingObfsMode = 14;\n  string xPaddingKey = 15;\n  string xPaddingHeader = 16;\n  string xPaddingPlacement = 17;\n  string xPaddingMethod = 18;\n  string uplinkHTTPMethod = 19;\n  string sessionPlacement = 20;\n  string sessionKey = 21;\n  string seqPlacement = 22;\n  string seqKey = 23;\n  string uplinkDataPlacement = 24;\n  string uplinkDataKey = 25;\n  RangeConfig uplinkChunkSize = 26;\n  int32 serverMaxHeaderBytes = 27;\n}\n"
  },
  {
    "path": "transport/internet/splithttp/config_test.go",
    "content": "package splithttp_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/transport/internet/splithttp\"\n)\n\nfunc Test_GetNormalizedPath(t *testing.T) {\n\tc := Config{\n\t\tPath: \"/?world\",\n\t}\n\n\tpath := c.GetNormalizedPath()\n\tif path != \"/\" {\n\t\tt.Error(\"Unexpected: \", path)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/splithttp/connection.go",
    "content": "package splithttp\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\ntype splitConn struct {\n\twriter     io.WriteCloser\n\treader     io.ReadCloser\n\tremoteAddr net.Addr\n\tlocalAddr  net.Addr\n\tonClose    func()\n}\n\nfunc (c *splitConn) Write(b []byte) (int, error) {\n\treturn c.writer.Write(b)\n}\n\nfunc (c *splitConn) Read(b []byte) (int, error) {\n\treturn c.reader.Read(b)\n}\n\nfunc (c *splitConn) Close() error {\n\tif c.onClose != nil {\n\t\tc.onClose()\n\t}\n\n\terr := c.writer.Close()\n\terr2 := c.reader.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err2 != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *splitConn) LocalAddr() net.Addr {\n\treturn c.localAddr\n}\n\nfunc (c *splitConn) RemoteAddr() net.Addr {\n\treturn c.remoteAddr\n}\n\nfunc (c *splitConn) SetDeadline(t time.Time) error {\n\t// TODO cannot do anything useful\n\treturn nil\n}\n\nfunc (c *splitConn) SetReadDeadline(t time.Time) error {\n\t// TODO cannot do anything useful\n\treturn nil\n}\n\nfunc (c *splitConn) SetWriteDeadline(t time.Time) error {\n\t// TODO cannot do anything useful\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/splithttp/dialer.go",
    "content": "package splithttp\n\nimport (\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/http/httptrace\"\n\t\"net/url\"\n\treflect \"reflect\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/apernet/quic-go/http3\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/common/uuid\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/browser_dialer\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/udphop\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n\t\"golang.org/x/net/http2\"\n)\n\ntype dialerConf struct {\n\tnet.Destination\n\t*internet.MemoryStreamConfig\n}\n\nvar (\n\tglobalDialerMap    map[dialerConf]*XmuxManager\n\tglobalDialerAccess sync.Mutex\n)\n\nfunc getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (DialerClient, *XmuxClient) {\n\trealityConfig := reality.ConfigFromStreamSettings(streamSettings)\n\n\tif browser_dialer.HasBrowserDialer() && realityConfig == nil {\n\t\treturn &BrowserDialerClient{transportConfig: streamSettings.ProtocolSettings.(*Config)}, nil\n\t}\n\n\tglobalDialerAccess.Lock()\n\tdefer globalDialerAccess.Unlock()\n\n\tif globalDialerMap == nil {\n\t\tglobalDialerMap = make(map[dialerConf]*XmuxManager)\n\t}\n\n\tkey := dialerConf{dest, streamSettings}\n\n\txmuxManager, found := globalDialerMap[key]\n\n\tif !found {\n\t\ttransportConfig := streamSettings.ProtocolSettings.(*Config)\n\t\tvar xmuxConfig XmuxConfig\n\t\tif transportConfig.Xmux != nil {\n\t\t\txmuxConfig = *transportConfig.Xmux\n\t\t}\n\n\t\txmuxManager = NewXmuxManager(xmuxConfig, func() XmuxConn {\n\t\t\treturn createHTTPClient(dest, streamSettings)\n\t\t})\n\t\tglobalDialerMap[key] = xmuxManager\n\t}\n\n\txmuxClient := xmuxManager.GetXmuxClient(ctx)\n\treturn xmuxClient.XmuxConn.(DialerClient), xmuxClient\n}\n\nfunc decideHTTPVersion(tlsConfig *tls.Config, realityConfig *reality.Config) string {\n\tif realityConfig != nil {\n\t\treturn \"2\"\n\t}\n\tif tlsConfig == nil {\n\t\treturn \"1.1\"\n\t}\n\tif len(tlsConfig.NextProtocol) != 1 {\n\t\treturn \"2\"\n\t}\n\tif tlsConfig.NextProtocol[0] == \"http/1.1\" {\n\t\treturn \"1.1\"\n\t}\n\tif tlsConfig.NextProtocol[0] == \"h3\" {\n\t\treturn \"3\"\n\t}\n\treturn \"2\"\n}\n\nfunc createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) DialerClient {\n\ttlsConfig := tls.ConfigFromStreamSettings(streamSettings)\n\trealityConfig := reality.ConfigFromStreamSettings(streamSettings)\n\n\thttpVersion := decideHTTPVersion(tlsConfig, realityConfig)\n\tif httpVersion == \"3\" {\n\t\tdest.Network = net.Network_UDP // better to keep this line\n\t}\n\n\tvar gotlsConfig *gotls.Config\n\n\tif tlsConfig != nil {\n\t\tgotlsConfig = tlsConfig.GetTLSConfig(tls.WithDestination(dest))\n\t}\n\n\ttransportConfig := streamSettings.ProtocolSettings.(*Config)\n\n\tdialContext := func(ctxInner context.Context) (net.Conn, error) {\n\t\tconn, err := internet.DialSystem(ctxInner, dest, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif streamSettings.TcpmaskManager != nil {\n\t\t\tnewConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t}\n\t\t\tconn = newConn\n\t\t}\n\n\t\tif realityConfig != nil {\n\t\t\treturn reality.UClient(conn, realityConfig, ctxInner, dest)\n\t\t}\n\n\t\tif gotlsConfig != nil {\n\t\t\tif fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil {\n\t\t\t\tconn = tls.UClient(conn, gotlsConfig, fingerprint)\n\t\t\t\tif err := conn.(*tls.UConn).HandshakeContext(ctxInner); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconn = tls.Client(conn, gotlsConfig)\n\t\t\t}\n\t\t}\n\n\t\treturn conn, nil\n\t}\n\n\tvar keepAlivePeriod time.Duration\n\tif streamSettings.ProtocolSettings.(*Config).Xmux != nil {\n\t\tkeepAlivePeriod = time.Duration(streamSettings.ProtocolSettings.(*Config).Xmux.HKeepAlivePeriod) * time.Second\n\t}\n\n\tvar transport http.RoundTripper\n\n\tif httpVersion == \"3\" {\n\t\tquicParams := streamSettings.QuicParams\n\t\tif quicParams == nil {\n\t\t\tquicParams = &internet.QuicParams{}\n\t\t}\n\t\tif quicParams.UdpHop == nil {\n\t\t\tquicParams.UdpHop = &internet.UdpHop{}\n\t\t}\n\n\t\tquicConfig := &quic.Config{\n\t\t\tInitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,\n\t\t\tMaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,\n\t\t\tInitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,\n\t\t\tMaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,\n\t\t\tMaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,\n\t\t\tKeepAlivePeriod:                time.Duration(quicParams.KeepAlivePeriod) * time.Second,\n\t\t\tMaxIncomingStreams:             quicParams.MaxIncomingStreams,\n\t\t\tDisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,\n\t\t}\n\t\tif quicParams.MaxIdleTimeout == 0 {\n\t\t\tquicConfig.MaxIdleTimeout = net.ConnIdleTimeout\n\t\t}\n\t\tif quicParams.KeepAlivePeriod == 0 {\n\t\t\tif keepAlivePeriod == 0 {\n\t\t\t\tquicConfig.KeepAlivePeriod = net.QuicgoH3KeepAlivePeriod\n\t\t\t}\n\t\t}\n\t\tif quicParams.MaxIncomingStreams == 0 {\n\t\t\t// these two are defaults of quic-go/http3. the default of quic-go (no\n\t\t\t// http3) is different, so it is hardcoded here for clarity.\n\t\t\t// https://github.com/quic-go/quic-go/blob/b8ea5c798155950fb5bbfdd06cad1939c9355878/http3/client.go#L36-L39\n\t\t\tquicConfig.MaxIncomingStreams = -1\n\t\t}\n\n\t\ttransport = &http3.Transport{\n\t\t\tQUICConfig:      quicConfig,\n\t\t\tTLSClientConfig: gotlsConfig,\n\t\t\tDial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\t\tudphopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {\n\t\t\t\t\tconn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\terrors.LogDebug(context.Background(), \"skip hop: failed to dial to dest\")\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New()\n\t\t\t\t\t}\n\n\t\t\t\t\tvar udpConn net.PacketConn\n\n\t\t\t\t\tswitch c := conn.(type) {\n\t\t\t\t\tcase *internet.PacketConnWrapper:\n\t\t\t\t\t\tudpConn = c.PacketConn\n\t\t\t\t\tcase *net.UDPConn:\n\t\t\t\t\t\tudpConn = c\n\t\t\t\t\tdefault:\n\t\t\t\t\t\terrors.LogDebug(context.Background(), \"skip hop: udphop requires being at the outermost level \", reflect.TypeOf(c))\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New()\n\t\t\t\t\t}\n\n\t\t\t\t\treturn udpConn, nil\n\t\t\t\t}\n\n\t\t\t\tvar index int\n\t\t\t\tif len(quicParams.UdpHop.Ports) > 0 {\n\t\t\t\t\tindex = rand.Intn(len(quicParams.UdpHop.Ports))\n\t\t\t\t\tdest.Port = net.Port(quicParams.UdpHop.Ports[index])\n\t\t\t\t}\n\n\t\t\t\tconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tvar udpConn net.PacketConn\n\t\t\t\tvar udpAddr *net.UDPAddr\n\n\t\t\t\tswitch c := conn.(type) {\n\t\t\t\tcase *internet.PacketConnWrapper:\n\t\t\t\t\tudpConn = c.PacketConn\n\t\t\t\t\tudpAddr, err = net.ResolveUDPAddr(\"udp\", c.Dest.String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\tcase *net.UDPConn:\n\t\t\t\t\tudpConn = c\n\t\t\t\t\tudpAddr, err = net.ResolveUDPAddr(\"udp\", c.RemoteAddr().String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tudpConn = &internet.FakePacketConn{Conn: c}\n\t\t\t\t\tudpAddr, err = net.ResolveUDPAddr(\"udp\", c.RemoteAddr().String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(quicParams.UdpHop.Ports) > 0 {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"udphop requires being at the outermost level \", reflect.TypeOf(c))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif len(quicParams.UdpHop.Ports) > 0 {\n\t\t\t\t\taddr := &udphop.UDPHopAddr{\n\t\t\t\t\t\tIP:    udpAddr.IP,\n\t\t\t\t\t\tPorts: quicParams.UdpHop.Ports,\n\t\t\t\t\t}\n\t\t\t\t\tudpConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, udphopDialer, udpConn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"udphop err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif streamSettings.UdpmaskManager != nil {\n\t\t\t\t\tudpConn, err = streamSettings.UdpmaskManager.WrapPacketConnClient(udpConn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tquicConn, err := quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tswitch quicParams.Congestion {\n\t\t\t\tcase \"force-brutal\":\n\t\t\t\t\terrors.LogDebug(context.Background(), quicConn.RemoteAddr(), \" \", \"congestion brutal bytes per second \", quicParams.BrutalUp)\n\t\t\t\t\tcongestion.UseBrutal(quicConn, quicParams.BrutalUp)\n\t\t\t\tcase \"reno\":\n\t\t\t\t\terrors.LogDebug(context.Background(), quicConn.RemoteAddr(), \" \", \"congestion reno\")\n\t\t\t\tdefault:\n\t\t\t\t\terrors.LogDebug(context.Background(), quicConn.RemoteAddr(), \" \", \"congestion bbr\")\n\t\t\t\t\tcongestion.UseBBR(quicConn)\n\t\t\t\t}\n\n\t\t\t\treturn quicConn, nil\n\t\t\t},\n\t\t}\n\t} else if httpVersion == \"2\" {\n\t\tif keepAlivePeriod == 0 {\n\t\t\tkeepAlivePeriod = net.ChromeH2KeepAlivePeriod\n\t\t}\n\t\tif keepAlivePeriod < 0 {\n\t\t\tkeepAlivePeriod = 0\n\t\t}\n\t\ttransport = &http2.Transport{\n\t\t\tDialTLSContext: func(ctxInner context.Context, network string, addr string, cfg *gotls.Config) (net.Conn, error) {\n\t\t\t\treturn dialContext(ctxInner)\n\t\t\t},\n\t\t\tIdleConnTimeout: net.ConnIdleTimeout,\n\t\t\tReadIdleTimeout: keepAlivePeriod,\n\t\t}\n\t} else {\n\t\thttpDialContext := func(ctxInner context.Context, network string, addr string) (net.Conn, error) {\n\t\t\treturn dialContext(ctxInner)\n\t\t}\n\n\t\ttransport = &http.Transport{\n\t\t\tDialTLSContext:  httpDialContext,\n\t\t\tDialContext:     httpDialContext,\n\t\t\tIdleConnTimeout: net.ConnIdleTimeout,\n\t\t\t// chunked transfer download with KeepAlives is buggy with\n\t\t\t// http.Client and our custom dial context.\n\t\t\tDisableKeepAlives: true,\n\t\t}\n\t}\n\n\tclient := &DefaultDialerClient{\n\t\ttransportConfig: transportConfig,\n\t\tclient: &http.Client{\n\t\t\tTransport: transport,\n\t\t},\n\t\thttpVersion:    httpVersion,\n\t\tuploadRawPool:  &sync.Pool{},\n\t\tdialUploadConn: dialContext,\n\t}\n\n\treturn client\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, Dial))\n}\n\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\ttlsConfig := tls.ConfigFromStreamSettings(streamSettings)\n\trealityConfig := reality.ConfigFromStreamSettings(streamSettings)\n\n\thttpVersion := decideHTTPVersion(tlsConfig, realityConfig)\n\tif httpVersion == \"3\" {\n\t\tdest.Network = net.Network_UDP\n\t}\n\n\ttransportConfiguration := streamSettings.ProtocolSettings.(*Config)\n\tvar requestURL url.URL\n\n\tif tlsConfig != nil || realityConfig != nil {\n\t\trequestURL.Scheme = \"https\"\n\t} else {\n\t\trequestURL.Scheme = \"http\"\n\t}\n\trequestURL.Host = transportConfiguration.Host\n\tif requestURL.Host == \"\" && tlsConfig != nil {\n\t\trequestURL.Host = tlsConfig.ServerName\n\t}\n\tif requestURL.Host == \"\" && realityConfig != nil {\n\t\trequestURL.Host = realityConfig.ServerName\n\t}\n\tif requestURL.Host == \"\" {\n\t\trequestURL.Host = dest.Address.String()\n\t}\n\n\trequestURL.Path = transportConfiguration.GetNormalizedPath()\n\trequestURL.RawQuery = transportConfiguration.GetNormalizedQuery()\n\n\thttpClient, xmuxClient := getHTTPClient(ctx, dest, streamSettings)\n\n\tmode := transportConfiguration.Mode\n\tif mode == \"\" || mode == \"auto\" {\n\t\tmode = \"packet-up\"\n\t\tif realityConfig != nil {\n\t\t\tmode = \"stream-one\"\n\t\t\tif transportConfiguration.DownloadSettings != nil {\n\t\t\t\tmode = \"stream-up\"\n\t\t\t}\n\t\t}\n\t}\n\n\tsessionId := \"\"\n\tif mode != \"stream-one\" {\n\t\tsessionIdUuid := uuid.New()\n\t\tsessionId = sessionIdUuid.String()\n\t}\n\n\terrors.LogInfo(ctx, fmt.Sprintf(\"XHTTP is dialing to %s, mode %s, HTTP version %s, host %s\", dest, mode, httpVersion, requestURL.Host))\n\n\trequestURL2 := requestURL\n\thttpClient2 := httpClient\n\txmuxClient2 := xmuxClient\n\tif transportConfiguration.DownloadSettings != nil {\n\t\tglobalDialerAccess.Lock()\n\t\tif streamSettings.DownloadSettings == nil {\n\t\t\tstreamSettings.DownloadSettings = common.Must2(internet.ToMemoryStreamConfig(transportConfiguration.DownloadSettings))\n\t\t\tif streamSettings.SocketSettings != nil && streamSettings.SocketSettings.Penetrate {\n\t\t\t\tstreamSettings.DownloadSettings.SocketSettings = streamSettings.SocketSettings\n\t\t\t}\n\t\t}\n\t\tglobalDialerAccess.Unlock()\n\t\tmemory2 := streamSettings.DownloadSettings\n\t\tdest2 := *memory2.Destination // just panic\n\t\ttlsConfig2 := tls.ConfigFromStreamSettings(memory2)\n\t\trealityConfig2 := reality.ConfigFromStreamSettings(memory2)\n\t\thttpVersion2 := decideHTTPVersion(tlsConfig2, realityConfig2)\n\t\tif httpVersion2 == \"3\" {\n\t\t\tdest2.Network = net.Network_UDP\n\t\t}\n\t\tif tlsConfig2 != nil || realityConfig2 != nil {\n\t\t\trequestURL2.Scheme = \"https\"\n\t\t} else {\n\t\t\trequestURL2.Scheme = \"http\"\n\t\t}\n\t\tconfig2 := memory2.ProtocolSettings.(*Config)\n\t\trequestURL2.Host = config2.Host\n\t\tif requestURL2.Host == \"\" && tlsConfig2 != nil {\n\t\t\trequestURL2.Host = tlsConfig2.ServerName\n\t\t}\n\t\tif requestURL2.Host == \"\" && realityConfig2 != nil {\n\t\t\trequestURL2.Host = realityConfig2.ServerName\n\t\t}\n\t\tif requestURL2.Host == \"\" {\n\t\t\trequestURL2.Host = dest2.Address.String()\n\t\t}\n\t\trequestURL2.Path = config2.GetNormalizedPath()\n\t\trequestURL2.RawQuery = config2.GetNormalizedQuery()\n\t\thttpClient2, xmuxClient2 = getHTTPClient(ctx, dest2, memory2)\n\t\terrors.LogInfo(ctx, fmt.Sprintf(\"XHTTP is downloading from %s, mode %s, HTTP version %s, host %s\", dest2, \"stream-down\", httpVersion2, requestURL2.Host))\n\t}\n\n\tif xmuxClient != nil {\n\t\txmuxClient.OpenUsage.Add(1)\n\t}\n\tif xmuxClient2 != nil && xmuxClient2 != xmuxClient {\n\t\txmuxClient2.OpenUsage.Add(1)\n\t}\n\tvar closed atomic.Int32\n\n\treader, writer := io.Pipe()\n\tconn := splitConn{\n\t\twriter: writer,\n\t\tonClose: func() {\n\t\t\tif closed.Add(1) > 1 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif xmuxClient != nil {\n\t\t\t\txmuxClient.OpenUsage.Add(-1)\n\t\t\t}\n\t\t\tif xmuxClient2 != nil && xmuxClient2 != xmuxClient {\n\t\t\t\txmuxClient2.OpenUsage.Add(-1)\n\t\t\t}\n\t\t},\n\t}\n\n\tvar err error\n\tif mode == \"stream-one\" {\n\t\trequestURL.Path = transportConfiguration.GetNormalizedPath()\n\t\tif xmuxClient != nil {\n\t\t\txmuxClient.LeftRequests.Add(-1)\n\t\t}\n\t\tconn.reader, conn.remoteAddr, conn.localAddr, err = httpClient.OpenStream(ctx, requestURL.String(), sessionId, reader, false)\n\t\tif err != nil { // browser dialer only\n\t\t\treturn nil, err\n\t\t}\n\t\treturn stat.Connection(&conn), nil\n\t} else { // stream-down\n\t\tif xmuxClient2 != nil {\n\t\t\txmuxClient2.LeftRequests.Add(-1)\n\t\t}\n\t\tconn.reader, conn.remoteAddr, conn.localAddr, err = httpClient2.OpenStream(ctx, requestURL2.String(), sessionId, nil, false)\n\t\tif err != nil { // browser dialer only\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif mode == \"stream-up\" {\n\t\tif xmuxClient != nil {\n\t\t\txmuxClient.LeftRequests.Add(-1)\n\t\t}\n\t\t_, _, _, err = httpClient.OpenStream(ctx, requestURL.String(), sessionId, reader, true)\n\t\tif err != nil { // browser dialer only\n\t\t\treturn nil, err\n\t\t}\n\t\treturn stat.Connection(&conn), nil\n\t}\n\n\tscMaxEachPostBytes := transportConfiguration.GetNormalizedScMaxEachPostBytes()\n\tscMinPostsIntervalMs := transportConfiguration.GetNormalizedScMinPostsIntervalMs()\n\n\tif scMaxEachPostBytes.From <= 0 {\n\t\tpanic(\"`scMaxEachPostBytes` should be bigger than 0\")\n\t}\n\n\tmaxUploadSize := scMaxEachPostBytes.rand()\n\t// WithSizeLimit(0) will still allow single bytes to pass, and a lot of\n\t// code relies on this behavior. Subtract 1 so that together with\n\t// uploadWriter wrapper, exact size limits can be enforced\n\t// uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize - 1))\n\tuploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(max(0, maxUploadSize-buf.Size)))\n\n\tconn.writer = uploadWriter{\n\t\tuploadPipeWriter,\n\t\tmaxUploadSize,\n\t}\n\n\tgo func() {\n\t\tvar seq int64\n\t\tvar lastWrite time.Time\n\n\t\tfor {\n\t\t\t// by offloading the uploads into a buffered pipe, multiple conn.Write\n\t\t\t// calls get automatically batched together into larger POST requests.\n\t\t\t// without batching, bandwidth is extremely limited.\n\t\t\tremainder, err := uploadPipeReader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tdoSplit := atomic.Bool{}\n\t\t\tfor doSplit.Store(true); doSplit.Load(); {\n\t\t\t\tvar chunk buf.MultiBuffer\n\t\t\t\tremainder, chunk = buf.SplitSize(remainder, maxUploadSize)\n\t\t\t\tif chunk.IsEmpty() {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\twroteRequest := done.New()\n\n\t\t\t\tctx := httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{\n\t\t\t\t\tWroteRequest: func(httptrace.WroteRequestInfo) {\n\t\t\t\t\t\twroteRequest.Close()\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\tseqStr := strconv.FormatInt(seq, 10)\n\t\t\t\tseq += 1\n\n\t\t\t\tif scMinPostsIntervalMs.From > 0 {\n\t\t\t\t\ttime.Sleep(time.Duration(scMinPostsIntervalMs.rand())*time.Millisecond - time.Since(lastWrite))\n\t\t\t\t}\n\n\t\t\t\tlastWrite = time.Now()\n\n\t\t\t\tif xmuxClient != nil && (xmuxClient.LeftRequests.Add(-1) <= 0 ||\n\t\t\t\t\t(xmuxClient.UnreusableAt != time.Time{} && lastWrite.After(xmuxClient.UnreusableAt))) {\n\t\t\t\t\thttpClient, xmuxClient = getHTTPClient(ctx, dest, streamSettings)\n\t\t\t\t}\n\n\t\t\t\tgo func() {\n\t\t\t\t\terr := httpClient.PostPacket(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\trequestURL.String(),\n\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\tseqStr,\n\t\t\t\t\t\t&buf.MultiBufferContainer{MultiBuffer: chunk},\n\t\t\t\t\t\tint64(chunk.Len()),\n\t\t\t\t\t)\n\t\t\t\t\twroteRequest.Close()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to send upload\")\n\t\t\t\t\t\tuploadPipeReader.Interrupt()\n\t\t\t\t\t\tdoSplit.Store(false)\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\tif _, ok := httpClient.(*DefaultDialerClient); ok {\n\t\t\t\t\t<-wroteRequest.Wait()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn stat.Connection(&conn), nil\n}\n\n// A wrapper around pipe that ensures the size limit is exactly honored.\n//\n// The MultiBuffer pipe accepts any single WriteMultiBuffer call even if that\n// single MultiBuffer exceeds the size limit, and then starts blocking on the\n// next WriteMultiBuffer call. This means that ReadMultiBuffer can return more\n// bytes than the size limit. We work around this by splitting a potentially\n// too large write up into multiple.\ntype uploadWriter struct {\n\t*pipe.Writer\n\tmaxLen int32\n}\n\nfunc (w uploadWriter) Write(b []byte) (int, error) {\n\t/*\n\t\tcapacity := int(w.maxLen - w.Len())\n\t\tif capacity > 0 && capacity < len(b) {\n\t\t\tb = b[:capacity]\n\t\t}\n\t*/\n\n\tbuffer := buf.MultiBufferContainer{}\n\tcommon.Must2(buffer.Write(b))\n\n\tvar writed int\n\tfor _, buff := range buffer.MultiBuffer {\n\t\terr := w.WriteMultiBuffer(buf.MultiBuffer{buff})\n\t\tif err != nil {\n\t\t\treturn writed, err\n\t\t}\n\t\twrited += int(buff.Len())\n\t}\n\treturn writed, nil\n}\n"
  },
  {
    "path": "transport/internet/splithttp/h1_conn.go",
    "content": "package splithttp\n\nimport (\n\t\"bufio\"\n\t\"net\"\n)\n\ntype H1Conn struct {\n\tUnreadedResponsesCount int\n\tRespBufReader          *bufio.Reader\n\tnet.Conn\n}\n\nfunc NewH1Conn(conn net.Conn) *H1Conn {\n\treturn &H1Conn{\n\t\tRespBufReader: bufio.NewReader(conn),\n\t\tConn:          conn,\n\t}\n}\n"
  },
  {
    "path": "transport/internet/splithttp/hub.go",
    "content": "package splithttp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/apernet/quic-go\"\n\t\"github.com/apernet/quic-go/http3\"\n\tgoreality \"github.com/xtls/reality\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\thttp_proto \"github.com/xtls/xray-core/common/protocol/http\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/hysteria/congestion\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\ntype requestHandler struct {\n\tconfig         *Config\n\thost           string\n\tpath           string\n\tln             *Listener\n\tsessionMu      *sync.Mutex\n\tsessions       sync.Map\n\tlocalAddr      net.Addr\n\tsocketSettings *internet.SocketConfig\n}\n\ntype httpSession struct {\n\tuploadQueue *uploadQueue\n\t// for as long as the GET request is not opened by the client, this will be\n\t// open (\"undone\"), and the session may be expired within a certain TTL.\n\t// after the client connects, this becomes \"done\" and the session lives as\n\t// long as the GET request.\n\tisFullyConnected *done.Instance\n}\n\nfunc (h *requestHandler) upsertSession(sessionId string) *httpSession {\n\t// fast path\n\tcurrentSessionAny, ok := h.sessions.Load(sessionId)\n\tif ok {\n\t\treturn currentSessionAny.(*httpSession)\n\t}\n\n\t// slow path\n\th.sessionMu.Lock()\n\tdefer h.sessionMu.Unlock()\n\n\tcurrentSessionAny, ok = h.sessions.Load(sessionId)\n\tif ok {\n\t\treturn currentSessionAny.(*httpSession)\n\t}\n\n\ts := &httpSession{\n\t\tuploadQueue:      NewUploadQueue(h.ln.config.GetNormalizedScMaxBufferedPosts()),\n\t\tisFullyConnected: done.New(),\n\t}\n\n\th.sessions.Store(sessionId, s)\n\n\tshouldReap := done.New()\n\tgo func() {\n\t\ttime.Sleep(30 * time.Second)\n\t\tshouldReap.Close()\n\t}()\n\tgo func() {\n\t\tselect {\n\t\tcase <-shouldReap.Wait():\n\t\t\th.sessions.Delete(sessionId)\n\t\t\ts.uploadQueue.Close()\n\t\tcase <-s.isFullyConnected.Wait():\n\t\t}\n\t}()\n\n\treturn s\n}\n\nfunc (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\tif len(h.host) > 0 && !internet.IsValidHTTPHost(request.Host, h.host) {\n\t\terrors.LogInfo(context.Background(), \"failed to validate host, request:\", request.Host, \", config:\", h.host)\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\n\tif !strings.HasPrefix(request.URL.Path, h.path) {\n\t\terrors.LogInfo(context.Background(), \"failed to validate path, request:\", request.URL.Path, \", config:\", h.path)\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\n\th.config.WriteResponseHeader(writer, request.Method, request.Header)\n\tlength := int(h.config.GetNormalizedXPaddingBytes().rand())\n\tconfig := XPaddingConfig{Length: length}\n\n\tif h.config.XPaddingObfsMode {\n\t\tconfig.Placement = XPaddingPlacement{\n\t\t\tPlacement: h.config.XPaddingPlacement,\n\t\t\tKey:       h.config.XPaddingKey,\n\t\t\tHeader:    h.config.XPaddingHeader,\n\t\t}\n\t\tconfig.Method = PaddingMethod(h.config.XPaddingMethod)\n\t} else {\n\t\tconfig.Placement = XPaddingPlacement{\n\t\t\tPlacement: PlacementHeader,\n\t\t\tHeader:    \"X-Padding\",\n\t\t}\n\t}\n\n\th.config.ApplyXPaddingToResponse(writer, config)\n\n\tif request.Method == \"OPTIONS\" {\n\t\twriter.WriteHeader(http.StatusOK)\n\t\treturn\n\t}\n\n\t/*\n\t\tclientVer := []int{0, 0, 0}\n\t\tx_version := strings.Split(request.URL.Query().Get(\"x_version\"), \".\")\n\t\tfor j := 0; j < 3 && len(x_version) > j; j++ {\n\t\t\tclientVer[j], _ = strconv.Atoi(x_version[j])\n\t\t}\n\t*/\n\n\tvalidRange := h.config.GetNormalizedXPaddingBytes()\n\tpaddingValue, paddingPlacement := h.config.ExtractXPaddingFromRequest(request, h.config.XPaddingObfsMode)\n\n\tif !h.config.IsPaddingValid(paddingValue, validRange.From, validRange.To, PaddingMethod(h.config.XPaddingMethod)) {\n\t\terrors.LogInfo(context.Background(), \"invalid padding (\"+paddingPlacement+\") length:\", int32(len(paddingValue)))\n\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tsessionId, seqStr := h.config.ExtractMetaFromRequest(request, h.path)\n\n\tif sessionId == \"\" && h.config.Mode != \"\" && h.config.Mode != \"auto\" && h.config.Mode != \"stream-one\" && h.config.Mode != \"stream-up\" {\n\t\terrors.LogInfo(context.Background(), \"stream-one mode is not allowed\")\n\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tvar forwardedAddrs []net.Address\n\tif h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {\n\t\tfor _, key := range h.socketSettings.TrustedXForwardedFor {\n\t\t\tif len(request.Header.Values(key)) > 0 {\n\t\t\t\tforwardedAddrs = http_proto.ParseXForwardedFor(request.Header)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tforwardedAddrs = http_proto.ParseXForwardedFor(request.Header)\n\t}\n\tvar remoteAddr net.Addr\n\tvar err error\n\tremoteAddr, err = net.ResolveTCPAddr(\"tcp\", request.RemoteAddr)\n\tif err != nil {\n\t\tremoteAddr = &net.TCPAddr{\n\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\tPort: 0,\n\t\t}\n\t}\n\tif request.ProtoMajor == 3 {\n\t\tremoteAddr = &net.UDPAddr{\n\t\t\tIP:   remoteAddr.(*net.TCPAddr).IP,\n\t\t\tPort: remoteAddr.(*net.TCPAddr).Port,\n\t\t}\n\t}\n\tif len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {\n\t\tremoteAddr = &net.TCPAddr{\n\t\t\tIP:   forwardedAddrs[0].IP(),\n\t\t\tPort: 0,\n\t\t}\n\t}\n\n\tvar currentSession *httpSession\n\tif sessionId != \"\" {\n\t\tcurrentSession = h.upsertSession(sessionId)\n\t}\n\tscMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To)\n\tisUplinkRequest := false\n\n\tswitch request.Method {\n\tcase \"GET\":\n\t\tisUplinkRequest = seqStr != \"\"\n\tdefault:\n\t\tisUplinkRequest = true\n\t}\n\n\tuplinkDataKey := h.config.UplinkDataKey\n\n\tif isUplinkRequest && sessionId != \"\" { // stream-up, packet-up\n\t\tif seqStr == \"\" {\n\t\t\tif h.config.Mode != \"\" && h.config.Mode != \"auto\" && h.config.Mode != \"stream-up\" {\n\t\t\t\terrors.LogInfo(context.Background(), \"stream-up mode is not allowed\")\n\t\t\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t\thttpSC := &httpServerConn{\n\t\t\t\tInstance:       done.New(),\n\t\t\t\tReader:         request.Body,\n\t\t\t\tResponseWriter: writer,\n\t\t\t}\n\t\t\terr = currentSession.uploadQueue.Push(Packet{\n\t\t\t\tReader: httpSC,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to upload (PushReader)\")\n\t\t\t\twriter.WriteHeader(http.StatusConflict)\n\t\t\t} else {\n\t\t\t\twriter.Header().Set(\"X-Accel-Buffering\", \"no\")\n\t\t\t\twriter.Header().Set(\"Cache-Control\", \"no-store\")\n\t\t\t\twriter.WriteHeader(http.StatusOK)\n\t\t\t\tscStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()\n\t\t\t\treferrer := request.Header.Get(\"Referer\")\n\t\t\t\tif referrer != \"\" && scStreamUpServerSecs.To > 0 {\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\t_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand())))\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttime.Sleep(time.Duration(scStreamUpServerSecs.rand()) * time.Second)\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t\tselect {\n\t\t\t\tcase <-request.Context().Done():\n\t\t\t\tcase <-httpSC.Wait():\n\t\t\t\t}\n\t\t\t}\n\t\t\thttpSC.Close()\n\t\t\treturn\n\t\t}\n\n\t\tif h.config.Mode != \"\" && h.config.Mode != \"auto\" && h.config.Mode != \"packet-up\" {\n\t\t\terrors.LogInfo(context.Background(), \"packet-up mode is not allowed\")\n\t\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tdataPlacement := h.config.GetNormalizedUplinkDataPlacement()\n\t\tvar headerPayload []byte\n\t\tif dataPlacement == PlacementAuto || dataPlacement == PlacementHeader {\n\t\t\tvar headerPayloadChunks []string\n\t\t\tfor i := 0; true; i++ {\n\t\t\t\tchunk := request.Header.Get(fmt.Sprintf(\"%s-%d\", uplinkDataKey, i))\n\t\t\t\tif chunk == \"\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\theaderPayloadChunks = append(headerPayloadChunks, chunk)\n\t\t\t}\n\t\t\theaderPayloadEncoded := strings.Join(headerPayloadChunks, \"\")\n\t\t\theaderPayload, err = base64.RawURLEncoding.DecodeString(headerPayloadEncoded)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfo(context.Background(), \"Invalid base64 in header's payload: \", err.Error())\n\t\t\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tvar cookiePayload []byte\n\t\tif dataPlacement == PlacementAuto || dataPlacement == PlacementCookie {\n\t\t\tvar cookiePayloadChunks []string\n\t\t\tfor i := 0; true; i++ {\n\t\t\t\tcookieName := fmt.Sprintf(\"%s_%d\", uplinkDataKey, i)\n\t\t\t\tif c, _ := request.Cookie(cookieName); c != nil {\n\t\t\t\t\tcookiePayloadChunks = append(cookiePayloadChunks, c.Value)\n\t\t\t\t} else {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tcookiePayloadEncoded := strings.Join(cookiePayloadChunks, \"\")\n\t\t\tcookiePayload, err = base64.RawURLEncoding.DecodeString(cookiePayloadEncoded)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfo(context.Background(), \"Invalid base64 in cookies' payload: \", err.Error())\n\t\t\t\twriter.WriteHeader(http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tvar bodyPayload []byte\n\t\tif dataPlacement == PlacementAuto || dataPlacement == PlacementBody {\n\t\t\tbodyPayload, err = io.ReadAll(io.LimitReader(request.Body, int64(scMaxEachPostBytes)+1))\n\t\t\tif err != nil {\n\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to upload (ReadAll)\")\n\t\t\t\twriter.WriteHeader(http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tpayload := slices.Concat(headerPayload, cookiePayload, bodyPayload)\n\n\t\tif len(payload) > scMaxEachPostBytes {\n\t\t\terrors.LogInfo(context.Background(), \"Too large upload. scMaxEachPostBytes is set to \", scMaxEachPostBytes, \"but request size exceed it. Adjust scMaxEachPostBytes on the server to be at least as large as client.\")\n\t\t\twriter.WriteHeader(http.StatusRequestEntityTooLarge)\n\t\t\treturn\n\t\t}\n\n\t\tseq, err := strconv.ParseUint(seqStr, 10, 64)\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to upload (ParseUint)\")\n\t\t\twriter.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\terr = currentSession.uploadQueue.Push(Packet{\n\t\t\tPayload: payload,\n\t\t\tSeq:     seq,\n\t\t})\n\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to upload (PushPayload)\")\n\t\t\twriter.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tif len(bodyPayload) == 0 {\n\t\t\t// Methods without a body are usually cached by default.\n\t\t\twriter.Header().Set(\"Cache-Control\", \"no-store\")\n\t\t}\n\n\t\twriter.WriteHeader(http.StatusOK)\n\t} else if request.Method == \"GET\" || sessionId == \"\" { // stream-down, stream-one\n\t\tif sessionId != \"\" {\n\t\t\t// after GET is done, the connection is finished. disable automatic\n\t\t\t// session reaping, and handle it in defer\n\t\t\tcurrentSession.isFullyConnected.Close()\n\t\t\tdefer h.sessions.Delete(sessionId)\n\t\t}\n\n\t\t// magic header instructs nginx + apache to not buffer response body\n\t\twriter.Header().Set(\"X-Accel-Buffering\", \"no\")\n\t\t// A web-compliant header telling all middleboxes to disable caching.\n\t\t// Should be able to prevent overloading the cache, or stop CDNs from\n\t\t// teeing the response stream into their cache, causing slowdowns.\n\t\twriter.Header().Set(\"Cache-Control\", \"no-store\")\n\n\t\tif !h.config.NoSSEHeader {\n\t\t\t// magic header to make the HTTP middle box consider this as SSE to disable buffer\n\t\t\twriter.Header().Set(\"Content-Type\", \"text/event-stream\")\n\t\t}\n\n\t\twriter.WriteHeader(http.StatusOK)\n\t\twriter.(http.Flusher).Flush()\n\n\t\thttpSC := &httpServerConn{\n\t\t\tInstance:       done.New(),\n\t\t\tReader:         request.Body,\n\t\t\tResponseWriter: writer,\n\t\t}\n\t\tconn := splitConn{\n\t\t\twriter:     httpSC,\n\t\t\treader:     httpSC,\n\t\t\tremoteAddr: remoteAddr,\n\t\t\tlocalAddr:  h.localAddr,\n\t\t}\n\t\tif sessionId != \"\" { // if not stream-one\n\t\t\tconn.reader = currentSession.uploadQueue\n\t\t}\n\n\t\th.ln.addConn(stat.Connection(&conn))\n\n\t\t// \"A ResponseWriter may not be used after [Handler.ServeHTTP] has returned.\"\n\t\tselect {\n\t\tcase <-request.Context().Done():\n\t\tcase <-httpSC.Wait():\n\t\t}\n\n\t\tconn.Close()\n\t} else {\n\t\terrors.LogInfo(context.Background(), \"unsupported method: \", request.Method)\n\t\twriter.WriteHeader(http.StatusMethodNotAllowed)\n\t}\n}\n\ntype httpServerConn struct {\n\tsync.Mutex\n\t*done.Instance\n\tio.Reader // no need to Close request.Body\n\thttp.ResponseWriter\n}\n\nfunc (c *httpServerConn) Write(b []byte) (int, error) {\n\tc.Lock()\n\tdefer c.Unlock()\n\tif c.Done() {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\tn, err := c.ResponseWriter.Write(b)\n\tif err == nil {\n\t\tc.ResponseWriter.(http.Flusher).Flush()\n\t}\n\treturn n, err\n}\n\nfunc (c *httpServerConn) Close() error {\n\tc.Lock()\n\tdefer c.Unlock()\n\treturn c.Instance.Close()\n}\n\ntype Listener struct {\n\tsync.Mutex\n\tserver     http.Server\n\th3server   *http3.Server\n\tlistener   net.Listener\n\th3listener *quic.EarlyListener\n\tconfig     *Config\n\taddConn    internet.ConnHandler\n\tisH3       bool\n}\n\nfunc ListenXH(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {\n\tl := &Listener{\n\t\taddConn: addConn,\n\t}\n\tl.config = streamSettings.ProtocolSettings.(*Config)\n\tif l.config != nil {\n\t\tif streamSettings.SocketSettings == nil {\n\t\t\tstreamSettings.SocketSettings = &internet.SocketConfig{}\n\t\t}\n\t}\n\thandler := &requestHandler{\n\t\tconfig:         l.config,\n\t\thost:           l.config.Host,\n\t\tpath:           l.config.GetNormalizedPath(),\n\t\tln:             l,\n\t\tsessionMu:      &sync.Mutex{},\n\t\tsessions:       sync.Map{},\n\t\tsocketSettings: streamSettings.SocketSettings,\n\t}\n\ttlsConfig := getTLSConfig(streamSettings)\n\tl.isH3 = len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == \"h3\"\n\n\tvar err error\n\tif port == net.Port(0) { // unix\n\t\tl.listener, err = internet.ListenSystem(ctx, &net.UnixAddr{\n\t\t\tName: address.Domain(),\n\t\t\tNet:  \"unix\",\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen UNIX domain socket for XHTTP on \", address).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening UNIX domain socket for XHTTP on \", address)\n\t} else if l.isH3 { // quic\n\t\tConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{\n\t\t\tIP:   address.IP(),\n\t\t\tPort: int(port),\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen UDP for XHTTP/3 on \", address, \":\", port).Base(err)\n\t\t}\n\t\tif streamSettings.UdpmaskManager != nil {\n\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)\n\t\t\tif err != nil {\n\t\t\t\tConn.Close()\n\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t}\n\t\t\tConn = pktConn\n\t\t}\n\n\t\tquicParams := streamSettings.QuicParams\n\t\tif quicParams == nil {\n\t\t\tquicParams = &internet.QuicParams{}\n\t\t}\n\n\t\tquicConfig := &quic.Config{\n\t\t\tInitialStreamReceiveWindow:     quicParams.InitStreamReceiveWindow,\n\t\t\tMaxStreamReceiveWindow:         quicParams.MaxStreamReceiveWindow,\n\t\t\tInitialConnectionReceiveWindow: quicParams.InitConnReceiveWindow,\n\t\t\tMaxConnectionReceiveWindow:     quicParams.MaxConnReceiveWindow,\n\t\t\tMaxIdleTimeout:                 time.Duration(quicParams.MaxIdleTimeout) * time.Second,\n\t\t\tMaxIncomingStreams:             quicParams.MaxIncomingStreams,\n\t\t\tDisablePathMTUDiscovery:        quicParams.DisablePathMtuDiscovery,\n\t\t}\n\n\t\tl.h3listener, err = quic.ListenEarly(Conn, tlsConfig, quicConfig)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen QUIC for XHTTP/3 on \", address, \":\", port).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening QUIC for XHTTP/3 on \", address, \":\", port)\n\n\t\thandler.localAddr = l.h3listener.Addr()\n\n\t\tl.h3server = &http3.Server{\n\t\t\tHandler: handler,\n\t\t}\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tconn, err := l.h3listener.Accept(context.Background())\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"XHTTP/3 listener closed\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tswitch quicParams.Congestion {\n\t\t\t\tcase \"force-brutal\":\n\t\t\t\t\terrors.LogDebug(context.Background(), conn.RemoteAddr(), \" \", \"congestion brutal bytes per second \", quicParams.BrutalUp)\n\t\t\t\t\tcongestion.UseBrutal(conn, quicParams.BrutalUp)\n\t\t\t\tcase \"reno\":\n\t\t\t\t\terrors.LogDebug(context.Background(), conn.RemoteAddr(), \" \", \"congestion reno\")\n\t\t\t\tdefault:\n\t\t\t\t\terrors.LogDebug(context.Background(), conn.RemoteAddr(), \" \", \"congestion bbr\")\n\t\t\t\t\tcongestion.UseBBR(conn)\n\t\t\t\t}\n\n\t\t\t\tgo func() {\n\t\t\t\t\tif err := l.h3server.ServeQUICConn(conn); err != nil {\n\t\t\t\t\t\terrors.LogDebugInner(ctx, err, \"XHTTP/3 connection ended\")\n\t\t\t\t\t}\n\t\t\t\t\t_ = conn.CloseWithError(0, \"\")\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\t} else { // tcp\n\t\tl.listener, err = internet.ListenSystem(ctx, &net.TCPAddr{\n\t\t\tIP:   address.IP(),\n\t\t\tPort: int(port),\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen TCP for XHTTP on \", address, \":\", port).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening TCP for XHTTP on \", address, \":\", port)\n\t}\n\n\tif !l.isH3 && streamSettings.TcpmaskManager != nil {\n\t\tl.listener, _ = streamSettings.TcpmaskManager.WrapListener(l.listener)\n\t}\n\n\t// tcp/unix (h1/h2)\n\tif l.listener != nil {\n\t\tif config := tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\t\tif tlsConfig := config.GetTLSConfig(); tlsConfig != nil {\n\t\t\t\tl.listener = gotls.NewListener(l.listener, tlsConfig)\n\t\t\t}\n\t\t}\n\t\tif config := reality.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\t\tl.listener = goreality.NewListener(l.listener, config.GetREALITYConfig())\n\t\t}\n\n\t\thandler.localAddr = l.listener.Addr()\n\n\t\t// server can handle both plaintext HTTP/1.1 and h2c\n\t\tprotocols := new(http.Protocols)\n\t\tprotocols.SetHTTP1(true)\n\t\tprotocols.SetUnencryptedHTTP2(true)\n\t\tl.server = http.Server{\n\t\t\tHandler:           handler,\n\t\t\tReadHeaderTimeout: time.Second * 4,\n\t\t\tMaxHeaderBytes:    l.config.GetNormalizedServerMaxHeaderBytes(),\n\t\t\tProtocols:         protocols,\n\t\t}\n\t\tgo func() {\n\t\t\tif err := l.server.Serve(l.listener); err != nil {\n\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to serve HTTP for XHTTP\")\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn l, err\n}\n\n// Addr implements net.Listener.Addr().\nfunc (ln *Listener) Addr() net.Addr {\n\tif ln.h3listener != nil {\n\t\treturn ln.h3listener.Addr()\n\t}\n\tif ln.listener != nil {\n\t\treturn ln.listener.Addr()\n\t}\n\treturn nil\n}\n\n// Close implements net.Listener.Close().\nfunc (ln *Listener) Close() error {\n\tif ln.h3server != nil {\n\t\tif err := ln.h3server.Close(); err != nil {\n\t\t\t_ = ln.h3listener.Close()\n\t\t\treturn err\n\t\t}\n\t\treturn ln.h3listener.Close()\n\t} else if ln.listener != nil {\n\t\treturn ln.listener.Close()\n\t}\n\treturn errors.New(\"listener does not have an HTTP/3 server or a net.listener\")\n}\nfunc getTLSConfig(streamSettings *internet.MemoryStreamConfig) *gotls.Config {\n\tconfig := tls.ConfigFromStreamSettings(streamSettings)\n\tif config == nil {\n\t\treturn &gotls.Config{}\n\t}\n\treturn config.GetTLSConfig()\n}\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, ListenXH))\n}\n"
  },
  {
    "path": "transport/internet/splithttp/mux.go",
    "content": "package splithttp\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"math\"\n\t\"math/big\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype XmuxConn interface {\n\tIsClosed() bool\n}\n\ntype XmuxClient struct {\n\tXmuxConn     XmuxConn\n\tOpenUsage    atomic.Int32\n\tleftUsage    int32\n\tLeftRequests atomic.Int32\n\tUnreusableAt time.Time\n}\n\ntype XmuxManager struct {\n\txmuxConfig  XmuxConfig\n\tconcurrency int32\n\tconnections int32\n\tnewConnFunc func() XmuxConn\n\txmuxClients []*XmuxClient\n}\n\nfunc NewXmuxManager(xmuxConfig XmuxConfig, newConnFunc func() XmuxConn) *XmuxManager {\n\treturn &XmuxManager{\n\t\txmuxConfig:  xmuxConfig,\n\t\tconcurrency: xmuxConfig.GetNormalizedMaxConcurrency().rand(),\n\t\tconnections: xmuxConfig.GetNormalizedMaxConnections().rand(),\n\t\tnewConnFunc: newConnFunc,\n\t\txmuxClients: make([]*XmuxClient, 0),\n\t}\n}\n\nfunc (m *XmuxManager) newXmuxClient() *XmuxClient {\n\txmuxClient := &XmuxClient{\n\t\tXmuxConn:  m.newConnFunc(),\n\t\tleftUsage: -1,\n\t}\n\tif x := m.xmuxConfig.GetNormalizedCMaxReuseTimes().rand(); x > 0 {\n\t\txmuxClient.leftUsage = x - 1\n\t}\n\txmuxClient.LeftRequests.Store(math.MaxInt32)\n\tif x := m.xmuxConfig.GetNormalizedHMaxRequestTimes().rand(); x > 0 {\n\t\txmuxClient.LeftRequests.Store(x)\n\t}\n\tif x := m.xmuxConfig.GetNormalizedHMaxReusableSecs().rand(); x > 0 {\n\t\txmuxClient.UnreusableAt = time.Now().Add(time.Duration(x) * time.Second)\n\t}\n\tm.xmuxClients = append(m.xmuxClients, xmuxClient)\n\treturn xmuxClient\n}\n\nfunc (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when locking\n\tfor i := 0; i < len(m.xmuxClients); {\n\t\txmuxClient := m.xmuxClients[i]\n\t\tif xmuxClient.XmuxConn.IsClosed() ||\n\t\t\txmuxClient.leftUsage == 0 ||\n\t\t\txmuxClient.LeftRequests.Load() <= 0 ||\n\t\t\t(xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) {\n\t\t\terrors.LogDebug(ctx, \"XMUX: removing xmuxClient, IsClosed() = \", xmuxClient.XmuxConn.IsClosed(),\n\t\t\t\t\", OpenUsage = \", xmuxClient.OpenUsage.Load(),\n\t\t\t\t\", leftUsage = \", xmuxClient.leftUsage,\n\t\t\t\t\", LeftRequests = \", xmuxClient.LeftRequests.Load(),\n\t\t\t\t\", UnreusableAt = \", xmuxClient.UnreusableAt)\n\t\t\tm.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...)\n\t\t} else {\n\t\t\ti++\n\t\t}\n\t}\n\n\tif len(m.xmuxClients) == 0 {\n\t\terrors.LogDebug(ctx, \"XMUX: creating xmuxClient because xmuxClients is empty\")\n\t\treturn m.newXmuxClient()\n\t}\n\n\tif m.connections > 0 && len(m.xmuxClients) < int(m.connections) {\n\t\terrors.LogDebug(ctx, \"XMUX: creating xmuxClient because maxConnections was not hit, xmuxClients = \", len(m.xmuxClients))\n\t\treturn m.newXmuxClient()\n\t}\n\n\txmuxClients := make([]*XmuxClient, 0)\n\tif m.concurrency > 0 {\n\t\tfor _, xmuxClient := range m.xmuxClients {\n\t\t\tif xmuxClient.OpenUsage.Load() < m.concurrency {\n\t\t\t\txmuxClients = append(xmuxClients, xmuxClient)\n\t\t\t}\n\t\t}\n\t} else {\n\t\txmuxClients = m.xmuxClients\n\t}\n\n\tif len(xmuxClients) == 0 {\n\t\terrors.LogDebug(ctx, \"XMUX: creating xmuxClient because maxConcurrency was hit, xmuxClients = \", len(m.xmuxClients))\n\t\treturn m.newXmuxClient()\n\t}\n\n\ti, _ := rand.Int(rand.Reader, big.NewInt(int64(len(xmuxClients))))\n\txmuxClient := xmuxClients[i.Int64()]\n\tif xmuxClient.leftUsage > 0 {\n\t\txmuxClient.leftUsage -= 1\n\t}\n\treturn xmuxClient\n}\n"
  },
  {
    "path": "transport/internet/splithttp/mux_test.go",
    "content": "package splithttp_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/transport/internet/splithttp\"\n)\n\ntype fakeRoundTripper struct{}\n\nfunc (f *fakeRoundTripper) IsClosed() bool {\n\treturn false\n}\n\nfunc TestMaxConnections(t *testing.T) {\n\txmuxConfig := XmuxConfig{\n\t\tMaxConnections: &RangeConfig{From: 4, To: 4},\n\t}\n\n\txmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {\n\t\treturn &fakeRoundTripper{}\n\t})\n\n\txmuxClients := make(map[interface{}]struct{})\n\tfor i := 0; i < 8; i++ {\n\t\txmuxClients[xmuxManager.GetXmuxClient(context.Background())] = struct{}{}\n\t}\n\n\tif len(xmuxClients) != 4 {\n\t\tt.Error(\"did not get 4 distinct clients, got \", len(xmuxClients))\n\t}\n}\n\nfunc TestCMaxReuseTimes(t *testing.T) {\n\txmuxConfig := XmuxConfig{\n\t\tCMaxReuseTimes: &RangeConfig{From: 2, To: 2},\n\t}\n\n\txmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {\n\t\treturn &fakeRoundTripper{}\n\t})\n\n\txmuxClients := make(map[interface{}]struct{})\n\tfor i := 0; i < 64; i++ {\n\t\txmuxClients[xmuxManager.GetXmuxClient(context.Background())] = struct{}{}\n\t}\n\n\tif len(xmuxClients) != 32 {\n\t\tt.Error(\"did not get 32 distinct clients, got \", len(xmuxClients))\n\t}\n}\n\nfunc TestMaxConcurrency(t *testing.T) {\n\txmuxConfig := XmuxConfig{\n\t\tMaxConcurrency: &RangeConfig{From: 2, To: 2},\n\t}\n\n\txmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {\n\t\treturn &fakeRoundTripper{}\n\t})\n\n\txmuxClients := make(map[interface{}]struct{})\n\tfor i := 0; i < 64; i++ {\n\t\txmuxClient := xmuxManager.GetXmuxClient(context.Background())\n\t\txmuxClient.OpenUsage.Add(1)\n\t\txmuxClients[xmuxClient] = struct{}{}\n\t}\n\n\tif len(xmuxClients) != 32 {\n\t\tt.Error(\"did not get 32 distinct clients, got \", len(xmuxClients))\n\t}\n}\n\nfunc TestDefault(t *testing.T) {\n\txmuxConfig := XmuxConfig{}\n\n\txmuxManager := NewXmuxManager(xmuxConfig, func() XmuxConn {\n\t\treturn &fakeRoundTripper{}\n\t})\n\n\txmuxClients := make(map[interface{}]struct{})\n\tfor i := 0; i < 64; i++ {\n\t\txmuxClient := xmuxManager.GetXmuxClient(context.Background())\n\t\txmuxClient.OpenUsage.Add(1)\n\t\txmuxClients[xmuxClient] = struct{}{}\n\t}\n\n\tif len(xmuxClients) != 1 {\n\t\tt.Error(\"did not get 1 distinct clients, got \", len(xmuxClients))\n\t}\n}\n"
  },
  {
    "path": "transport/internet/splithttp/splithttp.go",
    "content": "package splithttp\n\nconst protocolName = \"splithttp\"\n"
  },
  {
    "path": "transport/internet/splithttp/splithttp_test.go",
    "content": "package splithttp_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/testing/servers/udp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t. \"github.com/xtls/xray-core/transport/internet/splithttp\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nfunc Test_ListenXHAndDial(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"/sh\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := c.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\tctx := context.Background()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"splithttp\",\n\t\tProtocolSettings: &Config{Path: \"sh\"},\n\t}\n\tconn, err := Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tfmt.Println(\"test2\")\n\tn, _ := io.ReadFull(conn, b[:])\n\tfmt.Println(\"string is\", n)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(conn.Close())\n\tconn, err = Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 2\"))\n\tcommon.Must(err)\n\tn, _ = io.ReadFull(conn, b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\tcommon.Must(conn.Close())\n\n\tcommon.Must(listen.Close())\n}\n\nfunc TestDialWithRemoteAddr(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"sh\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\t_, err := c.Read(b[:])\n\t\t\t// common.Must(err)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = c.Write([]byte(c.RemoteAddr().String()))\n\t\t\tcommon.Must(err)\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\n\tconn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"splithttp\",\n\t\tProtocolSettings: &Config{Path: \"sh\", Headers: map[string]string{\"X-Forwarded-For\": \"1.1.1.1\"}},\n\t})\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, _ := io.ReadFull(conn, b[:])\n\tif string(b[:n]) != \"1.1.1.1:0\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(listen.Close())\n}\n\nfunc Test_ListenXHAndDial_TLS(t *testing.T) {\n\tif runtime.GOARCH == \"arm64\" {\n\t\treturn\n\t}\n\n\tlistenPort := tcp.PickPort()\n\n\tstart := time.Now()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"shs\",\n\t\t},\n\t\tSecurityType: \"tls\",\n\t\tSecuritySettings: &tls.Config{\n\t\t\tCertificate:          []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t},\n\t}\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {\n\t\tgo func() {\n\t\t\tdefer conn.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tconn.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := conn.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(conn.Write([]byte(\"Response\")))\n\t\t}()\n\t})\n\tcommon.Must(err)\n\tdefer listen.Close()\n\n\tconn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, _ := io.ReadFull(conn, b[:])\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tend := time.Now()\n\tif !end.Before(start.Add(time.Second * 5)) {\n\t\tt.Error(\"end: \", end, \" start: \", start)\n\t}\n}\n\nfunc Test_ListenXHAndDial_H2C(t *testing.T) {\n\tif runtime.GOARCH == \"arm64\" {\n\t\treturn\n\t}\n\n\tlistenPort := tcp.PickPort()\n\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"shs\",\n\t\t},\n\t}\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {\n\t\tgo func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t})\n\tcommon.Must(err)\n\tdefer listen.Close()\n\n\tprotocols := new(http.Protocols)\n\tprotocols.SetUnencryptedHTTP2(true)\n\tclient := http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProtocols: protocols,\n\t\t},\n\t}\n\n\tresp, err := client.Get(\"http://\" + net.LocalHostIP.String() + \":\" + listenPort.String())\n\tcommon.Must(err)\n\n\tif resp.StatusCode != 404 {\n\t\tt.Error(\"Expected 404 but got:\", resp.StatusCode)\n\t}\n\n\tif resp.ProtoMajor != 2 {\n\t\tt.Error(\"Expected h2 but got:\", resp.ProtoMajor)\n\t}\n}\n\nfunc Test_ListenXHAndDial_QUIC(t *testing.T) {\n\tif runtime.GOARCH == \"arm64\" {\n\t\treturn\n\t}\n\n\tlistenPort := udp.PickPort()\n\n\tstart := time.Now()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"shs\",\n\t\t},\n\t\tSecurityType: \"tls\",\n\t\tSecuritySettings: &tls.Config{\n\t\t\tCertificate:          []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t\tNextProtocol:         []string{\"h3\"},\n\t\t},\n\t}\n\n\tserverClosed := false\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {\n\t\tgo func() {\n\t\t\tdefer conn.Close()\n\n\t\t\tb := buf.New()\n\t\t\tdefer b.Release()\n\n\t\t\tfor {\n\t\t\t\tb.Clear()\n\t\t\t\tif _, err := b.ReadFrom(conn); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcommon.Must2(conn.Write(b.Bytes()))\n\t\t\t}\n\n\t\t\tserverClosed = true\n\t\t}()\n\t})\n\tcommon.Must(err)\n\tdefer listen.Close()\n\n\ttime.Sleep(time.Second)\n\n\tconn, err := Dial(context.Background(), net.UDPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\n\tconst N = 1024\n\tb1 := make([]byte, N)\n\tcommon.Must2(rand.Read(b1))\n\tb2 := buf.New()\n\n\tcommon.Must2(conn.Write(b1))\n\n\tb2.Clear()\n\tcommon.Must2(b2.ReadFullFrom(conn, N))\n\tif r := cmp.Diff(b2.Bytes(), b1); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\tcommon.Must2(conn.Write(b1))\n\n\tb2.Clear()\n\tcommon.Must2(b2.ReadFullFrom(conn, N))\n\tif r := cmp.Diff(b2.Bytes(), b1); r != \"\" {\n\t\tt.Error(r)\n\t}\n\n\tconn.Close()\n\ttime.Sleep(100 * time.Millisecond)\n\tif !serverClosed {\n\t\tt.Error(\"server did not get closed\")\n\t}\n\n\tend := time.Now()\n\tif !end.Before(start.Add(time.Second * 5)) {\n\t\tt.Error(\"end: \", end, \" start: \", start)\n\t}\n}\n\nfunc Test_ListenXHAndDial_Unix(t *testing.T) {\n\ttempDir := t.TempDir()\n\ttempSocket := tempDir + \"/server.sock\"\n\n\tlisten, err := ListenXH(context.Background(), net.DomainAddress(tempSocket), 0, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"/sh\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := c.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\tctx := context.Background()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tHost: \"example.com\",\n\t\t\tPath: \"sh\",\n\t\t},\n\t}\n\tconn, err := Dial(ctx, net.UnixDestination(net.DomainAddress(tempSocket)), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tfmt.Println(\"test2\")\n\tn, _ := io.ReadFull(conn, b[:])\n\tfmt.Println(\"string is\", n)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(conn.Close())\n\tconn, err = Dial(ctx, net.UnixDestination(net.DomainAddress(tempSocket)), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 2\"))\n\tcommon.Must(err)\n\tn, _ = io.ReadFull(conn, b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\tcommon.Must(conn.Close())\n\n\tcommon.Must(listen.Close())\n}\n\nfunc Test_queryString(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\t// this querystring does not have any effect, but sometimes people blindly copy it from websocket config. make sure the outbound doesn't break\n\t\t\tPath: \"/sh?ed=2048\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := c.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\tctx := context.Background()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"splithttp\",\n\t\tProtocolSettings: &Config{Path: \"sh?ed=2048\"},\n\t}\n\tconn, err := Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tfmt.Println(\"test2\")\n\tn, _ := io.ReadFull(conn, b[:])\n\tfmt.Println(\"string is\", n)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(conn.Close())\n\tcommon.Must(listen.Close())\n}\n\nfunc Test_maxUpload(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"splithttp\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"/sh\",\n\t\t\tScMaxEachPostBytes: &RangeConfig{\n\t\t\t\tFrom: 10000,\n\t\t\t\tTo:   10000,\n\t\t\t},\n\t\t},\n\t}\n\n\tuploadReceived := make([]byte, 10001)\n\tlisten, err := ListenXH(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\tio.ReadFull(c, uploadReceived)\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\tctx := context.Background()\n\n\tconn, err := Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\n\t// send a slightly too large upload\n\tupload := make([]byte, 10001)\n\trand.Read(upload)\n\t_, err = conn.Write(upload)\n\tcommon.Must(err)\n\n\tvar b [10240]byte\n\tn, _ := io.ReadFull(conn, b[:])\n\tfmt.Println(\"string is\", n)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\tcommon.Must(conn.Close())\n\n\tif !bytes.Equal(upload, uploadReceived) {\n\t\tt.Error(\"incorrect upload\", upload, uploadReceived)\n\t}\n\n\tcommon.Must(listen.Close())\n}\n"
  },
  {
    "path": "transport/internet/splithttp/upload_queue.go",
    "content": "package splithttp\n\n// upload_queue is a specialized priorityqueue + channel to reorder generic\n// packets by a sequence number\n\nimport (\n\t\"container/heap\"\n\t\"io\"\n\t\"runtime\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype Packet struct {\n\tReader  io.ReadCloser\n\tPayload []byte\n\tSeq     uint64\n}\n\ntype uploadQueue struct {\n\treader          io.ReadCloser\n\tnomore          bool\n\tpushedPackets   chan Packet\n\twriteCloseMutex sync.Mutex\n\theap            uploadHeap\n\tnextSeq         uint64\n\tclosed          bool\n\tmaxPackets      int\n}\n\nfunc NewUploadQueue(maxPackets int) *uploadQueue {\n\treturn &uploadQueue{\n\t\tpushedPackets: make(chan Packet, maxPackets),\n\t\theap:          uploadHeap{},\n\t\tnextSeq:       0,\n\t\tclosed:        false,\n\t\tmaxPackets:    maxPackets,\n\t}\n}\n\nfunc (h *uploadQueue) Push(p Packet) error {\n\th.writeCloseMutex.Lock()\n\tdefer h.writeCloseMutex.Unlock()\n\n\tif h.closed {\n\t\treturn errors.New(\"packet queue closed\")\n\t}\n\tif h.nomore {\n\t\treturn errors.New(\"h.reader already exists\")\n\t}\n\tif p.Reader != nil {\n\t\th.nomore = true\n\t}\n\th.pushedPackets <- p\n\treturn nil\n}\n\nfunc (h *uploadQueue) Close() error {\n\th.writeCloseMutex.Lock()\n\tdefer h.writeCloseMutex.Unlock()\n\n\tif !h.closed {\n\t\th.closed = true\n\t\truntime.Gosched() // hope Read() gets the packet\n\tf:\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase p := <-h.pushedPackets:\n\t\t\t\tif p.Reader != nil {\n\t\t\t\t\th.reader = p.Reader\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak f\n\t\t\t}\n\t\t}\n\t\tclose(h.pushedPackets)\n\t}\n\tif h.reader != nil {\n\t\treturn h.reader.Close()\n\t}\n\treturn nil\n}\n\nfunc (h *uploadQueue) Read(b []byte) (int, error) {\n\tif h.reader != nil {\n\t\treturn h.reader.Read(b)\n\t}\n\n\tif h.closed {\n\t\treturn 0, io.EOF\n\t}\n\n\tif len(h.heap) == 0 {\n\t\tpacket, more := <-h.pushedPackets\n\t\tif !more {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\tif packet.Reader != nil {\n\t\t\th.reader = packet.Reader\n\t\t\treturn h.reader.Read(b)\n\t\t}\n\t\theap.Push(&h.heap, packet)\n\t}\n\n\tfor len(h.heap) > 0 {\n\t\tpacket := heap.Pop(&h.heap).(Packet)\n\t\tn := 0\n\n\t\tif packet.Seq == h.nextSeq {\n\t\t\tcopy(b, packet.Payload)\n\t\t\tn = min(len(b), len(packet.Payload))\n\n\t\t\tif n < len(packet.Payload) {\n\t\t\t\t// partial read\n\t\t\t\tpacket.Payload = packet.Payload[n:]\n\t\t\t\theap.Push(&h.heap, packet)\n\t\t\t} else {\n\t\t\t\th.nextSeq = packet.Seq + 1\n\t\t\t}\n\n\t\t\treturn n, nil\n\t\t}\n\n\t\t// misordered packet\n\t\tif packet.Seq > h.nextSeq {\n\t\t\tif len(h.heap) > h.maxPackets {\n\t\t\t\t// the \"reassembly buffer\" is too large, and we want to\n\t\t\t\t// constrain memory usage somehow. let's tear down the\n\t\t\t\t// connection, and hope the application retries.\n\t\t\t\treturn 0, errors.New(\"packet queue is too large\")\n\t\t\t}\n\t\t\theap.Push(&h.heap, packet)\n\t\t\tpacket2, more := <-h.pushedPackets\n\t\t\tif !more {\n\t\t\t\treturn 0, io.EOF\n\t\t\t}\n\t\t\theap.Push(&h.heap, packet2)\n\t\t}\n\t}\n\n\treturn 0, nil\n}\n\n// heap code directly taken from https://pkg.go.dev/container/heap\ntype uploadHeap []Packet\n\nfunc (h uploadHeap) Len() int           { return len(h) }\nfunc (h uploadHeap) Less(i, j int) bool { return h[i].Seq < h[j].Seq }\nfunc (h uploadHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }\n\nfunc (h *uploadHeap) Push(x any) {\n\t// Push and Pop use pointer receivers because they modify the slice's length,\n\t// not just its contents.\n\t*h = append(*h, x.(Packet))\n}\n\nfunc (h *uploadHeap) Pop() any {\n\told := *h\n\tn := len(old)\n\tx := old[n-1]\n\t*h = old[0 : n-1]\n\treturn x\n}\n"
  },
  {
    "path": "transport/internet/splithttp/upload_queue_test.go",
    "content": "package splithttp_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t. \"github.com/xtls/xray-core/transport/internet/splithttp\"\n)\n\nfunc Test_regression_readzero(t *testing.T) {\n\tq := NewUploadQueue(10)\n\tq.Push(Packet{\n\t\tPayload: []byte(\"x\"),\n\t\tSeq:     0,\n\t})\n\tbuf := make([]byte, 20)\n\tn, err := q.Read(buf)\n\tcommon.Must(err)\n\tif n != 1 {\n\t\tt.Error(\"n=\", n)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/splithttp/xpadding.go",
    "content": "package splithttp\n\nimport (\n\t\"crypto/rand\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"golang.org/x/net/http2/hpack\"\n)\n\ntype PaddingMethod string\n\nconst (\n\tPaddingMethodRepeatX  PaddingMethod = \"repeat-x\"\n\tPaddingMethodTokenish PaddingMethod = \"tokenish\"\n)\n\nconst charsetBase62 = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"\n\n// Huffman encoding gives ~20% size reduction for base62 sequences\nconst avgHuffmanBytesPerCharBase62 = 0.8\n\nconst validationTolerance = 2\n\ntype XPaddingPlacement struct {\n\tPlacement string\n\tKey       string\n\tHeader    string\n\tRawURL    string\n}\n\ntype XPaddingConfig struct {\n\tLength    int\n\tPlacement XPaddingPlacement\n\tMethod    PaddingMethod\n}\n\nfunc randStringFromCharset(n int, charset string) (string, bool) {\n\tif n <= 0 || len(charset) == 0 {\n\t\treturn \"\", false\n\t}\n\n\tm := len(charset)\n\tlimit := byte(256 - (256 % m))\n\n\tresult := make([]byte, n)\n\ti := 0\n\n\tbuf := make([]byte, 256)\n\tfor i < n {\n\t\tif _, err := rand.Read(buf); err != nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tfor _, rb := range buf {\n\t\t\tif rb >= limit {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult[i] = charset[int(rb)%m]\n\t\t\ti++\n\t\t\tif i == n {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn string(result), true\n}\n\nfunc absInt(x int) int {\n\tif x < 0 {\n\t\treturn -x\n\t}\n\treturn x\n}\n\nfunc GenerateTokenishPaddingBase62(targetHuffmanBytes int) string {\n\tn := int(math.Ceil(float64(targetHuffmanBytes) / avgHuffmanBytesPerCharBase62))\n\tif n < 1 {\n\t\tn = 1\n\t}\n\n\trandBase62Str, ok := randStringFromCharset(n, charsetBase62)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\n\tconst maxIter = 150\n\tadjustChar := byte('X')\n\n\t// Adjust until close enough\n\tfor iter := 0; iter < maxIter; iter++ {\n\t\tcurrentLength := int(hpack.HuffmanEncodeLength(randBase62Str))\n\t\tdiff := currentLength - targetHuffmanBytes\n\n\t\tif absInt(diff) <= validationTolerance {\n\t\t\treturn randBase62Str\n\t\t}\n\n\t\tif diff < 0 {\n\t\t\t// Too small -> append padding char(s)\n\t\t\trandBase62Str += string(adjustChar)\n\n\t\t\t// Avoid a long run of identical chars\n\t\t\tif adjustChar == 'X' {\n\t\t\t\tadjustChar = 'Z'\n\t\t\t} else {\n\t\t\t\tadjustChar = 'X'\n\t\t\t}\n\t\t} else {\n\t\t\t// Too big -> remove from the end\n\t\t\tif len(randBase62Str) <= 1 {\n\t\t\t\treturn randBase62Str\n\t\t\t}\n\t\t\trandBase62Str = randBase62Str[:len(randBase62Str)-1]\n\t\t}\n\t}\n\n\treturn randBase62Str\n}\n\nfunc GeneratePadding(method PaddingMethod, length int) string {\n\tif length <= 0 {\n\t\treturn \"\"\n\t}\n\n\t// https://www.rfc-editor.org/rfc/rfc7541.html#appendix-B\n\t// h2's HPACK Header Compression feature employs a huffman encoding using a static table.\n\t// 'X' and 'Z' are assigned an 8 bit code, so HPACK compression won't change actual padding length on the wire.\n\t// https://www.rfc-editor.org/rfc/rfc9204.html#section-4.1.2-2\n\t// h3's similar QPACK feature uses the same huffman table.\n\n\tswitch method {\n\tcase PaddingMethodRepeatX:\n\t\treturn strings.Repeat(\"X\", length)\n\tcase PaddingMethodTokenish:\n\t\tpaddingValue := GenerateTokenishPaddingBase62(length)\n\t\tif paddingValue == \"\" {\n\t\t\treturn strings.Repeat(\"X\", length)\n\t\t}\n\t\treturn paddingValue\n\tdefault:\n\t\treturn strings.Repeat(\"X\", length)\n\t}\n}\n\nfunc ApplyPaddingToCookie(req *http.Request, name, value string) {\n\tif req == nil || name == \"\" || value == \"\" {\n\t\treturn\n\t}\n\treq.AddCookie(&http.Cookie{\n\t\tName:  name,\n\t\tValue: value,\n\t\tPath:  \"/\",\n\t})\n}\n\nfunc ApplyPaddingToResponseCookie(writer http.ResponseWriter, name, value string) {\n\tif name == \"\" || value == \"\" {\n\t\treturn\n\t}\n\thttp.SetCookie(writer, &http.Cookie{\n\t\tName:  name,\n\t\tValue: value,\n\t\tPath:  \"/\",\n\t})\n}\n\nfunc ApplyPaddingToQuery(u *url.URL, key, value string) {\n\tif u == nil || key == \"\" || value == \"\" {\n\t\treturn\n\t}\n\tq := u.Query()\n\tq.Set(key, value)\n\tu.RawQuery = q.Encode()\n}\n\nfunc (c *Config) GetNormalizedXPaddingBytes() RangeConfig {\n\tif c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 {\n\t\treturn RangeConfig{\n\t\t\tFrom: 100,\n\t\t\tTo:   1000,\n\t\t}\n\t}\n\n\treturn *c.XPaddingBytes\n}\n\nfunc (c *Config) ApplyXPaddingToHeader(h http.Header, config XPaddingConfig) {\n\tif h == nil {\n\t\treturn\n\t}\n\n\tpaddingValue := GeneratePadding(config.Method, config.Length)\n\n\tswitch p := config.Placement; p.Placement {\n\tcase PlacementHeader:\n\t\th.Set(p.Header, paddingValue)\n\tcase PlacementQueryInHeader:\n\t\tu, err := url.Parse(p.RawURL)\n\t\tif err != nil || u == nil {\n\t\t\treturn\n\t\t}\n\t\tu.RawQuery = p.Key + \"=\" + paddingValue\n\t\th.Set(p.Header, u.String())\n\t}\n}\n\nfunc (c *Config) ApplyXPaddingToRequest(req *http.Request, config XPaddingConfig) {\n\tif req == nil {\n\t\treturn\n\t}\n\tif req.Header == nil {\n\t\treq.Header = make(http.Header)\n\t}\n\n\tplacement := config.Placement.Placement\n\n\tif placement == PlacementHeader || placement == PlacementQueryInHeader {\n\t\tc.ApplyXPaddingToHeader(req.Header, config)\n\t\treturn\n\t}\n\n\tpaddingValue := GeneratePadding(config.Method, config.Length)\n\n\tswitch placement {\n\tcase PlacementCookie:\n\t\tApplyPaddingToCookie(req, config.Placement.Key, paddingValue)\n\tcase PlacementQuery:\n\t\tApplyPaddingToQuery(req.URL, config.Placement.Key, paddingValue)\n\t}\n}\n\nfunc (c *Config) ApplyXPaddingToResponse(writer http.ResponseWriter, config XPaddingConfig) {\n\tplacement := config.Placement.Placement\n\n\tif placement == PlacementHeader || placement == PlacementQueryInHeader {\n\t\tc.ApplyXPaddingToHeader(writer.Header(), config)\n\t\treturn\n\t}\n\n\tpaddingValue := GeneratePadding(config.Method, config.Length)\n\n\tswitch placement {\n\tcase PlacementCookie:\n\t\tApplyPaddingToResponseCookie(writer, config.Placement.Key, paddingValue)\n\t}\n}\n\nfunc (c *Config) ExtractXPaddingFromRequest(req *http.Request, obfsMode bool) (string, string) {\n\tif req == nil {\n\t\treturn \"\", \"\"\n\t}\n\n\tif !obfsMode {\n\t\treferrer := req.Header.Get(\"Referer\")\n\n\t\tif referrer != \"\" {\n\t\t\tif referrerURL, err := url.Parse(referrer); err == nil {\n\t\t\t\tpaddingValue := referrerURL.Query().Get(\"x_padding\")\n\t\t\t\tpaddingPlacement := PlacementQueryInHeader + \"=Referer, key=x_padding\"\n\t\t\t\treturn paddingValue, paddingPlacement\n\t\t\t}\n\t\t} else {\n\t\t\tpaddingValue := req.URL.Query().Get(\"x_padding\")\n\t\t\treturn paddingValue, PlacementQuery + \", key=x_padding\"\n\t\t}\n\t}\n\n\tkey := c.XPaddingKey\n\theader := c.XPaddingHeader\n\n\tif cookie, err := req.Cookie(key); err == nil {\n\t\tif cookie != nil && cookie.Value != \"\" {\n\t\t\tpaddingValue := cookie.Value\n\t\t\tpaddingPlacement := PlacementCookie + \", key=\" + key\n\t\t\treturn paddingValue, paddingPlacement\n\t\t}\n\t}\n\n\theaderValue := req.Header.Get(header)\n\n\tif headerValue != \"\" {\n\t\tif c.XPaddingPlacement == PlacementHeader {\n\t\t\tpaddingPlacement := PlacementHeader + \"=\" + header\n\t\t\treturn headerValue, paddingPlacement\n\t\t}\n\n\t\tif parsedURL, err := url.Parse(headerValue); err == nil {\n\t\t\tpaddingPlacement := PlacementQueryInHeader + \"=\" + header + \", key=\" + key\n\n\t\t\treturn parsedURL.Query().Get(key), paddingPlacement\n\t\t}\n\t}\n\n\tqueryValue := req.URL.Query().Get(key)\n\n\tif queryValue != \"\" {\n\t\tpaddingPlacement := PlacementQuery + \", key=\" + key\n\t\treturn queryValue, paddingPlacement\n\t}\n\n\treturn \"\", \"\"\n}\n\nfunc (c *Config) IsPaddingValid(paddingValue string, from, to int32, method PaddingMethod) bool {\n\tif paddingValue == \"\" {\n\t\treturn false\n\t}\n\tif to <= 0 {\n\t\tr := c.GetNormalizedXPaddingBytes()\n\t\tfrom, to = r.From, r.To\n\t}\n\n\tswitch method {\n\tcase PaddingMethodRepeatX:\n\t\tn := int32(len(paddingValue))\n\t\treturn n >= from && n <= to\n\tcase PaddingMethodTokenish:\n\t\tconst tolerance = int32(validationTolerance)\n\n\t\tn := int32(hpack.HuffmanEncodeLength(paddingValue))\n\t\tf := from - tolerance\n\t\tt := to + tolerance\n\t\tif f < 0 {\n\t\t\tf = 0\n\t\t}\n\t\treturn n >= f && n <= t\n\tdefault:\n\t\tn := int32(len(paddingValue))\n\t\treturn n >= from && n <= to\n\t}\n}\n"
  },
  {
    "path": "transport/internet/stat/connection.go",
    "content": "package stat\n\nimport (\n\t\"net\"\n\n\t\"github.com/xtls/xray-core/features/stats\"\n)\n\ntype Connection interface {\n\tnet.Conn\n}\n\ntype CounterConnection struct {\n\tConnection\n\tReadCounter  stats.Counter\n\tWriteCounter stats.Counter\n}\n\nfunc (c *CounterConnection) Read(b []byte) (int, error) {\n\tnBytes, err := c.Connection.Read(b)\n\tif c.ReadCounter != nil {\n\t\tc.ReadCounter.Add(int64(nBytes))\n\t}\n\n\treturn nBytes, err\n}\n\nfunc (c *CounterConnection) Write(b []byte) (int, error) {\n\tnBytes, err := c.Connection.Write(b)\n\tif c.WriteCounter != nil {\n\t\tc.WriteCounter.Add(int64(nBytes))\n\t}\n\treturn nBytes, err\n}\n\nfunc TryUnwrapStatsConn(conn net.Conn) net.Conn {\n\tif conn == nil {\n\t\treturn conn\n\t}\n\tif conn, ok := conn.(*CounterConnection); ok {\n\t\treturn conn.Connection\n\t}\n\treturn conn\n}\n"
  },
  {
    "path": "transport/internet/system_dialer.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/sagernet/sing/common/control\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/dns\"\n\t\"github.com/xtls/xray-core/features/outbound\"\n)\n\nvar effectiveSystemDialer SystemDialer = &DefaultSystemDialer{}\n\ntype SystemDialer interface {\n\tDial(ctx context.Context, source net.Address, destination net.Destination, sockopt *SocketConfig) (net.Conn, error)\n\tDestIpAddress() net.IP\n}\n\ntype DefaultSystemDialer struct {\n\tcontrollers []control.Func\n\tdns         dns.Client\n\tobm         outbound.Manager\n}\n\nfunc resolveSrcAddr(network net.Network, src net.Address) net.Addr {\n\tif src == nil || src == net.AnyIP {\n\t\treturn nil\n\t}\n\n\tif network == net.Network_TCP {\n\t\treturn &net.TCPAddr{\n\t\t\tIP:   src.IP(),\n\t\t\tPort: 0,\n\t\t}\n\t}\n\n\treturn &net.UDPAddr{\n\t\tIP:   src.IP(),\n\t\tPort: 0,\n\t}\n}\n\nfunc hasBindAddr(sockopt *SocketConfig) bool {\n\treturn sockopt != nil && len(sockopt.BindAddress) > 0 && sockopt.BindPort > 0\n}\n\nfunc (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {\n\terrors.LogDebug(ctx, \"dialing to \"+dest.String())\n\n\tif dest.Network == net.Network_UDP && !hasBindAddr(sockopt) {\n\t\tsrcAddr := resolveSrcAddr(net.Network_UDP, src)\n\t\tif srcAddr == nil {\n\t\t\tsrcAddr = &net.UDPAddr{\n\t\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\t\tPort: 0,\n\t\t\t}\n\t\t}\n\t\tvar lc net.ListenConfig\n\t\tdestAddr, err := net.ResolveUDPAddr(\"udp\", dest.NetAddr())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlc.Control = func(network, address string, c syscall.RawConn) error {\n\t\t\tfor _, ctl := range d.controllers {\n\t\t\t\tif err := ctl(network, address, c); err != nil {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to apply external controller\")\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn c.Control(func(fd uintptr) {\n\t\t\t\tif sockopt != nil {\n\t\t\t\t\tif err := applyOutboundSocketOptions(network, destAddr.String(), fd, sockopt); err != nil {\n\t\t\t\t\t\terrors.LogInfo(ctx, err, \"failed to apply socket options\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tpacketConn, err := lc.ListenPacket(ctx, srcAddr.Network(), srcAddr.String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &PacketConnWrapper{\n\t\t\tPacketConn: packetConn,\n\t\t\tDest:       destAddr,\n\t\t}, nil\n\t}\n\t// Chrome defaults\n\tkeepAliveConfig := net.KeepAliveConfig{\n\t\tEnable:   true,\n\t\tIdle:     45 * time.Second,\n\t\tInterval: 45 * time.Second,\n\t\tCount:    -1,\n\t}\n\tkeepAlive := time.Duration(0)\n\tif sockopt != nil {\n\t\tif sockopt.TcpKeepAliveIdle*sockopt.TcpKeepAliveInterval < 0 {\n\t\t\treturn nil, errors.New(\"invalid TcpKeepAliveIdle or TcpKeepAliveInterval value: \", sockopt.TcpKeepAliveIdle, \" \", sockopt.TcpKeepAliveInterval)\n\t\t}\n\t\tif sockopt.TcpKeepAliveIdle < 0 || sockopt.TcpKeepAliveInterval < 0 {\n\t\t\tkeepAlive = -1\n\t\t\tkeepAliveConfig.Enable = false\n\t\t}\n\t\tif sockopt.TcpKeepAliveIdle > 0 {\n\t\t\tkeepAliveConfig.Idle = time.Duration(sockopt.TcpKeepAliveIdle) * time.Second\n\t\t}\n\t\tif sockopt.TcpKeepAliveInterval > 0 {\n\t\t\tkeepAliveConfig.Interval = time.Duration(sockopt.TcpKeepAliveInterval) * time.Second\n\t\t}\n\t}\n\tdialer := &net.Dialer{\n\t\tTimeout:         time.Second * 16,\n\t\tLocalAddr:       resolveSrcAddr(dest.Network, src),\n\t\tKeepAlive:       keepAlive,\n\t\tKeepAliveConfig: keepAliveConfig,\n\t}\n\n\tif sockopt != nil || len(d.controllers) > 0 {\n\t\tif sockopt != nil && sockopt.TcpMptcp {\n\t\t\tdialer.SetMultipathTCP(true)\n\t\t}\n\t\tdialer.Control = func(network, address string, c syscall.RawConn) error {\n\t\t\tfor _, ctl := range d.controllers {\n\t\t\t\tif err := ctl(network, address, c); err != nil {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to apply external controller\")\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn c.Control(func(fd uintptr) {\n\t\t\t\tif sockopt != nil {\n\t\t\t\t\tif err := applyOutboundSocketOptions(network, address, fd, sockopt); err != nil {\n\t\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to apply socket options\")\n\t\t\t\t\t}\n\t\t\t\t\tif dest.Network == net.Network_UDP && hasBindAddr(sockopt) {\n\t\t\t\t\t\tif err := bindAddr(fd, sockopt.BindAddress, sockopt.BindPort); err != nil {\n\t\t\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to bind source address to \", sockopt.BindAddress)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\treturn dialer.DialContext(ctx, dest.Network.SystemString(), dest.NetAddr())\n}\n\nfunc (d *DefaultSystemDialer) DestIpAddress() net.IP {\n\treturn nil\n}\n\ntype PacketConnWrapper struct {\n\tnet.PacketConn\n\tDest net.Addr\n}\n\nfunc (c *PacketConnWrapper) Read(p []byte) (int, error) {\n\tn, _, err := c.PacketConn.ReadFrom(p)\n\treturn n, err\n}\n\nfunc (c *PacketConnWrapper) Write(p []byte) (int, error) {\n\treturn c.PacketConn.WriteTo(p, c.Dest)\n}\n\nfunc (c *PacketConnWrapper) RemoteAddr() net.Addr {\n\treturn c.Dest\n}\n\ntype SystemDialerAdapter interface {\n\tDial(network string, address string) (net.Conn, error)\n}\n\ntype SimpleSystemDialer struct {\n\tadapter SystemDialerAdapter\n}\n\nfunc WithAdapter(dialer SystemDialerAdapter) SystemDialer {\n\treturn &SimpleSystemDialer{\n\t\tadapter: dialer,\n\t}\n}\n\nfunc (v *SimpleSystemDialer) Dial(ctx context.Context, src net.Address, dest net.Destination, sockopt *SocketConfig) (net.Conn, error) {\n\treturn v.adapter.Dial(dest.Network.SystemString(), dest.NetAddr())\n}\n\nfunc (d *SimpleSystemDialer) DestIpAddress() net.IP {\n\treturn nil\n}\n\n// UseAlternativeSystemDialer replaces the current system dialer with a given one.\n// Caller must ensure there is no race condition.\n//\n// xray:api:stable\nfunc UseAlternativeSystemDialer(dialer SystemDialer) {\n\tif dialer == nil {\n\t\tdialer = &DefaultSystemDialer{}\n\t}\n\teffectiveSystemDialer = dialer\n}\n\n// RegisterDialerController adds a controller to the effective system dialer.\n// The controller can be used to operate on file descriptors before they are put into use.\n// It only works when effective dialer is the default dialer.\n//\n// xray:api:beta\nfunc RegisterDialerController(ctl control.Func) error {\n\tif ctl == nil {\n\t\treturn errors.New(\"nil listener controller\")\n\t}\n\n\tdialer, ok := effectiveSystemDialer.(*DefaultSystemDialer)\n\tif !ok {\n\t\treturn errors.New(\"RegisterListenerController not supported in custom dialer\")\n\t}\n\n\tdialer.controllers = append(dialer.controllers, ctl)\n\treturn nil\n}\n\ntype FakePacketConn struct {\n\tnet.Conn\n}\n\nfunc (c *FakePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tn, err = c.Read(p)\n\treturn n, c.RemoteAddr(), err\n}\n\nfunc (c *FakePacketConn) WriteTo(p []byte, _ net.Addr) (n int, err error) {\n\treturn c.Write(p)\n}\n\nfunc (c *FakePacketConn) LocalAddr() net.Addr {\n\treturn &net.UDPAddr{\n\t\tIP:   []byte{0, 0, 0, 0},\n\t\tPort: 0,\n\t}\n}\n\nfunc (c *FakePacketConn) RemoteAddr() net.Addr {\n\treturn &net.UDPAddr{\n\t\tIP:   []byte{0, 0, 0, 0},\n\t\tPort: 0,\n\t}\n}\n"
  },
  {
    "path": "transport/internet/system_listener.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/pires/go-proxyproto\"\n\t\"github.com/sagernet/sing/common/control\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nvar effectiveListener = DefaultListener{}\n\ntype DefaultListener struct {\n\tcontrollers []control.Func\n}\n\nfunc getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []control.Func) func(network, address string, c syscall.RawConn) error {\n\treturn func(network, address string, c syscall.RawConn) error {\n\t\treturn c.Control(func(fd uintptr) {\n\t\t\tfor _, controller := range controllers {\n\t\t\t\tif err := controller(network, address, c); err != nil {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to apply external controller\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif sockopt != nil {\n\t\t\t\tif err := applyInboundSocketOptions(network, fd, sockopt); err != nil {\n\t\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to apply socket options to incoming connection\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsetReusePort(fd)\n\t\t})\n\t}\n}\n\n// For some reason, other component of ray will assume the listener is a TCP listener and have valid remote address.\n// But in fact it doesn't. So we need to wrap the listener to make it return 0.0.0.0(unspecified) as remote address.\n// If other issues encountered, we should able to fix it here.\ntype UnixListenerWrapper struct {\n\t*net.UnixListener\n\tlocker *FileLocker\n}\n\nfunc (l *UnixListenerWrapper) Accept() (net.Conn, error) {\n\tconn, err := l.UnixListener.Accept()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &UnixConnWrapper{UnixConn: conn.(*net.UnixConn)}, nil\n}\n\nfunc (l *UnixListenerWrapper) Close() error {\n\tif l.locker != nil {\n\t\tl.locker.Release()\n\t\tl.locker = nil\n\t}\n\treturn l.UnixListener.Close()\n}\n\ntype UnixConnWrapper struct {\n\t*net.UnixConn\n}\n\nfunc (conn *UnixConnWrapper) RemoteAddr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP: []byte{0, 0, 0, 0},\n\t}\n}\n\nfunc (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (l net.Listener, err error) {\n\tvar lc net.ListenConfig\n\tvar network, address string\n\t// callback is called after the Listen function returns\n\tcallback := func(l net.Listener, err error) (net.Listener, error) {\n\t\treturn l, err\n\t}\n\n\tswitch addr := addr.(type) {\n\tcase *net.TCPAddr:\n\t\tnetwork = addr.Network()\n\t\taddress = addr.String()\n\t\tlc.Control = getControlFunc(ctx, sockopt, dl.controllers)\n\t\t// default disable keepalive\n\t\tlc.KeepAlive = -1\n\t\tif sockopt != nil {\n\t\t\tif sockopt.TcpKeepAliveIdle*sockopt.TcpKeepAliveInterval < 0 {\n\t\t\t\treturn nil, errors.New(\"invalid TcpKeepAliveIdle or TcpKeepAliveInterval value: \", sockopt.TcpKeepAliveIdle, \" \", sockopt.TcpKeepAliveInterval)\n\t\t\t}\n\t\t\tlc.KeepAliveConfig = net.KeepAliveConfig{\n\t\t\t\tEnable:   false,\n\t\t\t\tIdle:     -1,\n\t\t\t\tInterval: -1,\n\t\t\t\tCount:    -1,\n\t\t\t}\n\t\t\tif sockopt.TcpKeepAliveIdle > 0 {\n\t\t\t\tlc.KeepAliveConfig.Enable = true\n\t\t\t\tlc.KeepAliveConfig.Idle = time.Duration(sockopt.TcpKeepAliveIdle) * time.Second\n\t\t\t}\n\t\t\tif sockopt.TcpKeepAliveInterval > 0 {\n\t\t\t\tlc.KeepAliveConfig.Enable = true\n\t\t\t\tlc.KeepAliveConfig.Interval = time.Duration(sockopt.TcpKeepAliveInterval) * time.Second\n\t\t\t}\n\t\t\tif sockopt.TcpMptcp {\n\t\t\t\tlc.SetMultipathTCP(true)\n\t\t\t}\n\t\t}\n\tcase *net.UnixAddr:\n\t\tlc.Control = nil\n\t\tnetwork = addr.Network()\n\t\taddress = addr.Name\n\n\t\tif (runtime.GOOS == \"linux\" || runtime.GOOS == \"android\") && address[0] == '@' {\n\t\t\t// linux abstract unix domain socket is lockfree\n\t\t\tif len(address) > 1 && address[1] == '@' {\n\t\t\t\t// but may need padding to work with haproxy\n\t\t\t\tfullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))\n\t\t\t\tcopy(fullAddr, address[1:])\n\t\t\t\taddress = string(fullAddr)\n\t\t\t}\n\t\t} else {\n\t\t\t// split permission from address\n\t\t\tvar filePerm *os.FileMode\n\t\t\tif s := strings.Split(address, \",\"); len(s) == 2 {\n\t\t\t\taddress = s[0]\n\t\t\t\tperm, perr := strconv.ParseUint(s[1], 8, 32)\n\t\t\t\tif perr != nil {\n\t\t\t\t\treturn nil, errors.New(\"failed to parse permission: \" + s[1]).Base(perr)\n\t\t\t\t}\n\n\t\t\t\tmode := os.FileMode(perm)\n\t\t\t\tfilePerm = &mode\n\t\t\t}\n\t\t\t// normal unix domain socket needs lock\n\t\t\tlocker := &FileLocker{\n\t\t\t\tpath: address + \".lock\",\n\t\t\t}\n\t\t\tif err := locker.Acquire(); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// set callback to combine listener and set permission\n\t\t\tcallback = func(l net.Listener, err error) (net.Listener, error) {\n\t\t\t\tif err != nil {\n\t\t\t\t\tlocker.Release()\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tl = &UnixListenerWrapper{UnixListener: l.(*net.UnixListener), locker: locker}\n\t\t\t\tif filePerm == nil {\n\t\t\t\t\treturn l, nil\n\t\t\t\t}\n\t\t\t\terr = os.Chmod(address, *filePerm)\n\t\t\t\tif err != nil {\n\t\t\t\t\tl.Close()\n\t\t\t\t\treturn nil, errors.New(\"failed to set permission for \" + address).Base(err)\n\t\t\t\t}\n\t\t\t\treturn l, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tl, err = callback(lc.Listen(ctx, network, address))\n\tif err == nil && sockopt != nil && sockopt.AcceptProxyProtocol {\n\t\tpolicyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil }\n\t\tl = &proxyproto.Listener{Listener: l, Policy: policyFunc}\n\t}\n\treturn l, err\n}\n\nfunc (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) {\n\tvar lc net.ListenConfig\n\n\tlc.Control = getControlFunc(ctx, sockopt, dl.controllers)\n\n\treturn lc.ListenPacket(ctx, addr.Network(), addr.String())\n}\n\n// RegisterListenerController adds a controller to the effective system listener.\n// The controller can be used to operate on file descriptors before they are put into use.\n//\n// xray:api:beta\nfunc RegisterListenerController(controller control.Func) error {\n\tif controller == nil {\n\t\treturn errors.New(\"nil listener controller\")\n\t}\n\n\teffectiveListener.controllers = append(effectiveListener.controllers, controller)\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/system_listener_test.go",
    "content": "package internet_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/sagernet/sing/common/control\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc TestRegisterListenerController(t *testing.T) {\n\tvar gotFd uintptr\n\n\tcommon.Must(internet.RegisterListenerController(func(network, address string, conn syscall.RawConn) error {\n\t\treturn control.Raw(conn, func(fd uintptr) error {\n\t\t\tgotFd = fd\n\t\t\treturn nil\n\t\t})\n\t}))\n\n\tconn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{\n\t\tIP: net.IPv4zero,\n\t}, nil)\n\tcommon.Must(err)\n\tcommon.Must(conn.Close())\n\n\tif gotFd == 0 {\n\t\tt.Error(\"expected none-zero fd, but actually 0\")\n\t}\n}\n"
  },
  {
    "path": "transport/internet/tagged/tagged.go",
    "content": "package tagged\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/features/routing\"\n)\n\ntype DialFunc func(ctx context.Context, dispatcher routing.Dispatcher, dest net.Destination, tag string) (net.Conn, error)\n\nvar Dialer DialFunc\n"
  },
  {
    "path": "transport/internet/tagged/taggedimpl/impl.go",
    "content": "package taggedimpl\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/core\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport/internet/tagged\"\n)\n\nfunc DialTaggedOutbound(ctx context.Context, dispatcher routing.Dispatcher, dest net.Destination, tag string) (net.Conn, error) {\n\tif core.FromContext(ctx) == nil {\n\t\treturn nil, errors.New(\"Instance context variable is not in context, dial denied. \")\n\t}\n\tcontent := new(session.Content)\n\tcontent.SkipDNSResolve = true\n\n\tctx = session.ContextWithContent(ctx, content)\n\tctx = session.SetForcedOutboundTagToContext(ctx, tag)\n\n\tr, err := dispatcher.Dispatch(ctx, dest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar readerOpt cnc.ConnectionOption\n\tif dest.Network == net.Network_TCP {\n\t\treaderOpt = cnc.ConnectionOutputMulti(r.Reader)\n\t} else {\n\t\treaderOpt = cnc.ConnectionOutputMultiUDP(r.Reader)\n\t}\n\treturn cnc.NewConnection(cnc.ConnectionInputMulti(r.Writer), readerOpt), nil\n}\n\nfunc init() {\n\ttagged.Dialer = DialTaggedOutbound\n}\n"
  },
  {
    "path": "transport/internet/tagged/taggedimpl/taggedimpl.go",
    "content": "package taggedimpl\n"
  },
  {
    "path": "transport/internet/tcp/config.go",
    "content": "package tcp\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/tcp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/tcp/config.proto\n\npackage tcp\n\nimport (\n\tserial \"github.com/xtls/xray-core/common/serial\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate               protoimpl.MessageState `protogen:\"open.v1\"`\n\tHeaderSettings      *serial.TypedMessage   `protobuf:\"bytes,2,opt,name=header_settings,json=headerSettings,proto3\" json:\"header_settings,omitempty\"`\n\tAcceptProxyProtocol bool                   `protobuf:\"varint,3,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3\" json:\"accept_proxy_protocol,omitempty\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_tcp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_tcp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_tcp_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetHeaderSettings() *serial.TypedMessage {\n\tif x != nil {\n\t\treturn x.HeaderSettings\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetAcceptProxyProtocol() bool {\n\tif x != nil {\n\t\treturn x.AcceptProxyProtocol\n\t}\n\treturn false\n}\n\nvar File_transport_internet_tcp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_tcp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#transport/internet/tcp/config.proto\\x12\\x1bxray.transport.internet.tcp\\x1a!common/serial/typed_message.proto\\\"\\x8d\\x01\\n\" +\n\t\"\\x06Config\\x12I\\n\" +\n\t\"\\x0fheader_settings\\x18\\x02 \\x01(\\v2 .xray.common.serial.TypedMessageR\\x0eheaderSettings\\x122\\n\" +\n\t\"\\x15accept_proxy_protocol\\x18\\x03 \\x01(\\bR\\x13acceptProxyProtocolJ\\x04\\b\\x01\\x10\\x02Bs\\n\" +\n\t\"\\x1fcom.xray.transport.internet.tcpP\\x01Z0github.com/xtls/xray-core/transport/internet/tcp\\xaa\\x02\\x1bXray.Transport.Internet.Tcpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_tcp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_tcp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_tcp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_tcp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_tcp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_tcp_config_proto_rawDesc), len(file_transport_internet_tcp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_tcp_config_proto_rawDescData\n}\n\nvar file_transport_internet_tcp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_tcp_config_proto_goTypes = []any{\n\t(*Config)(nil),              // 0: xray.transport.internet.tcp.Config\n\t(*serial.TypedMessage)(nil), // 1: xray.common.serial.TypedMessage\n}\nvar file_transport_internet_tcp_config_proto_depIdxs = []int32{\n\t1, // 0: xray.transport.internet.tcp.Config.header_settings:type_name -> xray.common.serial.TypedMessage\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_tcp_config_proto_init() }\nfunc file_transport_internet_tcp_config_proto_init() {\n\tif File_transport_internet_tcp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_tcp_config_proto_rawDesc), len(file_transport_internet_tcp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_tcp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_tcp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_tcp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_tcp_config_proto = out.File\n\tfile_transport_internet_tcp_config_proto_goTypes = nil\n\tfile_transport_internet_tcp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/tcp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.tcp;\noption csharp_namespace = \"Xray.Transport.Internet.Tcp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/tcp\";\noption java_package = \"com.xray.transport.internet.tcp\";\noption java_multiple_files = true;\n\nimport \"common/serial/typed_message.proto\";\n\nmessage Config {\n  reserved 1;\n  xray.common.serial.TypedMessage header_settings = 2;\n  bool accept_proxy_protocol = 3;\n}\n"
  },
  {
    "path": "transport/internet/tcp/dialer.go",
    "content": "package tcp\n\nimport (\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/session\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\n// Dial dials a new TCP connection to the given destination.\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\terrors.LogInfo(ctx, \"dialing TCP to \", dest)\n\tconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif streamSettings.TcpmaskManager != nil {\n\t\tnewConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t}\n\t\tconn = newConn\n\t}\n\n\tif config := tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tmitmServerName := session.MitmServerNameFromContext(ctx)\n\t\tmitmAlpn11 := session.MitmAlpn11FromContext(ctx)\n\t\tvar tlsConfig *gotls.Config\n\t\tif tls.IsFromMitm(config.ServerName) {\n\t\t\ttlsConfig = config.GetTLSConfig(tls.WithOverrideName(mitmServerName))\n\t\t} else {\n\t\t\ttlsConfig = config.GetTLSConfig(tls.WithDestination(dest))\n\t\t}\n\n\t\tisFromMitmVerify := false\n\t\tif r, ok := tlsConfig.Rand.(*tls.RandCarrier); ok && len(r.VerifyPeerCertByName) > 0 {\n\t\t\tfor i, name := range r.VerifyPeerCertByName {\n\t\t\t\tif tls.IsFromMitm(name) {\n\t\t\t\t\tisFromMitmVerify = true\n\t\t\t\t\tr.VerifyPeerCertByName[0], r.VerifyPeerCertByName[i] = r.VerifyPeerCertByName[i], r.VerifyPeerCertByName[0]\n\t\t\t\t\tr.VerifyPeerCertByName = r.VerifyPeerCertByName[1:]\n\t\t\t\t\tafter := mitmServerName\n\t\t\t\t\tfor {\n\t\t\t\t\t\tif len(after) > 0 {\n\t\t\t\t\t\t\tr.VerifyPeerCertByName = append(r.VerifyPeerCertByName, after)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, after, _ = strings.Cut(after, \".\")\n\t\t\t\t\t\tif !strings.Contains(after, \".\") {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tslices.Reverse(r.VerifyPeerCertByName)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tisFromMitmAlpn := len(tlsConfig.NextProtos) == 1 && tls.IsFromMitm(tlsConfig.NextProtos[0])\n\t\tif isFromMitmAlpn {\n\t\t\tif mitmAlpn11 {\n\t\t\t\ttlsConfig.NextProtos[0] = \"http/1.1\"\n\t\t\t} else {\n\t\t\t\ttlsConfig.NextProtos = []string{\"h2\", \"http/1.1\"}\n\t\t\t}\n\t\t}\n\t\tif fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil {\n\t\t\tconn = tls.UClient(conn, tlsConfig, fingerprint)\n\t\t\tif len(tlsConfig.NextProtos) == 1 && tlsConfig.NextProtos[0] == \"http/1.1\" { // allow manually specify\n\t\t\t\terr = conn.(*tls.UConn).WebsocketHandshakeContext(ctx)\n\t\t\t} else {\n\t\t\t\terr = conn.(*tls.UConn).HandshakeContext(ctx)\n\t\t\t}\n\t\t} else {\n\t\t\tconn = tls.Client(conn, tlsConfig)\n\t\t\terr = conn.(*tls.Conn).HandshakeContext(ctx)\n\t\t}\n\t\tif err != nil {\n\t\t\tif isFromMitmVerify {\n\t\t\t\treturn nil, errors.New(\"MITM freedom RAW TLS: failed to verify Domain Fronting certificate from \" + mitmServerName).Base(err).AtWarning()\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tnegotiatedProtocol := conn.(tls.Interface).NegotiatedProtocol()\n\t\tif isFromMitmAlpn && !mitmAlpn11 && negotiatedProtocol != \"h2\" {\n\t\t\tconn.Close()\n\t\t\treturn nil, errors.New(\"MITM freedom RAW TLS: unexpected Negotiated Protocol (\" + negotiatedProtocol + \") with \" + mitmServerName).AtWarning()\n\t\t}\n\t} else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tif conn, err = reality.UClient(conn, config, ctx, dest); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ttcpSettings := streamSettings.ProtocolSettings.(*Config)\n\tif tcpSettings.HeaderSettings != nil {\n\t\theaderConfig, err := tcpSettings.HeaderSettings.GetInstance()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to get header settings\").Base(err).AtError()\n\t\t}\n\t\tauth, err := internet.CreateConnectionAuthenticator(headerConfig)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to create header authenticator\").Base(err).AtError()\n\t\t}\n\t\tconn = auth.Client(conn)\n\t}\n\treturn stat.Connection(conn), nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, Dial))\n}\n"
  },
  {
    "path": "transport/internet/tcp/hub.go",
    "content": "package tcp\n\nimport (\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"strings\"\n\t\"time\"\n\n\tgoreality \"github.com/xtls/reality\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/reality\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\n// Listener is an internet.Listener that listens for TCP connections.\ntype Listener struct {\n\tlistener      net.Listener\n\ttlsConfig     *gotls.Config\n\trealityConfig *goreality.Config\n\tauthConfig    internet.ConnectionAuthenticator\n\tconfig        *Config\n\taddConn       internet.ConnHandler\n}\n\n// ListenTCP creates a new Listener based on configurations.\nfunc ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {\n\tl := &Listener{\n\t\taddConn: handler,\n\t}\n\ttcpSettings := streamSettings.ProtocolSettings.(*Config)\n\tl.config = tcpSettings\n\tif l.config != nil {\n\t\tif streamSettings.SocketSettings == nil {\n\t\t\tstreamSettings.SocketSettings = &internet.SocketConfig{}\n\t\t}\n\t\tstreamSettings.SocketSettings.AcceptProxyProtocol = l.config.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocol\n\t}\n\tvar listener net.Listener\n\tvar err error\n\tif port == net.Port(0) { // unix\n\t\tif !address.Family().IsDomain() {\n\t\t\treturn nil, errors.New(\"invalid unix listen: \", address).AtError()\n\t\t}\n\t\tlistener, err = internet.ListenSystem(ctx, &net.UnixAddr{\n\t\t\tName: address.Domain(),\n\t\t\tNet:  \"unix\",\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen Unix Domain Socket on \", address).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening Unix Domain Socket on \", address)\n\t} else {\n\t\tlistener, err = internet.ListenSystem(ctx, &net.TCPAddr{\n\t\t\tIP:   address.IP(),\n\t\t\tPort: int(port),\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen TCP on \", address, \":\", port).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening TCP on \", address, \":\", port)\n\t}\n\n\tif streamSettings.TcpmaskManager != nil {\n\t\tlistener, _ = streamSettings.TcpmaskManager.WrapListener(listener)\n\t}\n\n\tif streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {\n\t\terrors.LogWarning(ctx, \"accepting PROXY protocol\")\n\t}\n\n\tl.listener = listener\n\n\tif config := tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tl.tlsConfig = config.GetTLSConfig()\n\t}\n\tif config := reality.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tl.realityConfig = config.GetREALITYConfig()\n\t\tgo goreality.DetectPostHandshakeRecordsLens(l.realityConfig)\n\t}\n\n\tif tcpSettings.HeaderSettings != nil {\n\t\theaderConfig, err := tcpSettings.HeaderSettings.GetInstance()\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid header settings\").Base(err).AtError()\n\t\t}\n\t\tauth, err := internet.CreateConnectionAuthenticator(headerConfig)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"invalid header settings.\").Base(err).AtError()\n\t\t}\n\t\tl.authConfig = auth\n\t}\n\n\tgo l.keepAccepting()\n\treturn l, nil\n}\n\nfunc (v *Listener) keepAccepting() {\n\tfor {\n\t\tconn, err := v.listener.Accept()\n\t\tif err != nil {\n\t\t\terrStr := err.Error()\n\t\t\tif strings.Contains(errStr, \"closed\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\terrors.LogWarningInner(context.Background(), err, \"failed to accepted raw connections\")\n\t\t\tif strings.Contains(errStr, \"too many\") {\n\t\t\t\ttime.Sleep(time.Millisecond * 500)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tgo func() {\n\t\t\tif v.tlsConfig != nil {\n\t\t\t\tconn = tls.Server(conn, v.tlsConfig)\n\t\t\t} else if v.realityConfig != nil {\n\t\t\t\tif conn, err = reality.Server(conn, v.realityConfig); err != nil {\n\t\t\t\t\terrors.LogInfo(context.Background(), err.Error())\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif v.authConfig != nil {\n\t\t\t\tconn = v.authConfig.Server(conn)\n\t\t\t}\n\t\t\tv.addConn(stat.Connection(conn))\n\t\t}()\n\t}\n}\n\n// Addr implements internet.Listener.Addr.\nfunc (v *Listener) Addr() net.Addr {\n\treturn v.listener.Addr()\n}\n\n// Close implements internet.Listener.Close.\nfunc (v *Listener) Close() error {\n\treturn v.listener.Close()\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, ListenTCP))\n}\n"
  },
  {
    "path": "transport/internet/tcp/sockopt_darwin.go",
    "content": "//go:build darwin\n// +build darwin\n\npackage tcp\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// GetOriginalDestination from tcp conn\nfunc GetOriginalDestination(conn stat.Connection) (net.Destination, error) {\n\tla := conn.LocalAddr()\n\tra := conn.RemoteAddr()\n\tip, port, err := internet.OriginalDst(la, ra)\n\tif err != nil {\n\t\treturn net.Destination{}, errors.New(\"failed to get destination\").Base(err)\n\t}\n\tdest := net.TCPDestination(net.IPAddress(ip), net.Port(port))\n\tif !dest.IsValid() {\n\t\treturn net.Destination{}, errors.New(\"failed to parse destination.\")\n\t}\n\treturn dest, nil\n}\n"
  },
  {
    "path": "transport/internet/tcp/sockopt_freebsd.go",
    "content": "//go:build freebsd\n// +build freebsd\n\npackage tcp\n\nimport (\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\n// GetOriginalDestination from tcp conn\nfunc GetOriginalDestination(conn stat.Connection) (net.Destination, error) {\n\tla := conn.LocalAddr()\n\tra := conn.RemoteAddr()\n\tip, port, err := internet.OriginalDst(la, ra)\n\tif err != nil {\n\t\treturn net.Destination{}, errors.New(\"failed to get destination\").Base(err)\n\t}\n\tdest := net.TCPDestination(net.IPAddress(ip), net.Port(port))\n\tif !dest.IsValid() {\n\t\treturn net.Destination{}, errors.New(\"failed to parse destination.\")\n\t}\n\treturn dest, nil\n}\n"
  },
  {
    "path": "transport/internet/tcp/sockopt_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage tcp\n\nimport (\n\t\"context\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nconst SO_ORIGINAL_DST = 80\n\nfunc GetOriginalDestination(conn stat.Connection) (net.Destination, error) {\n\tsysrawconn, f := conn.(syscall.Conn)\n\tif !f {\n\t\treturn net.Destination{}, errors.New(\"unable to get syscall.Conn\")\n\t}\n\trawConn, err := sysrawconn.SyscallConn()\n\tif err != nil {\n\t\treturn net.Destination{}, errors.New(\"failed to get sys fd\").Base(err)\n\t}\n\tvar dest net.Destination\n\terr = rawConn.Control(func(fd uintptr) {\n\t\tlevel := syscall.IPPROTO_IP\n\t\tif conn.RemoteAddr().String()[0] == '[' {\n\t\t\tlevel = syscall.IPPROTO_IPV6\n\t\t}\n\t\taddr, err := syscall.GetsockoptIPv6MTUInfo(int(fd), level, SO_ORIGINAL_DST)\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to call getsockopt\")\n\t\t\treturn\n\t\t}\n\t\tip := (*[4]byte)(unsafe.Pointer(&addr.Addr.Flowinfo))[:4]\n\t\tif level == syscall.IPPROTO_IPV6 {\n\t\t\tip = addr.Addr.Addr[:]\n\t\t}\n\t\tport := (*[2]byte)(unsafe.Pointer(&addr.Addr.Port))[:2]\n\t\tdest = net.TCPDestination(net.IPAddress(ip), net.PortFromBytes(port))\n\t})\n\tif err != nil {\n\t\treturn net.Destination{}, errors.New(\"failed to control connection\").Base(err)\n\t}\n\tif !dest.IsValid() {\n\t\treturn net.Destination{}, errors.New(\"failed to call getsockopt\")\n\t}\n\treturn dest, nil\n}\n"
  },
  {
    "path": "transport/internet/tcp/sockopt_linux_test.go",
    "content": "//go:build linux\n// +build linux\n\npackage tcp_test\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t. \"github.com/xtls/xray-core/transport/internet/tcp\"\n)\n\nfunc TestGetOriginalDestination(t *testing.T) {\n\ttcpServer := tcp.Server{}\n\tdest, err := tcpServer.Start()\n\tcommon.Must(err)\n\tdefer tcpServer.Close()\n\n\tconfig, err := internet.ToMemoryStreamConfig(nil)\n\tcommon.Must(err)\n\tconn, err := Dial(context.Background(), dest, config)\n\tcommon.Must(err)\n\tdefer conn.Close()\n\n\toriginalDest, err := GetOriginalDestination(conn)\n\tif !(dest == originalDest || strings.Contains(err.Error(), \"failed to call getsockopt\")) {\n\t\tt.Error(\"unexpected state\")\n\t}\n}\n"
  },
  {
    "path": "transport/internet/tcp/sockopt_other.go",
    "content": "//go:build !linux && !freebsd && !darwin\n// +build !linux,!freebsd,!darwin\n\npackage tcp\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc GetOriginalDestination(conn stat.Connection) (net.Destination, error) {\n\treturn net.Destination{}, nil\n}\n"
  },
  {
    "path": "transport/internet/tcp/tcp.go",
    "content": "package tcp\n\nconst protocolName = \"tcp\"\n"
  },
  {
    "path": "transport/internet/tcp_hub.go",
    "content": "package internet\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nvar transportListenerCache = make(map[string]ListenFunc)\n\nfunc RegisterTransportListener(protocol string, listener ListenFunc) error {\n\tif _, found := transportListenerCache[protocol]; found {\n\t\treturn errors.New(protocol, \" listener already registered.\").AtError()\n\t}\n\ttransportListenerCache[protocol] = listener\n\treturn nil\n}\n\ntype ConnHandler func(stat.Connection)\n\ntype ListenFunc func(ctx context.Context, address net.Address, port net.Port, settings *MemoryStreamConfig, handler ConnHandler) (Listener, error)\n\ntype Listener interface {\n\tClose() error\n\tAddr() net.Addr\n}\n\n// ListenUnix is the UDS version of ListenTCP\nfunc ListenUnix(ctx context.Context, address net.Address, settings *MemoryStreamConfig, handler ConnHandler) (Listener, error) {\n\tif settings == nil {\n\t\ts, err := ToMemoryStreamConfig(nil)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to create default unix stream settings\").Base(err)\n\t\t}\n\t\tsettings = s\n\t}\n\n\tprotocol := settings.ProtocolName\n\tlistenFunc := transportListenerCache[protocol]\n\tif listenFunc == nil {\n\t\treturn nil, errors.New(protocol, \" unix listener not registered.\").AtError()\n\t}\n\tlistener, err := listenFunc(ctx, address, net.Port(0), settings, handler)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to listen on unix address: \", address).Base(err)\n\t}\n\treturn listener, nil\n}\n\nfunc ListenTCP(ctx context.Context, address net.Address, port net.Port, settings *MemoryStreamConfig, handler ConnHandler) (Listener, error) {\n\tif settings == nil {\n\t\ts, err := ToMemoryStreamConfig(nil)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to create default stream settings\").Base(err)\n\t\t}\n\t\tsettings = s\n\t}\n\n\tif address.Family().IsDomain() && address.Domain() == \"localhost\" {\n\t\taddress = net.LocalHostIP\n\t}\n\n\tif address.Family().IsDomain() {\n\t\treturn nil, errors.New(\"domain address is not allowed for listening: \", address.Domain())\n\t}\n\n\tprotocol := settings.ProtocolName\n\tlistenFunc := transportListenerCache[protocol]\n\tif listenFunc == nil {\n\t\treturn nil, errors.New(protocol, \" listener not registered.\").AtError()\n\t}\n\tlistener, err := listenFunc(ctx, address, port, settings, handler)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to listen on address: \", address, \":\", port).Base(err)\n\t}\n\treturn listener, nil\n}\n\n// ListenSystem listens on a local address for incoming TCP connections.\n//\n// xray:api:beta\nfunc ListenSystem(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) {\n\treturn effectiveListener.Listen(ctx, addr, sockopt)\n}\n\n// ListenSystemPacket listens on a local address for incoming UDP connections.\n//\n// xray:api:beta\nfunc ListenSystemPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) {\n\treturn effectiveListener.ListenPacket(ctx, addr, sockopt)\n}\n"
  },
  {
    "path": "transport/internet/tls/config.go",
    "content": "package tls\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/ocsp\"\n\t\"github.com/xtls/xray-core/common/platform/filesystem\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nvar globalSessionCache = tls.NewLRUClientSessionCache(128)\n\n// ParseCertificate converts a cert.Certificate to Certificate.\nfunc ParseCertificate(c *cert.Certificate) *Certificate {\n\tif c != nil {\n\t\tcertPEM, keyPEM := c.ToPEM()\n\t\treturn &Certificate{\n\t\t\tCertificate: certPEM,\n\t\t\tKey:         keyPEM,\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Config) loadSelfCertPool() (*x509.CertPool, error) {\n\troot := x509.NewCertPool()\n\tfor _, cert := range c.Certificate {\n\t\tif !root.AppendCertsFromPEM(cert.Certificate) {\n\t\t\treturn nil, errors.New(\"failed to append cert\").AtWarning()\n\t\t}\n\t}\n\treturn root, nil\n}\n\n// BuildCertificates builds a list of TLS certificates from proto definition.\nfunc (c *Config) BuildCertificates() []*tls.Certificate {\n\tcerts := make([]*tls.Certificate, 0, len(c.Certificate))\n\tfor _, entry := range c.Certificate {\n\t\tif entry.Usage != Certificate_ENCIPHERMENT {\n\t\t\tcontinue\n\t\t}\n\t\tgetX509KeyPair := func() *tls.Certificate {\n\t\t\tkeyPair, err := tls.X509KeyPair(entry.Certificate, entry.Key)\n\t\t\tif err != nil {\n\t\t\t\terrors.LogWarningInner(context.Background(), err, \"ignoring invalid X509 key pair\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tkeyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])\n\t\t\tif err != nil {\n\t\t\t\terrors.LogWarningInner(context.Background(), err, \"ignoring invalid certificate\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn &keyPair\n\t\t}\n\t\tif keyPair := getX509KeyPair(); keyPair != nil {\n\t\t\tcerts = append(certs, keyPair)\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\t\tindex := len(certs) - 1\n\t\tsetupOcspTicker(entry, func(isReloaded, isOcspstapling bool) {\n\t\t\tcert := certs[index]\n\t\t\tif isReloaded {\n\t\t\t\tif newKeyPair := getX509KeyPair(); newKeyPair != nil {\n\t\t\t\t\tcert = newKeyPair\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif isOcspstapling {\n\t\t\t\tif newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {\n\t\t\t\t\terrors.LogWarningInner(context.Background(), err, \"ignoring invalid OCSP\")\n\t\t\t\t} else if string(newOCSPData) != string(cert.OCSPStaple) {\n\t\t\t\t\tcert.OCSPStaple = newOCSPData\n\t\t\t\t}\n\t\t\t}\n\t\t\tcerts[index] = cert\n\t\t})\n\t}\n\treturn certs\n}\n\nfunc setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstapling bool)) {\n\tgo func() {\n\t\tif entry.OneTimeLoading {\n\t\t\treturn\n\t\t}\n\t\tvar isOcspstapling bool\n\t\thotReloadCertInterval := uint64(3600)\n\t\tif entry.OcspStapling != 0 {\n\t\t\thotReloadCertInterval = entry.OcspStapling\n\t\t\tisOcspstapling = true\n\t\t}\n\t\tt := time.NewTicker(time.Duration(hotReloadCertInterval) * time.Second)\n\t\tfor {\n\t\t\tvar isReloaded bool\n\t\t\tif entry.CertificatePath != \"\" && entry.KeyPath != \"\" {\n\t\t\t\tnewCert, err := filesystem.ReadCert(entry.CertificatePath)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to parse certificate\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tnewKey, err := filesystem.ReadCert(entry.KeyPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to parse key\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif string(newCert) != string(entry.Certificate) || string(newKey) != string(entry.Key) {\n\t\t\t\t\tentry.Certificate = newCert\n\t\t\t\t\tentry.Key = newKey\n\t\t\t\t\tisReloaded = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tcallback(isReloaded, isOcspstapling)\n\t\t\t<-t.C\n\t\t}\n\t}()\n}\n\nfunc isCertificateExpired(c *tls.Certificate) bool {\n\tif c.Leaf == nil && len(c.Certificate) > 0 {\n\t\tif pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {\n\t\t\tc.Leaf = pc\n\t\t}\n\t}\n\n\t// If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate.\n\treturn c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(time.Minute*2))\n}\n\nfunc issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, error) {\n\tparent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key)\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to parse raw certificate\").Base(err)\n\t}\n\tnewCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain))\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to generate new certificate for \", domain).Base(err)\n\t}\n\tnewCertPEM, newKeyPEM := newCert.ToPEM()\n\tif rawCA.BuildChain {\n\t\tnewCertPEM = bytes.Join([][]byte{newCertPEM, rawCA.Certificate}, []byte(\"\\n\"))\n\t}\n\tcert, err := tls.X509KeyPair(newCertPEM, newKeyPEM)\n\treturn &cert, err\n}\n\nfunc (c *Config) getCustomCA() []*Certificate {\n\tcerts := make([]*Certificate, 0, len(c.Certificate))\n\tfor _, certificate := range c.Certificate {\n\t\tif certificate.Usage == Certificate_AUTHORITY_ISSUE {\n\t\t\tcerts = append(certs, certificate)\n\t\t\tsetupOcspTicker(certificate, func(isReloaded, isOcspstapling bool) {})\n\t\t}\n\t}\n\treturn certs\n}\n\nfunc getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\tvar access sync.RWMutex\n\n\treturn func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\t\tdomain := hello.ServerName\n\t\tcertExpired := false\n\n\t\taccess.RLock()\n\t\tcertificate, found := c.NameToCertificate[domain]\n\t\taccess.RUnlock()\n\n\t\tif found {\n\t\t\tif !isCertificateExpired(certificate) {\n\t\t\t\treturn certificate, nil\n\t\t\t}\n\t\t\tcertExpired = true\n\t\t}\n\n\t\tif certExpired {\n\t\t\tnewCerts := make([]tls.Certificate, 0, len(c.Certificates))\n\n\t\t\taccess.Lock()\n\t\t\tfor _, certificate := range c.Certificates {\n\t\t\t\tif !isCertificateExpired(&certificate) {\n\t\t\t\t\tnewCerts = append(newCerts, certificate)\n\t\t\t\t} else if certificate.Leaf != nil {\n\t\t\t\t\texpTime := certificate.Leaf.NotAfter.Format(time.RFC3339)\n\t\t\t\t\terrors.LogInfo(context.Background(), \"old certificate for \", domain, \" (expire on \", expTime, \") discarded\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tc.Certificates = newCerts\n\t\t\taccess.Unlock()\n\t\t}\n\n\t\tvar issuedCertificate *tls.Certificate\n\n\t\t// Create a new certificate from existing CA if possible\n\t\tfor _, rawCert := range ca {\n\t\t\tif rawCert.Usage == Certificate_AUTHORITY_ISSUE {\n\t\t\t\tnewCert, err := issueCertificate(rawCert, domain)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to issue new certificate for \", domain)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tparsed, err := x509.ParseCertificate(newCert.Certificate[0])\n\t\t\t\tif err == nil {\n\t\t\t\t\tnewCert.Leaf = parsed\n\t\t\t\t\texpTime := parsed.NotAfter.Format(time.RFC3339)\n\t\t\t\t\terrors.LogInfo(context.Background(), \"new certificate for \", domain, \" (expire on \", expTime, \") issued\")\n\t\t\t\t} else {\n\t\t\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to parse new certificate for \", domain)\n\t\t\t\t}\n\n\t\t\t\taccess.Lock()\n\t\t\t\tc.Certificates = append(c.Certificates, *newCert)\n\t\t\t\tissuedCertificate = &c.Certificates[len(c.Certificates)-1]\n\t\t\t\taccess.Unlock()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif issuedCertificate == nil {\n\t\t\treturn nil, errors.New(\"failed to create a new certificate for \", domain)\n\t\t}\n\n\t\taccess.Lock()\n\t\tc.BuildNameToCertificate()\n\t\taccess.Unlock()\n\n\t\treturn issuedCertificate, nil\n\t}\n}\n\nfunc getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\treturn func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\t\tif len(certs) == 0 {\n\t\t\treturn nil, errNoCertificates\n\t\t}\n\t\tsni := strings.ToLower(hello.ServerName)\n\t\tif !rejectUnknownSNI && (len(certs) == 1 || sni == \"\") {\n\t\t\treturn certs[0], nil\n\t\t}\n\t\tgsni := \"*\"\n\t\tif index := strings.IndexByte(sni, '.'); index != -1 {\n\t\t\tgsni += sni[index:]\n\t\t}\n\t\tfor _, keyPair := range certs {\n\t\t\tif keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {\n\t\t\t\treturn keyPair, nil\n\t\t\t}\n\t\t\tfor _, name := range keyPair.Leaf.DNSNames {\n\t\t\t\tif name == sni || name == gsni {\n\t\t\t\t\treturn keyPair, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif rejectUnknownSNI {\n\t\t\treturn nil, errNoCertificates\n\t\t}\n\t\treturn certs[0], nil\n\t}\n}\n\nfunc (c *Config) parseServerName() string {\n\tif IsFromMitm(c.ServerName) {\n\t\treturn \"\"\n\t}\n\treturn c.ServerName\n}\n\nfunc (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) (err error) {\n\t// extract x509 certificates from rawCerts (verifiedChains will be nil if InsecureSkipVerify is true)\n\tcerts := make([]*x509.Certificate, len(rawCerts))\n\tfor i, asn1Data := range rawCerts {\n\t\tcerts[i], _ = x509.ParseCertificate(asn1Data)\n\t}\n\tif len(certs) == 0 {\n\t\treturn errors.New(\"unexpected certs\")\n\t}\n\n\t// directly return success if pinned cert is leaf\n\t// or replace RootCAs if pinned cert is CA (and can be used in VerifyPeerCertByName)\n\tCAs := r.RootCAs\n\tvar verifyResult verifyResult\n\tvar verifiedCert *x509.Certificate\n\tif r.PinnedPeerCertSha256 != nil {\n\t\tverifyResult, verifiedCert = verifyChain(certs, r.PinnedPeerCertSha256)\n\t\tswitch verifyResult {\n\t\tcase certNotFound:\n\t\t\treturn errors.New(\"peer cert is unrecognized (against pinnedPeerCertSha256)\")\n\t\tcase foundLeaf:\n\t\t\treturn nil\n\t\tcase foundCA:\n\t\t\tCAs = x509.NewCertPool()\n\t\t\tCAs.AddCert(verifiedCert)\n\t\tdefault:\n\t\t\tpanic(\"impossible pinnedPeerCertSha256 verify result\")\n\t\t}\n\t}\n\n\tif r.VerifyPeerCertByName != nil { // RAW's Dial() may make it empty but not nil\n\t\topts := x509.VerifyOptions{\n\t\t\tRoots:         CAs,\n\t\t\tCurrentTime:   time.Now(),\n\t\t\tIntermediates: x509.NewCertPool(),\n\t\t}\n\t\tfor _, cert := range certs[1:] {\n\t\t\topts.Intermediates.AddCert(cert)\n\t\t}\n\t\tfor _, opts.DNSName = range r.VerifyPeerCertByName {\n\t\t\tif _, err := certs[0].Verify(opts); err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tif verifyResult == foundCA {\n\t\t\terrors.New(\"peer cert is invalid (against pinned CA and verifyPeerCertByName)\")\n\t\t}\n\t\treturn errors.New(\"peer cert is invalid (against root CAs and verifyPeerCertByName)\")\n\t}\n\n\tif verifyResult == foundCA { // if found CA, we need to verify here\n\t\topts := x509.VerifyOptions{\n\t\t\tRoots:         CAs,\n\t\t\tCurrentTime:   time.Now(),\n\t\t\tIntermediates: x509.NewCertPool(),\n\t\t\tDNSName:       r.Config.ServerName,\n\t\t}\n\t\tfor _, cert := range certs[1:] {\n\t\t\topts.Intermediates.AddCert(cert)\n\t\t}\n\t\tif _, err := certs[0].Verify(opts); err == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.New(\"peer cert is invalid (against pinned CA and serverName)\")\n\t}\n\n\treturn nil // r.PinnedPeerCertSha256==nil && r.verifyPeerCertByName==nil\n}\n\ntype RandCarrier struct {\n\tConfig               *tls.Config\n\tRootCAs              *x509.CertPool\n\tVerifyPeerCertByName []string\n\tPinnedPeerCertSha256 [][]byte\n}\n\nfunc (r *RandCarrier) Read(p []byte) (n int, err error) {\n\treturn rand.Read(p)\n}\n\n// GetTLSConfig converts this Config into tls.Config.\nfunc (c *Config) GetTLSConfig(opts ...Option) *tls.Config {\n\troot, err := c.getCertPool()\n\tif err != nil {\n\t\terrors.LogErrorInner(context.Background(), err, \"failed to load system root certificate\")\n\t}\n\n\tif c == nil {\n\t\treturn &tls.Config{\n\t\t\tClientSessionCache:     globalSessionCache,\n\t\t\tRootCAs:                root,\n\t\t\tSessionTicketsDisabled: true,\n\t\t}\n\t}\n\n\trandCarrier := &RandCarrier{\n\t\tRootCAs:              root,\n\t\tVerifyPeerCertByName: slices.Clone(c.VerifyPeerCertByName),\n\t\tPinnedPeerCertSha256: c.PinnedPeerCertSha256,\n\t}\n\tconfig := &tls.Config{\n\t\tInsecureSkipVerify:     c.AllowInsecure,\n\t\tRand:                   randCarrier,\n\t\tClientSessionCache:     globalSessionCache,\n\t\tRootCAs:                root,\n\t\tNextProtos:             slices.Clone(c.NextProtocol),\n\t\tSessionTicketsDisabled: !c.EnableSessionResumption,\n\t\tVerifyPeerCertificate:  randCarrier.verifyPeerCert,\n\t}\n\trandCarrier.Config = config\n\tif len(c.VerifyPeerCertByName) > 0 {\n\t\tconfig.InsecureSkipVerify = true\n\t} else {\n\t\trandCarrier.VerifyPeerCertByName = nil\n\t}\n\tif len(c.PinnedPeerCertSha256) > 0 {\n\t\tconfig.InsecureSkipVerify = true\n\t} else {\n\t\trandCarrier.PinnedPeerCertSha256 = nil\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(config)\n\t}\n\n\tcaCerts := c.getCustomCA()\n\tif len(caCerts) > 0 {\n\t\tconfig.GetCertificate = getGetCertificateFunc(config, caCerts)\n\t} else {\n\t\tconfig.GetCertificate = getNewGetCertificateFunc(c.BuildCertificates(), c.RejectUnknownSni)\n\t}\n\n\tif sn := c.parseServerName(); len(sn) > 0 {\n\t\tconfig.ServerName = sn\n\t}\n\n\tif len(c.CurvePreferences) > 0 {\n\t\tconfig.CurvePreferences = ParseCurveName(c.CurvePreferences)\n\t}\n\n\tif len(config.NextProtos) == 0 {\n\t\tconfig.NextProtos = []string{\"h2\", \"http/1.1\"}\n\t}\n\n\tswitch c.MinVersion {\n\tcase \"1.0\":\n\t\tconfig.MinVersion = tls.VersionTLS10\n\tcase \"1.1\":\n\t\tconfig.MinVersion = tls.VersionTLS11\n\tcase \"1.2\":\n\t\tconfig.MinVersion = tls.VersionTLS12\n\tcase \"1.3\":\n\t\tconfig.MinVersion = tls.VersionTLS13\n\t}\n\n\tswitch c.MaxVersion {\n\tcase \"1.0\":\n\t\tconfig.MaxVersion = tls.VersionTLS10\n\tcase \"1.1\":\n\t\tconfig.MaxVersion = tls.VersionTLS11\n\tcase \"1.2\":\n\t\tconfig.MaxVersion = tls.VersionTLS12\n\tcase \"1.3\":\n\t\tconfig.MaxVersion = tls.VersionTLS13\n\t}\n\n\tif len(c.CipherSuites) > 0 {\n\t\tid := make(map[string]uint16)\n\t\tfor _, s := range tls.CipherSuites() {\n\t\t\tid[s.Name] = s.ID\n\t\t}\n\t\tfor _, n := range strings.Split(c.CipherSuites, \":\") {\n\t\t\tif id[n] != 0 {\n\t\t\t\tconfig.CipherSuites = append(config.CipherSuites, id[n])\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(c.MasterKeyLog) > 0 && c.MasterKeyLog != \"none\" {\n\t\twriter, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)\n\t\tif err != nil {\n\t\t\terrors.LogErrorInner(context.Background(), err, \"failed to open \", c.MasterKeyLog, \" as master key log\")\n\t\t} else {\n\t\t\tconfig.KeyLogWriter = writer\n\t\t}\n\t}\n\tif len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {\n\t\terr := ApplyECH(c, config)\n\t\tif err != nil {\n\t\t\tif c.EchForceQuery == \"full\" {\n\t\t\t\terrors.LogError(context.Background(), err)\n\t\t\t} else {\n\t\t\t\terrors.LogInfo(context.Background(), err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn config\n}\n\n// Option for building TLS config.\ntype Option func(*tls.Config)\n\n// WithDestination sets the server name in TLS config.\n// Due to the incorrect structure of GetTLSConfig(), the config.ServerName will always be empty.\n// So the real logic for SNI is:\n// set it to dest -> overwrite it with servername(if it's len>0).\nfunc WithDestination(dest net.Destination) Option {\n\treturn func(config *tls.Config) {\n\t\tif config.ServerName == \"\" {\n\t\t\tconfig.ServerName = dest.Address.String()\n\t\t}\n\t}\n}\n\nfunc WithOverrideName(serverName string) Option {\n\treturn func(config *tls.Config) {\n\t\tconfig.ServerName = serverName\n\t}\n}\n\n// WithNextProto sets the ALPN values in TLS config.\nfunc WithNextProto(protocol ...string) Option {\n\treturn func(config *tls.Config) {\n\t\tif len(config.NextProtos) == 0 {\n\t\t\tconfig.NextProtos = protocol\n\t\t}\n\t}\n}\n\n// ConfigFromStreamSettings fetches Config from stream settings. Nil if not found.\nfunc ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {\n\tif settings == nil {\n\t\treturn nil\n\t}\n\tconfig, ok := settings.SecuritySettings.(*Config)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc ParseCurveName(curveNames []string) []tls.CurveID {\n\tcurveMap := map[string]tls.CurveID{\n\t\t\"curvep256\":          tls.CurveP256,\n\t\t\"curvep384\":          tls.CurveP384,\n\t\t\"curvep521\":          tls.CurveP521,\n\t\t\"x25519\":             tls.X25519,\n\t\t\"x25519mlkem768\":     tls.X25519MLKEM768,\n\t\t\"secp256r1mlkem768\":  tls.SecP256r1MLKEM768,\n\t\t\"secp384r1mlkem1024\": tls.SecP384r1MLKEM1024,\n\t}\n\n\tvar curveIDs []tls.CurveID\n\tfor _, name := range curveNames {\n\t\tif curveID, ok := curveMap[strings.ToLower(name)]; ok {\n\t\t\tcurveIDs = append(curveIDs, curveID)\n\t\t} else {\n\t\t\terrors.LogWarning(context.Background(), \"unsupported curve name: \"+name)\n\t\t}\n\t}\n\treturn curveIDs\n}\n\nfunc IsFromMitm(str string) bool {\n\treturn strings.ToLower(str) == \"frommitm\"\n}\n\ntype verifyResult int\n\nconst (\n\tcertNotFound verifyResult = iota\n\tfoundLeaf\n\tfoundCA\n)\n\nfunc verifyChain(certs []*x509.Certificate, pinnedPeerCertSha256 [][]byte) (verifyResult, *x509.Certificate) {\n\tleafHash := GenerateCertHash(certs[0])\n\tfor _, c := range pinnedPeerCertSha256 {\n\t\tif hmac.Equal(leafHash, c) {\n\t\t\treturn foundLeaf, nil\n\t\t}\n\t}\n\tcerts = certs[1:] // skip leaf\n\tfor _, cert := range certs {\n\t\tcertHash := GenerateCertHash(cert)\n\t\tfor _, c := range pinnedPeerCertSha256 {\n\t\t\tif hmac.Equal(certHash, c) {\n\t\t\t\tif cert.IsCA {\n\t\t\t\t\treturn foundCA, cert\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn certNotFound, nil\n}\n"
  },
  {
    "path": "transport/internet/tls/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/tls/config.proto\n\npackage tls\n\nimport (\n\tinternet \"github.com/xtls/xray-core/transport/internet\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Certificate_Usage int32\n\nconst (\n\tCertificate_ENCIPHERMENT     Certificate_Usage = 0\n\tCertificate_AUTHORITY_VERIFY Certificate_Usage = 1\n\tCertificate_AUTHORITY_ISSUE  Certificate_Usage = 2\n)\n\n// Enum value maps for Certificate_Usage.\nvar (\n\tCertificate_Usage_name = map[int32]string{\n\t\t0: \"ENCIPHERMENT\",\n\t\t1: \"AUTHORITY_VERIFY\",\n\t\t2: \"AUTHORITY_ISSUE\",\n\t}\n\tCertificate_Usage_value = map[string]int32{\n\t\t\"ENCIPHERMENT\":     0,\n\t\t\"AUTHORITY_VERIFY\": 1,\n\t\t\"AUTHORITY_ISSUE\":  2,\n\t}\n)\n\nfunc (x Certificate_Usage) Enum() *Certificate_Usage {\n\tp := new(Certificate_Usage)\n\t*p = x\n\treturn p\n}\n\nfunc (x Certificate_Usage) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Certificate_Usage) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_transport_internet_tls_config_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Certificate_Usage) Type() protoreflect.EnumType {\n\treturn &file_transport_internet_tls_config_proto_enumTypes[0]\n}\n\nfunc (x Certificate_Usage) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Certificate_Usage.Descriptor instead.\nfunc (Certificate_Usage) EnumDescriptor() ([]byte, []int) {\n\treturn file_transport_internet_tls_config_proto_rawDescGZIP(), []int{0, 0}\n}\n\ntype Certificate struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// TLS certificate in x509 format.\n\tCertificate []byte `protobuf:\"bytes,1,opt,name=certificate,proto3\" json:\"certificate,omitempty\"`\n\t// TLS key in x509 format.\n\tKey          []byte            `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tUsage        Certificate_Usage `protobuf:\"varint,3,opt,name=usage,proto3,enum=xray.transport.internet.tls.Certificate_Usage\" json:\"usage,omitempty\"`\n\tOcspStapling uint64            `protobuf:\"varint,4,opt,name=ocsp_stapling,json=ocspStapling,proto3\" json:\"ocsp_stapling,omitempty\"`\n\t// TLS certificate path\n\tCertificatePath string `protobuf:\"bytes,5,opt,name=certificate_path,json=certificatePath,proto3\" json:\"certificate_path,omitempty\"`\n\t// TLS Key path\n\tKeyPath string `protobuf:\"bytes,6,opt,name=key_path,json=keyPath,proto3\" json:\"key_path,omitempty\"`\n\t// If true, one-Time Loading\n\tOneTimeLoading bool `protobuf:\"varint,7,opt,name=One_time_loading,json=OneTimeLoading,proto3\" json:\"One_time_loading,omitempty\"`\n\tBuildChain     bool `protobuf:\"varint,8,opt,name=build_chain,json=buildChain,proto3\" json:\"build_chain,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *Certificate) Reset() {\n\t*x = Certificate{}\n\tmi := &file_transport_internet_tls_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Certificate) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Certificate) ProtoMessage() {}\n\nfunc (x *Certificate) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_tls_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Certificate.ProtoReflect.Descriptor instead.\nfunc (*Certificate) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_tls_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Certificate) GetCertificate() []byte {\n\tif x != nil {\n\t\treturn x.Certificate\n\t}\n\treturn nil\n}\n\nfunc (x *Certificate) GetKey() []byte {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn nil\n}\n\nfunc (x *Certificate) GetUsage() Certificate_Usage {\n\tif x != nil {\n\t\treturn x.Usage\n\t}\n\treturn Certificate_ENCIPHERMENT\n}\n\nfunc (x *Certificate) GetOcspStapling() uint64 {\n\tif x != nil {\n\t\treturn x.OcspStapling\n\t}\n\treturn 0\n}\n\nfunc (x *Certificate) GetCertificatePath() string {\n\tif x != nil {\n\t\treturn x.CertificatePath\n\t}\n\treturn \"\"\n}\n\nfunc (x *Certificate) GetKeyPath() string {\n\tif x != nil {\n\t\treturn x.KeyPath\n\t}\n\treturn \"\"\n}\n\nfunc (x *Certificate) GetOneTimeLoading() bool {\n\tif x != nil {\n\t\treturn x.OneTimeLoading\n\t}\n\treturn false\n}\n\nfunc (x *Certificate) GetBuildChain() bool {\n\tif x != nil {\n\t\treturn x.BuildChain\n\t}\n\treturn false\n}\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAllowInsecure bool                   `protobuf:\"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3\" json:\"allow_insecure,omitempty\"`\n\t// List of certificates to be served on server.\n\tCertificate []*Certificate `protobuf:\"bytes,2,rep,name=certificate,proto3\" json:\"certificate,omitempty\"`\n\t// Override server name.\n\tServerName string `protobuf:\"bytes,3,opt,name=server_name,json=serverName,proto3\" json:\"server_name,omitempty\"`\n\t// Lists of string as ALPN values.\n\tNextProtocol []string `protobuf:\"bytes,4,rep,name=next_protocol,json=nextProtocol,proto3\" json:\"next_protocol,omitempty\"`\n\t// Whether or not to enable session (ticket) resumption.\n\tEnableSessionResumption bool `protobuf:\"varint,5,opt,name=enable_session_resumption,json=enableSessionResumption,proto3\" json:\"enable_session_resumption,omitempty\"`\n\t// If true, root certificates on the system will not be loaded for\n\t// verification.\n\tDisableSystemRoot bool `protobuf:\"varint,6,opt,name=disable_system_root,json=disableSystemRoot,proto3\" json:\"disable_system_root,omitempty\"`\n\t// The minimum TLS version.\n\tMinVersion string `protobuf:\"bytes,7,opt,name=min_version,json=minVersion,proto3\" json:\"min_version,omitempty\"`\n\t// The maximum TLS version.\n\tMaxVersion string `protobuf:\"bytes,8,opt,name=max_version,json=maxVersion,proto3\" json:\"max_version,omitempty\"`\n\t// Specify cipher suites, except for TLS 1.3.\n\tCipherSuites string `protobuf:\"bytes,9,opt,name=cipher_suites,json=cipherSuites,proto3\" json:\"cipher_suites,omitempty\"`\n\t// TLS Client Hello fingerprint (uTLS).\n\tFingerprint      string `protobuf:\"bytes,11,opt,name=fingerprint,proto3\" json:\"fingerprint,omitempty\"`\n\tRejectUnknownSni bool   `protobuf:\"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3\" json:\"reject_unknown_sni,omitempty\"`\n\tMasterKeyLog     string `protobuf:\"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3\" json:\"master_key_log,omitempty\"`\n\t// Lists of string as CurvePreferences values.\n\tCurvePreferences     []string               `protobuf:\"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3\" json:\"curve_preferences,omitempty\"`\n\tVerifyPeerCertByName []string               `protobuf:\"bytes,17,rep,name=verify_peer_cert_by_name,json=verifyPeerCertByName,proto3\" json:\"verify_peer_cert_by_name,omitempty\"`\n\tEchServerKeys        []byte                 `protobuf:\"bytes,18,opt,name=ech_server_keys,json=echServerKeys,proto3\" json:\"ech_server_keys,omitempty\"`\n\tEchConfigList        string                 `protobuf:\"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3\" json:\"ech_config_list,omitempty\"`\n\tEchForceQuery        string                 `protobuf:\"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3\" json:\"ech_force_query,omitempty\"`\n\tEchSocketSettings    *internet.SocketConfig `protobuf:\"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3\" json:\"ech_socket_settings,omitempty\"`\n\tPinnedPeerCertSha256 [][]byte               `protobuf:\"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3\" json:\"pinned_peer_cert_sha256,omitempty\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_tls_config_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_tls_config_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_tls_config_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Config) GetAllowInsecure() bool {\n\tif x != nil {\n\t\treturn x.AllowInsecure\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetCertificate() []*Certificate {\n\tif x != nil {\n\t\treturn x.Certificate\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetServerName() string {\n\tif x != nil {\n\t\treturn x.ServerName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetNextProtocol() []string {\n\tif x != nil {\n\t\treturn x.NextProtocol\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetEnableSessionResumption() bool {\n\tif x != nil {\n\t\treturn x.EnableSessionResumption\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetDisableSystemRoot() bool {\n\tif x != nil {\n\t\treturn x.DisableSystemRoot\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetMinVersion() string {\n\tif x != nil {\n\t\treturn x.MinVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetMaxVersion() string {\n\tif x != nil {\n\t\treturn x.MaxVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetCipherSuites() string {\n\tif x != nil {\n\t\treturn x.CipherSuites\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetFingerprint() string {\n\tif x != nil {\n\t\treturn x.Fingerprint\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetRejectUnknownSni() bool {\n\tif x != nil {\n\t\treturn x.RejectUnknownSni\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetMasterKeyLog() string {\n\tif x != nil {\n\t\treturn x.MasterKeyLog\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetCurvePreferences() []string {\n\tif x != nil {\n\t\treturn x.CurvePreferences\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetVerifyPeerCertByName() []string {\n\tif x != nil {\n\t\treturn x.VerifyPeerCertByName\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetEchServerKeys() []byte {\n\tif x != nil {\n\t\treturn x.EchServerKeys\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetEchConfigList() string {\n\tif x != nil {\n\t\treturn x.EchConfigList\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetEchForceQuery() string {\n\tif x != nil {\n\t\treturn x.EchForceQuery\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetEchSocketSettings() *internet.SocketConfig {\n\tif x != nil {\n\t\treturn x.EchSocketSettings\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetPinnedPeerCertSha256() [][]byte {\n\tif x != nil {\n\t\treturn x.PinnedPeerCertSha256\n\t}\n\treturn nil\n}\n\nvar File_transport_internet_tls_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_tls_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#transport/internet/tls/config.proto\\x12\\x1bxray.transport.internet.tls\\x1a\\x1ftransport/internet/config.proto\\\"\\x83\\x03\\n\" +\n\t\"\\vCertificate\\x12 \\n\" +\n\t\"\\vcertificate\\x18\\x01 \\x01(\\fR\\vcertificate\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x02 \\x01(\\fR\\x03key\\x12D\\n\" +\n\t\"\\x05usage\\x18\\x03 \\x01(\\x0e2..xray.transport.internet.tls.Certificate.UsageR\\x05usage\\x12#\\n\" +\n\t\"\\rocsp_stapling\\x18\\x04 \\x01(\\x04R\\focspStapling\\x12)\\n\" +\n\t\"\\x10certificate_path\\x18\\x05 \\x01(\\tR\\x0fcertificatePath\\x12\\x19\\n\" +\n\t\"\\bkey_path\\x18\\x06 \\x01(\\tR\\akeyPath\\x12(\\n\" +\n\t\"\\x10One_time_loading\\x18\\a \\x01(\\bR\\x0eOneTimeLoading\\x12\\x1f\\n\" +\n\t\"\\vbuild_chain\\x18\\b \\x01(\\bR\\n\" +\n\t\"buildChain\\\"D\\n\" +\n\t\"\\x05Usage\\x12\\x10\\n\" +\n\t\"\\fENCIPHERMENT\\x10\\x00\\x12\\x14\\n\" +\n\t\"\\x10AUTHORITY_VERIFY\\x10\\x01\\x12\\x13\\n\" +\n\t\"\\x0fAUTHORITY_ISSUE\\x10\\x02\\\"\\xf5\\x06\\n\" +\n\t\"\\x06Config\\x12%\\n\" +\n\t\"\\x0eallow_insecure\\x18\\x01 \\x01(\\bR\\rallowInsecure\\x12J\\n\" +\n\t\"\\vcertificate\\x18\\x02 \\x03(\\v2(.xray.transport.internet.tls.CertificateR\\vcertificate\\x12\\x1f\\n\" +\n\t\"\\vserver_name\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"serverName\\x12#\\n\" +\n\t\"\\rnext_protocol\\x18\\x04 \\x03(\\tR\\fnextProtocol\\x12:\\n\" +\n\t\"\\x19enable_session_resumption\\x18\\x05 \\x01(\\bR\\x17enableSessionResumption\\x12.\\n\" +\n\t\"\\x13disable_system_root\\x18\\x06 \\x01(\\bR\\x11disableSystemRoot\\x12\\x1f\\n\" +\n\t\"\\vmin_version\\x18\\a \\x01(\\tR\\n\" +\n\t\"minVersion\\x12\\x1f\\n\" +\n\t\"\\vmax_version\\x18\\b \\x01(\\tR\\n\" +\n\t\"maxVersion\\x12#\\n\" +\n\t\"\\rcipher_suites\\x18\\t \\x01(\\tR\\fcipherSuites\\x12 \\n\" +\n\t\"\\vfingerprint\\x18\\v \\x01(\\tR\\vfingerprint\\x12,\\n\" +\n\t\"\\x12reject_unknown_sni\\x18\\f \\x01(\\bR\\x10rejectUnknownSni\\x12$\\n\" +\n\t\"\\x0emaster_key_log\\x18\\x0f \\x01(\\tR\\fmasterKeyLog\\x12+\\n\" +\n\t\"\\x11curve_preferences\\x18\\x10 \\x03(\\tR\\x10curvePreferences\\x126\\n\" +\n\t\"\\x18verify_peer_cert_by_name\\x18\\x11 \\x03(\\tR\\x14verifyPeerCertByName\\x12&\\n\" +\n\t\"\\x0fech_server_keys\\x18\\x12 \\x01(\\fR\\rechServerKeys\\x12&\\n\" +\n\t\"\\x0fech_config_list\\x18\\x13 \\x01(\\tR\\rechConfigList\\x12&\\n\" +\n\t\"\\x0fech_force_query\\x18\\x14 \\x01(\\tR\\rechForceQuery\\x12U\\n\" +\n\t\"\\x13ech_socket_settings\\x18\\x15 \\x01(\\v2%.xray.transport.internet.SocketConfigR\\x11echSocketSettings\\x125\\n\" +\n\t\"\\x17pinned_peer_cert_sha256\\x18\\x16 \\x03(\\fR\\x14pinnedPeerCertSha256Bs\\n\" +\n\t\"\\x1fcom.xray.transport.internet.tlsP\\x01Z0github.com/xtls/xray-core/transport/internet/tls\\xaa\\x02\\x1bXray.Transport.Internet.Tlsb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_tls_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_tls_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_tls_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_tls_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_tls_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_tls_config_proto_rawDesc), len(file_transport_internet_tls_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_tls_config_proto_rawDescData\n}\n\nvar file_transport_internet_tls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_transport_internet_tls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_tls_config_proto_goTypes = []any{\n\t(Certificate_Usage)(0),        // 0: xray.transport.internet.tls.Certificate.Usage\n\t(*Certificate)(nil),           // 1: xray.transport.internet.tls.Certificate\n\t(*Config)(nil),                // 2: xray.transport.internet.tls.Config\n\t(*internet.SocketConfig)(nil), // 3: xray.transport.internet.SocketConfig\n}\nvar file_transport_internet_tls_config_proto_depIdxs = []int32{\n\t0, // 0: xray.transport.internet.tls.Certificate.usage:type_name -> xray.transport.internet.tls.Certificate.Usage\n\t1, // 1: xray.transport.internet.tls.Config.certificate:type_name -> xray.transport.internet.tls.Certificate\n\t3, // 2: xray.transport.internet.tls.Config.ech_socket_settings:type_name -> xray.transport.internet.SocketConfig\n\t3, // [3:3] is the sub-list for method output_type\n\t3, // [3:3] is the sub-list for method input_type\n\t3, // [3:3] is the sub-list for extension type_name\n\t3, // [3:3] is the sub-list for extension extendee\n\t0, // [0:3] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_tls_config_proto_init() }\nfunc file_transport_internet_tls_config_proto_init() {\n\tif File_transport_internet_tls_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_tls_config_proto_rawDesc), len(file_transport_internet_tls_config_proto_rawDesc)),\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_tls_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_tls_config_proto_depIdxs,\n\t\tEnumInfos:         file_transport_internet_tls_config_proto_enumTypes,\n\t\tMessageInfos:      file_transport_internet_tls_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_tls_config_proto = out.File\n\tfile_transport_internet_tls_config_proto_goTypes = nil\n\tfile_transport_internet_tls_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/tls/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.tls;\noption csharp_namespace = \"Xray.Transport.Internet.Tls\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/tls\";\noption java_package = \"com.xray.transport.internet.tls\";\noption java_multiple_files = true;\n\nimport \"transport/internet/config.proto\";\n\nmessage Certificate {\n  // TLS certificate in x509 format.\n  bytes certificate = 1;\n\n  // TLS key in x509 format.\n  bytes key = 2;\n\n  enum Usage {\n    ENCIPHERMENT = 0;\n    AUTHORITY_VERIFY = 1;\n    AUTHORITY_ISSUE = 2;\n  }\n\n  Usage usage = 3;\n\n  uint64 ocsp_stapling = 4;\n\n  // TLS certificate path\n  string certificate_path = 5;\n\n  // TLS Key path\n  string key_path = 6;\n\n  // If true, one-Time Loading\n  bool One_time_loading = 7;\n\n  bool build_chain = 8;\n}\n\nmessage Config {\n  bool allow_insecure = 1;\n\n  // List of certificates to be served on server.\n  repeated Certificate certificate = 2;\n\n  // Override server name.\n  string server_name = 3;\n\n  // Lists of string as ALPN values.\n  repeated string next_protocol = 4;\n\n  // Whether or not to enable session (ticket) resumption.\n  bool enable_session_resumption = 5;\n\n  // If true, root certificates on the system will not be loaded for\n  // verification.\n  bool disable_system_root = 6;\n\n  // The minimum TLS version.\n  string min_version = 7;\n\n  // The maximum TLS version.\n  string max_version = 8;\n\n  // Specify cipher suites, except for TLS 1.3.\n  string cipher_suites = 9;\n\n  // TLS Client Hello fingerprint (uTLS).\n  string fingerprint = 11;\n\n  bool reject_unknown_sni = 12;\n\n  string master_key_log = 15;\n\n  // Lists of string as CurvePreferences values.\n  repeated string curve_preferences = 16;\n\n  repeated string verify_peer_cert_by_name = 17;\n\n  bytes ech_server_keys = 18;\n\n  string ech_config_list = 19;\n\n  string ech_force_query = 20;\n\n  SocketConfig ech_socket_settings = 21;\n\n  repeated bytes pinned_peer_cert_sha256 = 22;\n}\n"
  },
  {
    "path": "transport/internet/tls/config_other.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage tls\n\nimport (\n\t\"crypto/x509\"\n\t\"sync\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n)\n\ntype rootCertsCache struct {\n\tsync.Mutex\n\tpool *x509.CertPool\n}\n\nfunc (c *rootCertsCache) load() (*x509.CertPool, error) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tif c.pool != nil {\n\t\treturn c.pool, nil\n\t}\n\n\tpool, err := x509.SystemCertPool()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.pool = pool\n\treturn pool, nil\n}\n\nvar rootCerts rootCertsCache\n\nfunc (c *Config) getCertPool() (*x509.CertPool, error) {\n\tif c.DisableSystemRoot {\n\t\treturn c.loadSelfCertPool()\n\t}\n\n\tif len(c.Certificate) == 0 {\n\t\treturn rootCerts.load()\n\t}\n\n\tpool, err := x509.SystemCertPool()\n\tif err != nil {\n\t\treturn nil, errors.New(\"system root\").AtWarning().Base(err)\n\t}\n\tfor _, cert := range c.Certificate {\n\t\tif !pool.AppendCertsFromPEM(cert.Certificate) {\n\t\t\treturn nil, errors.New(\"append cert to root\").AtWarning().Base(err)\n\t\t}\n\t}\n\treturn pool, nil\n}\n"
  },
  {
    "path": "transport/internet/tls/config_test.go",
    "content": "package tls_test\n\nimport (\n\tgotls \"crypto/tls\"\n\t\"crypto/x509\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t. \"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\nfunc TestCertificateIssuing(t *testing.T) {\n\tct, _ := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))\n\tcertificate := ParseCertificate(ct)\n\tcertificate.Usage = Certificate_AUTHORITY_ISSUE\n\n\tc := &Config{\n\t\tCertificate: []*Certificate{\n\t\t\tcertificate,\n\t\t},\n\t}\n\n\ttlsConfig := c.GetTLSConfig()\n\txrayCert, err := tlsConfig.GetCertificate(&gotls.ClientHelloInfo{\n\t\tServerName: \"www.example.com\",\n\t})\n\tcommon.Must(err)\n\n\tx509Cert, err := x509.ParseCertificate(xrayCert.Certificate[0])\n\tcommon.Must(err)\n\tif !x509Cert.NotAfter.After(time.Now()) {\n\t\tt.Error(\"NotAfter: \", x509Cert.NotAfter)\n\t}\n}\n\nfunc TestExpiredCertificate(t *testing.T) {\n\tcaCert, _ := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))\n\texpiredCert, _ := cert.MustGenerate(caCert, cert.NotAfter(time.Now().Add(time.Minute*-2)), cert.CommonName(\"www.example.com\"), cert.DNSNames(\"www.example.com\"))\n\n\tcertificate := ParseCertificate(caCert)\n\tcertificate.Usage = Certificate_AUTHORITY_ISSUE\n\n\tcertificate2 := ParseCertificate(expiredCert)\n\n\tc := &Config{\n\t\tCertificate: []*Certificate{\n\t\t\tcertificate,\n\t\t\tcertificate2,\n\t\t},\n\t}\n\n\ttlsConfig := c.GetTLSConfig()\n\txrayCert, err := tlsConfig.GetCertificate(&gotls.ClientHelloInfo{\n\t\tServerName: \"www.example.com\",\n\t})\n\tcommon.Must(err)\n\n\tx509Cert, err := x509.ParseCertificate(xrayCert.Certificate[0])\n\tcommon.Must(err)\n\tif !x509Cert.NotAfter.After(time.Now()) {\n\t\tt.Error(\"NotAfter: \", x509Cert.NotAfter)\n\t}\n}\n\nfunc TestInsecureCertificates(t *testing.T) {\n\tc := &Config{}\n\n\ttlsConfig := c.GetTLSConfig()\n\tif len(tlsConfig.CipherSuites) > 0 {\n\t\tt.Fatal(\"Unexpected tls cipher suites list: \", tlsConfig.CipherSuites)\n\t}\n}\n\nfunc BenchmarkCertificateIssuing(b *testing.B) {\n\tct, _ := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))\n\tcertificate := ParseCertificate(ct)\n\tcertificate.Usage = Certificate_AUTHORITY_ISSUE\n\n\tc := &Config{\n\t\tCertificate: []*Certificate{\n\t\t\tcertificate,\n\t\t},\n\t}\n\n\ttlsConfig := c.GetTLSConfig()\n\tlenCerts := len(tlsConfig.Certificates)\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = tlsConfig.GetCertificate(&gotls.ClientHelloInfo{\n\t\t\tServerName: \"www.example.com\",\n\t\t})\n\t\tdelete(tlsConfig.NameToCertificate, \"www.example.com\")\n\t\ttlsConfig.Certificates = tlsConfig.Certificates[:lenCerts]\n\t}\n}\n"
  },
  {
    "path": "transport/internet/tls/config_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage tls\n\nimport \"crypto/x509\"\n\nfunc (c *Config) getCertPool() (*x509.CertPool, error) {\n\tif c.DisableSystemRoot {\n\t\treturn c.loadSelfCertPool()\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "transport/internet/tls/ech.go",
    "content": "package tls\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\t\"github.com/xtls/xray-core/common/crypto\"\n\tdns2 \"github.com/xtls/xray-core/features/dns\"\n\t\"golang.org/x/net/http2\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"golang.org/x/crypto/cryptobyte\"\n)\n\nfunc ApplyECH(c *Config, config *tls.Config) error {\n\tvar ECHConfig []byte\n\tvar err error\n\n\tvar nameToQuery string\n\tif net.ParseAddress(config.ServerName).Family().IsDomain() {\n\t\tnameToQuery = config.ServerName\n\t}\n\tvar DNSServer string\n\n\t// for server\n\tif len(c.EchServerKeys) != 0 {\n\t\tKeySets, err := ConvertToGoECHKeys(c.EchServerKeys)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"Failed to unmarshal ECHKeySetList: \", err)\n\t\t}\n\t\tconfig.EncryptedClientHelloKeys = KeySets\n\t}\n\n\t// for client\n\tif len(c.EchConfigList) != 0 {\n\t\tECHForceQuery := c.EchForceQuery\n\t\tswitch ECHForceQuery {\n\t\tcase \"none\", \"half\", \"full\":\n\t\tcase \"\":\n\t\t\tECHForceQuery = \"full\" // default to full\n\t\tdefault:\n\t\t\tpanic(\"Invalid ECHForceQuery: \" + c.EchForceQuery)\n\t\t}\n\t\tdefer func() {\n\t\t\t// if failed to get ECHConfig, use an invalid one to make connection fail\n\t\t\tif err != nil || len(ECHConfig) == 0 {\n\t\t\t\tif ECHForceQuery == \"full\" {\n\t\t\t\t\tECHConfig = []byte{1, 1, 4, 5, 1, 4}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconfig.EncryptedClientHelloConfigList = ECHConfig\n\t\t}()\n\t\t// direct base64 config\n\t\tif strings.Contains(c.EchConfigList, \"://\") {\n\t\t\t// query config from dns\n\t\t\tparts := strings.Split(c.EchConfigList, \"+\")\n\t\t\tif len(parts) == 2 {\n\t\t\t\t// parse ECH DNS server in format of \"example.com+https://1.1.1.1/dns-query\"\n\t\t\t\tnameToQuery = parts[0]\n\t\t\t\tDNSServer = parts[1]\n\t\t\t} else if len(parts) == 1 {\n\t\t\t\t// normal format\n\t\t\t\tDNSServer = parts[0]\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"Invalid ECH DNS server format: \", c.EchConfigList)\n\t\t\t}\n\t\t\tif nameToQuery == \"\" {\n\t\t\t\treturn errors.New(\"Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query\")\n\t\t\t}\n\t\t\tECHConfig, err = QueryRecord(nameToQuery, DNSServer, c.EchForceQuery, c.EchSocketSettings)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"Failed to query ECH DNS record for domain: \", nameToQuery, \" at server: \", DNSServer).Base(err)\n\t\t\t}\n\t\t} else {\n\t\t\tECHConfig, err = base64.StdEncoding.DecodeString(c.EchConfigList)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"Failed to unmarshal ECHConfigList: \", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype ECHConfigCache struct {\n\tconfigRecord atomic.Pointer[echConfigRecord]\n\t// updateLock is not for preventing concurrent read/write, but for preventing concurrent update\n\tUpdateLock sync.Mutex\n}\n\ntype echConfigRecord struct {\n\tconfig []byte\n\texpire time.Time\n\terr    error\n}\n\nvar (\n\t// The keys for both maps must be generated by ECHCacheKey().\n\tGlobalECHConfigCache = utils.NewTypedSyncMap[string, *ECHConfigCache]()\n\tclientForECHDOH      = utils.NewTypedSyncMap[string, *http.Client]()\n)\n\n// sockopt can be nil if not specified.\n// if for clientForECHDOH, domain can be empty.\nfunc ECHCacheKey(server, domain string, sockopt *internet.SocketConfig) string {\n\treturn server + \"|\" + domain + \"|\" + fmt.Sprintf(\"%p\", sockopt)\n}\n\n// Update updates the ECH config for given domain and server.\n// this method is concurrent safe, only one update request will be sent, others get the cache.\n// if isLockedUpdate is true, it will not try to acquire the lock.\nfunc (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {\n\tif !isLockedUpdate {\n\t\tc.UpdateLock.Lock()\n\t\tdefer c.UpdateLock.Unlock()\n\t}\n\t// Double check cache after acquiring lock\n\tconfigRecord := c.configRecord.Load()\n\tif configRecord.expire.After(time.Now()) && configRecord.err == nil {\n\t\terrors.LogDebug(context.Background(), \"Cache hit for domain after double check: \", domain)\n\t\treturn configRecord.config, configRecord.err\n\t}\n\t// Query ECH config from DNS server\n\terrors.LogDebug(context.Background(), \"Trying to query ECH config for domain: \", domain, \" with ECH server: \", server)\n\techConfig, ttl, err := dnsQuery(server, domain, sockopt)\n\t// if in \"full\", directly return\n\tif err != nil && forceQuery == \"full\" {\n\t\treturn nil, err\n\t}\n\tif ttl == 0 {\n\t\tttl = dns2.DefaultTTL\n\t}\n\tconfigRecord = &echConfigRecord{\n\t\tconfig: echConfig,\n\t\texpire: time.Now().Add(time.Duration(ttl) * time.Second),\n\t\terr:    err,\n\t}\n\tc.configRecord.Store(configRecord)\n\treturn configRecord.config, configRecord.err\n}\n\n// QueryRecord returns the ECH config for given domain.\n// If the record is not in cache or expired, it will query the DNS server and update the cache.\nfunc QueryRecord(domain string, server string, forceQuery string, sockopt *internet.SocketConfig) ([]byte, error) {\n\tGlobalECHConfigCacheKey := ECHCacheKey(server, domain, sockopt)\n\techConfigCache, ok := GlobalECHConfigCache.Load(GlobalECHConfigCacheKey)\n\tif !ok {\n\t\techConfigCache = &ECHConfigCache{}\n\t\techConfigCache.configRecord.Store(&echConfigRecord{})\n\t\techConfigCache, _ = GlobalECHConfigCache.LoadOrStore(GlobalECHConfigCacheKey, echConfigCache)\n\t}\n\tconfigRecord := echConfigCache.configRecord.Load()\n\tif configRecord.expire.After(time.Now()) && (configRecord.err == nil || forceQuery == \"none\") {\n\t\terrors.LogDebug(context.Background(), \"Cache hit for domain: \", domain)\n\t\treturn configRecord.config, configRecord.err\n\t}\n\n\t// If expire is zero value, it means we are in initial state, wait for the query to finish\n\t// otherwise return old value immediately and update in a goroutine\n\t// but if the cache is too old, wait for update\n\tif configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {\n\t\treturn echConfigCache.Update(domain, server, false, forceQuery, sockopt)\n\t} else {\n\t\t// If someone already acquired the lock, it means it is updating, do not start another update goroutine\n\t\tif echConfigCache.UpdateLock.TryLock() {\n\t\t\tgo func() {\n\t\t\t\tdefer echConfigCache.UpdateLock.Unlock()\n\t\t\t\techConfigCache.Update(domain, server, true, forceQuery, sockopt)\n\t\t\t}()\n\t\t}\n\t\treturn configRecord.config, configRecord.err\n\t}\n}\n\n// dnsQuery is the real func for sending type65 query for given domain to given DNS server.\n// return ECH config, TTL and error\nfunc dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]byte, uint32, error) {\n\tm := new(dns.Msg)\n\tvar dnsResolve []byte\n\tm.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)\n\t// for DOH server\n\tif strings.HasPrefix(server, \"https://\") || strings.HasPrefix(server, \"h2c://\") {\n\t\th2c := strings.HasPrefix(server, \"h2c://\")\n\t\tm.SetEdns0(4096, false) // 4096 is the buffer size, false means no DNSSEC\n\t\tpadding := &dns.EDNS0_PADDING{Padding: make([]byte, int(crypto.RandBetween(100, 300)))}\n\t\tif opt := m.IsEdns0(); opt != nil {\n\t\t\topt.Option = append(opt.Option, padding)\n\t\t}\n\t\t// always 0 in DOH\n\t\tm.Id = 0\n\t\tmsg, err := m.Pack()\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tvar client *http.Client\n\t\tserverKey := ECHCacheKey(server, \"\", sockopt)\n\t\tif client, _ = clientForECHDOH.Load(serverKey); client == nil {\n\t\t\t// All traffic sent by core should via xray's internet.DialSystem\n\t\t\t// This involves the behavior of some Android VPN GUI clients\n\t\t\ttr := &http2.Transport{\n\t\t\t\tIdleConnTimeout: net.ConnIdleTimeout,\n\t\t\t\tReadIdleTimeout: net.ChromeH2KeepAlivePeriod,\n\t\t\t\tDialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {\n\t\t\t\t\tdest, err := net.ParseDestination(network + \":\" + addr)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tvar conn net.Conn\n\n\t\t\t\t\tconn, err = internet.DialSystem(ctx, dest, sockopt)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tif !h2c {\n\t\t\t\t\t\tu, err := url.Parse(server)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconn = utls.UClient(conn, &utls.Config{ServerName: u.Hostname()}, utls.HelloChrome_Auto)\n\t\t\t\t\t\tif err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {\n\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn conn, nil\n\t\t\t\t},\n\t\t\t}\n\t\t\tc := &http.Client{\n\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\tTransport: tr,\n\t\t\t}\n\t\t\tclient, _ = clientForECHDOH.LoadOrStore(serverKey, c)\n\t\t}\n\t\treq, err := http.NewRequest(\"POST\", server, bytes.NewReader(msg))\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\treq.Header.Set(\"Accept\", \"application/dns-message\")\n\t\treq.Header.Set(\"Content-Type\", \"application/dns-message\")\n\t\treq.Header.Set(\"User-Agent\", utils.ChromeUA)\n\t\treq.Header.Set(\"X-Padding\", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))\n\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn nil, 0, errors.New(\"query failed with response code:\", resp.StatusCode)\n\t\t}\n\t\tdnsResolve = respBody\n\t} else if strings.HasPrefix(server, \"udp://\") { // for classic udp dns server\n\t\tudpServerAddr := server[len(\"udp://\"):]\n\t\t// default port 53 if not specified\n\t\tif !strings.Contains(udpServerAddr, \":\") {\n\t\t\tudpServerAddr = udpServerAddr + \":53\"\n\t\t}\n\t\tdest, err := net.ParseDestination(\"udp\" + \":\" + udpServerAddr)\n\t\tif err != nil {\n\t\t\treturn nil, 0, errors.New(\"failed to parse udp dns server \", udpServerAddr, \" for ECH: \", err)\n\t\t}\n\t\tdnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\t// use xray's internet.DialSystem as mentioned above\n\t\tconn, err := internet.DialSystem(dnsTimeoutCtx, dest, sockopt)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tdefer func() {\n\t\t\terr := conn.Close()\n\t\t\tif err != nil {\n\t\t\t\terrors.LogDebug(context.Background(), \"Failed to close connection: \", err)\n\t\t\t}\n\t\t}()\n\t\tmsg, err := m.Pack()\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tconn.Write(msg)\n\t\tudpResponse := make([]byte, 512)\n\t\tconn.SetReadDeadline(time.Now().Add(5 * time.Second))\n\t\t_, err = conn.Read(udpResponse)\n\t\tif err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t\tdnsResolve = udpResponse\n\t}\n\trespMsg := new(dns.Msg)\n\terr := respMsg.Unpack(dnsResolve)\n\tif err != nil {\n\t\treturn nil, 0, errors.New(\"failed to unpack dns response for ECH: \", err)\n\t}\n\tif len(respMsg.Answer) > 0 {\n\t\tfor _, answer := range respMsg.Answer {\n\t\t\tif https, ok := answer.(*dns.HTTPS); ok && https.Hdr.Name == dns.Fqdn(domain) {\n\t\t\t\tfor _, v := range https.Value {\n\t\t\t\t\tif echConfig, ok := v.(*dns.SVCBECHConfig); ok {\n\t\t\t\t\t\terrors.LogDebug(context.Background(), \"Get ECH config:\", echConfig.String(), \" TTL:\", respMsg.Answer[0].Header().Ttl)\n\t\t\t\t\t\treturn echConfig.ECH, answer.Header().Ttl, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// empty is valid, means no ECH config found\n\treturn nil, dns2.DefaultTTL, nil\n}\n\nvar ErrInvalidLen = errors.New(\"goech: invalid length\")\n\nfunc ConvertToGoECHKeys(data []byte) ([]tls.EncryptedClientHelloKey, error) {\n\tvar keys []tls.EncryptedClientHelloKey\n\ts := cryptobyte.String(data)\n\tfor !s.Empty() {\n\t\tif len(s) < 2 {\n\t\t\treturn keys, ErrInvalidLen\n\t\t}\n\t\tkeyLength := int(binary.BigEndian.Uint16(s[:2]))\n\t\tif len(s) < keyLength+4 {\n\t\t\treturn keys, ErrInvalidLen\n\t\t}\n\t\tconfigLength := int(binary.BigEndian.Uint16(s[keyLength+2 : keyLength+4]))\n\t\tif len(s) < 2+keyLength+2+configLength {\n\t\t\treturn keys, ErrInvalidLen\n\t\t}\n\t\tchild := cryptobyte.String(s[:2+keyLength+2+configLength])\n\t\tvar (\n\t\t\tsk, config cryptobyte.String\n\t\t)\n\t\tif !child.ReadUint16LengthPrefixed(&sk) || !child.ReadUint16LengthPrefixed(&config) || !child.Empty() {\n\t\t\treturn keys, ErrInvalidLen\n\t\t}\n\t\tif !s.Skip(2 + keyLength + 2 + configLength) {\n\t\t\treturn keys, ErrInvalidLen\n\t\t}\n\t\tkeys = append(keys, tls.EncryptedClientHelloKey{\n\t\t\tConfig:     config,\n\t\t\tPrivateKey: sk,\n\t\t})\n\t}\n\treturn keys, nil\n}\n"
  },
  {
    "path": "transport/internet/tls/ech_test.go",
    "content": "package tls\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/xtls/xray-core/common\"\n)\n\nfunc TestECHDial(t *testing.T) {\n\tconfig := &Config{\n\t\tServerName:    \"cloudflare.com\",\n\t\tEchConfigList: \"encryptedsni.com+udp://1.1.1.1\",\n\t}\n\t// test concurrent Dial(to test cache problem)\n\twg := sync.WaitGroup{}\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tTLSConfig := config.GetTLSConfig()\n\t\t\tTLSConfig.NextProtos = []string{\"http/1.1\"}\n\t\t\tclient := &http.Client{\n\t\t\t\tTransport: &http.Transport{\n\t\t\t\t\tTLSClientConfig: TLSConfig,\n\t\t\t\t},\n\t\t\t}\n\t\t\tresp, err := client.Get(\"https://cloudflare.com/cdn-cgi/trace\")\n\t\t\tcommon.Must(err)\n\t\t\tdefer resp.Body.Close()\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tcommon.Must(err)\n\t\t\tif !strings.Contains(string(body), \"sni=encrypted\") {\n\t\t\t\tt.Error(\"ECH Dial success but SNI is not encrypted\")\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n\t// check cache\n\techConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey(\"udp://1.1.1.1\", \"encryptedsni.com\", nil))\n\tif !ok {\n\t\tt.Error(\"ECH config cache not found\")\n\n\t}\n\tok = echConfigCache.UpdateLock.TryLock()\n\tif !ok {\n\t\tt.Error(\"ECH config cache dead lock detected\")\n\t}\n\techConfigCache.UpdateLock.Unlock()\n\tconfigRecord := echConfigCache.configRecord.Load()\n\tif configRecord == nil {\n\t\tt.Error(\"ECH config record not found in cache\")\n\t}\n}\n\nfunc TestECHDialFail(t *testing.T) {\n\tconfig := &Config{\n\t\tServerName:    \"cloudflare.com\",\n\t\tEchConfigList: \"udp://127.0.0.1\",\n\t\tEchForceQuery: \"half\",\n\t}\n\tconfig.GetTLSConfig()\n\t// check cache\n\techConfigCache, ok := GlobalECHConfigCache.Load(ECHCacheKey(\"udp://127.0.0.1\", \"cloudflare.com\", nil))\n\tif !ok {\n\t\tt.Error(\"ECH config cache not found\")\n\t}\n\tconfigRecord := echConfigCache.configRecord.Load()\n\tif configRecord == nil {\n\t\tt.Error(\"ECH config record not found in cache\")\n\t\treturn\n\t}\n\tif configRecord.err == nil {\n\t\tt.Error(\"unexpected nil error in ECH config record\")\n\t}\n}\n"
  },
  {
    "path": "transport/internet/tls/grpc.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\tgotls \"crypto/tls\"\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\t\"google.golang.org/grpc/credentials\"\n)\n\n// grpcUtlsInfo contains the auth information for a TLS authenticated connection.\n// It implements the AuthInfo interface.\ntype grpcUtlsInfo struct {\n\tState utls.ConnectionState\n\tcredentials.CommonAuthInfo\n\t// This API is experimental.\n\tSPIFFEID *url.URL\n}\n\n// AuthType returns the type of TLSInfo as a string.\nfunc (t grpcUtlsInfo) AuthType() string {\n\treturn \"utls\"\n}\n\n// GetSecurityValue returns security info requested by channelz.\nfunc (t grpcUtlsInfo) GetSecurityValue() credentials.ChannelzSecurityValue {\n\tv := &credentials.TLSChannelzSecurityValue{\n\t\tStandardName: \"0x\" + strconv.FormatUint(uint64(t.State.CipherSuite), 16),\n\t}\n\t// Currently there's no way to get LocalCertificate info from tls package.\n\tif len(t.State.PeerCertificates) > 0 {\n\t\tv.RemoteCertificate = t.State.PeerCertificates[0].Raw\n\t}\n\treturn v\n}\n\n// grpcUtls is the credentials required for authenticating a connection using TLS.\ntype grpcUtls struct {\n\tconfig      *gotls.Config\n\tfingerprint *utls.ClientHelloID\n}\n\nfunc (c grpcUtls) Info() credentials.ProtocolInfo {\n\treturn credentials.ProtocolInfo{\n\t\tSecurityProtocol: \"tls\",\n\t\tSecurityVersion:  \"1.2\",\n\t\tServerName:       c.config.ServerName,\n\t}\n}\n\nfunc (c *grpcUtls) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ credentials.AuthInfo, err error) {\n\t// use local cfg to avoid clobbering ServerName if using multiple endpoints\n\tcfg := c.config.Clone()\n\tif cfg.ServerName == \"\" {\n\t\tserverName, _, err := net.SplitHostPort(authority)\n\t\tif err != nil {\n\t\t\t// If the authority had no host port or if the authority cannot be parsed, use it as-is.\n\t\t\tserverName = authority\n\t\t}\n\t\tcfg.ServerName = serverName\n\t}\n\tconn := UClient(rawConn, cfg, c.fingerprint).(*UConn)\n\terrChannel := make(chan error, 1)\n\tgo func() {\n\t\terrChannel <- conn.HandshakeContext(ctx)\n\t\tclose(errChannel)\n\t}()\n\tselect {\n\tcase err := <-errChannel:\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t\treturn nil, nil, err\n\t\t}\n\tcase <-ctx.Done():\n\t\tconn.Close()\n\t\treturn nil, nil, ctx.Err()\n\t}\n\ttlsInfo := grpcUtlsInfo{\n\t\tState: conn.ConnectionState(),\n\t\tCommonAuthInfo: credentials.CommonAuthInfo{\n\t\t\tSecurityLevel: credentials.PrivacyAndIntegrity,\n\t\t},\n\t}\n\treturn conn, tlsInfo, nil\n}\n\n// ServerHandshake will always panic. We don't support running uTLS as server.\nfunc (c *grpcUtls) ServerHandshake(net.Conn) (net.Conn, credentials.AuthInfo, error) {\n\tpanic(\"not available!\")\n}\n\nfunc (c *grpcUtls) Clone() credentials.TransportCredentials {\n\treturn NewGrpcUtls(c.config, c.fingerprint)\n}\n\nfunc (c *grpcUtls) OverrideServerName(serverNameOverride string) error {\n\tc.config.ServerName = serverNameOverride\n\treturn nil\n}\n\n// NewGrpcUtls uses c to construct a TransportCredentials based on uTLS.\nfunc NewGrpcUtls(c *gotls.Config, fingerprint *utls.ClientHelloID) credentials.TransportCredentials {\n\ttc := &grpcUtls{c.Clone(), fingerprint}\n\treturn tc\n}\n"
  },
  {
    "path": "transport/internet/tls/pin.go",
    "content": "package tls\n\nimport (\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n)\n\n// []byte must be ASN.1 DER content\nfunc GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {\n\tvar out [32]byte\n\tswitch v := any(cert).(type) {\n\tcase *x509.Certificate:\n\t\tout = sha256.Sum256(v.Raw)\n\tcase []byte:\n\t\tout = sha256.Sum256(v)\n\t}\n\treturn out[:]\n}\n\nfunc GenerateCertHashHex[T *x509.Certificate | []byte](cert T) string {\n\tvar out [32]byte\n\tswitch v := any(cert).(type) {\n\tcase *x509.Certificate:\n\t\tout = sha256.Sum256(v.Raw)\n\tcase []byte:\n\t\tout = sha256.Sum256(v)\n\t}\n\treturn hex.EncodeToString(out[:])\n}\n"
  },
  {
    "path": "transport/internet/tls/pin_test.go",
    "content": "package tls\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"encoding/pem\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n)\n\nfunc TestCalculateCertHash(t *testing.T) {\n\tconst Single = `-----BEGIN CERTIFICATE-----\nMIINWzCCC0OgAwIBAgITMwK6ajqdrV0tahuIrQAAArpqOjANBgkqhkiG9w0BAQwF\nADBdMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u\nMS4wLAYDVQQDEyVNaWNyb3NvZnQgQXp1cmUgUlNBIFRMUyBJc3N1aW5nIENBIDA0\nMB4XDTI1MDkwOTEwMzE1NloXDTI2MDMwODEwMzE1NlowYzELMAkGA1UEBhMCVVMx\nCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv\nZnQgQ29ycG9yYXRpb24xFTATBgNVBAMTDHd3dy5iaW5nLmNvbTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAMBflymLifrVkjp8K4/XrHSt+/xDrrZIJyTI\nJOhIGZJZ88sNjo4OChQWV8O3CTQwrbKJDd6KjZFFc6BPKpEJZ891w2zkymMbE7wh\nvQVviSCIVCO+49pLrEvfh5ZvdbXhtNzm/ZRvkoI8h4ZKPBRNmX5sGpSQ9p0loJBj\nJk1HbzLv0vRk5bLb/J6x7YexaAu86C9TjqnC4irO+AZZNI/0S70ZHxX+ETZVV0EX\nQU8UmqV68e4YhAQwiLYdAQw125n2hGWoLokQSZTyEiIIoubB00pE5zf0Qaq6Q4s8\nGo5Ukw1A4HjWMisHVKq369pgI8VDZtMzOhS+O0DEQZLwOFETZxECAwEAAaOCCQww\nggkIMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdgCWl2S/VViXrfdDh2g3CEJ3\n6fA61fak8zZuRqQ/D8qpxgAAAZkuEXLdAAAEAwBHMEUCIBLzX4AJgVJdQshSMBLS\nhBMQX8zgRm2U3IXjLk37JM3QAiEAkVrmCFx0+BM3NOoCAXBU1WzVuniPxJP3Ysbd\nOO3dkEAAdwBkEcRspBLsp4kcogIuALyrTygH1B41J6vq/tUDyX3N8AAAAZkuEXKd\nAAAEAwBIMEYCIQCCO1ys+tlI8Fhp4J/Dqk3VVtSi408Nuw8T6YciDL6LPgIhAPjp\nfm/gMkASgNimNuMFH8oiJbqeQ/yo2zQfub894iMuAHcAVmzVo3a+g9/jQrZ1xJwj\nJJinabrDgsurSaOHfZqzLQEAAAGZLhFy2QAABAMASDBGAiEA/93O6XiiYhfeANHh\n0n2nJyVvFAc6sBNT2S7WOR28vR0CIQC7i+leDRRIeY2BYJwaRlAqHlSyU4DZu5IG\ncaxiWFeavzAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMB\nMDwGCSsGAQQBgjcVBwQvMC0GJSsGAQQBgjcVCIe91xuB5+tGgoGdLo7QDIfw2h1d\ngqvnMIft8R8CAWQCAS0wgbQGCCsGAQUFBwEBBIGnMIGkMHMGCCsGAQUFBzAChmdo\ndHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUy\nMEF6dXJlJTIwUlNBJTIwVExTJTIwSXNzdWluZyUyMENBJTIwMDQlMjAtJTIweHNp\nZ24uY3J0MC0GCCsGAQUFBzABhiFodHRwOi8vb25lb2NzcC5taWNyb3NvZnQuY29t\nL29jc3AwHQYDVR0OBBYEFAsWImxddBew8yEv3yGDsmy90FzPMA4GA1UdDwEB/wQE\nAwIFoDCCBREGA1UdEQSCBQgwggUEghMqLnBsYXRmb3JtLmJpbmcuY29tggoqLmJp\nbmcuY29tgghiaW5nLmNvbYIWaWVvbmxpbmUubWljcm9zb2Z0LmNvbYITKi53aW5k\nb3dzc2VhcmNoLmNvbYIZY24uaWVvbmxpbmUubWljcm9zb2Z0LmNvbYIRKi5vcmln\naW4uYmluZy5jb22CDSoubW0uYmluZy5uZXSCDiouYXBpLmJpbmcuY29tgg0qLmNu\nLmJpbmcubmV0gg0qLmNuLmJpbmcuY29tghBzc2wtYXBpLmJpbmcuY29tghBzc2wt\nYXBpLmJpbmcubmV0gg4qLmFwaS5iaW5nLm5ldIIOKi5iaW5nYXBpcy5jb22CD2Jp\nbmdzYW5kYm94LmNvbYIWZmVlZGJhY2subWljcm9zb2Z0LmNvbYIbaW5zZXJ0bWVk\naWEuYmluZy5vZmZpY2UubmV0gg5yLmJhdC5iaW5nLmNvbYIQKi5yLmJhdC5iaW5n\nLmNvbYIPKi5kaWN0LmJpbmcuY29tgg4qLnNzbC5iaW5nLmNvbYIQKi5hcHBleC5i\naW5nLmNvbYIWKi5wbGF0Zm9ybS5jbi5iaW5nLmNvbYINd3AubS5iaW5nLmNvbYIM\nKi5tLmJpbmcuY29tgg9nbG9iYWwuYmluZy5jb22CEXdpbmRvd3NzZWFyY2guY29t\ngg5zZWFyY2gubXNuLmNvbYIRKi5iaW5nc2FuZGJveC5jb22CGSouYXBpLnRpbGVz\nLmRpdHUubGl2ZS5jb22CGCoudDAudGlsZXMuZGl0dS5saXZlLmNvbYIYKi50MS50\naWxlcy5kaXR1LmxpdmUuY29tghgqLnQyLnRpbGVzLmRpdHUubGl2ZS5jb22CGCou\ndDMudGlsZXMuZGl0dS5saXZlLmNvbYILM2QubGl2ZS5jb22CE2FwaS5zZWFyY2gu\nbGl2ZS5jb22CFGJldGEuc2VhcmNoLmxpdmUuY29tghVjbndlYi5zZWFyY2gubGl2\nZS5jb22CDWRpdHUubGl2ZS5jb22CEWZhcmVjYXN0LmxpdmUuY29tgg5pbWFnZS5s\naXZlLmNvbYIPaW1hZ2VzLmxpdmUuY29tghFsb2NhbC5saXZlLmNvbS5hdYIUbG9j\nYWxzZWFyY2gubGl2ZS5jb22CFGxzNGQuc2VhcmNoLmxpdmUuY29tgg1tYWlsLmxp\ndmUuY29tghFtYXBpbmRpYS5saXZlLmNvbYIObG9jYWwubGl2ZS5jb22CDW1hcHMu\nbGl2ZS5jb22CEG1hcHMubGl2ZS5jb20uYXWCD21pbmRpYS5saXZlLmNvbYINbmV3\ncy5saXZlLmNvbYIcb3JpZ2luLmNud2ViLnNlYXJjaC5saXZlLmNvbYIWcHJldmll\ndy5sb2NhbC5saXZlLmNvbYIPc2VhcmNoLmxpdmUuY29tghJ0ZXN0Lm1hcHMubGl2\nZS5jb22CDnZpZGVvLmxpdmUuY29tgg92aWRlb3MubGl2ZS5jb22CFXZpcnR1YWxl\nYXJ0aC5saXZlLmNvbYIMd2FwLmxpdmUuY29tghJ3ZWJtYXN0ZXIubGl2ZS5jb22C\nFXd3dy5sb2NhbC5saXZlLmNvbS5hdYIUd3d3Lm1hcHMubGl2ZS5jb20uYXWCE3dl\nYm1hc3RlcnMubGl2ZS5jb22CGGVjbi5kZXYudmlydHVhbGVhcnRoLm5ldIIMd3d3\nLmJpbmcuY29tMAwGA1UdEwEB/wQCMAAwagYDVR0fBGMwYTBfoF2gW4ZZaHR0cDov\nL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwQXp1cmUl\nMjBSU0ElMjBUTFMlMjBJc3N1aW5nJTIwQ0ElMjAwNC5jcmwwZgYDVR0gBF8wXTBR\nBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3Nv\nZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMAgGBmeBDAECAjAfBgNV\nHSMEGDAWgBQ7cNFT6XYlnWCoymYPxpuub1QWajAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEMBQADggIBAEQCoppNllgoHtfLJt2m7cVL\nAILYFxJdi9qc4LUBfaQEdUwAfsC1pSk5YFB0aGcmVFKMvMMOeENOrWgNJVTLYI05\n8mu6XmbiqUeIu1Rlye/yNirYm33Js2f3VXYp6HSzisF5cWq4QwYqA6XIMfDl61/y\nIXVb5l5eTfproM2grn3RcVVbk5DuEUfyDPzYYNm8elxzac4RrbkDif/b+tVFxmrJ\nCUx1o3VLiVVzbIFCDc5r6pPArm1EdgseJ7pRdXzg6flwA0INRpeLCpjtvkHeZCh7\nGS2JUBhFv7M+lneJljNU/trTkYiho+ZRW9AgLcN73c4+1wHttPHk+w19m5Ge182V\nHzCQdO27IGovKN8jkprGafGxYhyCn4KdSYbRrG7fjkckzpJrjCpF2/bJJ+o4Zi9P\nrJIKHzY5lIMXcD7wwwT2WwlKXoTDrgm4QKN18V+kZaoOILdKyMlEww4jPFUqk6j1\n0Qeod55F5h4tCq2lmwDIa/jyWTGgqTr4UESqj46NB5+JkGYl0O1PPbS1nUm9sN1l\nhkY45iskXVXqLl6AVVcXyxMTefD43M81tFVuJJgpdD/BaMaXAuBdNDfTQcJwhP99\nuI6HqHFD3iEct8fBkYfQiwH2e1eu9OwgujiWHsutyK8VvzVB3/YnhQ/TzciRjPqz\n7ykUutQNUALq8dQwoTnK\n-----END CERTIFICATE-----\n\n`\n\tt.Run(\"singlepublickey\", func(t *testing.T) {\n\t\tblock, _ := pem.Decode([]byte(Single))\n\t\tcert, err := x509.ParseCertificate(block.Bytes)\n\t\tassert.Equal(t, err, nil)\n\t\thash := GenerateCertHash(cert)\n\t\tfingerprint, _ := hex.DecodeString(\"ae243d668ec9c7f74a0dcd1ad21c6676b4efe30c39728934b362093af886bf77\")\n\t\tassert.Equal(t, fingerprint, hash)\n\t})\n}\n\nfunc TestVerifyPeerLeafCert(t *testing.T) {\n\tleafCert, leafHash := cert.MustGenerate(nil, cert.DNSNames(\"example.com\"))\n\tleaf := common.Must2(x509.ParseCertificate(leafCert.Certificate))\n\n\tr := &RandCarrier{\n\t\tConfig: &tls.Config{\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\tPinnedPeerCertSha256: [][]byte{leafHash[:]},\n\t}\n\n\trawCerts := [][]byte{leaf.Raw}\n\terr := r.verifyPeerCert(rawCerts, nil)\n\tif err != nil {\n\t\tt.Fatal(\"expected to verify leaf cert signed by pinned CA, but got error:\", err)\n\t}\n\n\t// make the pinned hash incorrect\n\tr.PinnedPeerCertSha256[0][0] += 1\n\terr = r.verifyPeerCert(rawCerts, nil)\n\tif err == nil {\n\t\tt.Fatal(\"expected to fail verifying leaf cert with incorrect pinned CA hash, but got no error\")\n\t}\n}\n\nfunc TestVerifyPeerCACert(t *testing.T) {\n\tcaCert, caHash := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))\n\tca := common.Must2(x509.ParseCertificate(caCert.Certificate))\n\n\tleafCert, _ := cert.MustGenerate(caCert, cert.DNSNames(\"example.com\"))\n\tleaf := common.Must2(x509.ParseCertificate(leafCert.Certificate))\n\n\tr := &RandCarrier{\n\t\tConfig: &tls.Config{\n\t\t\tServerName: \"example.com\",\n\t\t},\n\t\tPinnedPeerCertSha256: [][]byte{caHash[:]},\n\t}\n\n\trawCerts := [][]byte{leaf.Raw, ca.Raw}\n\terr := r.verifyPeerCert(rawCerts, nil)\n\tif err != nil {\n\t\tt.Fatal(\"expected to verify leaf cert signed by pinned CA, but got error:\", err)\n\t}\n\n\t// make the pinned hash incorrect\n\tr.PinnedPeerCertSha256[0][0] += 1\n\terr = r.verifyPeerCert(rawCerts, nil)\n\tif err == nil {\n\t\tt.Fatal(\"expected to fail verifying leaf cert with incorrect pinned CA hash, but got no error\")\n\t}\n}\n"
  },
  {
    "path": "transport/internet/tls/tls.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"math/big\"\n\t\"time\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/utils\"\n)\n\ntype Interface interface {\n\tnet.Conn\n\tHandshakeContext(ctx context.Context) error\n\tVerifyHostname(host string) error\n\tHandshakeContextServerName(ctx context.Context) string\n\tNegotiatedProtocol() string\n}\n\nvar _ buf.Writer = (*Conn)(nil)\nvar _ Interface = (*Conn)(nil)\n\ntype Conn struct {\n\t*tls.Conn\n}\n\nconst tlsCloseTimeout = 250 * time.Millisecond\n\nfunc (c *Conn) Close() error {\n\ttimer := time.AfterFunc(tlsCloseTimeout, func() {\n\t\tc.Conn.NetConn().Close()\n\t})\n\tdefer timer.Stop()\n\treturn c.Conn.Close()\n}\n\nfunc (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tmb = buf.Compact(mb)\n\tmb, err := buf.WriteMultiBuffer(c, mb)\n\tbuf.ReleaseMulti(mb)\n\treturn err\n}\n\nfunc (c *Conn) HandshakeContextServerName(ctx context.Context) string {\n\tif err := c.HandshakeContext(ctx); err != nil {\n\t\treturn \"\"\n\t}\n\treturn c.ConnectionState().ServerName\n}\n\nfunc (c *Conn) NegotiatedProtocol() string {\n\tstate := c.ConnectionState()\n\treturn state.NegotiatedProtocol\n}\n\n// Client initiates a TLS client handshake on the given connection.\nfunc Client(c net.Conn, config *tls.Config) net.Conn {\n\ttlsConn := tls.Client(c, config)\n\treturn &Conn{Conn: tlsConn}\n}\n\n// Server initiates a TLS server handshake on the given connection.\nfunc Server(c net.Conn, config *tls.Config) net.Conn {\n\ttlsConn := tls.Server(c, config)\n\treturn &Conn{Conn: tlsConn}\n}\n\ntype UConn struct {\n\t*utls.UConn\n}\n\nvar _ Interface = (*UConn)(nil)\n\nfunc (c *UConn) Close() error {\n\ttimer := time.AfterFunc(tlsCloseTimeout, func() {\n\t\tc.Conn.NetConn().Close()\n\t})\n\tdefer timer.Stop()\n\treturn c.Conn.Close()\n}\n\nfunc (c *UConn) HandshakeContextServerName(ctx context.Context) string {\n\tif err := c.HandshakeContext(ctx); err != nil {\n\t\treturn \"\"\n\t}\n\treturn c.ConnectionState().ServerName\n}\n\n// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send\n// http/1.1 in its ALPN.\nfunc (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {\n\t// Build the handshake state. This will apply every variable of the TLS of the\n\t// fingerprint in the UConn\n\tif err := c.BuildHandshakeState(); err != nil {\n\t\treturn err\n\t}\n\tconfig := *utils.AccessField[*utls.Config](c, \"config\")\n\t// Do not modify outer ALPN to http/1.1 if ECH is used\n\t// Outer ALPN will be h2,http/1.1, and real ALPN in config will be hidden in ECH\n\tif config.EncryptedClientHelloConfigList != nil {\n\t\treturn c.HandshakeContext(ctx)\n\t}\n\t// Iterate over extensions and check for utls.ALPNExtension\n\thasALPNExtension := false\n\tfor _, extension := range c.Extensions {\n\t\tif alpn, ok := extension.(*utls.ALPNExtension); ok {\n\t\t\thasALPNExtension = true\n\t\t\talpn.AlpnProtocols = []string{\"http/1.1\"}\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasALPNExtension { // Append extension if doesn't exists\n\t\tc.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{\"http/1.1\"}})\n\t}\n\t// Rebuild the client hello and do the handshake\n\tif err := c.BuildHandshakeState(); err != nil {\n\t\treturn err\n\t}\n\treturn c.HandshakeContext(ctx)\n}\n\nfunc (c *UConn) NegotiatedProtocol() string {\n\tstate := c.ConnectionState()\n\treturn state.NegotiatedProtocol\n}\n\nfunc UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {\n\tutlsConn := utls.UClient(c, copyConfig(config), *fingerprint)\n\treturn &UConn{UConn: utlsConn}\n}\n\nfunc GeneraticUClient(c net.Conn, config *tls.Config) *utls.UConn {\n\treturn utls.UClient(c, copyConfig(config), utls.HelloChrome_Auto)\n}\n\nfunc copyConfig(c *tls.Config) *utls.Config {\n\tconfig := &utls.Config{\n\t\tRand:                           c.Rand,\n\t\tRootCAs:                        c.RootCAs,\n\t\tServerName:                     c.ServerName,\n\t\tInsecureSkipVerify:             c.InsecureSkipVerify,\n\t\tVerifyPeerCertificate:          c.VerifyPeerCertificate,\n\t\tKeyLogWriter:                   c.KeyLogWriter,\n\t\tEncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,\n\t}\n\tif config.EncryptedClientHelloConfigList != nil {\n\t\tconfig.NextProtos = c.NextProtos\n\t}\n\treturn config\n}\n\nfunc init() {\n\tbigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ModernFingerprints))))\n\tstopAt := int(bigInt.Int64())\n\ti := 0\n\tfor _, v := range ModernFingerprints {\n\t\tif i == stopAt {\n\t\t\tPresetFingerprints[\"random\"] = v\n\t\t\tbreak\n\t\t}\n\t\ti++\n\t}\n\tweights := utls.DefaultWeights\n\tweights.TLSVersMax_Set_VersionTLS13 = 1\n\tweights.FirstKeyShare_Set_CurveP256 = 0\n\trandomized := utls.HelloRandomizedALPN\n\trandomized.Seed, _ = utls.NewPRNGSeed()\n\trandomized.Weights = &weights\n\trandomizednoalpn := utls.HelloRandomizedNoALPN\n\trandomizednoalpn.Seed, _ = utls.NewPRNGSeed()\n\trandomizednoalpn.Weights = &weights\n\tPresetFingerprints[\"randomized\"] = &randomized\n\tPresetFingerprints[\"randomizednoalpn\"] = &randomizednoalpn\n}\n\nfunc GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {\n\tif name == \"\" {\n\t\treturn &utls.HelloChrome_Auto\n\t}\n\tif fingerprint = PresetFingerprints[name]; fingerprint != nil {\n\t\treturn\n\t}\n\tif fingerprint = ModernFingerprints[name]; fingerprint != nil {\n\t\treturn\n\t}\n\tif fingerprint = OtherFingerprints[name]; fingerprint != nil {\n\t\treturn\n\t}\n\treturn\n}\n\nvar PresetFingerprints = map[string]*utls.ClientHelloID{\n\t// Recommended preset options in GUI clients\n\t\"chrome\":           &utls.HelloChrome_Auto,\n\t\"firefox\":          &utls.HelloFirefox_Auto,\n\t\"safari\":           &utls.HelloSafari_Auto,\n\t\"ios\":              &utls.HelloIOS_Auto,\n\t\"android\":          &utls.HelloAndroid_11_OkHttp,\n\t\"edge\":             &utls.HelloEdge_Auto,\n\t\"360\":              &utls.Hello360_Auto,\n\t\"qq\":               &utls.HelloQQ_Auto,\n\t\"random\":           nil,\n\t\"randomized\":       nil,\n\t\"randomizednoalpn\": nil,\n\t\"unsafe\":           nil,\n}\n\nvar ModernFingerprints = map[string]*utls.ClientHelloID{\n\t// One of these will be chosen as `random` at startup\n\t\"hellofirefox_99\":         &utls.HelloFirefox_99,\n\t\"hellofirefox_102\":        &utls.HelloFirefox_102,\n\t\"hellofirefox_105\":        &utls.HelloFirefox_105,\n\t\"hellofirefox_120\":        &utls.HelloFirefox_120,\n\t\"hellochrome_83\":          &utls.HelloChrome_83,\n\t\"hellochrome_87\":          &utls.HelloChrome_87,\n\t\"hellochrome_96\":          &utls.HelloChrome_96,\n\t\"hellochrome_100\":         &utls.HelloChrome_100,\n\t\"hellochrome_102\":         &utls.HelloChrome_102,\n\t\"hellochrome_106_shuffle\": &utls.HelloChrome_106_Shuffle,\n\t\"hellochrome_120\":         &utls.HelloChrome_120,\n\t\"hellochrome_131\":         &utls.HelloChrome_131,\n\t\"helloios_13\":             &utls.HelloIOS_13,\n\t\"helloios_14\":             &utls.HelloIOS_14,\n\t\"helloedge_85\":            &utls.HelloEdge_85,\n\t\"helloedge_106\":           &utls.HelloEdge_106,\n\t\"hellosafari_16_0\":        &utls.HelloSafari_16_0,\n\t\"hello360_11_0\":           &utls.Hello360_11_0,\n\t\"helloqq_11_1\":            &utls.HelloQQ_11_1,\n}\n\nvar OtherFingerprints = map[string]*utls.ClientHelloID{\n\t// Golang, randomized, auto, and fingerprints that are too old\n\t\"hellogolang\":            &utls.HelloGolang,\n\t\"hellorandomized\":        &utls.HelloRandomized,\n\t\"hellorandomizedalpn\":    &utls.HelloRandomizedALPN,\n\t\"hellorandomizednoalpn\":  &utls.HelloRandomizedNoALPN,\n\t\"hellofirefox_auto\":      &utls.HelloFirefox_Auto,\n\t\"hellofirefox_55\":        &utls.HelloFirefox_55,\n\t\"hellofirefox_56\":        &utls.HelloFirefox_56,\n\t\"hellofirefox_63\":        &utls.HelloFirefox_63,\n\t\"hellofirefox_65\":        &utls.HelloFirefox_65,\n\t\"hellochrome_auto\":       &utls.HelloChrome_Auto,\n\t\"hellochrome_58\":         &utls.HelloChrome_58,\n\t\"hellochrome_62\":         &utls.HelloChrome_62,\n\t\"hellochrome_70\":         &utls.HelloChrome_70,\n\t\"hellochrome_72\":         &utls.HelloChrome_72,\n\t\"helloios_auto\":          &utls.HelloIOS_Auto,\n\t\"helloios_11_1\":          &utls.HelloIOS_11_1,\n\t\"helloios_12_1\":          &utls.HelloIOS_12_1,\n\t\"helloandroid_11_okhttp\": &utls.HelloAndroid_11_OkHttp,\n\t\"helloedge_auto\":         &utls.HelloEdge_Auto,\n\t\"hellosafari_auto\":       &utls.HelloSafari_Auto,\n\t\"hello360_auto\":          &utls.Hello360_Auto,\n\t\"hello360_7_5\":           &utls.Hello360_7_5,\n\t\"helloqq_auto\":           &utls.HelloQQ_Auto,\n\n\t// Chrome betas'\n\t\"hellochrome_100_psk\":              &utls.HelloChrome_100_PSK,\n\t\"hellochrome_112_psk_shuf\":         &utls.HelloChrome_112_PSK_Shuf,\n\t\"hellochrome_114_padding_psk_shuf\": &utls.HelloChrome_114_Padding_PSK_Shuf,\n\t\"hellochrome_115_pq\":               &utls.HelloChrome_115_PQ,\n\t\"hellochrome_115_pq_psk\":           &utls.HelloChrome_115_PQ_PSK,\n\t\"hellochrome_120_pq\":               &utls.HelloChrome_120_PQ,\n}\n"
  },
  {
    "path": "transport/internet/tls/unsafe.go",
    "content": "package tls\n\nimport _ \"unsafe\"\n\n//go:linkname errNoCertificates crypto/tls.errNoCertificates\nvar errNoCertificates error\n"
  },
  {
    "path": "transport/internet/udp/config.go",
    "content": "package udp\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/udp/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/udp/config.proto\n\npackage udp\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_udp_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_udp_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_udp_config_proto_rawDescGZIP(), []int{0}\n}\n\nvar File_transport_internet_udp_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_udp_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"#transport/internet/udp/config.proto\\x12\\x1bxray.transport.internet.udp\\\"\\b\\n\" +\n\t\"\\x06ConfigBs\\n\" +\n\t\"\\x1fcom.xray.transport.internet.udpP\\x01Z0github.com/xtls/xray-core/transport/internet/udp\\xaa\\x02\\x1bXray.Transport.Internet.Udpb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_udp_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_udp_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_udp_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_udp_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_udp_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_udp_config_proto_rawDesc), len(file_transport_internet_udp_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_udp_config_proto_rawDescData\n}\n\nvar file_transport_internet_udp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_transport_internet_udp_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.udp.Config\n}\nvar file_transport_internet_udp_config_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_udp_config_proto_init() }\nfunc file_transport_internet_udp_config_proto_init() {\n\tif File_transport_internet_udp_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_udp_config_proto_rawDesc), len(file_transport_internet_udp_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_udp_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_udp_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_udp_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_udp_config_proto = out.File\n\tfile_transport_internet_udp_config_proto_goTypes = nil\n\tfile_transport_internet_udp_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/udp/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.udp;\noption csharp_namespace = \"Xray.Transport.Internet.Udp\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/udp\";\noption java_package = \"com.xray.transport.internet.udp\";\noption java_multiple_files = true;\n\nmessage Config {}\n"
  },
  {
    "path": "transport/internet/udp/dialer.go",
    "content": "package udp\n\nimport (\n\t\"context\"\n\treflect \"reflect\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/net/cnc\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n)\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName,\n\t\tfunc(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\t\t\tvar sockopt *internet.SocketConfig\n\t\t\tif streamSettings != nil {\n\t\t\t\tsockopt = streamSettings.SocketSettings\n\t\t\t}\n\t\t\tconn, err := internet.DialSystem(ctx, dest, sockopt)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif streamSettings != nil && streamSettings.UdpmaskManager != nil {\n\t\t\t\tswitch c := conn.(type) {\n\t\t\t\tcase *internet.PacketConnWrapper:\n\t\t\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c.PacketConn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tc.PacketConn = pktConn\n\t\t\t\tcase *net.UDPConn:\n\t\t\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tconn = &internet.PacketConnWrapper{\n\t\t\t\t\t\tPacketConn: pktConn,\n\t\t\t\t\t\tDest:       c.RemoteAddr().(*net.UDPAddr),\n\t\t\t\t\t}\n\t\t\t\tcase *cnc.Connection:\n\t\t\t\t\tfakeConn := &internet.FakePacketConn{Conn: c}\n\t\t\t\t\tpktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tconn = &internet.PacketConnWrapper{\n\t\t\t\t\t\tPacketConn: pktConn,\n\t\t\t\t\t\tDest: &net.UDPAddr{\n\t\t\t\t\t\t\tIP:   []byte{0, 0, 0, 0},\n\t\t\t\t\t\t\tPort: 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tconn.Close()\n\t\t\t\t\treturn nil, errors.New(\"unknown conn \", reflect.TypeOf(c))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// TODO: handle dialer options\n\t\t\treturn conn, nil\n\t\t}))\n}\n"
  },
  {
    "path": "transport/internet/udp/dispatcher.go",
    "content": "package udp\n\nimport (\n\t\"context\"\n\tgoerrors \"errors\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n)\n\ntype ResponseCallback func(ctx context.Context, packet *udp.Packet)\n\ntype connEntry struct {\n\tlink   *transport.Link\n\ttimer  *signal.ActivityTimer\n\tcancel context.CancelFunc\n\tclosed bool\n}\n\nfunc (c *connEntry) Close() error {\n\tc.timer.SetTimeout(0)\n\treturn nil\n}\n\nfunc (c *connEntry) terminate() {\n\tif c.closed {\n\t\tpanic(\"terminate called more than once\")\n\t}\n\tc.closed = true\n\tc.cancel()\n\tcommon.Interrupt(c.link.Reader)\n\tcommon.Interrupt(c.link.Writer)\n}\n\ntype Dispatcher struct {\n\tsync.RWMutex\n\tconn       *connEntry\n\tdispatcher routing.Dispatcher\n\tcallback   ResponseCallback\n\tcallClose  func() error\n\tclosed     bool\n}\n\nfunc NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Dispatcher {\n\treturn &Dispatcher{\n\t\tdispatcher: dispatcher,\n\t\tcallback:   callback,\n\t}\n}\n\nfunc (v *Dispatcher) RemoveRay() {\n\tv.Lock()\n\tdefer v.Unlock()\n\tv.closed = true\n\tif v.conn != nil {\n\t\tv.conn.Close()\n\t\tv.conn = nil\n\t}\n}\n\nfunc (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*connEntry, error) {\n\tv.Lock()\n\tdefer v.Unlock()\n\n\tif v.closed {\n\t\treturn nil, errors.New(\"dispatcher is closed\")\n\t}\n\n\tif v.conn != nil {\n\t\tif v.conn.closed {\n\t\t\tv.conn = nil\n\t\t} else {\n\t\t\treturn v.conn, nil\n\t\t}\n\t}\n\n\terrors.LogInfo(ctx, \"establishing new connection for \", dest)\n\n\tctx, cancel := context.WithCancel(ctx)\n\n\tlink, err := v.dispatcher.Dispatch(ctx, dest)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, errors.New(\"failed to dispatch request to \", dest).Base(err)\n\t}\n\n\tentry := &connEntry{\n\t\tlink:   link,\n\t\tcancel: cancel,\n\t}\n\n\tentry.timer = signal.CancelAfterInactivity(ctx, entry.terminate, time.Minute)\n\tv.conn = entry\n\tgo handleInput(ctx, entry, dest, v.callback, v.callClose)\n\treturn entry, nil\n}\n\nfunc (v *Dispatcher) Dispatch(ctx context.Context, destination net.Destination, payload *buf.Buffer) {\n\t// TODO: Add user to destString\n\terrors.LogDebug(ctx, \"dispatch request to: \", destination)\n\n\tconn, err := v.getInboundRay(ctx, destination)\n\tif err != nil {\n\t\terrors.LogInfoInner(ctx, err, \"failed to get inbound\")\n\t\treturn\n\t}\n\toutputStream := conn.link.Writer\n\tif outputStream != nil {\n\t\tif err := outputStream.WriteMultiBuffer(buf.MultiBuffer{payload}); err != nil {\n\t\t\terrors.LogInfoInner(ctx, err, \"failed to write first UDP payload\")\n\t\t\tconn.Close()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc handleInput(ctx context.Context, conn *connEntry, dest net.Destination, callback ResponseCallback, callClose func() error) {\n\tdefer func() {\n\t\tconn.Close()\n\t\tif callClose != nil {\n\t\t\tcallClose()\n\t\t}\n\t}()\n\n\tinput := conn.link.Reader\n\ttimer := conn.timer\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\tmb, err := input.ReadMultiBuffer()\n\t\tif err != nil {\n\t\t\tif !goerrors.Is(err, io.EOF) {\n\t\t\t\terrors.LogInfoInner(ctx, err, \"failed to handle UDP input\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\ttimer.Update()\n\t\tfor _, b := range mb {\n\t\t\tif b.UDP != nil {\n\t\t\t\tdest = *b.UDP\n\t\t\t}\n\t\t\tcallback(ctx, &udp.Packet{\n\t\t\t\tPayload: b,\n\t\t\t\tSource:  dest,\n\t\t\t})\n\t\t}\n\t}\n}\n\ntype dispatcherConn struct {\n\tdispatcher *Dispatcher\n\tcache      chan *udp.Packet\n\tdone       *done.Instance\n\tctx        context.Context\n}\n\nfunc DialDispatcher(ctx context.Context, dispatcher routing.Dispatcher) (net.PacketConn, error) {\n\tc := &dispatcherConn{\n\t\tcache: make(chan *udp.Packet, 16),\n\t\tdone:  done.New(),\n\t\tctx:   ctx,\n\t}\n\n\td := &Dispatcher{\n\t\tdispatcher: dispatcher,\n\t\tcallback:   c.callback,\n\t\tcallClose:  c.Close,\n\t}\n\tc.dispatcher = d\n\treturn c, nil\n}\n\nfunc (c *dispatcherConn) callback(ctx context.Context, packet *udp.Packet) {\n\tselect {\n\tcase <-c.done.Wait():\n\t\tpacket.Payload.Release()\n\t\treturn\n\tcase c.cache <- packet:\n\tdefault:\n\t\tpacket.Payload.Release()\n\t\treturn\n\t}\n}\n\nfunc (c *dispatcherConn) ReadFrom(p []byte) (int, net.Addr, error) {\n\tvar packet *udp.Packet\ns:\n\tselect {\n\tcase <-c.done.Wait():\n\t\tselect {\n\t\tcase packet = <-c.cache:\n\t\t\tbreak s\n\t\tdefault:\n\t\t\treturn 0, nil, io.EOF\n\t\t}\n\tcase packet = <-c.cache:\n\t}\n\treturn copy(p, packet.Payload.Bytes()), &net.UDPAddr{\n\t\tIP:   packet.Source.Address.IP(),\n\t\tPort: int(packet.Source.Port),\n\t}, nil\n}\n\nfunc (c *dispatcherConn) WriteTo(p []byte, addr net.Addr) (int, error) {\n\tbuffer := buf.New()\n\traw := buffer.Extend(buf.Size)\n\tn := copy(raw, p)\n\tbuffer.Resize(0, int32(n))\n\n\tdestination := net.DestinationFromAddr(addr)\n\tbuffer.UDP = &destination\n\tc.dispatcher.Dispatch(c.ctx, destination, buffer)\n\treturn n, nil\n}\n\nfunc (c *dispatcherConn) Close() error {\n\treturn c.done.Close()\n}\n\nfunc (c *dispatcherConn) LocalAddr() net.Addr {\n\treturn &net.UDPAddr{\n\t\tIP:   []byte{0, 0, 0, 0},\n\t\tPort: 0,\n\t}\n}\n\nfunc (c *dispatcherConn) SetDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (c *dispatcherConn) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (c *dispatcherConn) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n"
  },
  {
    "path": "transport/internet/udp/dispatcher_test.go",
    "content": "package udp_test\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/features/routing\"\n\t\"github.com/xtls/xray-core/transport\"\n\t. \"github.com/xtls/xray-core/transport/internet/udp\"\n\t\"github.com/xtls/xray-core/transport/pipe\"\n)\n\ntype TestDispatcher struct {\n\tOnDispatch func(ctx context.Context, dest net.Destination) (*transport.Link, error)\n}\n\nfunc (d *TestDispatcher) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {\n\treturn d.OnDispatch(ctx, dest)\n}\n\nfunc (d *TestDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {\n\treturn nil\n}\n\nfunc (d *TestDispatcher) Start() error {\n\treturn nil\n}\n\nfunc (d *TestDispatcher) Close() error {\n\treturn nil\n}\n\nfunc (*TestDispatcher) Type() interface{} {\n\treturn routing.DispatcherType()\n}\n\nfunc TestSameDestinationDispatching(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tuplinkReader, uplinkWriter := pipe.New(pipe.WithSizeLimit(1024))\n\tdownlinkReader, downlinkWriter := pipe.New(pipe.WithSizeLimit(1024))\n\n\tgo func() {\n\t\tfor {\n\t\t\tdata, err := uplinkReader.ReadMultiBuffer()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\terr = downlinkWriter.WriteMultiBuffer(data)\n\t\t\tcommon.Must(err)\n\t\t}\n\t}()\n\n\tvar count uint32\n\ttd := &TestDispatcher{\n\t\tOnDispatch: func(ctx context.Context, dest net.Destination) (*transport.Link, error) {\n\t\t\tatomic.AddUint32(&count, 1)\n\t\t\treturn &transport.Link{Reader: downlinkReader, Writer: uplinkWriter}, nil\n\t\t},\n\t}\n\tdest := net.UDPDestination(net.LocalHostIP, 53)\n\n\tb := buf.New()\n\tb.WriteString(\"abcd\")\n\n\tvar msgCount uint32\n\tdispatcher := NewDispatcher(td, func(ctx context.Context, packet *udp.Packet) {\n\t\tatomic.AddUint32(&msgCount, 1)\n\t})\n\n\tdispatcher.Dispatch(ctx, dest, b)\n\tfor i := 0; i < 5; i++ {\n\t\tdispatcher.Dispatch(ctx, dest, b)\n\t}\n\n\ttime.Sleep(time.Second)\n\tcancel()\n\n\tif count != 1 {\n\t\tt.Error(\"count: \", count)\n\t}\n\tif v := atomic.LoadUint32(&msgCount); v != 6 {\n\t\tt.Error(\"msgCount: \", v)\n\t}\n}\n"
  },
  {
    "path": "transport/internet/udp/hub.go",
    "content": "package udp\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/udp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\ntype HubOption func(h *Hub)\n\nfunc HubCapacity(capacity int) HubOption {\n\treturn func(h *Hub) {\n\t\th.capacity = capacity\n\t}\n}\n\nfunc HubReceiveOriginalDestination(r bool) HubOption {\n\treturn func(h *Hub) {\n\t\th.recvOrigDest = r\n\t}\n}\n\ntype Hub struct {\n\tconn         net.PacketConn\n\tudpConn      *net.UDPConn\n\tcache        chan *udp.Packet\n\tcapacity     int\n\trecvOrigDest bool\n}\n\nfunc ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, options ...HubOption) (*Hub, error) {\n\thub := &Hub{\n\t\tcapacity:     256,\n\t\trecvOrigDest: false,\n\t}\n\tfor _, opt := range options {\n\t\topt(hub)\n\t}\n\n\tif address.Family().IsDomain() && address.Domain() == \"localhost\" {\n\t\taddress = net.LocalHostIP\n\t}\n\n\tif address.Family().IsDomain() {\n\t\treturn nil, errors.New(\"domain address is not allowed for listening: \", address.Domain())\n\t}\n\n\tvar sockopt *internet.SocketConfig\n\tif streamSettings != nil {\n\t\tsockopt = streamSettings.SocketSettings\n\t}\n\tif sockopt != nil && sockopt.ReceiveOriginalDestAddress {\n\t\thub.recvOrigDest = true\n\t}\n\n\tvar err error\n\thub.conn, err = internet.ListenSystemPacket(ctx, &net.UDPAddr{\n\t\tIP:   address.IP(),\n\t\tPort: int(port),\n\t}, sockopt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\traw := hub.conn\n\n\tif streamSettings.UdpmaskManager != nil {\n\t\thub.conn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)\n\t\tif err != nil {\n\t\t\traw.Close()\n\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t}\n\t}\n\n\terrors.LogInfo(ctx, \"listening UDP on \", address, \":\", port)\n\thub.udpConn, _ = hub.conn.(*net.UDPConn)\n\thub.cache = make(chan *udp.Packet, hub.capacity)\n\n\tgo hub.start()\n\treturn hub, nil\n}\n\n// Close implements net.Listener.\nfunc (h *Hub) Close() error {\n\th.conn.Close()\n\treturn nil\n}\n\nfunc (h *Hub) WriteTo(payload []byte, dest net.Destination) (int, error) {\n\treturn h.conn.WriteTo(payload, &net.UDPAddr{\n\t\tIP:   dest.Address.IP(),\n\t\tPort: int(dest.Port),\n\t})\n}\n\nfunc (h *Hub) start() {\n\tc := h.cache\n\tdefer close(c)\n\n\toobBytes := make([]byte, 256)\n\n\tfor {\n\t\tbuffer := buf.New()\n\t\tvar noob int\n\t\tvar udpAddr *net.UDPAddr\n\t\trawBytes := buffer.Extend(buf.Size)\n\n\t\tvar n int\n\t\tvar err error\n\t\tif h.udpConn != nil {\n\t\t\tn, noob, _, udpAddr, err = ReadUDPMsg(h.udpConn, rawBytes, oobBytes)\n\t\t} else {\n\t\t\tvar addr net.Addr\n\t\t\tn, addr, err = h.conn.ReadFrom(rawBytes)\n\t\t\tif err == nil {\n\t\t\t\tudpAddr = addr.(*net.UDPAddr)\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\terrors.LogInfoInner(context.Background(), err, \"failed to read UDP msg\")\n\t\t\tbuffer.Release()\n\t\t\tbreak\n\t\t}\n\t\tbuffer.Resize(0, int32(n))\n\n\t\tif buffer.IsEmpty() {\n\t\t\tbuffer.Release()\n\t\t\tcontinue\n\t\t}\n\n\t\tpayload := &udp.Packet{\n\t\t\tPayload: buffer,\n\t\t\tSource:  net.UDPDestination(net.IPAddress(udpAddr.IP), net.Port(udpAddr.Port)),\n\t\t}\n\t\tif h.recvOrigDest && noob > 0 {\n\t\t\tpayload.Target = RetrieveOriginalDest(oobBytes[:noob])\n\t\t\tif payload.Target.IsValid() {\n\t\t\t\terrors.LogDebug(context.Background(), \"UDP original destination: \", payload.Target)\n\t\t\t} else {\n\t\t\t\terrors.LogInfo(context.Background(), \"failed to read UDP original destination\")\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase c <- payload:\n\t\tdefault:\n\t\t\tbuffer.Release()\n\t\t\tpayload.Payload = nil\n\t\t}\n\t}\n}\n\n// Addr implements net.Listener.\nfunc (h *Hub) Addr() net.Addr {\n\treturn h.conn.LocalAddr()\n}\n\nfunc (h *Hub) Receive() <-chan *udp.Packet {\n\treturn h.cache\n}\n"
  },
  {
    "path": "transport/internet/udp/hub_darwin.go",
    "content": "//go:build darwin\n// +build darwin\n\npackage udp\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// RetrieveOriginalDest from stored laddr, caddr\nfunc RetrieveOriginalDest(oob []byte) net.Destination {\n\tdec := gob.NewDecoder(bytes.NewBuffer(oob))\n\tvar la, ra net.UDPAddr\n\tdec.Decode(&la)\n\tdec.Decode(&ra)\n\tip, port, err := internet.OriginalDst(&la, &ra)\n\tif err != nil {\n\t\treturn net.Destination{}\n\t}\n\treturn net.UDPDestination(net.IPAddress(ip), net.Port(port))\n}\n\n// ReadUDPMsg stores laddr, caddr for later use\nfunc ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) {\n\tnBytes, addr, err := conn.ReadFromUDP(payload)\n\tvar buf bytes.Buffer\n\tenc := gob.NewEncoder(&buf)\n\tudpAddr, ok := conn.LocalAddr().(*net.UDPAddr)\n\tif !ok {\n\t\treturn 0, 0, 0, nil, errors.New(\"invalid local address\")\n\t}\n\tif addr == nil {\n\t\treturn 0, 0, 0, nil, errors.New(\"invalid remote address\")\n\t}\n\tenc.Encode(udpAddr)\n\tenc.Encode(addr)\n\tvar reader io.Reader = &buf\n\tnoob, _ := reader.Read(oob)\n\treturn nBytes, noob, 0, addr, err\n}\n"
  },
  {
    "path": "transport/internet/udp/hub_freebsd.go",
    "content": "//go:build freebsd\n// +build freebsd\n\npackage udp\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"io\"\n\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\n// RetrieveOriginalDest from stored laddr, caddr\nfunc RetrieveOriginalDest(oob []byte) net.Destination {\n\tdec := gob.NewDecoder(bytes.NewBuffer(oob))\n\tvar la, ra net.UDPAddr\n\tdec.Decode(&la)\n\tdec.Decode(&ra)\n\tip, port, err := internet.OriginalDst(&la, &ra)\n\tif err != nil {\n\t\treturn net.Destination{}\n\t}\n\treturn net.UDPDestination(net.IPAddress(ip), net.Port(port))\n}\n\n// ReadUDPMsg stores laddr, caddr for later use\nfunc ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) {\n\tnBytes, addr, err := conn.ReadFromUDP(payload)\n\tvar buf bytes.Buffer\n\tenc := gob.NewEncoder(&buf)\n\tudpAddr, ok := conn.LocalAddr().(*net.UDPAddr)\n\tif !ok {\n\t\treturn 0, 0, 0, nil, errors.New(\"invalid local address\")\n\t}\n\tif addr == nil {\n\t\treturn 0, 0, 0, nil, errors.New(\"invalid remote address\")\n\t}\n\tenc.Encode(udpAddr)\n\tenc.Encode(addr)\n\tvar reader io.Reader = &buf\n\tnoob, _ := reader.Read(oob)\n\treturn nBytes, noob, 0, addr, err\n}\n"
  },
  {
    "path": "transport/internet/udp/hub_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage udp\n\nimport (\n\t\"syscall\"\n\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc RetrieveOriginalDest(oob []byte) net.Destination {\n\tmsgs, err := syscall.ParseSocketControlMessage(oob)\n\tif err != nil {\n\t\treturn net.Destination{}\n\t}\n\tfor _, msg := range msgs {\n\t\tif msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {\n\t\t\tip := net.IPAddress(msg.Data[4:8])\n\t\t\tport := net.PortFromBytes(msg.Data[2:4])\n\t\t\treturn net.UDPDestination(ip, port)\n\t\t} else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == unix.IPV6_RECVORIGDSTADDR {\n\t\t\tip := net.IPAddress(msg.Data[8:24])\n\t\t\tport := net.PortFromBytes(msg.Data[2:4])\n\t\t\treturn net.UDPDestination(ip, port)\n\t\t}\n\t}\n\treturn net.Destination{}\n}\n\nfunc ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) {\n\treturn conn.ReadMsgUDP(payload, oob)\n}\n"
  },
  {
    "path": "transport/internet/udp/hub_other.go",
    "content": "//go:build !linux && !freebsd && !darwin\n// +build !linux,!freebsd,!darwin\n\npackage udp\n\nimport (\n\t\"github.com/xtls/xray-core/common/net\"\n)\n\nfunc RetrieveOriginalDest(oob []byte) net.Destination {\n\treturn net.Destination{}\n}\n\nfunc ReadUDPMsg(conn *net.UDPConn, payload []byte, oob []byte) (int, int, int, *net.UDPAddr, error) {\n\tnBytes, addr, err := conn.ReadFromUDP(payload)\n\treturn nBytes, 0, 0, addr, err\n}\n"
  },
  {
    "path": "transport/internet/udp/udp.go",
    "content": "package udp\n\nconst protocolName = \"udp\"\n"
  },
  {
    "path": "transport/internet/websocket/config.go",
    "content": "package websocket\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/utils\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n)\n\nfunc (c *Config) GetNormalizedPath() string {\n\tpath := c.Path\n\tif path == \"\" {\n\t\treturn \"/\"\n\t}\n\tif path[0] != '/' {\n\t\treturn \"/\" + path\n\t}\n\treturn path\n}\n\nfunc (c *Config) GetRequestHeader() http.Header {\n\theader := http.Header{}\n\tfor k, v := range c.Header {\n\t\theader.Add(k, v)\n\t}\n\tif header.Get(\"User-Agent\") == \"\" {\n\t\theader.Set(\"User-Agent\", utils.ChromeUA)\n\t}\n\treturn header\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {\n\t\treturn new(Config)\n\t}))\n}\n"
  },
  {
    "path": "transport/internet/websocket/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.33.5\n// source: transport/internet/websocket/config.proto\n\npackage websocket\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate               protoimpl.MessageState `protogen:\"open.v1\"`\n\tHost                string                 `protobuf:\"bytes,1,opt,name=host,proto3\" json:\"host,omitempty\"`\n\tPath                string                 `protobuf:\"bytes,2,opt,name=path,proto3\" json:\"path,omitempty\"` // URL path to the WebSocket service. Empty value means root(/).\n\tHeader              map[string]string      `protobuf:\"bytes,3,rep,name=header,proto3\" json:\"header,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tAcceptProxyProtocol bool                   `protobuf:\"varint,4,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3\" json:\"accept_proxy_protocol,omitempty\"`\n\tEd                  uint32                 `protobuf:\"varint,5,opt,name=ed,proto3\" json:\"ed,omitempty\"`\n\tHeartbeatPeriod     uint32                 `protobuf:\"varint,6,opt,name=heartbeatPeriod,proto3\" json:\"heartbeatPeriod,omitempty\"`\n\tunknownFields       protoimpl.UnknownFields\n\tsizeCache           protoimpl.SizeCache\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tmi := &file_transport_internet_websocket_config_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_transport_internet_websocket_config_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_transport_internet_websocket_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetHost() string {\n\tif x != nil {\n\t\treturn x.Host\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *Config) GetHeader() map[string]string {\n\tif x != nil {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetAcceptProxyProtocol() bool {\n\tif x != nil {\n\t\treturn x.AcceptProxyProtocol\n\t}\n\treturn false\n}\n\nfunc (x *Config) GetEd() uint32 {\n\tif x != nil {\n\t\treturn x.Ed\n\t}\n\treturn 0\n}\n\nfunc (x *Config) GetHeartbeatPeriod() uint32 {\n\tif x != nil {\n\t\treturn x.HeartbeatPeriod\n\t}\n\treturn 0\n}\n\nvar File_transport_internet_websocket_config_proto protoreflect.FileDescriptor\n\nconst file_transport_internet_websocket_config_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\")transport/internet/websocket/config.proto\\x12!xray.transport.internet.websocket\\\"\\xa8\\x02\\n\" +\n\t\"\\x06Config\\x12\\x12\\n\" +\n\t\"\\x04host\\x18\\x01 \\x01(\\tR\\x04host\\x12\\x12\\n\" +\n\t\"\\x04path\\x18\\x02 \\x01(\\tR\\x04path\\x12M\\n\" +\n\t\"\\x06header\\x18\\x03 \\x03(\\v25.xray.transport.internet.websocket.Config.HeaderEntryR\\x06header\\x122\\n\" +\n\t\"\\x15accept_proxy_protocol\\x18\\x04 \\x01(\\bR\\x13acceptProxyProtocol\\x12\\x0e\\n\" +\n\t\"\\x02ed\\x18\\x05 \\x01(\\rR\\x02ed\\x12(\\n\" +\n\t\"\\x0fheartbeatPeriod\\x18\\x06 \\x01(\\rR\\x0fheartbeatPeriod\\x1a9\\n\" +\n\t\"\\vHeaderEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\x85\\x01\\n\" +\n\t\"%com.xray.transport.internet.websocketP\\x01Z6github.com/xtls/xray-core/transport/internet/websocket\\xaa\\x02!Xray.Transport.Internet.Websocketb\\x06proto3\"\n\nvar (\n\tfile_transport_internet_websocket_config_proto_rawDescOnce sync.Once\n\tfile_transport_internet_websocket_config_proto_rawDescData []byte\n)\n\nfunc file_transport_internet_websocket_config_proto_rawDescGZIP() []byte {\n\tfile_transport_internet_websocket_config_proto_rawDescOnce.Do(func() {\n\t\tfile_transport_internet_websocket_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_websocket_config_proto_rawDesc), len(file_transport_internet_websocket_config_proto_rawDesc)))\n\t})\n\treturn file_transport_internet_websocket_config_proto_rawDescData\n}\n\nvar file_transport_internet_websocket_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_transport_internet_websocket_config_proto_goTypes = []any{\n\t(*Config)(nil), // 0: xray.transport.internet.websocket.Config\n\tnil,            // 1: xray.transport.internet.websocket.Config.HeaderEntry\n}\nvar file_transport_internet_websocket_config_proto_depIdxs = []int32{\n\t1, // 0: xray.transport.internet.websocket.Config.header:type_name -> xray.transport.internet.websocket.Config.HeaderEntry\n\t1, // [1:1] is the sub-list for method output_type\n\t1, // [1:1] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_transport_internet_websocket_config_proto_init() }\nfunc file_transport_internet_websocket_config_proto_init() {\n\tif File_transport_internet_websocket_config_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_websocket_config_proto_rawDesc), len(file_transport_internet_websocket_config_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_transport_internet_websocket_config_proto_goTypes,\n\t\tDependencyIndexes: file_transport_internet_websocket_config_proto_depIdxs,\n\t\tMessageInfos:      file_transport_internet_websocket_config_proto_msgTypes,\n\t}.Build()\n\tFile_transport_internet_websocket_config_proto = out.File\n\tfile_transport_internet_websocket_config_proto_goTypes = nil\n\tfile_transport_internet_websocket_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "transport/internet/websocket/config.proto",
    "content": "syntax = \"proto3\";\n\npackage xray.transport.internet.websocket;\noption csharp_namespace = \"Xray.Transport.Internet.Websocket\";\noption go_package = \"github.com/xtls/xray-core/transport/internet/websocket\";\noption java_package = \"com.xray.transport.internet.websocket\";\noption java_multiple_files = true;\n\nmessage Config {\n  string host = 1;\n  string path = 2; // URL path to the WebSocket service. Empty value means root(/).\n  map<string, string> header = 3;\n  bool accept_proxy_protocol = 4;\n  uint32 ed = 5;\n  uint32 heartbeatPeriod = 6;\n}\n"
  },
  {
    "path": "transport/internet/websocket/connection.go",
    "content": "package websocket\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/serial\"\n)\n\nvar _ buf.Writer = (*connection)(nil)\n\n// connection is a wrapper for net.Conn over WebSocket connection.\n// remoteAddr is used to pass \"virtual\" remote IP addresses in X-Forwarded-For.\n// so we shouldn't directly read it form conn.\ntype connection struct {\n\tconn       *websocket.Conn\n\treader     io.Reader\n\tremoteAddr net.Addr\n}\n\nfunc NewConnection(conn *websocket.Conn, remoteAddr net.Addr, extraReader io.Reader, heartbeatPeriod uint32) *connection {\n\tif heartbeatPeriod != 0 {\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\ttime.Sleep(time.Duration(heartbeatPeriod) * time.Second)\n\t\t\t\tif err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Time{}); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn &connection{\n\t\tconn:       conn,\n\t\tremoteAddr: remoteAddr,\n\t\treader:     extraReader,\n\t}\n}\n\n// Read implements net.Conn.Read()\nfunc (c *connection) Read(b []byte) (int, error) {\n\tfor {\n\t\treader, err := c.getReader()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tnBytes, err := reader.Read(b)\n\t\tif errors.Cause(err) == io.EOF {\n\t\t\tc.reader = nil\n\t\t\tcontinue\n\t\t}\n\t\treturn nBytes, err\n\t}\n}\n\nfunc (c *connection) getReader() (io.Reader, error) {\n\tif c.reader != nil {\n\t\treturn c.reader, nil\n\t}\n\n\t_, reader, err := c.conn.NextReader()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.reader = reader\n\treturn reader, nil\n}\n\n// Write implements io.Writer.\nfunc (c *connection) Write(b []byte) (int, error) {\n\tif err := c.conn.WriteMessage(websocket.BinaryMessage, b); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(b), nil\n}\n\nfunc (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tmb = buf.Compact(mb)\n\tmb, err := buf.WriteMultiBuffer(c, mb)\n\tbuf.ReleaseMulti(mb)\n\treturn err\n}\n\nfunc (c *connection) Close() error {\n\tvar errs []interface{}\n\tif err := c.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, \"\"), time.Now().Add(time.Second*5)); err != nil {\n\t\terrs = append(errs, err)\n\t}\n\tif err := c.conn.Close(); err != nil {\n\t\terrs = append(errs, err)\n\t}\n\tif len(errs) > 0 {\n\t\treturn errors.New(\"failed to close connection\").Base(errors.New(serial.Concat(errs...)))\n\t}\n\treturn nil\n}\n\nfunc (c *connection) LocalAddr() net.Addr {\n\treturn c.conn.LocalAddr()\n}\n\nfunc (c *connection) RemoteAddr() net.Addr {\n\treturn c.remoteAddr\n}\n\nfunc (c *connection) SetDeadline(t time.Time) error {\n\tif err := c.SetReadDeadline(t); err != nil {\n\t\treturn err\n\t}\n\treturn c.SetWriteDeadline(t)\n}\n\nfunc (c *connection) SetReadDeadline(t time.Time) error {\n\treturn c.conn.SetReadDeadline(t)\n}\n\nfunc (c *connection) SetWriteDeadline(t time.Time) error {\n\treturn c.conn.SetWriteDeadline(t)\n}\n"
  },
  {
    "path": "transport/internet/websocket/dialer.go",
    "content": "package websocket\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/browser_dialer\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\n// Dial dials a WebSocket connection to the given destination.\nfunc Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {\n\terrors.LogInfo(ctx, \"creating connection to \", dest)\n\tvar conn net.Conn\n\tif streamSettings.ProtocolSettings.(*Config).Ed > 0 {\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tconn = &delayDialConn{\n\t\t\tdialed:         make(chan bool, 1),\n\t\t\tcancel:         cancel,\n\t\t\tctx:            ctx,\n\t\t\tdest:           dest,\n\t\t\tstreamSettings: streamSettings,\n\t\t}\n\t} else {\n\t\tvar err error\n\t\tif conn, err = dialWebSocket(ctx, dest, streamSettings, nil); err != nil {\n\t\t\treturn nil, errors.New(\"failed to dial WebSocket\").Base(err)\n\t\t}\n\t}\n\treturn stat.Connection(conn), nil\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportDialer(protocolName, Dial))\n}\n\nfunc dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig, ed []byte) (net.Conn, error) {\n\twsSettings := streamSettings.ProtocolSettings.(*Config)\n\n\tdialer := &websocket.Dialer{\n\t\tNetDial: func(network, addr string) (net.Conn, error) {\n\t\t\tconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif streamSettings.TcpmaskManager != nil {\n\t\t\t\tnewConn, err := streamSettings.TcpmaskManager.WrapConnClient(conn)\n\t\t\t\tif err != nil {\n\t\t\t\t\tconn.Close()\n\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t}\n\t\t\t\tconn = newConn\n\t\t\t}\n\n\t\t\treturn conn, err\n\t\t},\n\t\tReadBufferSize:   4 * 1024,\n\t\tWriteBufferSize:  4 * 1024,\n\t\tHandshakeTimeout: time.Second * 8,\n\t}\n\n\tprotocol := \"ws\"\n\n\ttConfig := tls.ConfigFromStreamSettings(streamSettings)\n\tif tConfig != nil {\n\t\tprotocol = \"wss\"\n\t\ttlsConfig := tConfig.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto(\"http/1.1\"))\n\t\tdialer.TLSClientConfig = tlsConfig\n\t\tif fingerprint := tls.GetFingerprint(tConfig.Fingerprint); fingerprint != nil {\n\t\t\tdialer.NetDialTLSContext = func(_ context.Context, _, addr string) (net.Conn, error) {\n\t\t\t\t// Like the NetDial in the dialer\n\t\t\t\tpconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to dial to \"+addr)\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif streamSettings.TcpmaskManager != nil {\n\t\t\t\t\tnewConn, err := streamSettings.TcpmaskManager.WrapConnClient(pconn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpconn.Close()\n\t\t\t\t\t\treturn nil, errors.New(\"mask err\").Base(err)\n\t\t\t\t\t}\n\t\t\t\t\tpconn = newConn\n\t\t\t\t}\n\n\t\t\t\t// TLS and apply the handshake\n\t\t\t\tcn := tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn)\n\t\t\t\tif err := cn.WebsocketHandshakeContext(ctx); err != nil {\n\t\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to dial to \"+addr)\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif !tlsConfig.InsecureSkipVerify {\n\t\t\t\t\tif err := cn.VerifyHostname(tlsConfig.ServerName); err != nil {\n\t\t\t\t\t\terrors.LogErrorInner(ctx, err, \"failed to dial to \"+addr)\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn cn, nil\n\t\t\t}\n\t\t}\n\t}\n\n\thost := dest.NetAddr()\n\tif (protocol == \"ws\" && dest.Port == 80) || (protocol == \"wss\" && dest.Port == 443) {\n\t\thost = dest.Address.String()\n\t}\n\turi := protocol + \"://\" + host + wsSettings.GetNormalizedPath()\n\n\tif browser_dialer.HasBrowserDialer() {\n\t\tconn, err := browser_dialer.DialWS(uri, ed)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil\n\t}\n\n\theader := wsSettings.GetRequestHeader()\n\t// See dialer.DialContext()\n\theader.Set(\"Host\", wsSettings.Host)\n\tif header.Get(\"Host\") == \"\" && tConfig != nil {\n\t\theader.Set(\"Host\", tConfig.ServerName)\n\t}\n\tif header.Get(\"Host\") == \"\" {\n\t\theader.Set(\"Host\", dest.Address.String())\n\t}\n\tif ed != nil {\n\t\t// RawURLEncoding is support by both V2Ray/V2Fly and XRay.\n\t\theader.Set(\"Sec-WebSocket-Protocol\", base64.RawURLEncoding.EncodeToString(ed))\n\t}\n\n\tconn, resp, err := dialer.DialContext(ctx, uri, header)\n\tif err != nil {\n\t\tvar reason string\n\t\tif resp != nil {\n\t\t\treason = resp.Status\n\t\t}\n\t\treturn nil, errors.New(\"failed to dial to (\", uri, \"): \", reason).Base(err)\n\t}\n\n\treturn NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil\n}\n\ntype delayDialConn struct {\n\tnet.Conn\n\tclosed         bool\n\tdialed         chan bool\n\tcancel         context.CancelFunc\n\tctx            context.Context\n\tdest           net.Destination\n\tstreamSettings *internet.MemoryStreamConfig\n}\n\nfunc (d *delayDialConn) Write(b []byte) (int, error) {\n\tif d.closed {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\tif d.Conn == nil {\n\t\ted := b\n\t\tif len(ed) > int(d.streamSettings.ProtocolSettings.(*Config).Ed) {\n\t\t\ted = nil\n\t\t}\n\t\tvar err error\n\t\tif d.Conn, err = dialWebSocket(d.ctx, d.dest, d.streamSettings, ed); err != nil {\n\t\t\td.Close()\n\t\t\treturn 0, errors.New(\"failed to dial WebSocket\").Base(err)\n\t\t}\n\t\td.dialed <- true\n\t\tif ed != nil {\n\t\t\treturn len(ed), nil\n\t\t}\n\t}\n\treturn d.Conn.Write(b)\n}\n\nfunc (d *delayDialConn) Read(b []byte) (int, error) {\n\tif d.closed {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\tif d.Conn == nil {\n\t\tselect {\n\t\tcase <-d.ctx.Done():\n\t\t\treturn 0, io.ErrUnexpectedEOF\n\t\tcase <-d.dialed:\n\t\t}\n\t}\n\treturn d.Conn.Read(b)\n}\n\nfunc (d *delayDialConn) Close() error {\n\td.closed = true\n\td.cancel()\n\tif d.Conn == nil {\n\t\treturn nil\n\t}\n\treturn d.Conn.Close()\n}\n"
  },
  {
    "path": "transport/internet/websocket/hub.go",
    "content": "package websocket\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/errors\"\n\t\"github.com/xtls/xray-core/common/net\"\n\thttp_proto \"github.com/xtls/xray-core/common/protocol/http\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\tv2tls \"github.com/xtls/xray-core/transport/internet/tls\"\n)\n\ntype requestHandler struct {\n\thost           string\n\tpath           string\n\tln             *Listener\n\tsocketSettings *internet.SocketConfig\n}\n\nvar replacer = strings.NewReplacer(\"+\", \"-\", \"/\", \"_\", \"=\", \"\")\n\nvar upgrader = &websocket.Upgrader{\n\tReadBufferSize:   0,\n\tWriteBufferSize:  0,\n\tHandshakeTimeout: time.Second * 4,\n\tCheckOrigin: func(r *http.Request) bool {\n\t\treturn true\n\t},\n}\n\nfunc (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {\n\tif len(h.host) > 0 && !internet.IsValidHTTPHost(request.Host, h.host) {\n\t\terrors.LogInfo(context.Background(), \"failed to validate host, request:\", request.Host, \", config:\", h.host)\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\tif request.URL.Path != h.path {\n\t\terrors.LogInfo(context.Background(), \"failed to validate path, request:\", request.URL.Path, \", config:\", h.path)\n\t\twriter.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\n\tvar extraReader io.Reader\n\tresponseHeader := http.Header{}\n\tif str := request.Header.Get(\"Sec-WebSocket-Protocol\"); str != \"\" {\n\t\tif ed, err := base64.RawURLEncoding.DecodeString(replacer.Replace(str)); err == nil && len(ed) > 0 {\n\t\t\textraReader = bytes.NewReader(ed)\n\t\t\tresponseHeader.Set(\"Sec-WebSocket-Protocol\", str)\n\t\t}\n\t}\n\n\tconn, err := upgrader.Upgrade(writer, request, responseHeader)\n\tif err != nil {\n\t\terrors.LogInfoInner(context.Background(), err, \"failed to convert to WebSocket connection\")\n\t\treturn\n\t}\n\n\tvar forwardedAddrs []net.Address\n\tif h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {\n\t\tfor _, key := range h.socketSettings.TrustedXForwardedFor {\n\t\t\tif len(request.Header.Values(key)) > 0 {\n\t\t\t\tforwardedAddrs = http_proto.ParseXForwardedFor(request.Header)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tforwardedAddrs = http_proto.ParseXForwardedFor(request.Header)\n\t}\n\tremoteAddr := conn.RemoteAddr()\n\tif len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {\n\t\tremoteAddr = &net.TCPAddr{\n\t\t\tIP:   forwardedAddrs[0].IP(),\n\t\t\tPort: int(0),\n\t\t}\n\t}\n\n\th.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod))\n}\n\ntype Listener struct {\n\tsync.Mutex\n\tserver   http.Server\n\tlistener net.Listener\n\tconfig   *Config\n\taddConn  internet.ConnHandler\n}\n\nfunc ListenWS(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {\n\tl := &Listener{\n\t\taddConn: addConn,\n\t}\n\twsSettings := streamSettings.ProtocolSettings.(*Config)\n\tl.config = wsSettings\n\tif l.config != nil {\n\t\tif streamSettings.SocketSettings == nil {\n\t\t\tstreamSettings.SocketSettings = &internet.SocketConfig{}\n\t\t}\n\t\tstreamSettings.SocketSettings.AcceptProxyProtocol = l.config.AcceptProxyProtocol || streamSettings.SocketSettings.AcceptProxyProtocol\n\t}\n\tvar listener net.Listener\n\tvar err error\n\tif port == net.Port(0) { // unix\n\t\tlistener, err = internet.ListenSystem(ctx, &net.UnixAddr{\n\t\t\tName: address.Domain(),\n\t\t\tNet:  \"unix\",\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen unix domain socket(for WS) on \", address).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening unix domain socket(for WS) on \", address)\n\t} else { // tcp\n\t\tlistener, err = internet.ListenSystem(ctx, &net.TCPAddr{\n\t\t\tIP:   address.IP(),\n\t\t\tPort: int(port),\n\t\t}, streamSettings.SocketSettings)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"failed to listen TCP(for WS) on \", address, \":\", port).Base(err)\n\t\t}\n\t\terrors.LogInfo(ctx, \"listening TCP(for WS) on \", address, \":\", port)\n\t}\n\n\tif streamSettings.TcpmaskManager != nil {\n\t\tlistener, _ = streamSettings.TcpmaskManager.WrapListener(listener)\n\t}\n\n\tif streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {\n\t\terrors.LogWarning(ctx, \"accepting PROXY protocol\")\n\t}\n\n\tif config := v2tls.ConfigFromStreamSettings(streamSettings); config != nil {\n\t\tif tlsConfig := config.GetTLSConfig(); tlsConfig != nil {\n\t\t\tlistener = tls.NewListener(listener, tlsConfig)\n\t\t}\n\t}\n\n\tl.listener = listener\n\n\tl.server = http.Server{\n\t\tHandler: &requestHandler{\n\t\t\thost:           wsSettings.Host,\n\t\t\tpath:           wsSettings.GetNormalizedPath(),\n\t\t\tln:             l,\n\t\t\tsocketSettings: streamSettings.SocketSettings,\n\t\t},\n\t\tReadHeaderTimeout: time.Second * 4,\n\t\tMaxHeaderBytes:    8192,\n\t}\n\n\tgo func() {\n\t\tif err := l.server.Serve(l.listener); err != nil {\n\t\t\terrors.LogWarningInner(ctx, err, \"failed to serve http for WebSocket\")\n\t\t}\n\t}()\n\n\treturn l, err\n}\n\n// Addr implements net.Listener.Addr().\nfunc (ln *Listener) Addr() net.Addr {\n\treturn ln.listener.Addr()\n}\n\n// Close implements net.Listener.Close().\nfunc (ln *Listener) Close() error {\n\treturn ln.listener.Close()\n}\n\nfunc init() {\n\tcommon.Must(internet.RegisterTransportListener(protocolName, ListenWS))\n}\n"
  },
  {
    "path": "transport/internet/websocket/ws.go",
    "content": "/*\nPackage websocket implements WebSocket transport\n\nWebSocket transport implements an HTTP(S) compliable, surveillance proof transport method with plausible deniability.\n*/\npackage websocket\n\nconst protocolName = \"websocket\"\n"
  },
  {
    "path": "transport/internet/websocket/ws_test.go",
    "content": "package websocket_test\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"github.com/xtls/xray-core/common/protocol/tls/cert\"\n\t\"github.com/xtls/xray-core/testing/servers/tcp\"\n\t\"github.com/xtls/xray-core/transport/internet\"\n\t\"github.com/xtls/xray-core/transport/internet/stat\"\n\t\"github.com/xtls/xray-core/transport/internet/tls\"\n\t. \"github.com/xtls/xray-core/transport/internet/websocket\"\n)\n\nfunc Test_listenWSAndDial(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenWS(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"websocket\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"ws\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\tc.SetReadDeadline(time.Now().Add(2 * time.Second))\n\t\t\t_, err := c.Read(b[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcommon.Must2(c.Write([]byte(\"Response\")))\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\n\tctx := context.Background()\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"websocket\",\n\t\tProtocolSettings: &Config{Path: \"ws\"},\n\t}\n\tconn, err := Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, err := conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(conn.Close())\n\tconn, err = Dial(ctx, net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 2\"))\n\tcommon.Must(err)\n\tn, err = conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"Response\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\tcommon.Must(conn.Close())\n\n\tcommon.Must(listen.Close())\n}\n\nfunc TestDialWithRemoteAddr(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tlisten, err := ListenWS(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{\n\t\tProtocolName: \"websocket\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"ws\",\n\t\t},\n\t}, func(conn stat.Connection) {\n\t\tgo func(c stat.Connection) {\n\t\t\tdefer c.Close()\n\n\t\t\tvar b [1024]byte\n\t\t\t_, err := c.Read(b[:])\n\t\t\t// common.Must(err)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t_, err = c.Write([]byte(c.RemoteAddr().String()))\n\t\t\tcommon.Must(err)\n\t\t}(conn)\n\t})\n\tcommon.Must(err)\n\n\tconn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), &internet.MemoryStreamConfig{\n\t\tProtocolName:     \"websocket\",\n\t\tProtocolSettings: &Config{Path: \"ws\", Header: map[string]string{\"X-Forwarded-For\": \"1.1.1.1\"}},\n\t})\n\n\tcommon.Must(err)\n\t_, err = conn.Write([]byte(\"Test connection 1\"))\n\tcommon.Must(err)\n\n\tvar b [1024]byte\n\tn, err := conn.Read(b[:])\n\tcommon.Must(err)\n\tif string(b[:n]) != \"1.1.1.1:0\" {\n\t\tt.Error(\"response: \", string(b[:n]))\n\t}\n\n\tcommon.Must(listen.Close())\n}\n\nfunc Test_listenWSAndDial_TLS(t *testing.T) {\n\tlistenPort := tcp.PickPort()\n\tif runtime.GOARCH == \"arm64\" {\n\t\treturn\n\t}\n\n\tstart := time.Now()\n\n\tct, ctHash := cert.MustGenerate(nil, cert.CommonName(\"localhost\"))\n\n\tstreamSettings := &internet.MemoryStreamConfig{\n\t\tProtocolName: \"websocket\",\n\t\tProtocolSettings: &Config{\n\t\t\tPath: \"wss\",\n\t\t},\n\t\tSecurityType: \"tls\",\n\t\tSecuritySettings: &tls.Config{\n\t\t\tCertificate:          []*tls.Certificate{tls.ParseCertificate(ct)},\n\t\t\tPinnedPeerCertSha256: [][]byte{ctHash[:]},\n\t\t},\n\t}\n\tlisten, err := ListenWS(context.Background(), net.LocalHostIP, listenPort, streamSettings, func(conn stat.Connection) {\n\t\tgo func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t})\n\tcommon.Must(err)\n\tdefer listen.Close()\n\n\tconn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress(\"localhost\"), listenPort), streamSettings)\n\tcommon.Must(err)\n\t_ = conn.Close()\n\n\tend := time.Now()\n\tif !end.Before(start.Add(time.Second * 5)) {\n\t\tt.Error(\"end: \", end, \" start: \", start)\n\t}\n}\n"
  },
  {
    "path": "transport/link.go",
    "content": "package transport\n\nimport \"github.com/xtls/xray-core/common/buf\"\n\n// Link is a utility for connecting between an inbound and an outbound proxy handler.\ntype Link struct {\n\tReader buf.Reader\n\tWriter buf.Writer\n}\n"
  },
  {
    "path": "transport/pipe/impl.go",
    "content": "package pipe\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n)\n\ntype state byte\n\nconst (\n\topen state = iota\n\tclosed\n\terrord\n)\n\ntype pipeOption struct {\n\tlimit           int32 // maximum buffer size in bytes\n\tdiscardOverflow bool\n}\n\nfunc (o *pipeOption) isFull(curSize int32) bool {\n\treturn o.limit >= 0 && curSize > o.limit\n}\n\ntype pipe struct {\n\tsync.Mutex\n\tdata        buf.MultiBuffer\n\treadSignal  *signal.Notifier\n\twriteSignal *signal.Notifier\n\tdone        *done.Instance\n\terrChan     chan error\n\toption      pipeOption\n\tstate       state\n}\n\nvar (\n\terrBufferFull = errors.New(\"buffer full\")\n\terrSlowDown   = errors.New(\"slow down\")\n)\n\nfunc (p *pipe) Len() int32 {\n\tdata := p.data\n\tif data == nil {\n\t\treturn 0\n\t}\n\treturn data.Len()\n}\n\nfunc (p *pipe) getState(forRead bool) error {\n\tswitch p.state {\n\tcase open:\n\t\tif !forRead && p.option.isFull(p.data.Len()) {\n\t\t\treturn errBufferFull\n\t\t}\n\t\treturn nil\n\tcase closed:\n\t\tif !forRead {\n\t\t\treturn io.ErrClosedPipe\n\t\t}\n\t\tif !p.data.IsEmpty() {\n\t\t\treturn nil\n\t\t}\n\t\treturn io.EOF\n\tcase errord:\n\t\treturn io.ErrClosedPipe\n\tdefault:\n\t\tpanic(\"impossible case\")\n\t}\n}\n\nfunc (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) {\n\tp.Lock()\n\tdefer p.Unlock()\n\n\tif err := p.getState(true); err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata := p.data\n\tp.data = nil\n\treturn data, nil\n}\n\nfunc (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\tfor {\n\t\tdata, err := p.readMultiBufferInternal()\n\t\tif data != nil || err != nil {\n\t\t\tp.writeSignal.Signal()\n\t\t\treturn data, err\n\t\t}\n\n\t\tselect {\n\t\tcase <-p.readSignal.Wait():\n\t\tcase <-p.done.Wait():\n\t\tcase err = <-p.errChan:\n\t\t\treturn nil, err\n\t\t}\n\t}\n}\n\nfunc (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {\n\ttimer := time.NewTimer(d)\n\tdefer timer.Stop()\n\n\tfor {\n\t\tdata, err := p.readMultiBufferInternal()\n\t\tif data != nil || err != nil {\n\t\t\tp.writeSignal.Signal()\n\t\t\treturn data, err\n\t\t}\n\n\t\tselect {\n\t\tcase <-p.readSignal.Wait():\n\t\tcase <-p.done.Wait():\n\t\tcase <-timer.C:\n\t\t\treturn nil, buf.ErrReadTimeout\n\t\t}\n\t}\n}\n\nfunc (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error {\n\tp.Lock()\n\tdefer p.Unlock()\n\n\tif err := p.getState(false); err != nil {\n\t\treturn err\n\t}\n\n\tif p.data == nil {\n\t\tp.data = mb\n\t} else {\n\t\tp.data, _ = buf.MergeMulti(p.data, mb)\n\t}\n\treturn nil\n}\n\nfunc (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\tif mb.IsEmpty() {\n\t\treturn nil\n\t}\n\n\tfor {\n\t\terr := p.writeMultiBufferInternal(mb)\n\t\tif err == nil {\n\t\t\tp.readSignal.Signal()\n\t\t\treturn nil\n\t\t}\n\n\t\tif err == errBufferFull {\n\t\t\tif p.option.discardOverflow {\n\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-p.writeSignal.Wait():\n\t\t\t\tcontinue\n\t\t\tcase <-p.done.Wait():\n\t\t\t\tbuf.ReleaseMulti(mb)\n\t\t\t\treturn io.ErrClosedPipe\n\t\t\t}\n\t\t}\n\n\t\tbuf.ReleaseMulti(mb)\n\t\tp.readSignal.Signal()\n\t\treturn err\n\t}\n}\n\nfunc (p *pipe) Close() error {\n\tp.Lock()\n\tdefer p.Unlock()\n\n\tif p.state == closed || p.state == errord {\n\t\treturn nil\n\t}\n\n\tp.state = closed\n\tcommon.Must(p.done.Close())\n\treturn nil\n}\n\n// Interrupt implements common.Interruptible.\nfunc (p *pipe) Interrupt() {\n\tp.Lock()\n\tdefer p.Unlock()\n\n\tif !p.data.IsEmpty() {\n\t\tbuf.ReleaseMulti(p.data)\n\t\tp.data = nil\n\t\tif p.state == closed {\n\t\t\tp.state = errord\n\t\t}\n\t}\n\n\tif p.state == closed || p.state == errord {\n\t\treturn\n\t}\n\n\tp.state = errord\n\n\tcommon.Must(p.done.Close())\n}\n"
  },
  {
    "path": "transport/pipe/pipe.go",
    "content": "package pipe\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common/signal\"\n\t\"github.com/xtls/xray-core/common/signal/done\"\n\t\"github.com/xtls/xray-core/features/policy\"\n)\n\n// Option for creating new Pipes.\ntype Option func(*pipeOption)\n\n// WithoutSizeLimit returns an Option for Pipe to have no size limit.\nfunc WithoutSizeLimit() Option {\n\treturn func(opt *pipeOption) {\n\t\topt.limit = -1\n\t}\n}\n\n// WithSizeLimit returns an Option for Pipe to have the given size limit.\nfunc WithSizeLimit(limit int32) Option {\n\treturn func(opt *pipeOption) {\n\t\topt.limit = limit\n\t}\n}\n\n// DiscardOverflow returns an Option for Pipe to discard writes if full.\nfunc DiscardOverflow() Option {\n\treturn func(opt *pipeOption) {\n\t\topt.discardOverflow = true\n\t}\n}\n\n// OptionsFromContext returns a list of Options from context.\nfunc OptionsFromContext(ctx context.Context) []Option {\n\tvar opt []Option\n\n\tbp := policy.BufferPolicyFromContext(ctx)\n\tif bp.PerConnection >= 0 {\n\t\topt = append(opt, WithSizeLimit(bp.PerConnection))\n\t} else {\n\t\topt = append(opt, WithoutSizeLimit())\n\t}\n\n\treturn opt\n}\n\n// New creates a new Reader and Writer that connects to each other.\nfunc New(opts ...Option) (*Reader, *Writer) {\n\tp := &pipe{\n\t\treadSignal:  signal.NewNotifier(),\n\t\twriteSignal: signal.NewNotifier(),\n\t\tdone:        done.New(),\n\t\terrChan:     make(chan error, 1),\n\t\toption: pipeOption{\n\t\t\tlimit: -1,\n\t\t},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(&(p.option))\n\t}\n\n\treturn &Reader{\n\t\t\tpipe: p,\n\t\t}, &Writer{\n\t\t\tpipe: p,\n\t\t}\n}\n"
  },
  {
    "path": "transport/pipe/pipe_test.go",
    "content": "package pipe_test\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t. \"github.com/xtls/xray-core/transport/pipe\"\n)\n\nfunc TestPipeReadWrite(t *testing.T) {\n\tpReader, pWriter := New(WithSizeLimit(1024))\n\n\tb := buf.New()\n\tb.WriteString(\"abcd\")\n\tcommon.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b}))\n\n\tb2 := buf.New()\n\tb2.WriteString(\"efg\")\n\tcommon.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b2}))\n\n\trb, err := pReader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif r := cmp.Diff(rb.String(), \"abcdefg\"); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestPipeInterrupt(t *testing.T) {\n\tpReader, pWriter := New(WithSizeLimit(1024))\n\tpayload := []byte{'a', 'b', 'c', 'd'}\n\tb := buf.New()\n\tb.Write(payload)\n\tcommon.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b}))\n\tpWriter.Interrupt()\n\n\trb, err := pReader.ReadMultiBuffer()\n\tif err != io.ErrClosedPipe {\n\t\tt.Fatal(\"expect io.ErrClosePipe, but got \", err)\n\t}\n\tif !rb.IsEmpty() {\n\t\tt.Fatal(\"expect empty buffer, but got \", rb.Len())\n\t}\n}\n\nfunc TestPipeClose(t *testing.T) {\n\tpReader, pWriter := New(WithSizeLimit(1024))\n\tpayload := []byte{'a', 'b', 'c', 'd'}\n\tb := buf.New()\n\tcommon.Must2(b.Write(payload))\n\tcommon.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{b}))\n\tcommon.Must(pWriter.Close())\n\n\trb, err := pReader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif rb.String() != string(payload) {\n\t\tt.Fatal(\"expect content \", string(payload), \" but actually \", rb.String())\n\t}\n\n\trb, err = pReader.ReadMultiBuffer()\n\tif err != io.EOF {\n\t\tt.Fatal(\"expected EOF, but got \", err)\n\t}\n\tif !rb.IsEmpty() {\n\t\tt.Fatal(\"expect empty buffer, but got \", rb.String())\n\t}\n}\n\nfunc TestPipeLimitZero(t *testing.T) {\n\tpReader, pWriter := New(WithSizeLimit(0))\n\tbb := buf.New()\n\tcommon.Must2(bb.Write([]byte{'a', 'b'}))\n\tcommon.Must(pWriter.WriteMultiBuffer(buf.MultiBuffer{bb}))\n\n\tvar errg errgroup.Group\n\terrg.Go(func() error {\n\t\tb := buf.New()\n\t\tb.Write([]byte{'c', 'd'})\n\t\treturn pWriter.WriteMultiBuffer(buf.MultiBuffer{b})\n\t})\n\terrg.Go(func() error {\n\t\ttime.Sleep(time.Second)\n\n\t\tvar container buf.MultiBufferContainer\n\t\tif err := buf.Copy(pReader, &container); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif r := cmp.Diff(container.String(), \"abcd\"); r != \"\" {\n\t\t\treturn errors.New(r)\n\t\t}\n\t\treturn nil\n\t})\n\terrg.Go(func() error {\n\t\ttime.Sleep(time.Second * 2)\n\t\treturn pWriter.Close()\n\t})\n\tif err := errg.Wait(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestPipeWriteMultiThread(t *testing.T) {\n\tpReader, pWriter := New(WithSizeLimit(0))\n\n\tvar errg errgroup.Group\n\tfor i := 0; i < 10; i++ {\n\t\terrg.Go(func() error {\n\t\t\tb := buf.New()\n\t\t\tb.WriteString(\"abcd\")\n\t\t\treturn pWriter.WriteMultiBuffer(buf.MultiBuffer{b})\n\t\t})\n\t}\n\ttime.Sleep(time.Millisecond * 100)\n\tpWriter.Close()\n\terrg.Wait()\n\n\tb, err := pReader.ReadMultiBuffer()\n\tcommon.Must(err)\n\tif r := cmp.Diff(b[0].Bytes(), []byte{'a', 'b', 'c', 'd'}); r != \"\" {\n\t\tt.Error(r)\n\t}\n}\n\nfunc TestInterfaces(t *testing.T) {\n\t_ = (buf.Reader)(new(Reader))\n\t_ = (buf.TimeoutReader)(new(Reader))\n\n\t_ = (common.Interruptible)(new(Reader))\n\t_ = (common.Interruptible)(new(Writer))\n\t_ = (common.Closable)(new(Writer))\n}\n\nfunc BenchmarkPipeReadWrite(b *testing.B) {\n\treader, writer := New(WithoutSizeLimit())\n\ta := buf.New()\n\ta.Extend(buf.Size)\n\tc := buf.MultiBuffer{a}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(writer.WriteMultiBuffer(c))\n\t\td, err := reader.ReadMultiBuffer()\n\t\tcommon.Must(err)\n\t\tc = d\n\t}\n}\n"
  },
  {
    "path": "transport/pipe/reader.go",
    "content": "package pipe\n\nimport (\n\t\"time\"\n\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\n// Reader is a buf.Reader that reads content from a pipe.\ntype Reader struct {\n\tpipe *pipe\n}\n\n// ReadMultiBuffer implements buf.Reader.\nfunc (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) {\n\treturn r.pipe.ReadMultiBuffer()\n}\n\n// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise.\nfunc (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {\n\treturn r.pipe.ReadMultiBufferTimeout(d)\n}\n\n// Interrupt implements common.Interruptible.\nfunc (r *Reader) Interrupt() {\n\tr.pipe.Interrupt()\n}\n\n// ReturnAnError makes ReadMultiBuffer return an error, only once.\nfunc (r *Reader) ReturnAnError(err error) {\n\tr.pipe.errChan <- err\n}\n\n// Recover catches an error set by ReturnAnError, if exists.\nfunc (r *Reader) Recover() (err error) {\n\tselect {\n\tcase err = <-r.pipe.errChan:\n\tdefault:\n\t}\n\treturn\n}\n"
  },
  {
    "path": "transport/pipe/writer.go",
    "content": "package pipe\n\nimport (\n\t\"github.com/xtls/xray-core/common/buf\"\n)\n\n// Writer is a buf.Writer that writes data into a pipe.\ntype Writer struct {\n\tpipe *pipe\n}\n\n// WriteMultiBuffer implements buf.Writer.\nfunc (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {\n\treturn w.pipe.WriteMultiBuffer(mb)\n}\n\n// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF.\nfunc (w *Writer) Close() error {\n\treturn w.pipe.Close()\n}\n\nfunc (w *Writer) Len() int32 {\n\treturn w.pipe.Len()\n}\n\n// Interrupt implements common.Interruptible.\nfunc (w *Writer) Interrupt() {\n\tw.pipe.Interrupt()\n}\n"
  }
]