Repository: Yuzuki616/V2bX Branch: master Commit: b54e9041dfab Files: 110 Total size: 315.0 KB Directory structure: gitextract_ebfl6u56/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ └── feature-request.md │ ├── build/ │ │ └── friendly-filenames.json │ ├── dependabot.yml │ └── workflows/ │ ├── codeql-analysis.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── api/ │ ├── iprecoder/ │ │ ├── interface.go │ │ ├── recorder.go │ │ ├── redis.go │ │ └── redis_test.go │ └── panel/ │ ├── node.go │ ├── node_test.go │ ├── panel.go │ ├── user.go │ └── utils.go ├── cmd/ │ ├── action_linux.go │ ├── cmd.go │ ├── common.go │ ├── common_test.go │ ├── install_linux.go │ ├── server.go │ ├── server_test.go │ ├── synctime.go │ ├── version.go │ ├── x25519.go │ └── x25519_test.go ├── common/ │ ├── crypt/ │ │ ├── aes.go │ │ └── x25519.go │ ├── exec/ │ │ └── exec.go │ ├── file/ │ │ └── file.go │ ├── format/ │ │ └── user.go │ ├── rate/ │ │ └── rate.go │ ├── systime/ │ │ ├── time_stub.go │ │ ├── time_unix.go │ │ └── time_windows.go │ └── task/ │ ├── task.go │ └── task_test.go ├── conf/ │ ├── conf.go │ ├── conf_test.go │ ├── core.go │ ├── log.go │ ├── node.go │ ├── old.go │ └── watch.go ├── core/ │ ├── core.go │ ├── hy/ │ │ ├── config.go │ │ ├── counter.go │ │ ├── counter_test.go │ │ ├── hy.go │ │ ├── ipmasker.go │ │ ├── kploader.go │ │ ├── mmdb.go │ │ ├── node.go │ │ ├── resolver.go │ │ ├── server.go │ │ ├── server_test.go │ │ └── user.go │ ├── imports/ │ │ ├── hy.go │ │ ├── imports.go │ │ └── xray.go │ ├── interface.go │ ├── selector.go │ └── xray/ │ ├── app/ │ │ ├── app.go │ │ └── dispatcher/ │ │ ├── config.pb.go │ │ ├── config.proto │ │ ├── default.go │ │ ├── dispatcher.go │ │ ├── errors.generated.go │ │ ├── fakednssniffer.go │ │ ├── sniffer.go │ │ ├── stats.go │ │ └── stats_test.go │ ├── distro/ │ │ └── all/ │ │ └── all.go │ ├── inbound.go │ ├── node.go │ ├── outbound.go │ ├── ss.go │ ├── trojan.go │ ├── user.go │ ├── vmess.go │ └── xray.go ├── example/ │ ├── config.yml.example │ ├── custom_inbound.json │ ├── custom_outbound.json │ ├── dns.json │ ├── route.json │ └── rulelist ├── go.mod ├── go.sum ├── limiter/ │ ├── clear.go │ ├── conn.go │ ├── conn_test.go │ ├── dynamic.go │ ├── limiter.go │ └── rule.go ├── main.go ├── node/ │ ├── cert.go │ ├── controller.go │ ├── lego/ │ │ ├── cert.go │ │ ├── lego.go │ │ ├── lego_test.go │ │ └── user.go │ ├── node.go │ ├── task.go │ └── user.go └── test_data/ ├── 1.key └── 1.pem ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: "Bug 反馈" about: 创建一个报告以帮助我们修复并改进XrayR title: '' labels: awaiting reply, bug assignees: '' --- **描述该错误** 简单地描述一下这个bug是什么 **复现** 复现该bug的步骤 **环境和版本** - 系统 [例如:Debian 11] - 架构 [例如:AMD64] - 面板 [例如:V2board] - 协议 [例如:vmess] - 版本 [例如:0.8.2.2] - 部署方式 [例如:一键脚本] **日志和错误** 请使用`xrayr log`查看并添加日志,以帮助解释你的问题 **额外的内容** 在这里添加关于问题的任何其他内容 ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- name: "功能建议" about: 给XrayR提出建议,让我们做得更好 title: '' labels: awaiting reply, feature-request assignees: '' --- **描述您想要的功能** 清晰简洁的功能描述。 **描述您考虑过的替代方案** 是否有任何替代方案可以解决这个问题? **附加上下文** 在此处添加有关功能请求的任何其他上下文或截图。 ================================================ FILE: .github/build/friendly-filenames.json ================================================ { "android-arm64": { "friendlyName": "android-arm64-v8a" }, "darwin-amd64": { "friendlyName": "macos-64" }, "darwin-arm64": { "friendlyName": "macos-arm64-v8a" }, "dragonfly-amd64": { "friendlyName": "dragonfly-64" }, "freebsd-386": { "friendlyName": "freebsd-32" }, "freebsd-amd64": { "friendlyName": "freebsd-64" }, "freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" }, "freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" }, "linux-386": { "friendlyName": "linux-32" }, "linux-amd64": { "friendlyName": "linux-64" }, "linux-arm5": { "friendlyName": "linux-arm32-v5" }, "linux-arm64": { "friendlyName": "linux-arm64-v8a" }, "linux-arm6": { "friendlyName": "linux-arm32-v6" }, "linux-arm7": { "friendlyName": "linux-arm32-v7a" }, "linux-mips64le": { "friendlyName": "linux-mips64le" }, "linux-mips64": { "friendlyName": "linux-mips64" }, "linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" }, "linux-mipsle": { "friendlyName": "linux-mips32le" }, "linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" }, "linux-mips": { "friendlyName": "linux-mips32" }, "linux-ppc64le": { "friendlyName": "linux-ppc64le" }, "linux-ppc64": { "friendlyName": "linux-ppc64" }, "linux-riscv64": { "friendlyName": "linux-riscv64" }, "linux-s390x": { "friendlyName": "linux-s390x" }, "openbsd-386": { "friendlyName": "openbsd-32" }, "openbsd-amd64": { "friendlyName": "openbsd-64" }, "openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" }, "openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" }, "windows-386": { "friendlyName": "windows-32" }, "windows-amd64": { "friendlyName": "windows-64" }, "windows-arm7": { "friendlyName": "windows-arm32-v7a" } } ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '43 22 * * 3' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 ================================================ FILE: .github/workflows/release.yml ================================================ name: Build and Release on: workflow_dispatch: push: branches: - master paths: - "**/*.go" - "go.mod" - "go.sum" - ".github/workflows/*.yml" pull_request: types: [opened, synchronize, reopened] paths: - "**/*.go" - "go.mod" - "go.sum" - ".github/workflows/*.yml" release: types: [published] jobs: build: strategy: matrix: # Include amd64 on all platforms. goos: [windows, freebsd, linux, dragonfly, darwin] goarch: [amd64, 386] exclude: # Exclude i386 on darwin and dragonfly. - goarch: 386 goos: dragonfly - goarch: 386 goos: darwin include: # BEIGIN MacOS ARM64 - goos: darwin goarch: arm64 # END MacOS ARM64 # BEGIN Linux ARM 5 6 7 - goos: linux goarch: arm goarm: 7 - goos: linux goarch: arm goarm: 6 - goos: linux goarch: arm goarm: 5 # END Linux ARM 5 6 7 # BEGIN Android ARM 8 - goos: android goarch: arm64 # END Android ARM 8 # BEGIN Other architectures # BEGIN riscv64 & ARM64 - goos: linux goarch: arm64 - goos: linux goarch: riscv64 # END riscv64 & ARM64 # BEGIN MIPS - goos: linux goarch: mips64 - goos: linux goarch: mips64le - goos: linux goarch: mipsle - goos: linux goarch: mips # END MIPS # BEGIN PPC - goos: linux goarch: ppc64 - goos: linux goarch: ppc64le # END PPC # BEGIN FreeBSD ARM - goos: freebsd goarch: arm64 - goos: freebsd goarch: arm goarm: 7 # END FreeBSD ARM # BEGIN S390X - goos: linux goarch: s390x # END S390X # END Other architectures fail-fast: false runs-on: ubuntu-latest env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} CGO_ENABLED: 0 steps: - name: Checkout codebase uses: actions/checkout@v3 - name: Show workflow information id: get_filename run: | export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json) echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME" echo "::set-output name=ASSET_NAME::$_NAME" echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV - name: Set up Go uses: actions/setup-go@v3 with: go-version: '1.19' - name: Get project dependencies run: go mod download - name: Get release version if: ${{ github.event_name == 'release' }} run: | echo "version=$(echo $GITHUB_REF | cut -d / -f 3)" >> $GITHUB_ENV - name: Get other version if: ${{ github.event_name != 'release' }} run: | echo "version=${{ github.sha }}" >> $GITHUB_ENV - name: Build V2bX run: | echo "version: $version" mkdir -p build_assets go build -v -o build_assets/V2bX -tags "xray hy" -trimpath -ldflags "-X 'github.com/Yuzuki616/V2bX/cmd.version=$version' -s -w -buildid=" - name: Build Mips softfloat V2bX if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle' run: | echo "version: $version" GOMIPS=softfloat go build -v -o build_assets/V2bX_softfloat -tags "xray hy" -trimpath -ldflags "-X 'github.com/Yuzuki616/V2bX/cmd.version=$version' -s -w -buildid=" - name: Rename Windows V2bX if: matrix.goos == 'windows' run: | cd ./build_assets || exit 1 mv V2bX V2bX.exe - name: Prepare to release run: | cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE cp ${GITHUB_WORKSPACE}/example/dns.json ./build_assets/dns.json cp ${GITHUB_WORKSPACE}/example/route.json ./build_assets/route.json cp ${GITHUB_WORKSPACE}/example/custom_outbound.json ./build_assets/custom_outbound.json cp ${GITHUB_WORKSPACE}/example/custom_inbound.json ./build_assets/custom_inbound.json cp ${GITHUB_WORKSPACE}/example/rulelist ./build_assets/rulelist cp ${GITHUB_WORKSPACE}/example/config.yml.example ./build_assets/config.yml LIST=('geoip geoip geoip' 'domain-list-community dlc geosite') for i in "${LIST[@]}" do INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}')) FILE_NAME="${INFO[2]}.dat" echo -e "Downloading ${FILE_NAME}..." curl -L "https://github.com/v2fly/${INFO[0]}/releases/latest/download/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME} echo -e "Verifying HASH key..." HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/latest/download/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')" [ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; } done - name: Create ZIP archive shell: bash run: | pushd build_assets || exit 1 touch -mt $(date +%Y01010000) * zip -9vr ../V2bX-$ASSET_NAME.zip . popd || exit 1 FILE=./V2bX-$ASSET_NAME.zip DGST=$FILE.dgst for METHOD in {"md5","sha1","sha256","sha512"} do openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST done - name: Change the name run: | mv build_assets V2bX-$ASSET_NAME - name: Upload files to Artifacts uses: actions/upload-artifact@v3 with: name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }} path: | ./V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}/* - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 if: github.event_name == 'release' with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ./V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}.zip* tag: ${{ github.ref }} file_glob: true ================================================ FILE: .gitignore ================================================ example/config.yml example/main example/XrayR example/XrayR* example/mytest example/access.logo example/error.log api/chooseparser.go.bak app/Inboundbuilder/.lego/ app/legocmd/.lego/ .vscode/launch.json example/.lego example/cert ./vscode .idea/* ================================================ FILE: LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: README.md ================================================ # V2bX **项目转移至[V2bX](https://github.com/InazumaV/V2bX),此仓库不再维护** [![](https://img.shields.io/badge/TgChat-%E4%BA%A4%E6%B5%81%E7%BE%A4-blue)](https://t.me/YuzukiProjects) A V2board node server based on multi core, modified from XrayR. 一个基于多种内核的V2board节点服务端,修改自XrayR,支持V2ay,Trojan,Shadowsocks协议。 **注意:1.1.0将更换为V2board1.7.0之后新增的Api,原Api将被移除,请1.7.0之前的用户使用1.1.0之前的版本。** ## 特点 * 永久开源且免费。 * 支持V2ray,Trojan, Shadowsocks多种协议。 * 支持Vless和XTLS等新特性。 * 支持单实例对接多节点,无需重复启动。 * 支持限制在线IP。 * 支持限制Tcp连接数。 * 支持节点端口级别、用户级别限速。 * 配置简单明了。 * 修改配置自动重启实例。 * 支持多种内核,易扩展。 * 支持条件编译,可仅编译需要的内核。 ## 功能介绍 | 功能 | v2ray | trojan | shadowsocks | hysteria | |-----------|-------|--------|-------------|----------| | 自动申请tls证书 | √ | √ | √ | √ | | 自动续签tls证书 | √ | √ | √ | √ | | 在线人数统计 | √ | √ | √ | √ | | 审计规则 | √ | √ | √ | | | 自定义DNS | √ | √ | √ | √ | | 在线IP数限制 | √ | √ | √ | √ | | 连接数限制 | √ | √ | √ | | | 跨节点IP数限制 | √ | √ | √ | | | 按照用户限速 | √ | √ | √ | | | 动态限速(未测试) | √ | √ | √ | | ## TODO - [ ] 重新实现动态限速 - [ ] 重新实现在线IP同步(跨节点在线IP限制) - [x] 集成基本操作Command(Start, Stop, Restart, Status, Uninstall) - [ ] 完善Hysteria内核支持 - [ ] 完善使用文档 - [ ] 尽可能统一日志输出格式 ## 软件安装 ### 一键安装 ``` wget -N https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.sh && bash install.sh ``` ### 手动安装 [手动安装教程(过时待更新)](https://yuzuki-1.gitbook.io/v2bx-doc/xrayr-xia-zai-he-an-zhuang/install/manual) ## 构建 ``` bash # 通过-tag选项指定要编译的内核, 可选 xray, hy go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD} -tags "xray hy" ``` ## 配置文件及详细使用教程 [详细使用教程](https://yuzuki-1.gitbook.io/v2bx-doc/) ## 免责声明 * 此项目用于本人自用,因此本人不能保证向后兼容性。 * 由于本人能力有限,不能保证所有功能的可用性,如果出现问题请在Issues反馈。 * 本人不对任何人使用本项目造成的任何后果承担责任。 * 本人比较多变,因此本项目可能会随想法或思路的变动随性更改项目结构或大规模重构代码,若不能接受请勿使用。 ## Thanks * [Project X](https://github.com/XTLS/) * [V2Fly](https://github.com/v2fly) * [VNet-V2ray](https://github.com/ProxyPanel/VNet-V2ray) * [Air-Universe](https://github.com/crossfw/Air-Universe) * [XrayR](https://github.com/XrayR/XrayR) ## Stars 增长记录 [![Stargazers over time](https://starchart.cc/Yuzuki616/V2bX.svg)](https://starchart.cc/Yuzuki616/V2bX) ================================================ FILE: api/iprecoder/interface.go ================================================ package iprecoder import ( "github.com/Yuzuki616/V2bX/limiter" ) type IpRecorder interface { SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.UserIpList, error) } ================================================ FILE: api/iprecoder/recorder.go ================================================ package iprecoder import ( "errors" "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" "github.com/go-resty/resty/v2" "github.com/goccy/go-json" "time" ) type Recorder struct { client *resty.Client *conf.RecorderConfig } func NewRecorder(c *conf.RecorderConfig) *Recorder { return &Recorder{ client: resty.New().SetTimeout(time.Duration(c.Timeout) * time.Second), RecorderConfig: c, } } func (r *Recorder) SyncOnlineIp(ips []limiter.UserIpList) ([]limiter.UserIpList, error) { rsp, err := r.client.R(). SetBody(ips). Post(r.Url + "/api/v1/SyncOnlineIp?token=" + r.Token) if err != nil { return nil, err } if rsp.StatusCode() != 200 { return nil, errors.New(rsp.String()) } ips = []limiter.UserIpList{} err = json.Unmarshal(rsp.Body(), &ips) if err != nil { return nil, err } return ips, nil } ================================================ FILE: api/iprecoder/redis.go ================================================ package iprecoder import ( "context" "fmt" "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" "github.com/go-redis/redis/v8" "strconv" "time" ) type Redis struct { *conf.RedisConfig client *redis.Client } func NewRedis(c *conf.RedisConfig) *Redis { return &Redis{ RedisConfig: c, client: redis.NewClient(&redis.Options{ Addr: c.Address, Password: c.Password, DB: c.Db, }), } } func (r *Redis) SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.UserIpList, error) { ctx := context.Background() for i := range Ips { err := r.client.SAdd(ctx, "UserList", Ips[i].Uid).Err() if err != nil { return nil, fmt.Errorf("add user failed: %s", err) } r.client.Expire(ctx, "UserList", time.Second*time.Duration(r.Expiry)) for _, ip := range Ips[i].IpList { err := r.client.SAdd(ctx, strconv.Itoa(Ips[i].Uid), ip).Err() if err != nil { return nil, fmt.Errorf("add ip failed: %s", err) } r.client.Expire(ctx, strconv.Itoa(Ips[i].Uid), time.Second*time.Duration(r.Expiry)) } } c := r.client.SMembers(ctx, "UserList") if c.Err() != nil { return nil, fmt.Errorf("get user list failed: %s", c.Err()) } Ips = make([]limiter.UserIpList, 0, len(c.Val())) for _, uid := range c.Val() { uidInt, err := strconv.Atoi(uid) if err != nil { return nil, fmt.Errorf("convert uid failed: %s", err) } ips := r.client.SMembers(ctx, uid) if ips.Err() != nil { return nil, fmt.Errorf("get ip list failed: %s", ips.Err()) } Ips = append(Ips, limiter.UserIpList{ Uid: uidInt, IpList: ips.Val(), }) } return Ips, nil } ================================================ FILE: api/iprecoder/redis_test.go ================================================ package iprecoder import ( "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" "log" "testing" ) func TestRedis_SyncOnlineIp(t *testing.T) { r := NewRedis(&conf.RedisConfig{ Address: "127.0.0.1:6379", Password: "", Db: 0, }) users, err := r.SyncOnlineIp([]limiter.UserIpList{ {1, []string{"5.5.5.5", "4.4.4.4"}}, }) if err != nil { t.Fatal(err) } log.Println(users) } ================================================ FILE: api/panel/node.go ================================================ package panel import ( "bytes" "encoding/base64" "fmt" "os" "reflect" "strconv" "strings" "time" "github.com/Yuzuki616/V2bX/common/crypt" "github.com/goccy/go-json" log "github.com/sirupsen/logrus" coreConf "github.com/xtls/xray-core/infra/conf" ) type CommonNodeRsp struct { Host string `json:"host"` ServerPort int `json:"server_port"` ServerName string `json:"server_name"` Routes []Route `json:"routes"` BaseConfig BaseConfig `json:"base_config"` } type Route struct { Id int `json:"id"` Match interface{} `json:"match"` Action string `json:"action"` //ActionValue interface{} `json:"action_value"` } type BaseConfig struct { PushInterval any `json:"push_interval"` PullInterval any `json:"pull_interval"` } type V2rayNodeRsp struct { Tls int `json:"tls"` Network string `json:"network"` NetworkSettings json.RawMessage `json:"networkSettings"` ServerName string `json:"server_name"` } type ShadowsocksNodeRsp struct { Cipher string `json:"cipher"` ServerKey string `json:"server_key"` } type HysteriaNodeRsp struct { UpMbps int `json:"up_mbps"` DownMbps int `json:"down_mbps"` Obfs string `json:"obfs"` } type NodeInfo struct { Id int Type string Rules Rules Host string Port int Network string ExtraConfig V2rayExtraConfig NetworkSettings json.RawMessage Tls bool ServerName string UpMbps int DownMbps int ServerKey string Cipher string HyObfs string PushInterval time.Duration PullInterval time.Duration } type Rules struct { Regexp []string Protocol []string } type V2rayExtraConfig struct { EnableVless string `json:"EnableVless"` VlessFlow string `json:"VlessFlow"` EnableReality string `json:"EnableReality"` RealityConfig *RealityConfig `json:"RealityConfig"` } type RealityConfig struct { Dest interface{} `yaml:"Dest" json:"Dest"` Xver string `yaml:"Xver" json:"Xver"` ServerNames []string `yaml:"ServerNames" json:"ServerNames"` PrivateKey string `yaml:"PrivateKey" json:"PrivateKey"` MinClientVer string `yaml:"MinClientVer" json:"MinClientVer"` MaxClientVer string `yaml:"MaxClientVer" json:"MaxClientVer"` MaxTimeDiff string `yaml:"MaxTimeDiff" json:"MaxTimeDiff"` ShortIds []string `yaml:"ShortIds" json:"ShortIds"` } func (c *Client) GetNodeInfo() (node *NodeInfo, err error) { const path = "/api/v1/server/UniProxy/config" r, err := c.client. R(). SetHeader("If-None-Match", c.nodeEtag). Get(path) if err = c.checkResponse(r, path, err); err != nil { return } if r.StatusCode() == 304 { return nil, nil } // parse common params node = &NodeInfo{ Id: c.NodeId, Type: c.NodeType, } common := CommonNodeRsp{} err = json.Unmarshal(r.Body(), &common) if err != nil { return nil, fmt.Errorf("decode common params error: %s", err) } for i := range common.Routes { // parse rules from routes var matchs []string if _, ok := common.Routes[i].Match.(string); ok { matchs = strings.Split(common.Routes[i].Match.(string), ",") } else if _, ok = common.Routes[i].Match.([]string); ok { matchs = common.Routes[i].Match.([]string) } else { temp := common.Routes[i].Match.([]interface{}) matchs = make([]string, len(temp)) for i := range temp { matchs[i] = temp[i].(string) } } switch common.Routes[i].Action { case "block": for _, v := range matchs { if strings.HasPrefix(v, "protocol:") { // protocol node.Rules.Protocol = append(node.Rules.Protocol, strings.TrimPrefix(v, "protocol:")) } else { // domain node.Rules.Regexp = append(node.Rules.Regexp, strings.TrimPrefix(v, "regexp:")) } } case "dns": if matchs[0] != "main" { break } dnsPath := os.Getenv("XRAY_DNS_PATH") if dnsPath == "" { break } dns := []byte(strings.Join(matchs[1:], "")) currentData, err := os.ReadFile(dnsPath) if err != nil { log.WithField("err", err).Panic("Failed to read XRAY_DNS_PATH") break } if !bytes.Equal(currentData, dns) { coreDnsConfig := &coreConf.DNSConfig{} if err = json.NewDecoder(bytes.NewReader(dns)).Decode(coreDnsConfig); err != nil { log.WithField("err", err).Panic("Failed to unmarshal DNS config") } _, err := coreDnsConfig.Build() if err != nil { log.WithField("err", err).Panic("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help") break } if err = os.Truncate(dnsPath, 0); err != nil { log.WithField("err", err).Panic("Failed to clear XRAY DNS PATH file") } if err = os.WriteFile(dnsPath, dns, 0644); err != nil { log.WithField("err", err).Panic("Failed to write DNS to XRAY DNS PATH file") } } } } node.ServerName = common.ServerName node.Host = common.Host node.Port = common.ServerPort node.PullInterval = intervalToTime(common.BaseConfig.PullInterval) node.PushInterval = intervalToTime(common.BaseConfig.PushInterval) // parse protocol params switch c.NodeType { case "v2ray": rsp := V2rayNodeRsp{} err = json.Unmarshal(r.Body(), &rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } node.Network = rsp.Network node.NetworkSettings = rsp.NetworkSettings node.ServerName = rsp.ServerName if rsp.Tls == 1 { node.Tls = true } err = json.Unmarshal(rsp.NetworkSettings, &node.ExtraConfig) if err != nil { return nil, fmt.Errorf("decode v2ray extra error: %s", err) } if node.ExtraConfig.EnableReality == "true" { if node.ExtraConfig.RealityConfig == nil { node.ExtraConfig.EnableReality = "false" } else { key := crypt.GenX25519Private([]byte(strconv.Itoa(c.NodeId) + c.NodeType + c.Token + node.ExtraConfig.RealityConfig.PrivateKey)) node.ExtraConfig.RealityConfig.PrivateKey = base64.RawURLEncoding.EncodeToString(key) } } case "shadowsocks": rsp := ShadowsocksNodeRsp{} err = json.Unmarshal(r.Body(), &rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } node.ServerKey = rsp.ServerKey node.Cipher = rsp.Cipher case "trojan": node.Tls = true case "hysteria": rsp := HysteriaNodeRsp{} err = json.Unmarshal(r.Body(), &rsp) if err != nil { return nil, fmt.Errorf("decode v2ray params error: %s", err) } node.DownMbps = rsp.DownMbps node.UpMbps = rsp.UpMbps node.HyObfs = rsp.Obfs } c.nodeEtag = r.Header().Get("ETag") return } func intervalToTime(i interface{}) time.Duration { switch reflect.TypeOf(i).Kind() { case reflect.Int: return time.Duration(i.(int)) * time.Second case reflect.String: i, _ := strconv.Atoi(i.(string)) return time.Duration(i) * time.Second case reflect.Float64: return time.Duration(i.(float64)) * time.Second default: return time.Duration(reflect.ValueOf(i).Int()) * time.Second } } ================================================ FILE: api/panel/node_test.go ================================================ package panel import ( "github.com/Yuzuki616/V2bX/conf" "log" "testing" ) var client *Client func init() { c, err := New(&conf.ApiConfig{ APIHost: "http://127.0.0.1", Key: "token", NodeType: "V2ray", NodeID: 1, }) if err != nil { log.Panic(err) } client = c } func TestClient_GetNodeInfo(t *testing.T) { log.Println(client.GetNodeInfo()) log.Println(client.GetNodeInfo()) } func TestClient_ReportUserTraffic(t *testing.T) { log.Println(client.ReportUserTraffic([]UserTraffic{ { UID: 10372, Upload: 1000, Download: 1000, }, })) } ================================================ FILE: api/panel/panel.go ================================================ package panel import ( "bufio" "fmt" "log" "os" "regexp" "strconv" "strings" "time" "github.com/Yuzuki616/V2bX/conf" "github.com/go-resty/resty/v2" ) // Panel is the interface for different panel's api. type Client struct { client *resty.Client APIHost string Token string NodeType string NodeId int LocalRuleList []*regexp.Regexp nodeEtag string userEtag string } func New(c *conf.ApiConfig) (*Client, error) { client := resty.New() client.SetRetryCount(3) if c.Timeout > 0 { client.SetTimeout(time.Duration(c.Timeout) * time.Second) } else { client.SetTimeout(5 * time.Second) } client.OnError(func(req *resty.Request, err error) { if v, ok := err.(*resty.ResponseError); ok { // v.Response contains the last response from the server // v.Err contains the original error log.Print(v.Err) } }) client.SetBaseURL(c.APIHost) // Check node type c.NodeType = strings.ToLower(c.NodeType) switch c.NodeType { case "v2ray", "trojan", "shadowsocks", "hysteria": default: return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType) } // set params client.SetQueryParams(map[string]string{ "node_type": c.NodeType, "node_id": strconv.Itoa(c.NodeID), "token": c.Key, }) // Read local rule list localRuleList := readLocalRuleList(c.RuleListPath) return &Client{ client: client, Token: c.Key, APIHost: c.APIHost, NodeType: c.NodeType, NodeId: c.NodeID, LocalRuleList: localRuleList, }, nil } // readLocalRuleList reads the local rule list file func readLocalRuleList(path string) (LocalRuleList []*regexp.Regexp) { LocalRuleList = make([]*regexp.Regexp, 0) if path != "" { // open the file file, err := os.Open(path) //handle errors while opening if err != nil { log.Printf("Error when opening file: %s", err) return } fileScanner := bufio.NewScanner(file) // read line by line for fileScanner.Scan() { LocalRuleList = append(LocalRuleList, regexp.MustCompile(fileScanner.Text())) } // handle first encountered error while reading if err := fileScanner.Err(); err != nil { log.Fatalf("Error while reading file: %s", err) return } } return } ================================================ FILE: api/panel/user.go ================================================ package panel import ( "fmt" "github.com/goccy/go-json" ) type OnlineUser struct { UID int IP string } type UserInfo struct { Id int `json:"id"` Uuid string `json:"uuid"` SpeedLimit int `json:"speed_limit"` } type UserListBody struct { //Msg string `json:"msg"` Users []UserInfo `json:"users"` } // GetUserList will pull user form sspanel func (c *Client) GetUserList() (UserList []UserInfo, err error) { const path = "/api/v1/server/UniProxy/user" r, err := c.client.R(). SetHeader("If-None-Match", c.userEtag). Get(path) err = c.checkResponse(r, path, err) if err != nil { return nil, err } err = c.checkResponse(r, path, err) if r.StatusCode() == 304 { return nil, nil } var userList *UserListBody err = json.Unmarshal(r.Body(), &userList) if err != nil { return nil, fmt.Errorf("unmarshal userlist error: %s", err) } c.userEtag = r.Header().Get("ETag") return userList.Users, nil } type UserTraffic struct { UID int Upload int64 Download int64 } // ReportUserTraffic reports the user traffic func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error { data := make(map[int][]int64, len(userTraffic)) for i := range userTraffic { data[userTraffic[i].UID] = []int64{userTraffic[i].Upload, userTraffic[i].Download} } const path = "/api/v1/server/UniProxy/push" r, err := c.client.R(). SetBody(data). ForceContentType("application/json"). Post(path) err = c.checkResponse(r, path, err) if err != nil { return err } return nil } ================================================ FILE: api/panel/utils.go ================================================ package panel import ( "fmt" "github.com/go-resty/resty/v2" path2 "path" ) // Debug set the client debug for client func (c *Client) Debug() { c.client.SetDebug(true) } func (c *Client) assembleURL(path string) string { return path2.Join(c.APIHost + path) } func (c *Client) checkResponse(res *resty.Response, path string, err error) error { if err != nil { return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err) } if res.StatusCode() >= 400 { body := res.Body() return fmt.Errorf("request %s failed: %s", c.assembleURL(path), string(body)) } return nil } ================================================ FILE: cmd/action_linux.go ================================================ package cmd import ( "fmt" "github.com/Yuzuki616/V2bX/common/exec" "github.com/spf13/cobra" "time" ) var ( startCommand = cobra.Command{ Use: "start", Short: "Start V2bX service", Run: startHandle, } stopCommand = cobra.Command{ Use: "stop", Short: "Stop V2bX service", Run: stopHandle, } restartCommand = cobra.Command{ Use: "restart", Short: "Restart V2bX service", Run: restartHandle, } logCommand = cobra.Command{ Use: "log", Short: "Output V2bX log", Run: func(_ *cobra.Command, _ []string) { exec.RunCommandStd("journalctl", "-u", "V2bX.service", "-e", "--no-pager", "-f") }, } ) func init() { command.AddCommand(&startCommand) command.AddCommand(&stopCommand) command.AddCommand(&restartCommand) command.AddCommand(&logCommand) } func startHandle(_ *cobra.Command, _ []string) { r, err := checkRunning() if err != nil { fmt.Println(Err("check status error: ", err)) fmt.Println(Err("V2bX启动失败")) return } if r { fmt.Println(Ok("V2bX已运行,无需再次启动,如需重启请选择重启")) } _, err = exec.RunCommandByShell("systemctl start V2bX.service") if err != nil { fmt.Println(Err("exec start cmd error: ", err)) fmt.Println(Err("V2bX启动失败")) return } time.Sleep(time.Second * 3) r, err = checkRunning() if err != nil { fmt.Println(Err("check status error: ", err)) fmt.Println(Err("V2bX启动失败")) } if !r { fmt.Println(Err("V2bX可能启动失败,请稍后使用 V2bX log 查看日志信息")) return } fmt.Println(Ok("V2bX 启动成功,请使用 V2bX log 查看运行日志")) } func stopHandle(_ *cobra.Command, _ []string) { _, err := exec.RunCommandByShell("systemctl stop V2bX.service") if err != nil { fmt.Println(Err("exec stop cmd error: ", err)) fmt.Println(Err("V2bX停止失败")) return } time.Sleep(2 * time.Second) r, err := checkRunning() if err != nil { fmt.Println(Err("check status error:", err)) fmt.Println(Err("V2bX停止失败")) return } if r { fmt.Println(Err("V2bX停止失败,可能是因为停止时间超过了两秒,请稍后查看日志信息")) return } fmt.Println(Ok("V2bX 停止成功")) } func restartHandle(_ *cobra.Command, _ []string) { _, err := exec.RunCommandByShell("systemctl restart V2bX.service") if err != nil { fmt.Println(Err("exec restart cmd error: ", err)) fmt.Println(Err("V2bX重启失败")) return } r, err := checkRunning() if err != nil { fmt.Println(Err("check status error: ", err)) fmt.Println(Err("V2bX重启失败")) return } if !r { fmt.Println(Err("V2bX可能启动失败,请稍后使用 V2bX log 查看日志信息")) return } fmt.Println(Ok("V2bX重启成功")) } ================================================ FILE: cmd/cmd.go ================================================ package cmd import ( log "github.com/sirupsen/logrus" _ "github.com/Yuzuki616/V2bX/core/imports" "github.com/spf13/cobra" ) var command = &cobra.Command{ Use: "V2bX", } func Run() { err := command.Execute() if err != nil { log.WithField("err", err).Error("Execute command failed") } } ================================================ FILE: cmd/common.go ================================================ package cmd import ( "fmt" "github.com/Yuzuki616/V2bX/common/exec" "strings" ) const ( red = "\033[0;31m" green = "\033[0;32m" yellow = "\033[0;33m" plain = "\033[0m" ) func checkRunning() (bool, error) { o, err := exec.RunCommandByShell("systemctl status V2bX | grep Active") if err != nil { return false, err } return strings.Contains(o, "running"), nil } func Err(msg ...any) string { return red + fmt.Sprint(msg...) + plain } func Ok(msg ...any) string { return green + fmt.Sprint(msg...) + plain } func Warn(msg ...any) string { return yellow + fmt.Sprint(msg...) + plain } ================================================ FILE: cmd/common_test.go ================================================ package cmd import "testing" func Test_printFailed(t *testing.T) { t.Log(Err("test")) } ================================================ FILE: cmd/install_linux.go ================================================ package cmd import ( "fmt" "github.com/Yuzuki616/V2bX/common/exec" "github.com/spf13/cobra" "os" "strings" ) var targetVersion string var ( updateCommand = cobra.Command{ Use: "update", Short: "Update V2bX version", Run: func(_ *cobra.Command, _ []string) { exec.RunCommandStd("bash", "<(curl -Ls https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.sh)", targetVersion) }, Args: cobra.NoArgs, } uninstallCommand = cobra.Command{ Use: "uninstall", Short: "Uninstall V2bX", Run: uninstallHandle, } ) func init() { updateCommand.PersistentFlags().StringVar(&targetVersion, "version", "", "update target version") command.AddCommand(&updateCommand) command.AddCommand(&uninstallCommand) } func uninstallHandle(_ *cobra.Command, _ []string) { var yes string fmt.Println(Warn("确定要卸载 V2bX 吗?(Y/n)")) fmt.Scan(&yes) if strings.ToLower(yes) != "y" { fmt.Println("已取消卸载") } _, err := exec.RunCommandByShell("systemctl stop V2bX&&systemctl disable V2bX") if err != nil { fmt.Println(Err("exec cmd error: ", err)) fmt.Println(Err("卸载失败")) return } _ = os.RemoveAll("/etc/systemd/system/V2bX.service") _ = os.RemoveAll("/etc/V2bX/") _ = os.RemoveAll("/usr/local/V2bX/") _ = os.RemoveAll("/bin/V2bX") _, err = exec.RunCommandByShell("systemctl daemon-reload&&systemctl reset-failed") if err != nil { fmt.Println(Err("exec cmd error: ", err)) fmt.Println(Err("卸载失败")) return } fmt.Println(Ok("卸载成功")) } ================================================ FILE: cmd/server.go ================================================ package cmd import ( log "github.com/sirupsen/logrus" "os" "os/signal" "runtime" "syscall" vCore "github.com/Yuzuki616/V2bX/core" "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" "github.com/Yuzuki616/V2bX/node" "github.com/spf13/cobra" ) var ( config string watch bool ) var serverCommand = cobra.Command{ Use: "server", Short: "Run V2bX server", Run: serverHandle, Args: cobra.NoArgs, } func init() { serverCommand.PersistentFlags(). StringVarP(&config, "config", "c", "/etc/V2bX/config.yml", "config file path") serverCommand.PersistentFlags(). BoolVarP(&watch, "watch", "w", true, "watch file path change") command.AddCommand(&serverCommand) } func serverHandle(_ *cobra.Command, _ []string) { showVersion() c := conf.New() err := c.LoadFromPath(config) if err != nil { log.WithField("err", err).Error("Load config file failed") return } limiter.Init() log.Info("Start V2bX...") vc, err := vCore.NewCore(&c.CoreConfig) if err != nil { log.WithField("err", err).Error("new core failed") return } err = vc.Start() if err != nil { log.WithField("err", err).Error("Start core failed") return } defer vc.Close() nodes := node.New() err = nodes.Start(c.NodesConfig, vc) if err != nil { log.WithField("err", err).Error("Run nodes failed") return } dns := os.Getenv("XRAY_DNS_PATH") if watch { err = c.Watch(config, dns, func() { nodes.Close() err = vc.Close() if err != nil { log.WithField("err", err).Error("Restart node failed") return } vc, err = vCore.NewCore(&c.CoreConfig) if err != nil { log.WithField("err", err).Error("New core failed") return } err = vc.Start() if err != nil { log.WithField("err", err).Error("Start core failed") return } err = nodes.Start(c.NodesConfig, vc) if err != nil { log.WithField("err", err).Error("Run nodes failed") return } runtime.GC() }) if err != nil { log.WithField("err", err).Error("start watch failed") return } } // clear memory runtime.GC() // wait exit signal { osSignals := make(chan os.Signal, 1) signal.Notify(osSignals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM) <-osSignals } } ================================================ FILE: cmd/server_test.go ================================================ package cmd import "testing" func TestRun(t *testing.T) { Run() } ================================================ FILE: cmd/synctime.go ================================================ package cmd import ( "fmt" "github.com/Yuzuki616/V2bX/common/systime" "github.com/beevik/ntp" "github.com/spf13/cobra" ) var ntpServer string var commandSyncTime = &cobra.Command{ Use: "synctime", Short: "Sync time from ntp server", Args: cobra.NoArgs, Run: synctimeHandle, } func init() { commandSyncTime.Flags().StringVar(&ntpServer, "server", "time.apple.com", "ntp server") command.AddCommand(commandSyncTime) } func synctimeHandle(_ *cobra.Command, _ []string) { t, err := ntp.Time(ntpServer) if err != nil { fmt.Println(Err("get time from server error: ", err)) fmt.Println(Err("同步时间失败")) return } err = systime.SetSystemTime(t) if err != nil { fmt.Println(Err("set system time error: ", err)) fmt.Println(Err("同步时间失败")) return } fmt.Println(Ok("同步时间成功")) } ================================================ FILE: cmd/version.go ================================================ package cmd import ( "fmt" "strings" vCore "github.com/Yuzuki616/V2bX/core" "github.com/spf13/cobra" ) var ( version = "TempVersion" //use ldflags replace codename = "V2bX" intro = "A V2board backend based on multi core" ) var versionCommand = cobra.Command{ Use: "version", Short: "Print version info", Run: func(_ *cobra.Command, _ []string) { showVersion() }, } func init() { command.AddCommand(&versionCommand) } func showVersion() { fmt.Printf("%s %s (%s) \n", codename, version, intro) fmt.Printf("Supported cores: %s\n", strings.Join(vCore.RegisteredCore(), ", ")) // Warning fmt.Println(Warn("This version need V2board version >= 1.7.0.")) fmt.Println(Warn("The version have many changed for config, please check your config file")) } ================================================ FILE: cmd/x25519.go ================================================ package cmd import ( "crypto/rand" "encoding/base64" "fmt" "strings" "github.com/Yuzuki616/V2bX/common/crypt" "github.com/spf13/cobra" "golang.org/x/crypto/curve25519" ) var x25519Command = cobra.Command{ Use: "x25519", Short: "Generate key pair for x25519 key exchange", Run: func(cmd *cobra.Command, args []string) { executeX25519() }, } func init() { command.AddCommand(&x25519Command) } func executeX25519() { var output string var err error defer func() { fmt.Println(output) }() var privateKey []byte var publicKey []byte var yes, key string fmt.Println("要基于节点信息生成密钥吗?(Y/n)") fmt.Scan(&yes) if strings.ToLower(yes) == "y" { var temp string fmt.Println("请输入节点id:") fmt.Scan(&temp) key = temp fmt.Println("请输入节点类型:") fmt.Scan(&temp) key += strings.ToLower(temp) fmt.Println("请输入Token:") fmt.Scan(&temp) key += temp privateKey = crypt.GenX25519Private([]byte(key)) } else { privateKey = make([]byte, curve25519.ScalarSize) if _, err = rand.Read(privateKey); err != nil { output = Err("read rand error: ", err) return } } if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil { output = Err("gen X25519 error: ", err) return } p := base64.RawURLEncoding.EncodeToString(privateKey) output = fmt.Sprint("Private key: ", p, "\nPublic key: ", base64.RawURLEncoding.EncodeToString(publicKey)) } ================================================ FILE: cmd/x25519_test.go ================================================ package cmd import "testing" func Test_executeX25519(t *testing.T) { executeX25519() } ================================================ FILE: common/crypt/aes.go ================================================ package crypt import ( "crypto/aes" "encoding/base64" ) func AesEncrypt(data []byte, key []byte) (string, error) { a, err := aes.NewCipher(key) if err != nil { return "", err } en := make([]byte, len(data)) a.Encrypt(en, data) return base64.StdEncoding.EncodeToString(en), nil } func AesDecrypt(data string, key []byte) (string, error) { d, err := base64.StdEncoding.DecodeString(data) if err != nil { return "", err } a, err := aes.NewCipher(key) if err != nil { return "", err } de := make([]byte, len(data)) a.Decrypt(de, d) return string(de), nil } ================================================ FILE: common/crypt/x25519.go ================================================ package crypt import ( "crypto/sha256" ) func GenX25519Private(data []byte) []byte { key := sha256.Sum256(data) key[0] &= 248 key[31] &= 127 key[31] |= 64 return key[:32] } ================================================ FILE: common/exec/exec.go ================================================ package exec import ( "errors" "os" "os/exec" ) func RunCommandByShell(cmd string) (string, error) { e := exec.Command("bash", "-c", cmd) out, err := e.CombinedOutput() if errors.Unwrap(err) == exec.ErrNotFound { e = exec.Command("sh", "-c", cmd) out, err = e.CombinedOutput() } return string(out), err } func RunCommandStd(name string, args ...string) { e := exec.Command(name, args...) e.Stdout = os.Stdout e.Stdin = os.Stdin e.Stderr = os.Stderr _ = e.Run() } ================================================ FILE: common/file/file.go ================================================ package file import "os" func IsExist(path string) bool { _, err := os.Stat(path) return err == nil || !os.IsNotExist(err) } ================================================ FILE: common/format/user.go ================================================ package format import ( "fmt" ) func UserTag(tag string, uuid string) string { return fmt.Sprintf("%s|%s", tag, uuid) } ================================================ FILE: common/rate/rate.go ================================================ package rate import ( "github.com/juju/ratelimit" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" ) type Writer struct { writer buf.Writer limiter *ratelimit.Bucket } func NewRateLimitWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer { return &Writer{ writer: writer, limiter: limiter, } } func (w *Writer) Close() error { return common.Close(w.writer) } func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error { w.limiter.Wait(int64(mb.Len())) return w.writer.WriteMultiBuffer(mb) } ================================================ FILE: common/systime/time_stub.go ================================================ //go:build !(windows || linux || darwin) package systime import ( "os" "time" ) func SetSystemTime(nowTime time.Time) error { return os.ErrInvalid } ================================================ FILE: common/systime/time_unix.go ================================================ //go:build linux || darwin package systime import ( "time" "golang.org/x/sys/unix" ) func SetSystemTime(nowTime time.Time) error { timeVal := unix.NsecToTimeval(nowTime.UnixNano()) return unix.Settimeofday(&timeVal) } ================================================ FILE: common/systime/time_windows.go ================================================ package systime import ( "time" "unsafe" "golang.org/x/sys/windows" ) func SetSystemTime(nowTime time.Time) error { var systemTime windows.Systemtime systemTime.Year = uint16(nowTime.Year()) systemTime.Month = uint16(nowTime.Month()) systemTime.Day = uint16(nowTime.Day()) systemTime.Hour = uint16(nowTime.Hour()) systemTime.Minute = uint16(nowTime.Minute()) systemTime.Second = uint16(nowTime.Second()) systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") proc := dllKernel32.NewProc("SetSystemTime") _, _, err := proc.Call( uintptr(unsafe.Pointer(&systemTime)), ) if err != nil && err.Error() != "The operation completed successfully." { return err } return nil } ================================================ FILE: common/task/task.go ================================================ package task import ( "sync" "time" ) // Task is a task that runs periodically. type Task struct { // Interval of the task being run Interval time.Duration // Execute is the task function Execute func() error access sync.Mutex timer *time.Timer running bool } func (t *Task) hasClosed() bool { t.access.Lock() defer t.access.Unlock() return !t.running } func (t *Task) checkedExecute(first bool) error { if t.hasClosed() { return nil } t.access.Lock() defer t.access.Unlock() if first { if err := t.Execute(); err != nil { t.running = false return err } } if !t.running { return nil } t.timer = time.AfterFunc(t.Interval, func() { t.checkedExecute(true) }) return nil } // Start implements common.Runnable. func (t *Task) Start(first bool) error { t.access.Lock() if t.running { t.access.Unlock() return nil } t.running = true t.access.Unlock() if err := t.checkedExecute(first); err != nil { t.access.Lock() t.running = false t.access.Unlock() return err } return nil } // Close implements common.Closable. func (t *Task) Close() { t.access.Lock() defer t.access.Unlock() t.running = false if t.timer != nil { t.timer.Stop() t.timer = nil } } ================================================ FILE: common/task/task_test.go ================================================ package task import ( "log" "testing" "time" ) func TestTask(t *testing.T) { ts := Task{Execute: func() error { log.Println("q") return nil }, Interval: time.Second} ts.Start(false) } ================================================ FILE: conf/conf.go ================================================ package conf import ( "fmt" "gopkg.in/yaml.v3" "io" "os" ) type Conf struct { CoreConfig CoreConfig `yaml:"CoreConfig"` NodesConfig []*NodeConfig `yaml:"Nodes"` } func New() *Conf { return &Conf{ CoreConfig: CoreConfig{ Type: "xray", XrayConfig: &XrayConfig{ LogConfig: NewLogConfig(), AssetPath: "/etc/V2bX/", DnsConfigPath: "", InboundConfigPath: "", OutboundConfigPath: "", RouteConfigPath: "", ConnectionConfig: NewConnectionConfig(), }, }, NodesConfig: []*NodeConfig{}, } } func (p *Conf) LoadFromPath(filePath string) error { f, err := os.Open(filePath) if err != nil { return fmt.Errorf("open config file error: %s", err) } defer f.Close() content, err := io.ReadAll(f) if err != nil { return fmt.Errorf("read file error: %s", err) } err = yaml.Unmarshal(content, p) if err != nil { return fmt.Errorf("decode config error: %s", err) } old := &OldConfig{} err = yaml.Unmarshal(content, old) if err == nil { migrateOldConfig(p, old) } return nil } ================================================ FILE: conf/conf_test.go ================================================ package conf import ( "log" "testing" ) func TestConf_LoadFromPath(t *testing.T) { c := New() t.Log(c.LoadFromPath("../example/config.yml.example")) } func TestConf_Watch(t *testing.T) { c := New() log.Println(c.Watch("../example/config.yml.example", func() { log.Println(1) })) select {} } ================================================ FILE: conf/core.go ================================================ package conf type CoreConfig struct { Type string `yaml:"Type"` XrayConfig *XrayConfig `yaml:"XrayConfig"` } type XrayConfig struct { LogConfig *LogConfig `yaml:"Log"` AssetPath string `yaml:"AssetPath"` DnsConfigPath string `yaml:"DnsConfigPath"` RouteConfigPath string `yaml:"RouteConfigPath"` ConnectionConfig *ConnectionConfig `yaml:"ConnectionConfig"` InboundConfigPath string `yaml:"InboundConfigPath"` OutboundConfigPath string `yaml:"OutboundConfigPath"` } type ConnectionConfig struct { Handshake uint32 `yaml:"handshake"` ConnIdle uint32 `yaml:"connIdle"` UplinkOnly uint32 `yaml:"uplinkOnly"` DownlinkOnly uint32 `yaml:"downlinkOnly"` BufferSize int32 `yaml:"bufferSize"` } func NewConnectionConfig() *ConnectionConfig { return &ConnectionConfig{ Handshake: 4, ConnIdle: 30, UplinkOnly: 2, DownlinkOnly: 4, BufferSize: 64, } } ================================================ FILE: conf/log.go ================================================ package conf type LogConfig struct { Level string `yaml:"Level"` AccessPath string `yaml:"AccessPath"` ErrorPath string `yaml:"ErrorPath"` } func NewLogConfig() *LogConfig { return &LogConfig{ Level: "warning", AccessPath: "", ErrorPath: "", } } ================================================ FILE: conf/node.go ================================================ package conf type NodeConfig struct { ApiConfig *ApiConfig `yaml:"ApiConfig"` ControllerConfig *ControllerConfig `yaml:"ControllerConfig"` } type ApiConfig struct { APIHost string `yaml:"ApiHost"` NodeID int `yaml:"NodeID"` Key string `yaml:"ApiKey"` NodeType string `yaml:"NodeType"` Timeout int `yaml:"Timeout"` RuleListPath string `yaml:"RuleListPath"` } type ControllerConfig struct { ListenIP string `yaml:"ListenIP"` SendIP string `yaml:"SendIP"` XrayOptions XrayOptions `yaml:"XrayOptions"` HyOptions HyOptions `yaml:"HyOptions"` LimitConfig LimitConfig `yaml:"LimitConfig"` CertConfig *CertConfig `yaml:"CertConfig"` } type RealityConfig struct { Dest interface{} `yaml:"Dest" json:"Dest"` Xver uint64 `yaml:"Xver" json:"Xver"` ServerNames []string `yaml:"ServerNames" json:"ServerNames"` PrivateKey string `yaml:"PrivateKey" json:"PrivateKey"` MinClientVer string `yaml:"MinClientVer" json:"MinClientVer"` MaxClientVer string `yaml:"MaxClientVer" json:"MaxClientVer"` MaxTimeDiff uint64 `yaml:"MaxTimeDiff" json:"MaxTimeDiff"` ShortIds []string `yaml:"ShortIds" json:"ShortIds"` } type XrayOptions struct { EnableProxyProtocol bool `yaml:"EnableProxyProtocol"` EnableDNS bool `yaml:"EnableDNS"` DNSType string `yaml:"DNSType"` EnableUot bool `yaml:"EnableUot"` EnableTFO bool `yaml:"EnableTFO"` DisableIVCheck bool `yaml:"DisableIVCheck"` DisableSniffing bool `yaml:"DisableSniffing"` EnableFallback bool `yaml:"EnableFallback"` FallBackConfigs []FallBackConfig `yaml:"FallBackConfigs"` } type HyOptions struct { Resolver string `yaml:"Resolver"` ResolvePreference string `yaml:"ResolvePreference"` SendDevice string `yaml:"SendDevice"` } type LimitConfig struct { EnableRealtime bool `yaml:"EnableRealtime"` SpeedLimit int `yaml:"SpeedLimit"` IPLimit int `yaml:"DeviceLimit"` ConnLimit int `yaml:"ConnLimit"` EnableIpRecorder bool `yaml:"EnableIpRecorder"` IpRecorderConfig *IpReportConfig `yaml:"IpRecorderConfig"` EnableDynamicSpeedLimit bool `yaml:"EnableDynamicSpeedLimit"` DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `yaml:"DynamicSpeedLimitConfig"` } type FallBackConfig struct { SNI string `yaml:"SNI"` Alpn string `yaml:"Alpn"` Path string `yaml:"Path"` Dest string `yaml:"Dest"` ProxyProtocolVer uint64 `yaml:"ProxyProtocolVer"` } type RecorderConfig struct { Url string `yaml:"Url"` Token string `yaml:"Token"` Timeout int `yaml:"Timeout"` } type RedisConfig struct { Address string `yaml:"Address"` Password string `yaml:"Password"` Db int `yaml:"Db"` Expiry int `json:"Expiry"` } type IpReportConfig struct { Periodic int `yaml:"Periodic"` Type string `yaml:"Type"` RecorderConfig *RecorderConfig `yaml:"RecorderConfig"` RedisConfig *RedisConfig `yaml:"RedisConfig"` EnableIpSync bool `yaml:"EnableIpSync"` } type DynamicSpeedLimitConfig struct { Periodic int `yaml:"Periodic"` Traffic int64 `yaml:"Traffic"` SpeedLimit int `yaml:"SpeedLimit"` ExpireTime int `yaml:"ExpireTime"` } type CertConfig struct { CertMode string `yaml:"CertMode"` // none, file, http, dns RejectUnknownSni bool `yaml:"RejectUnknownSni"` CertDomain string `yaml:"CertDomain"` CertFile string `yaml:"CertFile"` KeyFile string `yaml:"KeyFile"` Provider string `yaml:"Provider"` // alidns, cloudflare, gandi, godaddy.... Email string `yaml:"Email"` DNSEnv map[string]string `yaml:"DNSEnv"` RealityConfig *RealityConfig `yaml:"RealityConfig"` } ================================================ FILE: conf/old.go ================================================ package conf import "log" type OldConfig struct { NodesConfig []*struct { ApiConfig *OldApiConfig `yaml:"ApiConfig"` ControllerConfig *OldControllerConfig `yaml:"ControllerConfig"` } `yaml:"Nodes"` } type OldControllerConfig struct { ListenIP string `yaml:"ListenIP"` SendIP string `yaml:"SendIP"` EnableDNS bool `yaml:"EnableDNS"` DNSType string `yaml:"DNSType"` DisableUploadTraffic bool `yaml:"DisableUploadTraffic"` DisableGetRule bool `yaml:"DisableGetRule"` EnableProxyProtocol bool `yaml:"EnableProxyProtocol"` EnableFallback bool `yaml:"EnableFallback"` DisableIVCheck bool `yaml:"DisableIVCheck"` DisableSniffing bool `yaml:"DisableSniffing"` FallBackConfigs []*FallBackConfig `yaml:"FallBackConfigs"` EnableIpRecorder bool `yaml:"EnableIpRecorder"` IpRecorderConfig *IpReportConfig `yaml:"IpRecorderConfig"` EnableDynamicSpeedLimit bool `yaml:"EnableDynamicSpeedLimit"` DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `yaml:"DynamicSpeedLimitConfig"` CertConfig *CertConfig `yaml:"CertConfig"` } type OldApiConfig struct { APIHost string `yaml:"ApiHost"` NodeID int `yaml:"NodeID"` Key string `yaml:"ApiKey"` NodeType string `yaml:"NodeType"` EnableVless bool `yaml:"EnableVless"` Timeout int `yaml:"Timeout"` SpeedLimit int `yaml:"SpeedLimit"` DeviceLimit int `yaml:"DeviceLimit"` RuleListPath string `yaml:"RuleListPath"` DisableCustomConfig bool `yaml:"DisableCustomConfig"` } func migrateOldConfig(c *Conf, old *OldConfig) { changed := false for i, n := range c.NodesConfig { if i >= len(old.NodesConfig) { break } // limit config if old.NodesConfig[i].ApiConfig.SpeedLimit != 0 { n.ControllerConfig.LimitConfig.SpeedLimit = old.NodesConfig[i].ApiConfig.SpeedLimit changed = true } if old.NodesConfig[i].ApiConfig.DeviceLimit != 0 { n.ControllerConfig.LimitConfig.IPLimit = old.NodesConfig[i].ApiConfig.DeviceLimit changed = true } if old.NodesConfig[i].ControllerConfig.EnableDynamicSpeedLimit { n.ControllerConfig.LimitConfig.EnableDynamicSpeedLimit = true changed = true } if old.NodesConfig[i].ControllerConfig.DynamicSpeedLimitConfig != nil { n.ControllerConfig.LimitConfig.DynamicSpeedLimitConfig = old.NodesConfig[i].ControllerConfig.DynamicSpeedLimitConfig changed = true } if old.NodesConfig[i].ControllerConfig.EnableIpRecorder { n.ControllerConfig.LimitConfig.EnableIpRecorder = true changed = true } if old.NodesConfig[i].ControllerConfig.IpRecorderConfig != nil { n.ControllerConfig.LimitConfig.IpRecorderConfig = old.NodesConfig[i].ControllerConfig.IpRecorderConfig changed = true } } if changed { log.Println("Warning: This config file is old.") } } ================================================ FILE: conf/watch.go ================================================ package conf import ( "fmt" "github.com/fsnotify/fsnotify" "log" "path" "time" ) func (p *Conf) Watch(filePath, dnsPath string, reload func()) error { watcher, err := fsnotify.NewWatcher() if err != nil { return fmt.Errorf("new watcher error: %s", err) } go func() { var pre time.Time defer watcher.Close() for { select { case e := <-watcher.Events: if e.Has(fsnotify.Chmod) { continue } if pre.Add(10 * time.Second).After(time.Now()) { continue } pre = time.Now() go func() { time.Sleep(10 * time.Second) if e.Name == dnsPath { log.Println("DNS file changed, reloading...") } else { log.Println("config dir changed, reloading...") } *p = *New() err := p.LoadFromPath(filePath) if err != nil { log.Printf("reload config error: %s", err) } reload() log.Println("reload config success") }() case err := <-watcher.Errors: if err != nil { log.Printf("File watcher error: %s", err) } } } }() err = watcher.Add(path.Dir(filePath)) if err != nil { return fmt.Errorf("watch file error: %s", err) } if dnsPath != "" { err = watcher.Add(path.Dir(dnsPath)) if err != nil { return fmt.Errorf("watch dns file error: %s", err) } } return nil } ================================================ FILE: core/core.go ================================================ package core import ( "errors" "strings" "github.com/Yuzuki616/V2bX/conf" ) var ( cores = map[string]func(c *conf.CoreConfig) (Core, error){} ) func NewCore(c *conf.CoreConfig) (Core, error) { // multi core if types := strings.Split(c.Type, " "); len(types) > 1 { var cs []Core for _, t := range types { f, ok := cores[strings.ToLower(t)] if !ok { return nil, errors.New("unknown core type: " + t) } core1, err := f(c) if err != nil { return nil, err } cs = append(cs, core1) } return &Selector{ cores: cs, }, nil } // one core if f, ok := cores[strings.ToLower(c.Type)]; ok { return f(c) } else { return nil, errors.New("unknown core type") } } func RegisterCore(t string, f func(c *conf.CoreConfig) (Core, error)) { cores[t] = f } func RegisteredCore() []string { cs := make([]string, 0, len(cores)) for k := range cores { cs = append(cs, k) } return cs } ================================================ FILE: core/hy/config.go ================================================ package hy const ( mbpsToBps = 125000 minSpeedBPS = 16384 DefaultALPN = "hysteria" DefaultStreamReceiveWindow = 16777216 // 16 MB DefaultConnectionReceiveWindow = DefaultStreamReceiveWindow * 5 / 2 // 40 MB DefaultMaxIncomingStreams = 1024 DefaultMMDBFilename = "GeoLite2-Country.mmdb" ServerMaxIdleTimeoutSec = 60 DefaultClientIdleTimeoutSec = 20 DefaultClientHopIntervalSec = 10 ) func SpeedTrans(upM, downM int) (uint64, uint64) { up := uint64(upM) * mbpsToBps down := uint64(downM) * mbpsToBps return up, down } ================================================ FILE: core/hy/counter.go ================================================ package hy import ( "sync" "sync/atomic" ) type UserTrafficCounter struct { counters map[string]*counters lock sync.RWMutex } type counters struct { UpCounter atomic.Int64 DownCounter atomic.Int64 //ConnGauge atomic.Int64 } func NewUserTrafficCounter() *UserTrafficCounter { return &UserTrafficCounter{ counters: map[string]*counters{}, } } func (c *UserTrafficCounter) getCounters(auth string) *counters { c.lock.RLock() cts, ok := c.counters[auth] c.lock.RUnlock() if !ok { cts = &counters{} c.counters[auth] = cts } return cts } func (c *UserTrafficCounter) Rx(auth string, n int) { cts := c.getCounters(auth) cts.DownCounter.Add(int64(n)) } func (c *UserTrafficCounter) Tx(auth string, n int) { cts := c.getCounters(auth) cts.UpCounter.Add(int64(n)) } func (c *UserTrafficCounter) IncConn(_ string) { /*cts := c.getCounters(auth) cts.ConnGauge.Add(1)*/ return } func (c *UserTrafficCounter) DecConn(_ string) { /*cts := c.getCounters(auth) cts.ConnGauge.Add(1)*/ return } func (c *UserTrafficCounter) Reset(auth string) { cts := c.getCounters(auth) cts.UpCounter.Store(0) cts.DownCounter.Store(0) } func (c *UserTrafficCounter) Delete(auth string) { c.lock.Lock() delete(c.counters, auth) c.lock.Unlock() } ================================================ FILE: core/hy/counter_test.go ================================================ package hy import "testing" func TestUserTrafficCounter_Rx(t *testing.T) { } ================================================ FILE: core/hy/hy.go ================================================ package hy import ( "fmt" "sync" "github.com/Yuzuki616/V2bX/conf" vCore "github.com/Yuzuki616/V2bX/core" "github.com/hashicorp/go-multierror" ) func init() { vCore.RegisterCore("hy", NewHy) } type Hy struct { servers sync.Map } func NewHy(_ *conf.CoreConfig) (vCore.Core, error) { return &Hy{ servers: sync.Map{}, }, nil } func (h *Hy) Start() error { return nil } func (h *Hy) Close() error { var errs error h.servers.Range(func(tag, s any) bool { err := s.(*Server).Close() if err != nil { errs = multierror.Append(errs, fmt.Errorf("close %s error: %s", tag, err)) } return true }) if errs != nil { return errs } return nil } func (h *Hy) Protocols() []string { return []string{ "hysteria", } } ================================================ FILE: core/hy/ipmasker.go ================================================ package hy import ( "net" ) type ipMasker struct { IPv4Mask net.IPMask IPv6Mask net.IPMask } // Mask masks an address with the configured CIDR. // addr can be "host:port" or just host. func (m *ipMasker) Mask(addr string) string { if m.IPv4Mask == nil && m.IPv6Mask == nil { return addr } host, port, err := net.SplitHostPort(addr) if err != nil { // just host host, port = addr, "" } ip := net.ParseIP(host) if ip == nil { // not an IP address, return as is return addr } if ip4 := ip.To4(); ip4 != nil && m.IPv4Mask != nil { // IPv4 host = ip4.Mask(m.IPv4Mask).String() } else if ip6 := ip.To16(); ip6 != nil && m.IPv6Mask != nil { // IPv6 host = ip6.Mask(m.IPv6Mask).String() } if port != "" { return net.JoinHostPort(host, port) } else { return host } } var defaultIPMasker = &ipMasker{} ================================================ FILE: core/hy/kploader.go ================================================ package hy import ( "crypto/tls" "sync" "github.com/fsnotify/fsnotify" "github.com/sirupsen/logrus" ) type keypairLoader struct { certMu sync.RWMutex cert *tls.Certificate certPath string keyPath string } func newKeypairLoader(certPath, keyPath string) (*keypairLoader, error) { loader := &keypairLoader{ certPath: certPath, keyPath: keyPath, } cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, err } loader.cert = &cert watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err } go func() { for { select { case event, ok := <-watcher.Events: if !ok { return } switch event.Op { case fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Chmod: logrus.WithFields(logrus.Fields{ "file": event.Name, }).Info("Keypair change detected, reloading...") if err := loader.load(); err != nil { logrus.WithFields(logrus.Fields{ "error": err, }).Error("Failed to reload keypair") } else { logrus.Info("Keypair successfully reloaded") } case fsnotify.Remove: _ = watcher.Add(event.Name) // Workaround for vim // https://github.com/fsnotify/fsnotify/issues/92 } case err, ok := <-watcher.Errors: if !ok { return } logrus.WithFields(logrus.Fields{ "error": err, }).Error("Failed to watch keypair files for changes") } } }() err = watcher.Add(certPath) if err != nil { _ = watcher.Close() return nil, err } err = watcher.Add(keyPath) if err != nil { _ = watcher.Close() return nil, err } return loader, nil } func (kpr *keypairLoader) load() error { cert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath) if err != nil { return err } kpr.certMu.Lock() kpr.cert = &cert kpr.certMu.Unlock() return nil } func (kpr *keypairLoader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { kpr.certMu.RLock() defer kpr.certMu.RUnlock() return kpr.cert, nil } } ================================================ FILE: core/hy/mmdb.go ================================================ package hy import ( "os" "github.com/oschwald/geoip2-golang" "github.com/sirupsen/logrus" ) func loadMMDBReader(filename string) (*geoip2.Reader, error) { if _, err := os.Stat(filename); err != nil { if os.IsNotExist(err) { logrus.Info("GeoLite2 database not found, downloading...") logrus.WithFields(logrus.Fields{ "file": filename, }).Info("GeoLite2 database downloaded") return geoip2.Open(filename) } else { // some other error return nil, err } } else { // file exists, just open it return geoip2.Open(filename) } } ================================================ FILE: core/hy/node.go ================================================ package hy import ( "errors" "fmt" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" ) func (h *Hy) AddNode(tag string, info *panel.NodeInfo, c *conf.ControllerConfig) error { if info.Type != "hysteria" { return errors.New("the core not support " + info.Type) } switch c.CertConfig.CertMode { case "reality", "none", "": return errors.New("hysteria need normal tls cert") } l, err := limiter.GetLimiter(tag) if err != nil { return fmt.Errorf("get limiter error: %s", err) } s := NewServer(tag, l) err = s.runServer(info, c) if err != nil { return fmt.Errorf("run hy server error: %s", err) } h.servers.Store(tag, s) return nil } func (h *Hy) DelNode(tag string) error { if s, e := h.servers.Load(tag); e { err := s.(*Server).Close() if err != nil { return err } h.servers.Delete(tag) return nil } return errors.New("the node is not have") } ================================================ FILE: core/hy/resolver.go ================================================ package hy import ( "crypto/tls" "errors" "github.com/Yuzuki616/hysteria/core/utils" rdns "github.com/folbricht/routedns" "net" "net/url" "strings" ) var errInvalidSyntax = errors.New("invalid syntax") func setResolver(dns string) error { if net.ParseIP(dns) != nil { // Just an IP address, treat as UDP 53 dns = "udp://" + net.JoinHostPort(dns, "53") } var r rdns.Resolver if strings.HasPrefix(dns, "udp://") { // Standard UDP DNS resolver dns = strings.TrimPrefix(dns, "udp://") if dns == "" { return errInvalidSyntax } if _, _, err := utils.SplitHostPort(dns); err != nil { // Append the default DNS port dns = net.JoinHostPort(dns, "53") } client, err := rdns.NewDNSClient("dns-udp", dns, "udp", rdns.DNSClientOptions{}) if err != nil { return err } r = client } else if strings.HasPrefix(dns, "tcp://") { // Standard TCP DNS resolver dns = strings.TrimPrefix(dns, "tcp://") if dns == "" { return errInvalidSyntax } if _, _, err := utils.SplitHostPort(dns); err != nil { // Append the default DNS port dns = net.JoinHostPort(dns, "53") } client, err := rdns.NewDNSClient("dns-tcp", dns, "tcp", rdns.DNSClientOptions{}) if err != nil { return err } r = client } else if strings.HasPrefix(dns, "https://") { // DoH resolver if dohURL, err := url.Parse(dns); err != nil { return err } else { // Need to set bootstrap address to avoid loopback DNS lookup dohIPAddr, err := net.ResolveIPAddr("ip", dohURL.Hostname()) if err != nil { return err } client, err := rdns.NewDoHClient("doh", dns, rdns.DoHClientOptions{ BootstrapAddr: dohIPAddr.String(), }) if err != nil { return err } r = client } } else if strings.HasPrefix(dns, "tls://") { // DoT resolver dns = strings.TrimPrefix(dns, "tls://") if dns == "" { return errInvalidSyntax } dotHost, _, err := utils.SplitHostPort(dns) if err != nil { // Append the default DNS port dns = net.JoinHostPort(dns, "853") } // Need to set bootstrap address to avoid loopback DNS lookup dotIPAddr, err := net.ResolveIPAddr("ip", dotHost) if err != nil { return err } client, err := rdns.NewDoTClient("dot", dns, rdns.DoTClientOptions{ BootstrapAddr: dotIPAddr.String(), TLSConfig: new(tls.Config), }) if err != nil { return err } r = client } else if strings.HasPrefix(dns, "quic://") { // DoQ resolver dns = strings.TrimPrefix(dns, "quic://") if dns == "" { return errInvalidSyntax } doqHost, _, err := utils.SplitHostPort(dns) if err != nil { // Append the default DNS port dns = net.JoinHostPort(dns, "853") } // Need to set bootstrap address to avoid loopback DNS lookup doqIPAddr, err := net.ResolveIPAddr("ip", doqHost) if err != nil { return err } client, err := rdns.NewDoQClient("doq", dns, rdns.DoQClientOptions{ BootstrapAddr: doqIPAddr.String(), }) if err != nil { return err } r = client } else { return errInvalidSyntax } cache := rdns.NewCache("cache", r, rdns.CacheOptions{}) net.DefaultResolver = rdns.NewNetResolver(cache) return nil } ================================================ FILE: core/hy/server.go ================================================ package hy import ( "crypto/tls" "fmt" "io" "net" "sync" "sync/atomic" "time" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" "github.com/Yuzuki616/hysteria/core/sockopt" "github.com/Yuzuki616/quic-go" "github.com/Yuzuki616/hysteria/core/acl" "github.com/Yuzuki616/hysteria/core/cs" "github.com/Yuzuki616/hysteria/core/pktconns" "github.com/Yuzuki616/hysteria/core/pmtud" "github.com/Yuzuki616/hysteria/core/transport" "github.com/sirupsen/logrus" ) var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFactory{ "": pktconns.NewServerUDPConnFunc, "udp": pktconns.NewServerUDPConnFunc, "wechat": pktconns.NewServerWeChatConnFunc, "wechat-video": pktconns.NewServerWeChatConnFunc, "faketcp": pktconns.NewServerFakeTCPConnFunc, } type Server struct { tag string l *limiter.Limiter counter *UserTrafficCounter users sync.Map running atomic.Bool *cs.Server } func NewServer(tag string, l *limiter.Limiter) *Server { return &Server{ tag: tag, l: l, } } func (s *Server) runServer(node *panel.NodeInfo, c *conf.ControllerConfig) error { /*if c.HyOptions == nil { return errors.New("hy options is not vail") }*/ // Resolver if len(c.HyOptions.Resolver) > 0 { err := setResolver(c.HyOptions.Resolver) if err != nil { return fmt.Errorf("set resolver error: %s", err) } } // tls config kpl, err := newKeypairLoader(c.CertConfig.CertFile, c.CertConfig.KeyFile) if err != nil { return fmt.Errorf("load cert error: %s", err) } tlsConfig := &tls.Config{ GetCertificate: kpl.GetCertificateFunc(), NextProtos: []string{DefaultALPN}, MinVersion: tls.VersionTLS13, } // QUIC config quicConfig := &quic.Config{ InitialStreamReceiveWindow: DefaultStreamReceiveWindow, MaxStreamReceiveWindow: DefaultStreamReceiveWindow, InitialConnectionReceiveWindow: DefaultConnectionReceiveWindow, MaxConnectionReceiveWindow: DefaultConnectionReceiveWindow, MaxIncomingStreams: int64(DefaultMaxIncomingStreams), MaxIdleTimeout: ServerMaxIdleTimeoutSec * time.Second, KeepAlivePeriod: 0, // Keep alive should solely be client's responsibility DisablePathMTUDiscovery: false, EnableDatagrams: true, } if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery { logrus.Info("Path MTU Discovery is not yet supported on this platform") } // Resolve preference if len(c.HyOptions.ResolvePreference) > 0 { pref, err := transport.ResolvePreferenceFromString(c.HyOptions.Resolver) if err != nil { logrus.WithFields(logrus.Fields{ "error": err, }).Fatal("Failed to parse the resolve preference") } transport.DefaultServerTransport.ResolvePreference = pref } /*// SOCKS5 outbound if config.SOCKS5Outbound.Server != "" { transport.DefaultServerTransport.SOCKS5Client = transport.NewSOCKS5Client(config.SOCKS5Outbound.Server, config.SOCKS5Outbound.User, config.SOCKS5Outbound.Password) }*/ // Bind outbound if c.HyOptions.SendDevice != "" { iface, err := net.InterfaceByName(c.HyOptions.SendDevice) if err != nil { logrus.WithFields(logrus.Fields{ "error": err, }).Fatal("Failed to find the interface") } transport.DefaultServerTransport.LocalUDPIntf = iface sockopt.BindDialer(transport.DefaultServerTransport.Dialer, iface) } if c.SendIP != "" { ip := net.ParseIP(c.SendIP) if ip == nil { logrus.WithFields(logrus.Fields{ "error": err, }).Fatal("Failed to parse the address") } transport.DefaultServerTransport.Dialer.LocalAddr = &net.TCPAddr{IP: ip} transport.DefaultServerTransport.LocalUDPAddr = &net.UDPAddr{IP: ip} } // ACL var aclEngine *acl.Engine // Prometheus s.counter = NewUserTrafficCounter() // Packet conn pktConnFuncFactory := serverPacketConnFuncFactoryMap[""] if pktConnFuncFactory == nil { return fmt.Errorf("unsopport protocol") } pktConnFunc := pktConnFuncFactory(node.HyObfs) addr := fmt.Sprintf("%s:%d", c.ListenIP, node.Port) pktConn, err := pktConnFunc(addr) if err != nil { logrus.WithFields(logrus.Fields{ "error": err, "addr": addr, }).Fatal("Failed to listen on the UDP address") } // Server up, down := SpeedTrans(node.UpMbps, node.DownMbps) s.Server, err = cs.NewServer(tlsConfig, quicConfig, pktConn, transport.DefaultServerTransport, up, down, false, aclEngine, s.connectFunc, s.disconnectFunc, tcpRequestFunc, tcpErrorFunc, udpRequestFunc, udpErrorFunc, s.counter) if err != nil { return fmt.Errorf("new server error: %s", err) } logrus.WithField("addr", addr).Info("Server up and running") go func() { s.running.Store(true) defer func() { s.running.Store(false) }() err = s.Server.Serve() if err != nil { logrus.WithField("addr", addr).Errorf("serve error: %s", err) } }() return nil } func (s *Server) authByUser(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { if _, r := s.l.CheckLimit(string(auth), addr.String(), false); r { return false, "device limited" } if _, ok := s.users.Load(string(auth)); ok { return true, "Done" } return false, "Failed" } func (s *Server) connectFunc(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { s.l.ConnLimiter.AddConnCount(addr.String(), string(auth), false) ok, msg := s.authByUser(addr, auth, sSend, sRecv) if !ok { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), }).Info("Authentication failed, client rejected") return false, msg } logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "Uuid": string(auth), "Tag": s.tag, }).Info("Client connected") return ok, msg } func (s *Server) disconnectFunc(addr net.Addr, auth []byte, err error) { s.l.ConnLimiter.DelConnCount(addr.String(), string(auth)) logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "error": err, }).Info("Client disconnected") } func tcpRequestFunc(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string) { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "dst": defaultIPMasker.Mask(reqAddr), "action": actionToString(action, arg), }).Debug("TCP request") } func tcpErrorFunc(addr net.Addr, auth []byte, reqAddr string, err error) { if err != io.EOF { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "dst": defaultIPMasker.Mask(reqAddr), "error": err, }).Info("TCP error") } else { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "dst": defaultIPMasker.Mask(reqAddr), }).Debug("TCP EOF") } } func udpRequestFunc(addr net.Addr, auth []byte, sessionID uint32) { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "session": sessionID, }).Debug("UDP request") } func udpErrorFunc(addr net.Addr, auth []byte, sessionID uint32, err error) { if err != io.EOF { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "session": sessionID, "error": err, }).Info("UDP error") } else { logrus.WithFields(logrus.Fields{ "src": defaultIPMasker.Mask(addr.String()), "session": sessionID, }).Debug("UDP EOF") } } func actionToString(action acl.Action, arg string) string { switch action { case acl.ActionDirect: return "Direct" case acl.ActionProxy: return "Proxy" case acl.ActionBlock: return "Block" case acl.ActionHijack: return "Hijack to " + arg default: return "Unknown" } } ================================================ FILE: core/hy/server_test.go ================================================ package hy import ( "encoding/base64" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" "github.com/Yuzuki616/V2bX/limiter" "github.com/sirupsen/logrus" "log" "testing" "time" ) func TestServer(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) limiter.Init() l := limiter.AddLimiter("test", &conf.LimitConfig{}, nil) s := NewServer("test", l) t.Log(s.runServer(&panel.NodeInfo{ Port: 1145, UpMbps: 100, DownMbps: 100, HyObfs: "atresssdaaaadd", }, &conf.ControllerConfig{ ListenIP: "127.0.0.1", HyOptions: conf.HyOptions{}, CertConfig: &conf.CertConfig{ CertFile: "../../test_data/1.pem", KeyFile: "../../test_data/1.key", }, })) s.users.Store("test1111", struct{}{}) go func() { for { time.Sleep(10 * time.Second) auth := base64.StdEncoding.EncodeToString([]byte("test1111")) log.Println(auth) log.Println(s.counter.getCounters(auth).UpCounter.Load()) } }() select {} } ================================================ FILE: core/hy/user.go ================================================ package hy import ( "encoding/base64" "errors" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/core" ) func (h *Hy) AddUsers(p *core.AddUsersParams) (int, error) { s, ok := h.servers.Load(p.Tag) if !ok { return 0, errors.New("the node not have") } u := &s.(*Server).users for i := range p.UserInfo { u.Store(p.UserInfo[i].Uuid, struct{}{}) } return len(p.UserInfo), nil } func (h *Hy) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { v, _ := h.servers.Load(tag) s := v.(*Server) auth := base64.StdEncoding.EncodeToString([]byte(uuid)) up = s.counter.getCounters(auth).UpCounter.Load() down = s.counter.getCounters(auth).DownCounter.Load() if reset { s.counter.Reset(auth) } return } func (h *Hy) DelUsers(users []panel.UserInfo, tag string) error { v, e := h.servers.Load(tag) if !e { return errors.New("the node is not have") } s := v.(*Server) for i := range users { s.users.Delete(users[i].Uuid) s.counter.Delete(base64.StdEncoding.EncodeToString([]byte(users[i].Uuid))) } return nil } ================================================ FILE: core/imports/hy.go ================================================ //go:build hy package imports // not yet tested import _ "github.com/Yuzuki616/V2bX/core/hy" ================================================ FILE: core/imports/imports.go ================================================ package imports ================================================ FILE: core/imports/xray.go ================================================ //go:build xray package imports import _ "github.com/Yuzuki616/V2bX/core/xray" ================================================ FILE: core/interface.go ================================================ package core import ( "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" ) type AddUsersParams struct { Tag string Config *conf.ControllerConfig UserInfo []panel.UserInfo NodeInfo *panel.NodeInfo } type Core interface { Start() error Close() error AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error DelNode(tag string) error AddUsers(p *AddUsersParams) (added int, err error) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) DelUsers(users []panel.UserInfo, tag string) error Protocols() []string } ================================================ FILE: core/selector.go ================================================ package core import ( "errors" "sync" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" "github.com/hashicorp/go-multierror" ) type Selector struct { cores []Core nodes sync.Map } func (s *Selector) Start() error { for i := range s.cores { err := s.cores[i].Start() return err } return nil } func (s *Selector) Close() error { var errs error for i := range s.cores { errs = multierror.Append(errs, s.cores[i].Close()) } return errs } func isSupported(protocol string, protocols []string) bool { for i := range protocols { if protocol == protocols[i] { return true } } return false } func (s *Selector) AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error { for i := range s.cores { if !isSupported(info.Type, s.cores[i].Protocols()) { continue } err := s.cores[i].AddNode(tag, info, config) if err != nil { return err } s.nodes.Store(tag, i) return nil } return errors.New("the node type is not support") } func (s *Selector) DelNode(tag string) error { if t, e := s.nodes.Load(tag); e { err := s.cores[t.(int)].DelNode(tag) if err != nil { return err } s.nodes.Delete(tag) return nil } return errors.New("the node is not have") } func (s *Selector) AddUsers(p *AddUsersParams) (added int, err error) { t, e := s.nodes.Load(p.Tag) if !e { return 0, errors.New("the node is not have") } return s.cores[t.(int)].AddUsers(p) } func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { t, e := s.nodes.Load(tag) if !e { return 0, 0 } return s.cores[t.(int)].GetUserTraffic(tag, uuid, reset) } func (s *Selector) DelUsers(users []panel.UserInfo, tag string) error { t, e := s.nodes.Load(tag) if !e { return errors.New("the node is not have") } return s.cores[t.(int)].DelUsers(users, tag) } func (s *Selector) Protocols() []string { protocols := make([]string, 0) for i := range s.cores { protocols = append(protocols, s.cores[i].Protocols()...) } return protocols } ================================================ FILE: core/xray/app/app.go ================================================ // Package app contains the third-party app used to replace the default app in xray-core package app ================================================ FILE: core/xray/app/dispatcher/config.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.21.12 // source: config.proto package dispatcher import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type SessionConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *SessionConfig) Reset() { *x = SessionConfig{} if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SessionConfig) String() string { return protoimpl.X.MessageStringOf(x) } func (*SessionConfig) ProtoMessage() {} func (x *SessionConfig) ProtoReflect() protoreflect.Message { mi := &file_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead. func (*SessionConfig) Descriptor() ([]byte, []int) { return file_config_proto_rawDescGZIP(), []int{0} } type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Config) String() string { return protoimpl.X.MessageStringOf(x) } func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { mi := &file_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { return file_config_proto_rawDescGZIP(), []int{1} } func (x *Config) GetSettings() *SessionConfig { if x != nil { return x.Settings } return nil } var File_config_proto protoreflect.FileDescriptor var file_config_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18, 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x4d, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x59, 0x75, 0x7a, 0x75, 0x6b, 0x69, 0x36, 0x31, 0x36, 0x2f, 0x56, 0x32, 0x62, 0x58, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x62, 0x58, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_config_proto_rawDescOnce sync.Once file_config_proto_rawDescData = file_config_proto_rawDesc ) func file_config_proto_rawDescGZIP() []byte { file_config_proto_rawDescOnce.Do(func() { file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData) }) return file_config_proto_rawDescData } var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_config_proto_goTypes = []interface{}{ (*SessionConfig)(nil), // 0: v2bx.core.app.dispatcher.SessionConfig (*Config)(nil), // 1: v2bx.core.app.dispatcher.Config } var file_config_proto_depIdxs = []int32{ 0, // 0: v2bx.core.app.dispatcher.Config.settings:type_name -> v2bx.core.app.dispatcher.SessionConfig 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_config_proto_init() } func file_config_proto_init() { if File_config_proto != nil { return } if !protoimpl.UnsafeEnabled { file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SessionConfig); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_config_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_config_proto_goTypes, DependencyIndexes: file_config_proto_depIdxs, MessageInfos: file_config_proto_msgTypes, }.Build() File_config_proto = out.File file_config_proto_rawDesc = nil file_config_proto_goTypes = nil file_config_proto_depIdxs = nil } ================================================ FILE: core/xray/app/dispatcher/config.proto ================================================ syntax = "proto3"; package v2bx.core.app.dispatcher; option csharp_namespace = "V2bX.core.app.dispatcher"; option go_package = "github.com/Yuzuki616/V2bX/core/xray/app/dispatcher"; option java_package = "com.v2bx.core.app.dispatcher"; option java_multiple_files = true; message SessionConfig { reserved 1; } message Config { SessionConfig settings = 1; } ================================================ FILE: core/xray/app/dispatcher/default.go ================================================ package dispatcher //go:generate go run github.com/xtls/xray-core/common/errors/errorgen import ( "context" "fmt" "strings" "sync" "time" "github.com/Yuzuki616/V2bX/common/rate" "github.com/Yuzuki616/V2bX/limiter" routingSession "github.com/xtls/xray-core/features/routing/session" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/pipe" ) var errSniffingTimeout = newError("timeout on sniffing") type cachedReader struct { sync.Mutex reader *pipe.Reader cache buf.MultiBuffer } func (r *cachedReader) Cache(b *buf.Buffer) { mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100) r.Lock() if !mb.IsEmpty() { r.cache, _ = buf.MergeMulti(r.cache, mb) } b.Clear() rawBytes := b.Extend(buf.Size) n := r.cache.Copy(rawBytes) b.Resize(0, int32(n)) r.Unlock() } func (r *cachedReader) readInternal() buf.MultiBuffer { r.Lock() defer r.Unlock() if r.cache != nil && !r.cache.IsEmpty() { mb := r.cache r.cache = nil return mb } return nil } func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) { mb := r.readInternal() if mb != nil { return mb, nil } return r.reader.ReadMultiBuffer() } func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) { mb := r.readInternal() if mb != nil { return mb, nil } return r.reader.ReadMultiBufferTimeout(timeout) } func (r *cachedReader) Interrupt() { r.Lock() if r.cache != nil { r.cache = buf.ReleaseMulti(r.cache) } r.Unlock() r.reader.Interrupt() } // DefaultDispatcher is a default implementation of Dispatcher. type DefaultDispatcher struct { ohm outbound.Manager router routing.Router policy policy.Manager stats stats.Manager dns dns.Client fdns dns.FakeDNSEngine } func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { d := new(DefaultDispatcher) if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error { core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) { d.fdns = fdns }) return d.Init(config.(*Config), om, router, pm, sm, dc) }); err != nil { return nil, err } return d, nil })) } // Init initializes DefaultDispatcher. func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error { d.ohm = om d.router = router d.policy = pm d.stats = sm d.dns = dns return nil } // Type implements common.HasType. func (*DefaultDispatcher) Type() interface{} { return routing.DispatcherType() } // Start implements common.Runnable. func (*DefaultDispatcher) Start() error { return nil } // Close implements common.Closable. func (*DefaultDispatcher) Close() error { return nil } func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, *limiter.Limiter, error) { downOpt := pipe.OptionsFromContext(ctx) upOpt := downOpt if network == net.Network_UDP { var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns // Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs. // When target replies, server will restore the domain and send back to client. // Note: this map is not global but per connection context upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer { for i, buffer := range mb { if buffer.UDP == nil { continue } addr := buffer.UDP.Address if addr.Family().IsIP() { if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled { domain := fkr0.GetDomainFromFakeDNS(addr) if len(domain) > 0 { buffer.UDP.Address = net.DomainAddress(domain) newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) } else { newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx)) } } } else { if ip2domain == nil { ip2domain = new(sync.Map) newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx)) } domain := addr.Domain() ips, err := d.dns.LookupIP(domain, dns.IPOption{true, true, false}) if err == nil { for _, ip := range ips { ip2domain.Store(ip.String(), domain) } newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) } else { newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx)) } } } return mb })) downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer { for i, buffer := range mb { if buffer.UDP == nil { continue } addr := buffer.UDP.Address if addr.Family().IsIP() { if ip2domain == nil { continue } if domain, found := ip2domain.Load(addr.IP().String()); found { buffer.UDP.Address = net.DomainAddress(domain.(string)) newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) } } else { if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok { fakeIp := fkr0.GetFakeIPForDomain(addr.Domain()) buffer.UDP.Address = fakeIp[0] newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx)) } } } return mb })) } uplinkReader, uplinkWriter := pipe.New(upOpt...) downlinkReader, downlinkWriter := pipe.New(downOpt...) inboundLink := &transport.Link{ Reader: downlinkReader, Writer: uplinkWriter, } outboundLink := &transport.Link{ Reader: uplinkReader, Writer: downlinkWriter, } sessionInbound := session.InboundFromContext(ctx) var user *protocol.MemoryUser if sessionInbound != nil { user = sessionInbound.User } var limit *limiter.Limiter if user != nil && len(user.Email) > 0 { var err error limit, err = limiter.GetLimiter(sessionInbound.Tag) if err != nil { newError("Get limit info error: ", err).AtError().WriteToLog() common.Close(outboundLink.Writer) common.Close(inboundLink.Writer) common.Interrupt(outboundLink.Reader) common.Interrupt(inboundLink.Reader) return nil, nil, nil, newError("Get limit info error: ", err) } // Speed Limit and Device Limit w, reject := limit.CheckLimit(user.Email, sessionInbound.Source.Address.IP().String(), network == net.Network_TCP) if reject { newError("Limited ", user.Email, " by conn or ip").AtWarning().WriteToLog() common.Close(outboundLink.Writer) common.Close(inboundLink.Writer) common.Interrupt(outboundLink.Reader) common.Interrupt(inboundLink.Reader) return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip") } if w != nil { inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w) outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w) } p := d.policy.ForLevel(user.Level) if p.Stats.UserUplink { name := "user>>>" + user.Email + ">>>traffic>>>uplink" if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { inboundLink.Writer = &SizeStatWriter{ Counter: c, Writer: inboundLink.Writer, } } } if p.Stats.UserDownlink { name := "user>>>" + user.Email + ">>>traffic>>>downlink" if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { outboundLink.Writer = &SizeStatWriter{ Counter: c, Writer: outboundLink.Writer, } } } } return inboundLink, outboundLink, limit, nil } func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool { domain := result.Domain() if domain == "" { return false } for _, d := range request.ExcludeForDomain { if strings.ToLower(domain) == d { return false } } protocolString := result.Protocol() if resComp, ok := result.(SnifferResultComposite); ok { protocolString = resComp.ProtocolForDomainResult() } for _, p := range request.OverrideDestinationForProtocol { if strings.HasPrefix(protocolString, p) { return true } if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" && destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) { newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx)) return true } if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok { if resultSubset.IsProtoSubsetOf(p) { return true } } } return false } // Dispatch implements routing.Dispatcher. func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) { if !destination.IsValid() { panic("Dispatcher: Invalid destination.") } ob := &session.Outbound{ Target: destination, } ctx = session.ContextWithOutbound(ctx, ob) content := session.ContentFromContext(ctx) if content == nil { content = new(session.Content) ctx = session.ContextWithContent(ctx, content) } sniffingRequest := content.SniffingRequest inbound, outbound, l, err := d.getLink(ctx, destination.Network, sniffingRequest) if err != nil { return nil, err } if !sniffingRequest.Enabled { go d.routedDispatch(ctx, outbound, destination, l) } else { go func() { cReader := &cachedReader{ reader: outbound.Reader.(*pipe.Reader), } outbound.Reader = cReader result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network, l) if _, ok := err.(limitedError); ok { newError(err).AtInfo().WriteToLog() common.Close(outbound.Writer) common.Interrupt(outbound.Reader) return } if err == nil { content.Protocol = result.Protocol() } if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { domain := result.Domain() newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) destination.Address = net.ParseAddress(domain) if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" { ob.RouteTarget = destination } else { ob.Target = destination } } d.routedDispatch(ctx, outbound, destination, l) }() } return inbound, nil } // DispatchLink implements routing.Dispatcher. func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error { if !destination.IsValid() { return newError("Dispatcher: Invalid destination.") } ob := &session.Outbound{ Target: destination, } ctx = session.ContextWithOutbound(ctx, ob) content := session.ContentFromContext(ctx) if content == nil { content = new(session.Content) ctx = session.ContextWithContent(ctx, content) } sniffingRequest := content.SniffingRequest if !sniffingRequest.Enabled { go d.routedDispatch(ctx, outbound, destination, nil) } else { go func() { cReader := &cachedReader{ reader: outbound.Reader.(*pipe.Reader), } outbound.Reader = cReader result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network, nil) if _, ok := err.(limitedError); ok { newError(err).AtInfo().WriteToLog() common.Close(outbound.Writer) common.Interrupt(outbound.Reader) return } if err == nil { content.Protocol = result.Protocol() } if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { domain := result.Domain() newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) destination.Address = net.ParseAddress(domain) if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" { ob.RouteTarget = destination } else { ob.Target = destination } } d.routedDispatch(ctx, outbound, destination, nil) }() } return nil } type limitedError string func (l limitedError) Error() string { return string(l) } func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network, l *limiter.Limiter) (result SniffResult, err error) { payload := buf.New() defer payload.Release() defer func() { if err != nil { return } // Check if domain and protocol hit the rule sessionInbound := session.InboundFromContext(ctx) // Whether the inbound connection contains a user if sessionInbound.User != nil { if l == nil { l, err = limiter.GetLimiter(sessionInbound.Tag) if err != nil { return } } if l.CheckDomainRule(result.Domain()) { err = limitedError(fmt.Sprintf( "User %s access domain %s reject by rule", sessionInbound.User.Email, result.Domain())) } if l.CheckProtocolRule(result.Protocol()) { err = limitedError(fmt.Sprintf( "User %s access protocol %s reject by rule", sessionInbound.User.Email, result.Protocol())) } } }() sniffer := NewSniffer(ctx) metaresult, metadataErr := sniffer.SniffMetadata(ctx) if metadataOnly { return metaresult, metadataErr } contentResult, contentErr := func() (SniffResult, error) { totalAttempt := 0 for { select { case <-ctx.Done(): return nil, ctx.Err() default: totalAttempt++ if totalAttempt > 2 { return nil, errSniffingTimeout } cReader.Cache(payload) if !payload.IsEmpty() { result, err := sniffer.Sniff(ctx, payload.Bytes(), network) if err != common.ErrNoClue { return result, err } } if payload.IsFull() { return nil, errUnknownContent } } } }() if contentErr != nil && metadataErr == nil { return metaresult, nil } if contentErr == nil && metadataErr == nil { return CompositeResult(metaresult, contentResult), nil } return contentResult, contentErr } func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter) { ob := session.OutboundFromContext(ctx) if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() { proxied := hosts.LookupHosts(ob.Target.String()) if proxied != nil { ro := ob.RouteTarget == destination destination.Address = *proxied if ro { ob.RouteTarget = destination } else { ob.Target = destination } } } var handler outbound.Handler // del connect count if l != nil { sessionInbound := session.InboundFromContext(ctx) if sessionInbound.User != nil { if destination.Network == net.Network_TCP { defer func() { l.ConnLimiter.DelConnCount(sessionInbound.User.Email, sessionInbound.Source.Address.IP().String()) }() } } } routingLink := routingSession.AsRoutingContext(ctx) inTag := routingLink.GetInboundTag() isPickRoute := 0 if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" { ctx = session.SetForcedOutboundTagToContext(ctx, "") if h := d.ohm.GetHandler(forcedOutboundTag); h != nil { isPickRoute = 1 newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) handler = h } else { newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx)) common.Close(link.Writer) common.Interrupt(link.Reader) return } } else if d.router != nil { if route, err := d.router.PickRoute(routingLink); err == nil { outTag := route.GetOutboundTag() if h := d.ohm.GetHandler(outTag); h != nil { isPickRoute = 2 newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) handler = h } else { newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx)) } } else { newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx)) } } if handler == nil { handler = d.ohm.GetHandler(inTag) // Default outbound handier tag should be as same as the inbound tag } // If there is no outbound with tag as same as the inbound tag if handler == nil { handler = d.ohm.GetDefaultHandler() } if handler == nil { newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx)) common.Close(link.Writer) common.Interrupt(link.Reader) return } if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil { if tag := handler.Tag(); tag != "" { if inTag == "" { accessMessage.Detour = tag } else if isPickRoute == 1 { accessMessage.Detour = inTag + " ==> " + tag } else if isPickRoute == 2 { accessMessage.Detour = inTag + " -> " + tag } else { accessMessage.Detour = inTag + " >> " + tag } } log.Record(accessMessage) } handler.Dispatch(ctx, link) } ================================================ FILE: core/xray/app/dispatcher/dispatcher.go ================================================ package dispatcher //go:generate go run github.com/xtls/xray-core/common/errors/errorgen ================================================ FILE: core/xray/app/dispatcher/errors.generated.go ================================================ package dispatcher import "github.com/xtls/xray-core/common/errors" type errPathObjHolder struct{} func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) } ================================================ FILE: core/xray/app/dispatcher/fakednssniffer.go ================================================ package dispatcher import ( "context" "strings" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/dns" ) // newFakeDNSSniffer Creates a Fake DNS metadata sniffer func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) { var fakeDNSEngine dns.FakeDNSEngine { fakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil)) if fakeDNSEngineFeat != nil { fakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine) } } if fakeDNSEngine == nil { errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError() return protocolSnifferWithMetadata{}, errNotInit } return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) { Target := session.OutboundFromContext(ctx).Target if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP { domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address) if domainFromFakeDNS != "" { newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx)) return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil } } if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil { ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt) if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok { inPool := fkr0.IsIPInIPPool(Target.Address) ipAddressInRangeValue.addressInRange = &inPool } } return nil, common.ErrNoClue }, metadataSniffer: true}, nil } type fakeDNSSniffResult struct { domainName string } func (fakeDNSSniffResult) Protocol() string { return "fakedns" } func (f fakeDNSSniffResult) Domain() string { return f.domainName } type fakeDNSExtraOpts int const ipAddressInRange fakeDNSExtraOpts = 1 type ipAddressInRangeOpt struct { addressInRange *bool } type DNSThenOthersSniffResult struct { domainName string protocolOriginalName string } func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool { return strings.HasPrefix(protocolName, f.protocolOriginalName) } func (DNSThenOthersSniffResult) Protocol() string { return "fakedns+others" } func (f DNSThenOthersSniffResult) Domain() string { return f.domainName } func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) ( protocolSnifferWithMetadata, error, ) { // nolint: unparam // ctx may be used in the future _ = ctx return protocolSnifferWithMetadata{ protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) { ipAddressInRangeValue := &ipAddressInRangeOpt{} ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue) result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes) if err == nil { return result, nil } if ipAddressInRangeValue.addressInRange != nil { if *ipAddressInRangeValue.addressInRange { for _, v := range others { if v.metadataSniffer || bytes != nil { if result, err := v.protocolSniffer(ctx, bytes); err == nil { return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil } } } return nil, common.ErrNoClue } newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog() return nil, common.ErrNoClue } newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog() return nil, common.ErrNoClue }, metadataSniffer: false, }, nil } ================================================ FILE: core/xray/app/dispatcher/sniffer.go ================================================ package dispatcher import ( "context" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/protocol/bittorrent" "github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/protocol/quic" "github.com/xtls/xray-core/common/protocol/tls" ) type SniffResult interface { Protocol() string Domain() string } type protocolSniffer func(context.Context, []byte) (SniffResult, error) type protocolSnifferWithMetadata struct { protocolSniffer protocolSniffer // A Metadata sniffer will be invoked on connection establishment only, with nil body, // for both TCP and UDP connections // It will not be shown as a traffic type for routing unless there is no other successful sniffing. metadataSniffer bool network net.Network } type Sniffer struct { sniffer []protocolSnifferWithMetadata } func NewSniffer(ctx context.Context) *Sniffer { ret := &Sniffer{ sniffer: []protocolSnifferWithMetadata{ {func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP}, {func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP}, {func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP}, {func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP}, {func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP}, }, } if sniffer, err := newFakeDNSSniffer(ctx); err == nil { others := ret.sniffer ret.sniffer = append(ret.sniffer, sniffer) fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others) if err == nil { ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...) } } return ret } var errUnknownContent = newError("unknown content") func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) { var pendingSniffer []protocolSnifferWithMetadata for _, si := range s.sniffer { s := si.protocolSniffer if si.metadataSniffer || si.network != network { continue } result, err := s(c, payload) if err == common.ErrNoClue { pendingSniffer = append(pendingSniffer, si) continue } if err == nil && result != nil { return result, nil } } if len(pendingSniffer) > 0 { s.sniffer = pendingSniffer return nil, common.ErrNoClue } return nil, errUnknownContent } func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) { var pendingSniffer []protocolSnifferWithMetadata for _, si := range s.sniffer { s := si.protocolSniffer if !si.metadataSniffer { pendingSniffer = append(pendingSniffer, si) continue } result, err := s(c, nil) if err == common.ErrNoClue { pendingSniffer = append(pendingSniffer, si) continue } if err == nil && result != nil { return result, nil } } if len(pendingSniffer) > 0 { s.sniffer = pendingSniffer return nil, common.ErrNoClue } return nil, errUnknownContent } func CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult { return &compositeResult{domainResult: domainResult, protocolResult: protocolResult} } type compositeResult struct { domainResult SniffResult protocolResult SniffResult } func (c compositeResult) Protocol() string { return c.protocolResult.Protocol() } func (c compositeResult) Domain() string { return c.domainResult.Domain() } func (c compositeResult) ProtocolForDomainResult() string { return c.domainResult.Protocol() } type SnifferResultComposite interface { ProtocolForDomainResult() string } type SnifferIsProtoSubsetOf interface { IsProtoSubsetOf(protocolName string) bool } ================================================ FILE: core/xray/app/dispatcher/stats.go ================================================ package dispatcher import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/features/stats" ) type SizeStatWriter struct { Counter stats.Counter Writer buf.Writer } func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { w.Counter.Add(int64(mb.Len())) return w.Writer.WriteMultiBuffer(mb) } func (w *SizeStatWriter) Close() error { return common.Close(w.Writer) } func (w *SizeStatWriter) Interrupt() { common.Interrupt(w.Writer) } ================================================ FILE: core/xray/app/dispatcher/stats_test.go ================================================ package dispatcher_test import ( "testing" . "github.com/xtls/xray-core/app/dispatcher" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" ) type TestCounter int64 func (c *TestCounter) Value() int64 { return int64(*c) } func (c *TestCounter) Add(v int64) int64 { x := int64(*c) + v *c = TestCounter(x) return x } func (c *TestCounter) Set(v int64) int64 { *c = TestCounter(v) return v } func TestStatsWriter(t *testing.T) { var c TestCounter writer := &SizeStatWriter{ Counter: &c, Writer: buf.Discard, } mb := buf.MergeBytes(nil, []byte("abcd")) common.Must(writer.WriteMultiBuffer(mb)) mb = buf.MergeBytes(nil, []byte("efg")) common.Must(writer.WriteMultiBuffer(mb)) if c.Value() != 7 { t.Fatal("unexpected counter value. want 7, but got ", c.Value()) } } ================================================ FILE: core/xray/distro/all/all.go ================================================ package all import ( // The following are necessary as they register handlers in their init functions. // Mandatory features. Can't remove unless there are replacements. _ "github.com/xtls/xray-core/app/dispatcher" _ "github.com/xtls/xray-core/app/proxyman/inbound" _ "github.com/xtls/xray-core/app/proxyman/outbound" // Default commander and all its services. This is an optional feature. //_ "github.com/xtls/xray-core/app/commander" //_ "github.com/xtls/xray-core/app/log/command" //_ "github.com/xtls/xray-core/app/proxyman/command" //_ "github.com/xtls/xray-core/app/stats/command" // Developer preview services //_ "github.com/xtls/xray-core/app/observatory/command" // Other optional features. _ "github.com/xtls/xray-core/app/dns" _ "github.com/xtls/xray-core/app/dns/fakedns" _ "github.com/xtls/xray-core/app/log" _ "github.com/xtls/xray-core/app/metrics" _ "github.com/xtls/xray-core/app/policy" _ "github.com/xtls/xray-core/app/reverse" _ "github.com/xtls/xray-core/app/router" _ "github.com/xtls/xray-core/app/stats" // Fix dependency cycle caused by core import in internet package _ "github.com/xtls/xray-core/transport/internet/tagged/taggedimpl" // Developer preview features //_ "github.com/xtls/xray-core/app/observatory" // Inbound and outbound proxies. _ "github.com/xtls/xray-core/proxy/blackhole" _ "github.com/xtls/xray-core/proxy/dns" _ "github.com/xtls/xray-core/proxy/dokodemo" _ "github.com/xtls/xray-core/proxy/freedom" _ "github.com/xtls/xray-core/proxy/http" _ "github.com/xtls/xray-core/proxy/loopback" _ "github.com/xtls/xray-core/proxy/shadowsocks" _ "github.com/xtls/xray-core/proxy/socks" _ "github.com/xtls/xray-core/proxy/trojan" _ "github.com/xtls/xray-core/proxy/vless/inbound" _ "github.com/xtls/xray-core/proxy/vless/outbound" _ "github.com/xtls/xray-core/proxy/vmess/inbound" _ "github.com/xtls/xray-core/proxy/vmess/outbound" //_ "github.com/xtls/xray-core/proxy/wireguard" // Transports //_ "github.com/xtls/xray-core/transport/internet/domainsocket" _ "github.com/xtls/xray-core/transport/internet/grpc" _ "github.com/xtls/xray-core/transport/internet/http" //_ "github.com/xtls/xray-core/transport/internet/kcp" //_ "github.com/xtls/xray-core/transport/internet/quic" _ "github.com/xtls/xray-core/transport/internet/reality" _ "github.com/xtls/xray-core/transport/internet/tcp" _ "github.com/xtls/xray-core/transport/internet/tls" _ "github.com/xtls/xray-core/transport/internet/udp" _ "github.com/xtls/xray-core/transport/internet/websocket" // Transport headers _ "github.com/xtls/xray-core/transport/internet/headers/http" _ "github.com/xtls/xray-core/transport/internet/headers/noop" _ "github.com/xtls/xray-core/transport/internet/headers/srtp" _ "github.com/xtls/xray-core/transport/internet/headers/tls" _ "github.com/xtls/xray-core/transport/internet/headers/utp" _ "github.com/xtls/xray-core/transport/internet/headers/wechat" _ "github.com/xtls/xray-core/transport/internet/headers/wireguard" ) ================================================ FILE: core/xray/inbound.go ================================================ package xray import ( "crypto/rand" "encoding/base64" "encoding/hex" "errors" "fmt" "strconv" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" "github.com/goccy/go-json" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/core" coreConf "github.com/xtls/xray-core/infra/conf" ) // BuildInbound build Inbound config for different protocol func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) { in := &coreConf.InboundDetourConfig{} // Set network protocol t := coreConf.TransportProtocol(nodeInfo.Network) in.StreamSetting = &coreConf.StreamConfig{Network: &t} var err error switch nodeInfo.Type { case "v2ray": err = buildV2ray(config, nodeInfo, in) case "trojan": err = buildTrojan(config, in) case "shadowsocks": err = buildShadowsocks(config, nodeInfo, in) default: return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks", nodeInfo.Type) } if err != nil { return nil, err } // Set server port in.PortList = &coreConf.PortList{ Range: []coreConf.PortRange{{From: uint32(nodeInfo.Port), To: uint32(nodeInfo.Port)}}, } // Set Listen IP address ipAddress := net.ParseAddress(config.ListenIP) in.ListenOn = &coreConf.Address{Address: ipAddress} // Set SniffingConfig sniffingConfig := &coreConf.SniffingConfig{ Enabled: true, DestOverride: &coreConf.StringList{"http", "tls"}, } if config.XrayOptions.DisableSniffing { sniffingConfig.Enabled = false } in.SniffingConfig = sniffingConfig if *in.StreamSetting.Network == "tcp" { if in.StreamSetting.TCPSettings != nil { in.StreamSetting.TCPSettings.AcceptProxyProtocol = config.XrayOptions.EnableProxyProtocol } else { tcpSetting := &coreConf.TCPConfig{ AcceptProxyProtocol: config.XrayOptions.EnableProxyProtocol, } //Enable proxy protocol in.StreamSetting.TCPSettings = tcpSetting } } else if *in.StreamSetting.Network == "ws" { in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{ AcceptProxyProtocol: config.XrayOptions.EnableProxyProtocol} //Enable proxy protocol } // Set TLS or Reality settings if nodeInfo.Tls { if config.CertConfig == nil { return nil, errors.New("the CertConfig is not vail") } switch config.CertConfig.CertMode { case "none", "": break // disable case "reality": // Reality in.StreamSetting.Security = "reality" d, err := json.Marshal(config.CertConfig.RealityConfig.Dest) if err != nil { return nil, fmt.Errorf("marshal reality dest error: %s", err) } if len(config.CertConfig.RealityConfig.ShortIds) == 0 { config.CertConfig.RealityConfig.ShortIds = []string{""} } in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{ Dest: d, Xver: config.CertConfig.RealityConfig.Xver, ServerNames: config.CertConfig.RealityConfig.ServerNames, PrivateKey: config.CertConfig.RealityConfig.PrivateKey, MinClientVer: config.CertConfig.RealityConfig.MinClientVer, MaxClientVer: config.CertConfig.RealityConfig.MaxClientVer, MaxTimeDiff: config.CertConfig.RealityConfig.MaxTimeDiff, ShortIds: config.CertConfig.RealityConfig.ShortIds, } break case "remote": if nodeInfo.ExtraConfig.EnableReality == "true" { rc := nodeInfo.ExtraConfig.RealityConfig in.StreamSetting.Security = "reality" d, err := json.Marshal(rc.Dest) if err != nil { return nil, fmt.Errorf("marshal reality dest error: %s", err) } if len(rc.ShortIds) == 0 { rc.ShortIds = []string{""} } Xver, _ := strconv.ParseUint(rc.Xver, 10, 64) MaxTimeDiff, _ := strconv.ParseUint(rc.Xver, 10, 64) in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{ Dest: d, Xver: Xver, ServerNames: rc.ServerNames, PrivateKey: rc.PrivateKey, MinClientVer: rc.MinClientVer, MaxClientVer: rc.MaxClientVer, MaxTimeDiff: MaxTimeDiff, ShortIds: rc.ShortIds, } break } default: { // Normal tls in.StreamSetting.Security = "tls" in.StreamSetting.TLSSettings = &coreConf.TLSConfig{ Certs: []*coreConf.TLSCertConfig{ { CertFile: config.CertConfig.CertFile, KeyFile: config.CertConfig.KeyFile, OcspStapling: 3600, }, }, RejectUnknownSNI: config.CertConfig.RejectUnknownSni, } } } } // Support ProxyProtocol for any transport protocol if *in.StreamSetting.Network != "tcp" && *in.StreamSetting.Network != "ws" && config.XrayOptions.EnableProxyProtocol { socketConfig := &coreConf.SocketConfig{ AcceptProxyProtocol: config.XrayOptions.EnableProxyProtocol, TFO: config.XrayOptions.EnableTFO, } //Enable proxy protocol in.StreamSetting.SocketSettings = socketConfig } in.Tag = tag return in.Build() } func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error { if nodeInfo.ExtraConfig.EnableVless == "true" { //Set vless inbound.Protocol = "vless" if config.XrayOptions.EnableFallback { // Set fallback fallbackConfigs, err := buildVlessFallbacks(config.XrayOptions.FallBackConfigs) if err != nil { return err } s, err := json.Marshal(&coreConf.VLessInboundConfig{ Decryption: "none", Fallbacks: fallbackConfigs, }) if err != nil { return fmt.Errorf("marshal vless fallback config error: %s", err) } inbound.Settings = (*json.RawMessage)(&s) } else { var err error s, err := json.Marshal(&coreConf.VLessInboundConfig{ Decryption: "none", }) if err != nil { return fmt.Errorf("marshal vless config error: %s", err) } inbound.Settings = (*json.RawMessage)(&s) } } else { // Set vmess inbound.Protocol = "vmess" var err error s, err := json.Marshal(&coreConf.VMessInboundConfig{}) if err != nil { return fmt.Errorf("marshal vmess settings error: %s", err) } inbound.Settings = (*json.RawMessage)(&s) } if len(nodeInfo.NetworkSettings) == 0 { return nil } switch nodeInfo.Network { case "tcp": err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings) if err != nil { return fmt.Errorf("unmarshal tcp settings error: %s", err) } case "ws": err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.WSSettings) if err != nil { return fmt.Errorf("unmarshal ws settings error: %s", err) } case "grpc": err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.GRPCConfig) if err != nil { return fmt.Errorf("unmarshal grpc settings error: %s", err) } default: return errors.New("the network type is not vail") } return nil } func buildTrojan(config *conf.ControllerConfig, inbound *coreConf.InboundDetourConfig) error { inbound.Protocol = "trojan" if config.XrayOptions.EnableFallback { // Set fallback fallbackConfigs, err := buildTrojanFallbacks(config.XrayOptions.FallBackConfigs) if err != nil { return err } s, err := json.Marshal(&coreConf.TrojanServerConfig{ Fallbacks: fallbackConfigs, }) inbound.Settings = (*json.RawMessage)(&s) if err != nil { return fmt.Errorf("marshal trojan fallback config error: %s", err) } } else { s := []byte("{}") inbound.Settings = (*json.RawMessage)(&s) } t := coreConf.TransportProtocol("tcp") inbound.StreamSetting = &coreConf.StreamConfig{Network: &t} return nil } func buildShadowsocks(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error { inbound.Protocol = "shadowsocks" settings := &coreConf.ShadowsocksServerConfig{ Cipher: nodeInfo.Cipher, } p := make([]byte, 32) _, err := rand.Read(p) if err != nil { return fmt.Errorf("generate random password error: %s", err) } randomPasswd := hex.EncodeToString(p) cipher := nodeInfo.Cipher if nodeInfo.ServerKey != "" { settings.Password = nodeInfo.ServerKey randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd)) cipher = "" } defaultSSuser := &coreConf.ShadowsocksUserConfig{ Cipher: cipher, Password: randomPasswd, } settings.Users = append(settings.Users, defaultSSuser) settings.NetworkList = &coreConf.NetworkList{"tcp", "udp"} settings.IVCheck = true if config.XrayOptions.DisableIVCheck { settings.IVCheck = false } t := coreConf.TransportProtocol("tcp") inbound.StreamSetting = &coreConf.StreamConfig{Network: &t} s, err := json.Marshal(settings) inbound.Settings = (*json.RawMessage)(&s) if err != nil { return fmt.Errorf("marshal shadowsocks settings error: %s", err) } return nil } func buildVlessFallbacks(fallbackConfigs []conf.FallBackConfig) ([]*coreConf.VLessInboundFallback, error) { if fallbackConfigs == nil { return nil, fmt.Errorf("you must provide FallBackConfigs") } vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs)) for i, c := range fallbackConfigs { if c.Dest == "" { return nil, fmt.Errorf("dest is required for fallback fialed") } var dest json.RawMessage dest, err := json.Marshal(c.Dest) if err != nil { return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err) } vlessFallBacks[i] = &coreConf.VLessInboundFallback{ Name: c.SNI, Alpn: c.Alpn, Path: c.Path, Dest: dest, Xver: c.ProxyProtocolVer, } } return vlessFallBacks, nil } func buildTrojanFallbacks(fallbackConfigs []conf.FallBackConfig) ([]*coreConf.TrojanInboundFallback, error) { if fallbackConfigs == nil { return nil, fmt.Errorf("you must provide FallBackConfigs") } trojanFallBacks := make([]*coreConf.TrojanInboundFallback, len(fallbackConfigs)) for i, c := range fallbackConfigs { if c.Dest == "" { return nil, fmt.Errorf("dest is required for fallback fialed") } var dest json.RawMessage dest, err := json.Marshal(c.Dest) if err != nil { return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err) } trojanFallBacks[i] = &coreConf.TrojanInboundFallback{ Name: c.SNI, Alpn: c.Alpn, Path: c.Path, Dest: dest, Xver: c.ProxyProtocolVer, } } return trojanFallBacks, nil } ================================================ FILE: core/xray/node.go ================================================ package xray import ( "context" "fmt" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/inbound" "github.com/xtls/xray-core/features/outbound" ) func (c *Core) AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error { inboundConfig, err := buildInbound(config, info, tag) if err != nil { return fmt.Errorf("build inbound error: %s", err) } err = c.addInbound(inboundConfig) if err != nil { return fmt.Errorf("add inbound error: %s", err) } outBoundConfig, err := buildOutbound(config, tag) if err != nil { return fmt.Errorf("build outbound error: %s", err) } err = c.addOutbound(outBoundConfig) if err != nil { return fmt.Errorf("add outbound error: %s", err) } return nil } func (c *Core) addInbound(config *core.InboundHandlerConfig) error { rawHandler, err := core.CreateObject(c.Server, config) if err != nil { return err } handler, ok := rawHandler.(inbound.Handler) if !ok { return fmt.Errorf("not an InboundHandler: %s", err) } if err := c.ihm.AddHandler(context.Background(), handler); err != nil { return err } return nil } func (c *Core) addOutbound(config *core.OutboundHandlerConfig) error { rawHandler, err := core.CreateObject(c.Server, config) if err != nil { return err } handler, ok := rawHandler.(outbound.Handler) if !ok { return fmt.Errorf("not an InboundHandler: %s", err) } if err := c.ohm.AddHandler(context.Background(), handler); err != nil { return err } return nil } func (c *Core) DelNode(tag string) error { err := c.removeInbound(tag) if err != nil { return fmt.Errorf("remove in error: %s", err) } err = c.removeOutbound(tag) if err != nil { return fmt.Errorf("remove out error: %s", err) } return nil } func (c *Core) removeInbound(tag string) error { return c.ihm.RemoveHandler(context.Background(), tag) } func (c *Core) removeOutbound(tag string) error { err := c.ohm.RemoveHandler(context.Background(), tag) return err } ================================================ FILE: core/xray/outbound.go ================================================ package xray import ( "fmt" conf2 "github.com/Yuzuki616/V2bX/conf" "github.com/goccy/go-json" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/infra/conf" ) // BuildOutbound build freedom outbund config for addoutbound func buildOutbound(config *conf2.ControllerConfig, tag string) (*core.OutboundHandlerConfig, error) { outboundDetourConfig := &conf.OutboundDetourConfig{} outboundDetourConfig.Protocol = "freedom" outboundDetourConfig.Tag = tag // Build Send IP address if config.SendIP != "" { ipAddress := net.ParseAddress(config.SendIP) outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress} } // Freedom Protocol setting var domainStrategy = "Asis" if config.XrayOptions.EnableDNS { if config.XrayOptions.DNSType != "" { domainStrategy = config.XrayOptions.DNSType } else { domainStrategy = "UseIP" } } proxySetting := &conf.FreedomConfig{ DomainStrategy: domainStrategy, } var setting json.RawMessage setting, err := json.Marshal(proxySetting) if err != nil { return nil, fmt.Errorf("marshal proxy config error: %s", err) } outboundDetourConfig.Settings = &setting return outboundDetourConfig.Build() } ================================================ FILE: core/xray/ss.go ================================================ package xray import ( "encoding/base64" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/format" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/shadowsocks" "github.com/xtls/xray-core/proxy/shadowsocks_2022" "strings" ) func buildSSUsers(tag string, userInfo []panel.UserInfo, cypher string, serverKey string) (users []*protocol.User) { users = make([]*protocol.User, len(userInfo)) for i := range userInfo { users[i] = buildSSUser(tag, &userInfo[i], cypher, serverKey) } return users } func buildSSUser(tag string, userInfo *panel.UserInfo, cypher string, serverKey string) (user *protocol.User) { if serverKey == "" { ssAccount := &shadowsocks.Account{ Password: userInfo.Uuid, CipherType: getCipherFromString(cypher), } return &protocol.User{ Level: 0, Email: format.UserTag(tag, userInfo.Uuid), Account: serial.ToTypedMessage(ssAccount), } } else { var keyLength int switch cypher { case "2022-blake3-aes-128-gcm": keyLength = 16 case "2022-blake3-aes-256-gcm": keyLength = 32 } ssAccount := &shadowsocks_2022.User{ Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])), } return &protocol.User{ Level: 0, Email: format.UserTag(tag, userInfo.Uuid), Account: serial.ToTypedMessage(ssAccount), } } } func getCipherFromString(c string) shadowsocks.CipherType { switch strings.ToLower(c) { case "aes-128-gcm", "aead_aes_128_gcm": return shadowsocks.CipherType_AES_128_GCM case "aes-256-gcm", "aead_aes_256_gcm": return shadowsocks.CipherType_AES_256_GCM case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305": return shadowsocks.CipherType_CHACHA20_POLY1305 case "none", "plain": return shadowsocks.CipherType_NONE default: return shadowsocks.CipherType_UNKNOWN } } ================================================ FILE: core/xray/trojan.go ================================================ package xray import ( "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/format" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/proxy/trojan" ) func buildTrojanUsers(tag string, userInfo []panel.UserInfo) (users []*protocol.User) { users = make([]*protocol.User, len(userInfo)) for i := range userInfo { users[i] = buildTrojanUser(tag, &(userInfo)[i]) } return users } func buildTrojanUser(tag string, userInfo *panel.UserInfo) (user *protocol.User) { trojanAccount := &trojan.Account{ Password: userInfo.Uuid, } return &protocol.User{ Level: 0, Email: format.UserTag(tag, userInfo.Uuid), Account: serial.ToTypedMessage(trojanAccount), } } ================================================ FILE: core/xray/user.go ================================================ package xray import ( "context" "fmt" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/format" vCore "github.com/Yuzuki616/V2bX/core" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/proxy" ) func (c *Core) GetUserManager(tag string) (proxy.UserManager, error) { handler, err := c.ihm.GetHandler(context.Background(), tag) if err != nil { return nil, fmt.Errorf("no such inbound tag: %s", err) } inboundInstance, ok := handler.(proxy.GetInbound) if !ok { return nil, fmt.Errorf("handler %s is not implement proxy.GetInbound", tag) } userManager, ok := inboundInstance.GetInbound().(proxy.UserManager) if !ok { return nil, fmt.Errorf("handler %s is not implement proxy.UserManager", tag) } return userManager, nil } func (c *Core) DelUsers(users []panel.UserInfo, tag string) error { userManager, err := c.GetUserManager(tag) if err != nil { return fmt.Errorf("get user manager error: %s", err) } var up, down, user string for i := range users { user = format.UserTag(tag, users[i].Uuid) err = userManager.RemoveUser(context.Background(), user) if err != nil { return err } up = "user>>>" + user + ">>>traffic>>>uplink" down = "user>>>" + user + ">>>traffic>>>downlink" c.shm.UnregisterCounter(up) c.shm.UnregisterCounter(down) } return nil } func (c *Core) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { upName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>uplink" downName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>downlink" upCounter := c.shm.GetCounter(upName) downCounter := c.shm.GetCounter(downName) if reset { if upCounter != nil { up = upCounter.Set(0) } if downCounter != nil { down = downCounter.Set(0) } } else { if upCounter != nil { up = upCounter.Value() } if downCounter != nil { down = downCounter.Value() } } return up, down } func (c *Core) AddUsers(p *vCore.AddUsersParams) (added int, err error) { users := make([]*protocol.User, 0, len(p.UserInfo)) switch p.NodeInfo.Type { case "v2ray": if p.NodeInfo.ExtraConfig.EnableVless == "true" { users = buildVlessUsers(p.Tag, p.UserInfo, p.NodeInfo.ExtraConfig.VlessFlow) } else { users = buildVmessUsers(p.Tag, p.UserInfo) } case "trojan": users = buildTrojanUsers(p.Tag, p.UserInfo) case "shadowsocks": users = buildSSUsers(p.Tag, p.UserInfo, p.NodeInfo.Cipher, p.NodeInfo.ServerKey) default: return 0, fmt.Errorf("unsupported node type: %s", p.NodeInfo.Type) } man, err := c.GetUserManager(p.Tag) if err != nil { return 0, fmt.Errorf("get user manager error: %s", err) } for _, u := range users { mUser, err := u.ToMemoryUser() if err != nil { return 0, err } err = man.AddUser(context.Background(), mUser) if err != nil { return 0, err } } return len(users), nil } ================================================ FILE: core/xray/vmess.go ================================================ package xray import ( "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/format" "github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/infra/conf" "github.com/xtls/xray-core/proxy/vless" ) func buildVmessUsers(tag string, userInfo []panel.UserInfo) (users []*protocol.User) { users = make([]*protocol.User, len(userInfo)) for i, user := range userInfo { users[i] = buildVmessUser(tag, &user) } return users } func buildVmessUser(tag string, userInfo *panel.UserInfo) (user *protocol.User) { vmessAccount := &conf.VMessAccount{ ID: userInfo.Uuid, Security: "auto", } return &protocol.User{ Level: 0, Email: format.UserTag(tag, userInfo.Uuid), // Uid: InboundTag|email Account: serial.ToTypedMessage(vmessAccount.Build()), } } func buildVlessUsers(tag string, userInfo []panel.UserInfo, flow string) (users []*protocol.User) { users = make([]*protocol.User, len(userInfo)) for i := range userInfo { users[i] = buildVlessUser(tag, &(userInfo)[i], flow) } return users } func buildVlessUser(tag string, userInfo *panel.UserInfo, flow string) (user *protocol.User) { vlessAccount := &vless.Account{ Id: userInfo.Uuid, } vlessAccount.Flow = flow return &protocol.User{ Level: 0, Email: format.UserTag(tag, userInfo.Uuid), Account: serial.ToTypedMessage(vlessAccount), } } ================================================ FILE: core/xray/xray.go ================================================ package xray import ( "os" "sync" "github.com/Yuzuki616/V2bX/conf" vCore "github.com/Yuzuki616/V2bX/core" "github.com/Yuzuki616/V2bX/core/xray/app/dispatcher" _ "github.com/Yuzuki616/V2bX/core/xray/distro/all" "github.com/goccy/go-json" log "github.com/sirupsen/logrus" "github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/app/stats" "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/core" "github.com/xtls/xray-core/features/inbound" "github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/routing" statsFeature "github.com/xtls/xray-core/features/stats" coreConf "github.com/xtls/xray-core/infra/conf" ) func init() { vCore.RegisterCore("xray", New) } // Core Structure type Core struct { access sync.Mutex Server *core.Instance ihm inbound.Manager ohm outbound.Manager shm statsFeature.Manager dispatcher *dispatcher.DefaultDispatcher } func New(c *conf.CoreConfig) (vCore.Core, error) { return &Core{Server: getCore(c.XrayConfig)}, nil } func parseConnectionConfig(c *conf.ConnectionConfig) (policy *coreConf.Policy) { policy = &coreConf.Policy{ StatsUserUplink: true, StatsUserDownlink: true, Handshake: &c.Handshake, ConnectionIdle: &c.ConnIdle, UplinkOnly: &c.UplinkOnly, DownlinkOnly: &c.DownlinkOnly, BufferSize: &c.BufferSize, } return } func getCore(c *conf.XrayConfig) *core.Instance { os.Setenv("XRAY_LOCATION_ASSET", c.AssetPath) // Log Config coreLogConfig := &coreConf.LogConfig{} coreLogConfig.LogLevel = c.LogConfig.Level coreLogConfig.AccessLog = c.LogConfig.AccessPath coreLogConfig.ErrorLog = c.LogConfig.ErrorPath // DNS config coreDnsConfig := &coreConf.DNSConfig{} os.Setenv("XRAY_DNS_PATH", "") if c.DnsConfigPath != "" { if f, err := os.Open(c.DnsConfigPath); err != nil { log.WithField("err", err).Panic("Failed to read DNS config file") } else { if err = json.NewDecoder(f).Decode(coreDnsConfig); err != nil { log.WithField("err", err).Panic("Failed to unmarshal DNS config") } } os.Setenv("XRAY_DNS_PATH", c.DnsConfigPath) } dnsConfig, err := coreDnsConfig.Build() if err != nil { log.WithField("err", err).Panic("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help") } // Routing config coreRouterConfig := &coreConf.RouterConfig{} if c.RouteConfigPath != "" { if f, err := os.Open(c.RouteConfigPath); err != nil { log.WithField("err", err).Panic("Failed to read Routing config file") } else { if err = json.NewDecoder(f).Decode(coreRouterConfig); err != nil { log.WithField("err", err).Panic("Failed to unmarshal Routing config") } } } routeConfig, err := coreRouterConfig.Build() if err != nil { log.WithField("err", err).Panic("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html") } // Custom Inbound config var coreCustomInboundConfig []coreConf.InboundDetourConfig if c.InboundConfigPath != "" { if f, err := os.Open(c.InboundConfigPath); err != nil { log.WithField("err", err).Panic("Failed to read Custom Inbound config file") } else { if err = json.NewDecoder(f).Decode(&coreCustomInboundConfig); err != nil { log.WithField("err", err).Panic("Failed to unmarshal Custom Inbound config") } } } var inBoundConfig []*core.InboundHandlerConfig for _, config := range coreCustomInboundConfig { oc, err := config.Build() if err != nil { log.WithField("err", err).Panic("Failed to understand Inbound config, Please check: https://xtls.github.io/config/inbound.html for help") } inBoundConfig = append(inBoundConfig, oc) } // Custom Outbound config var coreCustomOutboundConfig []coreConf.OutboundDetourConfig if c.OutboundConfigPath != "" { if f, err := os.Open(c.OutboundConfigPath); err != nil { log.WithField("err", err).Panic("Failed to read Custom Outbound config file") } else { if err = json.NewDecoder(f).Decode(&coreCustomOutboundConfig); err != nil { log.WithField("err", err).Panic("Failed to unmarshal Custom Outbound config") } } } var outBoundConfig []*core.OutboundHandlerConfig for _, config := range coreCustomOutboundConfig { oc, err := config.Build() if err != nil { log.WithField("err", err).Panic("Failed to understand Outbound config, Please check: https://xtls.github.io/config/outbound.html for help") } outBoundConfig = append(outBoundConfig, oc) } // Policy config levelPolicyConfig := parseConnectionConfig(c.ConnectionConfig) corePolicyConfig := &coreConf.PolicyConfig{} corePolicyConfig.Levels = map[uint32]*coreConf.Policy{0: levelPolicyConfig} policyConfig, _ := corePolicyConfig.Build() // Build Core conf config := &core.Config{ App: []*serial.TypedMessage{ serial.ToTypedMessage(coreLogConfig.Build()), serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&stats.Config{}), serial.ToTypedMessage(&proxyman.InboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(policyConfig), serial.ToTypedMessage(dnsConfig), serial.ToTypedMessage(routeConfig), }, Inbound: inBoundConfig, Outbound: outBoundConfig, } server, err := core.New(config) if err != nil { log.WithField("err", err).Panic("failed to create instance") } log.Info("Xray Core Version: ", core.Version()) return server } // Start the Core func (c *Core) Start() error { c.access.Lock() defer c.access.Unlock() if err := c.Server.Start(); err != nil { return err } c.shm = c.Server.GetFeature(statsFeature.ManagerType()).(statsFeature.Manager) c.ihm = c.Server.GetFeature(inbound.ManagerType()).(inbound.Manager) c.ohm = c.Server.GetFeature(outbound.ManagerType()).(outbound.Manager) c.dispatcher = c.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher) return nil } // Close the core func (c *Core) Close() error { c.access.Lock() defer c.access.Unlock() c.ihm = nil c.ohm = nil c.shm = nil c.dispatcher = nil err := c.Server.Close() if err != nil { return err } return nil } func (c *Core) Protocols() []string { return []string{ "v2ray", "shadowsocks", "trojan", } } ================================================ FILE: example/config.yml.example ================================================ CoreConfig: Type: "xray" # Core type, default support "xray" and "hy". If you need many cores, use " " to split XrayConfig: Log: Level: warning # Log level: none, error, warning, info, debug AccessPath: # /etc/XrayR/access.Log ErrorPath: # /etc/XrayR/error.log DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.github.io/config/dns.html for help RouteConfigPath: # /etc/XrayR/route.json # Path to route config, check https://xtls.github.io/config/routing.html for help InboundConfigPath: # /etc/XrayR/custom_inbound.json # Path to custom inbound config, check https://xtls.github.io/config/inbound.html for help OutboundConfigPath: # /etc/XrayR/custom_outbound.json # Path to custom outbound config, check https://xtls.github.io/config/outbound.html for help ConnectionConfig: Handshake: 4 # Handshake time limit, Second ConnIdle: 30 # Connection idle time limit, Second UplinkOnly: 2 # Time limit when the connection downstream is closed, Second DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second BufferSize: 64 # The internal cache size of each connection, kB Nodes: - ApiConfig: ApiHost: "http://127.0.0.1:667" ApiKey: "123" NodeID: 41 NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan Timeout: 30 # Timeout for the api request RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file ControllerConfig: ListenIP: 0.0.0.0 # IP address you want to listen SendIP: 0.0.0.0 # IP address you want to send pacakage XrayOptions: EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy EnableTFO: false # Enable TCP Fast Open EnableProxyProtocol: false # Only works for WebSocket and TCP EnableFallback: false # Only support for Trojan and Vless FallBackConfigs: # Support multiple fallbacks - SNI: # TLS SNI(Server Name Indication), Empty for any Alpn: # Alpn, Empty for any Path: # HTTP PATH, Empty for any Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details. ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable HyOptions: Resolver: "udp://1.1.1.1:53" # DNS resolver address ResolvePreference: 64 # DNS IPv4/IPv6 preference. Available options: "64" (IPv6 first, fallback to IPv4), "46" (IPv4 first, fallback to IPv6), "6" (IPv6 only), "4" (IPv4 only) SendDevice: "eth0" # Bind device for outbound connections (usually requires root) LimitConfig: EnableRealtime: false # Check device limit on real time SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable ConnLimit: 0 # Connecting limit, only working for TCP, 0mean EnableIpRecorder: false # Enable online ip report IpRecorderConfig: Type: "Recorder" # Recorder type: Recorder, Redis RecorderConfig: Url: "http://127.0.0.1:123" # Report url Token: "123" # Report token Timeout: 10 # Report timeout, sec. RedisConfig: Address: "127.0.0.1:6379" # Redis address Password: "" # Redis password DB: 0 # Redis DB Expiry: 60 # redis expiry time, sec. Periodic: 60 # Report interval, sec. EnableIpSync: false # Enable online ip sync EnableDynamicSpeedLimit: false # Enable dynamic speed limit DynamicSpeedLimitConfig: Periodic: 60 # Time to check the user traffic , sec. Traffic: 0 # Traffic limit, MB SpeedLimit: 0 # Speed limit, Mbps ExpireTime: 0 # Time limit, sec. CertConfig: CertMode: dns # Option about how to get certificate: none, file, http, dns, reality, remote. Choose "none" will forcedly disable the tls config. CertDomain: "node1.test.com" # Domain to cert CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file KeyFile: /etc/XrayR/cert/node1.test.com.key Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/ Email: test@me.com DNSEnv: # DNS ENV option used by DNS provider ALICLOUD_ACCESS_KEY: aaa ALICLOUD_SECRET_KEY: bbb RealityConfig: # This config like RealityObject for xray-core, please check https://xtls.github.io/config/transport.html#realityobject Dest: 80 # Same fallback dest Xver: 0 # Same fallback xver ServerNames: - "example.com" - "www.example.com" PrivateKey: "" # Private key for server MinClientVer: "" # Min client version MaxClientVer: "" # Max client version MaxTimeDiff: 0 # Max time difference, ms ShortIds: # Short ids - "" - "0123456789abcdef" # - # ApiConfig: # ApiHost: "http://127.0.0.1:668" # ApiKey: "123" # NodeID: 4 # NodeType: Shadowsocks # Node type: V2ray, Shadowsocks, Trojan # Timeout: 30 # Timeout for the api request # EnableVless: false # Enable Vless for V2ray Type # EnableXTLS: false # Enable XTLS for V2ray and Trojan # SpeedLimit: 0 # Mbps, Local settings will replace remote settings # DeviceLimit: 0 # Local settings will replace remote settings # ControllerConfig: # ListenIP: 0.0.0.0 # IP address you want to listen # EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well # CertConfig: # CertMode: dns # Option about how to get certificate: none, file, http, dns # CertDomain: "node1.test.com" # Domain to cert # CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file # KeyFile: /etc/XrayR/cert/node1.test.com.pem # Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/ # Email: test@me.com # DNSEnv: # DNS ENV option used by DNS provider # ALICLOUD_ACCESS_KEY: aaa # ALICLOUD_SECRET_KEY: bbb ================================================ FILE: example/custom_inbound.json ================================================ [ { "listen": "0.0.0.0", "port": 1234, "protocol": "socks", "settings": { "auth": "noauth", "accounts": [ { "user": "my-username", "pass": "my-password" } ], "udp": false, "ip": "127.0.0.1", "userLevel": 0 } } ] ================================================ FILE: example/custom_outbound.json ================================================ [ { "tag": "IPv4_out", "protocol": "freedom", "settings": {} }, { "tag": "IPv6_out", "protocol": "freedom", "settings": { "domainStrategy": "UseIPv6" } }, { "tag": "socks5-warp", "protocol": "socks", "settings": { "servers": [{ "address": "127.0.0.1", "port": 40000 }] } }, { "protocol": "blackhole", "tag": "block" } ] ================================================ FILE: example/dns.json ================================================ { "servers": [ "1.1.1.1", "8.8.8.8", "localhost" ], "tag": "dns_inbound" } ================================================ FILE: example/route.json ================================================ { "domainStrategy": "IPOnDemand", "rules": [ { "type": "field", "outboundTag": "block", "ip": [ "geoip:private" ] }, { "type": "field", "outboundTag": "block", "protocol": [ "bittorrent" ] }, { "type": "field", "outboundTag": "socks5-warp", "domain": [""] }, { "type": "field", "outboundTag": "IPv6_out", "domain": [ "geosite:netflix" ] }, { "type": "field", "outboundTag": "IPv4_out", "network": "udp,tcp" } ] } ================================================ FILE: example/rulelist ================================================ (.+\.|^)(360|so)\.(cn|com) baidu.com google.com ================================================ FILE: go.mod ================================================ module github.com/Yuzuki616/V2bX go 1.19 require ( github.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5490 github.com/Yuzuki616/quic-go v0.34.1 github.com/beevik/ntp v1.2.0 github.com/folbricht/routedns v0.1.20 github.com/fsnotify/fsnotify v1.6.0 github.com/go-acme/lego/v4 v4.13.3 github.com/go-redis/redis/v8 v8.11.5 github.com/go-resty/resty/v2 v2.7.0 github.com/goccy/go-json v0.10.2 github.com/hashicorp/go-multierror v1.1.1 github.com/juju/ratelimit v1.0.2 github.com/oschwald/geoip2-golang v1.9.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/xtls/xray-core v1.8.3 golang.org/x/crypto v0.11.0 golang.org/x/sys v0.10.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 ) require ( cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.24 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/aws/aws-sdk-go v1.39.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/civo/civogo v0.3.11 // indirect github.com/cloudflare/cloudflare-go v0.70.0 // indirect github.com/coreos/go-iptables v0.6.0 // indirect github.com/cpu/goacmedns v0.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dnsimple/dnsimple-go v1.2.0 // indirect github.com/exoscale/egoscale v0.100.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/gaukas/godicttls v0.0.3 // indirect github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/jtacoma/uritemplates v1.0.0 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/klauspost/compress v1.16.6 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect github.com/labbsr0x/goh v1.0.1 // indirect github.com/linode/linodego v1.17.2 // indirect github.com/liquidweb/go-lwApi v0.0.5 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-go v1.6.3 // indirect github.com/lucas-clemente/quic-go v0.31.1 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/marten-seemann/qpack v0.3.0 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.4 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.2 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/miekg/dns v1.1.55 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect github.com/nrdcg/desec v0.7.0 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/freemyip v0.2.0 // indirect github.com/nrdcg/goinwx v0.8.2 // indirect github.com/nrdcg/namesilo v0.2.1 // indirect github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/porkbun v0.2.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect github.com/oschwald/maxminddb-golang v1.11.0 // indirect github.com/ovh/go-ovh v1.4.1 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pion/dtls/v2 v2.2.4 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v2 v2.0.0 // indirect github.com/pion/udp v0.1.4 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/otp v1.4.0 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/quic-go/quic-go v0.35.1 // indirect github.com/refraction-networking/utls v1.3.2 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/sacloud/api-client-go v0.2.8 // indirect github.com/sacloud/go-http v0.1.6 // indirect github.com/sacloud/iaas-api-go v1.11.1 // indirect github.com/sacloud/packages-go v0.0.9 // indirect github.com/sagernet/sing v0.2.5 // indirect github.com/sagernet/sing-shadowsocks v0.2.2 // indirect github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/softlayer-go v1.1.2 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect github.com/transip/gotransip/v6 v6.20.0 // indirect github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a // indirect github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.11.0 // indirect golang.org/x/oauth2 v0.9.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.10.0 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect lukechampine.com/blake3 v1.2.1 // indirect ) exclude gvisor.dev/gvisor v0.0.0-20230313184804-9bf6dd27710d ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 h1:vX+gnvBc56EbWYrmlhYbFYRaeikAke1GL84N4BEYOFE= github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ= github.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5490 h1:OqS0ywNFR0fEWteLyTcexUNkpdb3vTl5EdMn9gMJMCc= github.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5490/go.mod h1:Byg39a10tXQ6ysRz5r59YhVMUKgXAThI+0/LcVr+WOE= github.com/Yuzuki616/quic-go v0.34.1 h1:9Is+Dofzn6qJ9a9t4Ixe4oX7Cb4LU7u4zDozVB8DBDY= github.com/Yuzuki616/quic-go v0.34.1/go.mod h1:089qZpsXn7CL8kE7G2HurF0bLiZnzQdRIrT7PM0MMQs= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo= github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/beevik/ntp v1.2.0 h1:n1teVGbd4YM36FlGvWYfccBIdGzeaakHrTlo6RSL8mw= github.com/beevik/ntp v1.2.0/go.mod h1:vD6h1um4kzXpqmLTuu0cCLcC+NfvC0IC+ltmEDA8E78= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.70.0 h1:4opGbUygM8DjirUuaz23jn3akuAcnOCEx+0nQtQEcFo= github.com/cloudflare/cloudflare-go v0.70.0/go.mod h1:VW6GuazkaZ4xEDkFt24lkXQUsE8q7BiGqDniC2s8WEM= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/deepmap/oapi-codegen v1.9.1 h1:yHmEnA7jSTUMQgV+uN02WpZtwHnz2CBW3mZRIxr1vtI= github.com/deepmap/oapi-codegen v1.9.1/go.mod h1:PLqNAhdedP8ttRpBBkzLKU3bp+Fpy+tTgeAMlztR2cw= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM= github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exoscale/egoscale v0.100.1 h1:iXsV1Ei7daqe/6FYSCSDyrFs1iUG1l1X9qNh2uMw6z0= github.com/exoscale/egoscale v0.100.1/go.mod h1:BAb9p4rmyU+Wl400CJZO5270H2sXtdsZjLcm5xMKkz4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/folbricht/routedns v0.1.20 h1:OCbHLzgcctbp+GRE4QVfnZYd1du3G0muO/Ba9o4QCsw= github.com/folbricht/routedns v0.1.20/go.mod h1:D8Vd2zJKkIQncvKTBJyed2poKqgpWKFhwIFGdWxnmCQ= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk= github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-acme/lego/v4 v4.13.3 h1:aZ1S9FXIkCWG3Uw/rZKSD+MOuO8ZB1t6p9VCg6jJiNY= github.com/go-acme/lego/v4 v4.13.3/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k= github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c= github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0= github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtacoma/uritemplates v1.0.0 h1:xwx5sBF7pPAb0Uj8lDC1Q/aBPpOFyQza7OC705ZlLCo= github.com/jtacoma/uritemplates v1.0.0/go.mod h1:IhIICdE9OcvgUnGwTtJxgBQ+VrTrti5PcbLVSJianO8= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk= github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/labstack/echo/v4 v4.6.3/go.mod h1:Hk5OiHj0kDqmFq7aHe7eDqI7CUhuCrfpupQtLGGLm7A= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= github.com/lestrrat-go/codegen v1.0.2/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/linode/linodego v1.17.2 h1:b32dj4662PGG5P9qVa6nBezccWdqgukndlMIuPGq1CQ= github.com/linode/linodego v1.17.2/go.mod h1:C2iyT3Vg2O2sPxkWka4XAQ5WSUtm5LmTZ3Adw43Ra7Q= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA= github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4= github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g= github.com/marten-seemann/qtls-go1-18 v0.1.4 h1:ogomB+lWV3Vmwiu6RTwDVTMGx+9j7SEi98e8QB35Its= github.com/marten-seemann/qtls-go1-18 v0.1.4/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-19 v0.1.2 h1:ZevAEqKXH0bZmoOBPiqX2h5rhQ7cbZi+X+rlq2JUbCE= github.com/marten-seemann/qtls-go1-19 v0.1.2/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs= github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8= github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4= github.com/nrdcg/goinwx v0.8.2 h1:RmjiHlEA+lzi3toXyPSaE6hWnBQ0+G+1u7w8C6Fpp4g= github.com/nrdcg/goinwx v0.8.2/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= github.com/nrdcg/porkbun v0.2.0 h1:ghaqPtIKcffba99epWFkK3VWf6TKJT9WMXMgaTqv95Y= github.com/nrdcg/porkbun v0.2.0/go.mod h1:i0uLMn9ItFsLsSQIAeEu1wQ9/+6EvX1eQw15hulMMRw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nzdjb/go-metaname v1.0.0 h1:sNASlZC1RM3nSudtBTE1a3ZVTDyTpjqI5WXRPrdZ9Hg= github.com/nzdjb/go-metaname v1.0.0/go.mod h1:0GR0LshZax1Lz4VrOrfNSE4dGvTp7HGjiemdczXT2H4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU= github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM= github.com/ovh/go-ovh v1.4.1/go.mod h1:6bL6pPyUT7tBfI0pqOegJgRjgjuO+mOo+MyXd1EEC0M= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg= github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8= github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sacloud/api-client-go v0.2.8 h1:tIY6PZNBX900K66TqEPa4d6UIbedUczfCBnPJkzi8kw= github.com/sacloud/api-client-go v0.2.8/go.mod h1:0CV/kWNYlS1hCNdnk6Wx7Wdg8DPFCnv0zOIzdXjeAeY= github.com/sacloud/go-http v0.1.6 h1:lJGXDt9xrxJiDszRPaN9NIP8MVj10YKMzmnyzdSfI8w= github.com/sacloud/go-http v0.1.6/go.mod h1:oLAHoDJRkptf8sq4fE8oERLkdCh0kJWfWu+paoJY7I0= github.com/sacloud/iaas-api-go v1.11.1 h1:2MsFZ4H1uRdRVx2nVXuERWQ3swoFc3XreIV5hJ3Nsws= github.com/sacloud/iaas-api-go v1.11.1/go.mod h1:uBDSa06F/V0OnoR66jGdbH0PVnCJw+NeE9RVbVgMfss= github.com/sacloud/packages-go v0.0.9 h1:GbinkBLC/eirFhHpLjoDW6JV7+95Rnd2d8RWj7Afeks= github.com/sacloud/packages-go v0.0.9/go.mod h1:k+EEUMF2LlncjbNIJNOqLyZ9wjTESPIWIk1OA7x9j2Q= github.com/sagernet/sing v0.2.5 h1:N8sUluR8GZvR9DqUiH3FA3vBb4m/EDdOVTYUrDzJvmY= github.com/sagernet/sing v0.2.5/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing-shadowsocks v0.2.2 h1:ezSdVhrmIcwDXmCZF3bOJVMuVtTQWpda+1Op+Ie2TA4= github.com/sagernet/sing-shadowsocks v0.2.2/go.mod h1:JIBWG6a7orB2HxBxYElViQFLUQxFVG7DuqIj8gD7uCQ= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 h1:1WuWJu7/e8SqK+uQl7lfk/N/oMZTL2NE/TJsNKRNMc4= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 h1:ZTzdx88+AcnjqUfJwnz89UBrMSBQ1NEysg9u5d+dU9c= github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04/go.mod h1:5KS21fpch8TIMyAUv/qQqTa3GZfBDYgjaZbd2KXKYfg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/softlayer/softlayer-go v1.1.2 h1:rUSSGCyaxymvTOsaFjwr+cGxA8muw3xg2LSrIMNcN/c= github.com/softlayer/softlayer-go v1.1.2/go.mod h1:hvAbzGH4LRXA6yXY8BNx99yoqZ7urfDdtl9mvBf0G+g= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/transip/gotransip/v6 v6.20.0 h1:AuvwyOZ51f2brzMbTqlRy/wmaM3kF7Vx5Wds8xcDflY= github.com/transip/gotransip/v6 v6.20.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a h1:BOqgJ4jku0LHPDoR51RD8Mxmo0LHxCzJT/M9MemYdHo= github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg= github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A= github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c h1:mKnW6IGLw7uXu6DL6RitufZWcXS6hCnauXRUFof7rKM= github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c/go.mod h1:F4UyVEmq4/m5lAmx+GccrxyRCXmnBjzUL09JLTQFp94= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 h1:AMyzgjkh54WocjQSlCnT1LhDc/BKiUqtNOv40AkpURs= github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y= github.com/xtls/xray-core v1.8.3 h1:lxaVklPjLKqUU4ua4qH8SBaRcAaNHlH+LmXOx0U/Ejg= github.com/xtls/xray-core v1.8.3/go.mod h1:i7t4JFnq828P2+XK0XjGQ8W9x78iu+EJ7jI4l3sonIw= github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0= github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E= github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE= google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ns1/ns1-go.v2 v2.7.6 h1:mCPl7q0jbIGACXvGBljAuuApmKZo3rRi4tlRIEbMvjA= gopkg.in/ns1/ns1-go.v2 v2.7.6/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4= gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= ================================================ FILE: limiter/clear.go ================================================ package limiter import log "github.com/sirupsen/logrus" func ClearOnlineIP() error { log.WithField("Type", "Limiter"). Debug("Clear online ip...") limitLock.RLock() for _, l := range limiter { l.ConnLimiter.ClearOnlineIP() } limitLock.RUnlock() log.WithField("Type", "Limiter"). Debug("Clear online ip done") return nil } ================================================ FILE: limiter/conn.go ================================================ package limiter import ( "sync" "time" ) type ConnLimiter struct { realtime bool ipLimit int connLimit int count sync.Map // map[string]int ip sync.Map // map[string]map[string]int } func NewConnLimiter(conn int, ip int, realtime bool) *ConnLimiter { return &ConnLimiter{ realtime: realtime, connLimit: conn, ipLimit: ip, count: sync.Map{}, ip: sync.Map{}, } } func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bool) { if c.connLimit != 0 { if v, ok := c.count.Load(user); ok { if v.(int) >= c.connLimit { // over connection limit return true } else if isTcp { // tcp protocol // connection count add c.count.Store(user, v.(int)+1) } } else if isTcp { // tcp protocol // store connection count c.count.Store(user, 1) } } if c.ipLimit == 0 { return false } // first user map ipMap := new(sync.Map) if c.realtime { if isTcp { ipMap.Store(ip, 2) } else { ipMap.Store(ip, 1) } } else { ipMap.Store(ip, time.Now()) } // check user online ip if v, ok := c.ip.LoadOrStore(user, ipMap); ok { // have user ips := v.(*sync.Map) cn := 0 if online, ok := ips.Load(ip); ok { // online ip if c.realtime { if isTcp { // tcp count add ips.Store(ip, online.(int)+2) } } else { // update connect time for not realtime ips.Store(ip, time.Now()) } } else { // not online ip ips.Range(func(_, _ interface{}) bool { cn++ if cn >= c.ipLimit { limit = true return false } return true }) if limit { // over ip limit return } if c.realtime { if isTcp { ips.Store(ip, 2) } else { ips.Store(ip, 1) } } else { ips.Store(ip, time.Now()) } } } return } // DelConnCount Delete tcp connection count, no tcp do not use func (c *ConnLimiter) DelConnCount(user string, ip string) { if !c.realtime { return } if c.connLimit != 0 { if v, ok := c.count.Load(user); ok { if v.(int) == 1 { c.count.Delete(user) } else { c.count.Store(user, v.(int)-1) } } } if c.ipLimit == 0 { return } if i, ok := c.ip.Load(user); ok { is := i.(*sync.Map) if i, ok := is.Load(ip); ok { if i.(int) == 2 { is.Delete(ip) } else { is.Store(user, i.(int)-2) } notDel := false c.ip.Range(func(_, _ any) bool { notDel = true return false }) if !notDel { c.ip.Delete(user) } } } } // ClearOnlineIP Clear udp,icmp and other packet protocol online ip func (c *ConnLimiter) ClearOnlineIP() { c.ip.Range(func(u, v any) bool { userIp := v.(*sync.Map) notDel := false userIp.Range(func(ip, v any) bool { notDel = true if _, ok := v.(int); ok { if v.(int) == 1 { // clear packet ip for realtime userIp.Delete(ip) } return true } else { // clear ip for not realtime if v.(time.Time).Before(time.Now().Add(time.Minute)) { // 1 minute no active userIp.Delete(ip) } } return true }) if !notDel { c.ip.Delete(u) } return true }) } ================================================ FILE: limiter/conn_test.go ================================================ package limiter import ( "sync" "testing" "time" ) var c *ConnLimiter func init() { c = NewConnLimiter(1, 1, true) } func TestConnLimiter_AddConnCount(t *testing.T) { t.Log(c.AddConnCount("1", "1", true)) t.Log(c.AddConnCount("1", "2", true)) } func TestConnLimiter_DelConnCount(t *testing.T) { t.Log(c.AddConnCount("1", "1", true)) t.Log(c.AddConnCount("1", "2", true)) c.DelConnCount("1", "1") t.Log(c.AddConnCount("1", "2", true)) } func TestConnLimiter_ClearOnlineIP(t *testing.T) { t.Log(c.AddConnCount("1", "1", false)) t.Log(c.AddConnCount("1", "2", false)) c.ClearOnlineIP() t.Log(c.AddConnCount("1", "2", true)) c.DelConnCount("1", "2") t.Log(c.AddConnCount("1", "1", false)) // not realtime c.realtime = false t.Log(c.AddConnCount("3", "2", true)) c.ClearOnlineIP() t.Log(c.ip.Load("3")) time.Sleep(time.Minute) c.ClearOnlineIP() t.Log(c.ip.Load("3")) } func BenchmarkConnLimiter(b *testing.B) { wg := sync.WaitGroup{} for i := 0; i < b.N; i++ { wg.Add(1) go func() { c.AddConnCount("1", "2", true) c.DelConnCount("1", "2") wg.Done() }() } wg.Wait() } ================================================ FILE: limiter/dynamic.go ================================================ package limiter import ( "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/format" "time" ) func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limitNum int, expire int64) error { userLimit := &UserLimitInfo{ DynamicSpeedLimit: limitNum, ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(), } l.UserLimitInfo.Store(format.UserTag(tag, userInfo.Uuid), userLimit) return nil } // determineSpeedLimit returns the minimum non-zero rate func determineSpeedLimit(limit1, limit2 int) (limit int) { if limit1 == 0 || limit2 == 0 { if limit1 > limit2 { return limit1 } else if limit1 < limit2 { return limit2 } else { return 0 } } else { if limit1 > limit2 { return limit2 } else if limit1 < limit2 { return limit1 } else { return limit1 } } } ================================================ FILE: limiter/limiter.go ================================================ package limiter import ( "errors" "regexp" "sync" "time" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/format" "github.com/Yuzuki616/V2bX/conf" "github.com/juju/ratelimit" log "github.com/sirupsen/logrus" "github.com/xtls/xray-core/common/task" ) var limitLock sync.RWMutex var limiter map[string]*Limiter func Init() { limiter = map[string]*Limiter{} c := task.Periodic{ Interval: time.Minute * 2, Execute: ClearOnlineIP, } go func() { log.WithField("Type", "Limiter"). Debug("ClearOnlineIP started") time.Sleep(time.Minute * 2) _ = c.Start() }() } type Limiter struct { DomainRules []*regexp.Regexp ProtocolRules []string SpeedLimit int UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket } type UserLimitInfo struct { UID int SpeedLimit int DynamicSpeedLimit int ExpireTime int64 } func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter { info := &Limiter{ SpeedLimit: l.SpeedLimit, UserLimitInfo: new(sync.Map), ConnLimiter: NewConnLimiter(l.ConnLimit, l.IPLimit, l.EnableRealtime), SpeedLimiter: new(sync.Map), } for i := range users { if users[i].SpeedLimit != 0 { userLimit := &UserLimitInfo{ UID: users[i].Id, SpeedLimit: users[i].SpeedLimit, } info.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit) } } limitLock.Lock() limiter[tag] = info limitLock.Unlock() return info } func GetLimiter(tag string) (info *Limiter, err error) { limitLock.RLock() info, ok := limiter[tag] limitLock.RUnlock() if !ok { return nil, errors.New("not found") } return } func DeleteLimiter(tag string) { limitLock.Lock() delete(limiter, tag) limitLock.Unlock() } func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, deleted []panel.UserInfo) { for i := range deleted { l.UserLimitInfo.Delete(format.UserTag(tag, deleted[i].Uuid)) } for i := range added { if added[i].SpeedLimit != 0 { userLimit := &UserLimitInfo{ UID: added[i].Id, SpeedLimit: added[i].SpeedLimit, ExpireTime: 0, } l.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit) } } } func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int, expire time.Time) error { if v, ok := l.UserLimitInfo.Load(format.UserTag(tag, uuid)); ok { info := v.(*UserLimitInfo) info.DynamicSpeedLimit = limit info.ExpireTime = expire.Unix() } else { return errors.New("not found") } return nil } func (l *Limiter) CheckLimit(email string, ip string, isTcp bool) (Bucket *ratelimit.Bucket, Reject bool) { // ip and conn limiter if l.ConnLimiter.AddConnCount(email, ip, isTcp) { return nil, true } // check and gen speed limit Bucket nodeLimit := l.SpeedLimit userLimit := 0 if v, ok := l.UserLimitInfo.Load(email); ok { u := v.(*UserLimitInfo) if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 { if u.SpeedLimit != 0 { userLimit = u.SpeedLimit u.DynamicSpeedLimit = 0 u.ExpireTime = 0 } else { l.UserLimitInfo.Delete(email) } } else { userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit) } } limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit if limit > 0 { Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s if v, ok := l.SpeedLimiter.LoadOrStore(email, Bucket); ok { return v.(*ratelimit.Bucket), false } else { l.SpeedLimiter.Store(email, Bucket) return Bucket, false } } else { return nil, false } } type UserIpList struct { Uid int `json:"Uid"` IpList []string `json:"Ips"` } func determineDeviceLimit(nodeLimit, userLimit int) (limit int) { if nodeLimit == 0 || userLimit == 0 { if nodeLimit > userLimit { return nodeLimit } else if nodeLimit < userLimit { return userLimit } else { return 0 } } else { if nodeLimit > userLimit { return userLimit } else if nodeLimit < userLimit { return nodeLimit } else { return nodeLimit } } } ================================================ FILE: limiter/rule.go ================================================ package limiter import ( "regexp" "github.com/Yuzuki616/V2bX/api/panel" ) func (l *Limiter) CheckDomainRule(destination string) (reject bool) { // have rule for i := range l.DomainRules { if l.DomainRules[i].MatchString(destination) { reject = true break } } return } func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) { for i := range l.ProtocolRules { if l.ProtocolRules[i] == protocol { reject = true break } } return } func (l *Limiter) UpdateRule(rule *panel.Rules) error { l.DomainRules = make([]*regexp.Regexp, len(rule.Regexp)) for i := range rule.Regexp { l.DomainRules[i] = regexp.MustCompile(rule.Regexp[i]) } l.ProtocolRules = rule.Protocol return nil } ================================================ FILE: main.go ================================================ package main import "github.com/Yuzuki616/V2bX/cmd" func main() { cmd.Run() } ================================================ FILE: node/cert.go ================================================ package node import ( "fmt" "github.com/Yuzuki616/V2bX/common/file" "github.com/Yuzuki616/V2bX/node/lego" "log" ) func (c *Controller) renewCertTask() error { l, err := lego.New(c.CertConfig) if err != nil { log.Print("new lego error: ", err) return nil } err = l.RenewCert() if err != nil { log.Print("renew cert error: ", err) } return nil } func (c *Controller) requestCert() error { switch c.CertConfig.CertMode { case "reality", "none", "": return nil case "file": if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" { return fmt.Errorf("cert file path or key file path not exist") } return nil case "dns", "http": if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" { return fmt.Errorf("cert file path or key file path not exist") } if file.IsExist(c.CertConfig.CertFile) && file.IsExist(c.CertConfig.KeyFile) { return nil } l, err := lego.New(c.CertConfig) if err != nil { return fmt.Errorf("create lego object error: %s", err) } err = l.CreateCert() if err != nil { return fmt.Errorf("create cert error: %s", err) } return nil } return fmt.Errorf("unsupported certmode: %s", c.CertConfig.CertMode) } ================================================ FILE: node/controller.go ================================================ package node import ( "errors" "fmt" "github.com/Yuzuki616/V2bX/api/iprecoder" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/task" "github.com/Yuzuki616/V2bX/conf" vCore "github.com/Yuzuki616/V2bX/core" "github.com/Yuzuki616/V2bX/limiter" log "github.com/sirupsen/logrus" ) type Controller struct { server vCore.Core apiClient *panel.Client tag string limiter *limiter.Limiter traffic map[string]int64 userList []panel.UserInfo info *panel.NodeInfo ipRecorder iprecoder.IpRecorder nodeInfoMonitorPeriodic *task.Task userReportPeriodic *task.Task renewCertPeriodic *task.Task dynamicSpeedLimitPeriodic *task.Task onlineIpReportPeriodic *task.Task *conf.ControllerConfig } // NewController return a Node controller with default parameters. func NewController(server vCore.Core, api *panel.Client, config *conf.ControllerConfig) *Controller { controller := &Controller{ server: server, ControllerConfig: config, apiClient: api, } return controller } // Start implement the Start() function of the service interface func (c *Controller) Start() error { // First fetch Node Info var err error node, err := c.apiClient.GetNodeInfo() if err != nil { return fmt.Errorf("get node info error: %s", err) } // Update user c.userList, err = c.apiClient.GetUserList() if err != nil { return fmt.Errorf("get user list error: %s", err) } if len(c.userList) == 0 { return errors.New("add users error: not have any user") } c.tag = c.buildNodeTag(node) // add limiter l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList) // add rule limiter if err = l.UpdateRule(&node.Rules); err != nil { return fmt.Errorf("update rule error: %s", err) } c.limiter = l if node.Tls || node.Type == "hysteria" { err = c.requestCert() if err != nil { return fmt.Errorf("request cert error: %s", err) } } // Add new tag err = c.server.AddNode(c.tag, node, c.ControllerConfig) if err != nil { return fmt.Errorf("add new node error: %s", err) } added, err := c.server.AddUsers(&vCore.AddUsersParams{ Tag: c.tag, Config: c.ControllerConfig, UserInfo: c.userList, NodeInfo: node, }) if err != nil { return fmt.Errorf("add users error: %s", err) } log.WithField("tag", c.tag).Infof("Added %d new users", added) c.info = node c.startTasks(node) return nil } // Close implement the Close() function of the service interface func (c *Controller) Close() error { limiter.DeleteLimiter(c.tag) if c.nodeInfoMonitorPeriodic != nil { c.nodeInfoMonitorPeriodic.Close() } if c.userReportPeriodic != nil { c.userReportPeriodic.Close() } if c.renewCertPeriodic != nil { c.renewCertPeriodic.Close() } if c.dynamicSpeedLimitPeriodic != nil { c.dynamicSpeedLimitPeriodic.Close() } if c.onlineIpReportPeriodic != nil { c.onlineIpReportPeriodic.Close() } return nil } func (c *Controller) buildNodeTag(node *panel.NodeInfo) string { return fmt.Sprintf("%s-%s-%d", c.apiClient.APIHost, node.Type, node.Id) } ================================================ FILE: node/lego/cert.go ================================================ package lego import ( "fmt" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/providers/dns" "os" "strings" "time" ) func (l *Lego) SetProvider() error { switch l.config.CertMode { case "http": err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80")) if err != nil { return err } case "dns": for k, v := range l.config.DNSEnv { os.Setenv(k, v) } p, err := dns.NewDNSChallengeProviderByName(l.config.Provider) if err != nil { return fmt.Errorf("create dns challenge provider error: %s", err) } err = l.client.Challenge.SetDNS01Provider(p) if err != nil { return fmt.Errorf("set dns provider error: %s", err) } } return nil } func (l *Lego) CreateCert() (err error) { request := certificate.ObtainRequest{ Domains: []string{l.config.CertDomain}, } certificates, err := l.client.Certificate.Obtain(request) if err != nil { return fmt.Errorf("obtain certificate error: %s", err) } err = l.writeCert(certificates) return nil } func (l *Lego) RenewCert() error { file, err := os.ReadFile(l.config.CertFile) if err != nil { return fmt.Errorf("read cert file error: %s", err) } if e, err := l.CheckCert(file); !e { return nil } else if err != nil { return fmt.Errorf("check cert error: %s", err) } res, err := l.client.Certificate.Renew(certificate.Resource{ Domain: l.config.CertDomain, Certificate: file, }, false, false, "") if err != nil { return err } err = l.writeCert(res) return nil } func (l *Lego) CheckCert(file []byte) (bool, error) { cert, err := certcrypto.ParsePEMCertificate(file) if err != nil { return false, err } notAfter := int(time.Until(cert.NotAfter).Hours() / 24.0) if notAfter > 30 { return false, nil } return true, nil } func (l *Lego) parseParams(path string) string { r := strings.NewReplacer("{domain}", l.config.CertDomain, "{email}", l.config.Email) return r.Replace(path) } func (l *Lego) writeCert(certificates *certificate.Resource) error { err := checkPath(l.config.CertFile) if err != nil { return fmt.Errorf("check path error: %s", err) } err = os.WriteFile(l.parseParams(l.config.CertFile), certificates.Certificate, 0644) if err != nil { return err } err = checkPath(l.config.KeyFile) if err != nil { return fmt.Errorf("check path error: %s", err) } err = os.WriteFile(l.parseParams(l.config.KeyFile), certificates.PrivateKey, 0644) if err != nil { return err } return nil } ================================================ FILE: node/lego/lego.go ================================================ package lego import ( "fmt" "github.com/Yuzuki616/V2bX/common/file" "github.com/Yuzuki616/V2bX/conf" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/lego" "os" "path" ) type Lego struct { client *lego.Client config *conf.CertConfig } func New(config *conf.CertConfig) (*Lego, error) { user, err := NewUser(path.Join(path.Dir(config.CertFile), "user", fmt.Sprintf("user-%s.json", config.Email)), config.Email) if err != nil { return nil, fmt.Errorf("create user error: %s", err) } c := lego.NewConfig(user) //c.CADirURL = "http://192.168.99.100:4000/directory" c.Certificate.KeyType = certcrypto.RSA2048 client, err := lego.NewClient(c) if err != nil { return nil, err } l := Lego{ client: client, config: config, } err = l.SetProvider() if err != nil { return nil, fmt.Errorf("set provider error: %s", err) } return &l, nil } func checkPath(p string) error { if !file.IsExist(path.Dir(p)) { err := os.MkdirAll(path.Dir(p), 0755) if err != nil { return fmt.Errorf("create dir error: %s", err) } } return nil } ================================================ FILE: node/lego/lego_test.go ================================================ package lego import ( "github.com/Yuzuki616/V2bX/conf" "log" "os" "testing" ) var l *Lego func init() { var err error l, err = New(&conf.CertConfig{ CertMode: "dns", Email: "test@test.com", CertDomain: "test.test.com", Provider: "cloudflare", DNSEnv: map[string]string{ "CF_DNS_API_TOKEN": "123", }, CertFile: "./cert/1.pem", KeyFile: "./cert/1.key", }) if err != nil { log.Println(err) os.Exit(1) } } func TestLego_CreateCertByDns(t *testing.T) { err := l.CreateCert() if err != nil { t.Error(err) } } func TestLego_RenewCert(t *testing.T) { log.Println(l.RenewCert()) } ================================================ FILE: node/lego/user.go ================================================ package lego import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "github.com/Yuzuki616/V2bX/common/file" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/registration" "github.com/goccy/go-json" "os" ) type User struct { Email string `json:"Email"` Registration *registration.Resource `json:"Registration"` key crypto.PrivateKey KeyEncoded string `json:"Key"` } func (u *User) GetEmail() string { return u.Email } func (u *User) GetRegistration() *registration.Resource { return u.Registration } func (u *User) GetPrivateKey() crypto.PrivateKey { return u.key } func NewUser(path string, email string) (*User, error) { var user User if file.IsExist(path) { err := user.Load(path) if err != nil { return nil, err } if user.Email != email { user.Registration = nil user.Email = email err := registerUser(&user, path) if err != nil { return nil, err } } } else { user.Email = email err := registerUser(&user, path) if err != nil { return nil, err } } return &user, nil } func registerUser(user *User, path string) error { privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return fmt.Errorf("generate key error: %s", err) } user.key = privateKey c := lego.NewConfig(user) client, err := lego.NewClient(c) if err != nil { return fmt.Errorf("create lego client error: %s", err) } reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) if err != nil { return err } user.Registration = reg err = user.Save(path) if err != nil { return fmt.Errorf("save user error: %s", err) } return nil } func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) { encoded, err := x509.MarshalECPrivateKey(privKey) if err != nil { return "", err } pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: encoded}) return string(pemEncoded), nil } func (u *User) Save(path string) error { err := checkPath(path) if err != nil { return fmt.Errorf("check path error: %s", err) } u.KeyEncoded, _ = EncodePrivate(u.key.(*ecdsa.PrivateKey)) f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return err } err = json.NewEncoder(f).Encode(u) if err != nil { return fmt.Errorf("marshal json error: %s", err) } u.KeyEncoded = "" return nil } func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) { blockPriv, _ := pem.Decode([]byte(pemEncodedPriv)) x509EncodedPriv := blockPriv.Bytes privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv) return privateKey, err } func (u *User) Load(path string) error { f, err := os.Open(path) if err != nil { return fmt.Errorf("open file error: %s", err) } err = json.NewDecoder(f).Decode(u) if err != nil { return fmt.Errorf("unmarshal json error: %s", err) } u.key, err = u.DecodePrivate(u.KeyEncoded) if err != nil { return fmt.Errorf("decode private key error: %s", err) } return nil } ================================================ FILE: node/node.go ================================================ package node import ( "fmt" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/conf" vCore "github.com/Yuzuki616/V2bX/core" ) type Node struct { controllers []*Controller } func New() *Node { return &Node{} } func (n *Node) Start(nodes []*conf.NodeConfig, core vCore.Core) error { n.controllers = make([]*Controller, len(nodes)) for i, c := range nodes { p, err := panel.New(c.ApiConfig) if err != nil { return err } // Register controller service n.controllers[i] = NewController(core, p, c.ControllerConfig) err = n.controllers[i].Start() if err != nil { return fmt.Errorf("start node controller [%s-%s-%d] error: %s", c.ApiConfig.NodeType, c.ApiConfig.APIHost, c.ApiConfig.NodeID, err) } } return nil } func (n *Node) Close() { for _, c := range n.controllers { err := c.Close() if err != nil { panic(err) } } n.controllers = nil } ================================================ FILE: node/task.go ================================================ package node import ( "time" "github.com/Yuzuki616/V2bX/api/panel" "github.com/Yuzuki616/V2bX/common/task" vCore "github.com/Yuzuki616/V2bX/core" "github.com/Yuzuki616/V2bX/limiter" log "github.com/sirupsen/logrus" ) func (c *Controller) startTasks(node *panel.NodeInfo) { // fetch node info task c.nodeInfoMonitorPeriodic = &task.Task{ Interval: node.PullInterval, Execute: c.nodeInfoMonitor, } // fetch user list task c.userReportPeriodic = &task.Task{ Interval: node.PushInterval, Execute: c.reportUserTrafficTask, } log.WithField("tag", c.tag).Info("Start monitor node status") // delay to start nodeInfoMonitor _ = c.nodeInfoMonitorPeriodic.Start(false) log.WithField("tag", c.tag).Info("Start report node status") _ = c.userReportPeriodic.Start(false) if node.Tls { switch c.CertConfig.CertMode { case "reality", "none", "": default: c.renewCertPeriodic = &task.Task{ Interval: time.Hour * 24, Execute: c.renewCertTask, } log.WithField("tag", c.tag).Info("Start renew cert") // delay to start renewCert _ = c.renewCertPeriodic.Start(true) } } if c.LimitConfig.EnableDynamicSpeedLimit { c.traffic = make(map[string]int64) c.dynamicSpeedLimitPeriodic = &task.Task{ Interval: time.Duration(c.LimitConfig.DynamicSpeedLimitConfig.Periodic) * time.Second, Execute: c.SpeedChecker, } } } func (c *Controller) nodeInfoMonitor() (err error) { // get node info newNodeInfo, err := c.apiClient.GetNodeInfo() if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Get node info failed") return nil } // get user info newUserInfo, err := c.apiClient.GetUserList() if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Get user list failed") return nil } if newNodeInfo != nil { // nodeInfo changed if newUserInfo != nil { c.userList = newUserInfo } c.traffic = make(map[string]int64) // Remove old tag log.WithField("tag", c.tag).Info("Node changed, reload") err = c.server.DelNode(c.tag) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Delete node failed") return nil } // Remove Old limiter limiter.DeleteLimiter(c.tag) // Add new Limiter c.tag = c.buildNodeTag(newNodeInfo) l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList) // check cert if newNodeInfo.Tls || newNodeInfo.Type == "hysteria" { err = c.requestCert() if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Request cert failed") return nil } } // add new node err = c.server.AddNode(c.tag, newNodeInfo, c.ControllerConfig) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Add node failed") return nil } _, err = c.server.AddUsers(&vCore.AddUsersParams{ Tag: c.tag, Config: c.ControllerConfig, UserInfo: c.userList, NodeInfo: newNodeInfo, }) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Add users failed") return nil } err = l.UpdateRule(&newNodeInfo.Rules) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Update Rule failed") return nil } c.limiter = l // Check interval if c.nodeInfoMonitorPeriodic.Interval != newNodeInfo.PullInterval && newNodeInfo.PullInterval != 0 { c.nodeInfoMonitorPeriodic.Interval = newNodeInfo.PullInterval c.nodeInfoMonitorPeriodic.Close() _ = c.nodeInfoMonitorPeriodic.Start(false) } if c.userReportPeriodic.Interval != newNodeInfo.PushInterval && newNodeInfo.PushInterval != 0 { c.userReportPeriodic.Interval = newNodeInfo.PullInterval c.userReportPeriodic.Close() _ = c.userReportPeriodic.Start(false) } log.WithField("tag", c.tag).Infof("Added %d new users", len(c.userList)) c.info = newNodeInfo // exit return nil } // node no changed, check users if len(newUserInfo) == 0 { return nil } deleted, added := compareUserList(c.userList, newUserInfo) if len(deleted) > 0 { // have deleted users err = c.server.DelUsers(deleted, c.tag) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Delete users failed") return nil } } if len(added) > 0 { // have added users _, err = c.server.AddUsers(&vCore.AddUsersParams{ Tag: c.tag, Config: c.ControllerConfig, NodeInfo: c.info, UserInfo: added, }) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("Add users failed") return nil } } if len(added) > 0 || len(deleted) > 0 { // update Limiter c.limiter.UpdateUser(c.tag, added, deleted) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Error("limiter users failed") return nil } // clear traffic record if c.LimitConfig.EnableDynamicSpeedLimit { for i := range deleted { delete(c.traffic, deleted[i].Uuid) } } } c.userList = newUserInfo if len(added)+len(deleted) != 0 { log.WithField("tag", c.tag). Infof("%d user deleted, %d user added", len(deleted), len(added)) } return nil } func (c *Controller) SpeedChecker() error { for u, t := range c.traffic { if t >= c.LimitConfig.DynamicSpeedLimitConfig.Traffic { err := c.limiter.UpdateDynamicSpeedLimit(c.tag, u, c.LimitConfig.DynamicSpeedLimitConfig.SpeedLimit, time.Now().Add(time.Duration(c.LimitConfig.DynamicSpeedLimitConfig.ExpireTime)*time.Minute)) log.WithField("err", err).Error("Update dynamic speed limit failed") delete(c.traffic, u) } } return nil } ================================================ FILE: node/user.go ================================================ package node import ( "runtime" "strconv" "github.com/Yuzuki616/V2bX/api/panel" log "github.com/sirupsen/logrus" ) func (c *Controller) reportUserTrafficTask() (err error) { // Get User traffic userTraffic := make([]panel.UserTraffic, 0) for i := range c.userList { up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true) if up > 0 || down > 0 { if c.LimitConfig.EnableDynamicSpeedLimit { if _, ok := c.traffic[c.userList[i].Uuid]; ok { c.traffic[c.userList[i].Uuid] += up + down } else { c.traffic[c.userList[i].Uuid] = up + down } } userTraffic = append(userTraffic, panel.UserTraffic{ UID: (c.userList)[i].Id, Upload: up, Download: down}) } } if len(userTraffic) > 0 { err = c.apiClient.ReportUserTraffic(userTraffic) if err != nil { log.WithFields(log.Fields{ "tag": c.tag, "err": err, }).Info("Report user traffic failed") } else { log.WithField("tag", c.tag).Infof("Report %d online users", len(userTraffic)) } } userTraffic = nil runtime.GC() return nil } func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo) { tmp := map[string]struct{}{} tmp2 := map[string]struct{}{} for i := range old { tmp[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{} } l := len(tmp) for i := range new { e := new[i].Uuid + strconv.Itoa(new[i].SpeedLimit) tmp[e] = struct{}{} tmp2[e] = struct{}{} if l != len(tmp) { added = append(added, new[i]) l++ } } tmp = nil l = len(tmp2) for i := range old { tmp2[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{} if l != len(tmp2) { deleted = append(deleted, old[i]) l++ } } return deleted, added } ================================================ FILE: test_data/1.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAyryUpSI01T4jgPywpt4CIaf+MNgPn9DxHtJmuo1VB7Ysk13Z uOBVfS9HJoikbrcVeqENkR8Bq1vdgMLbHmlxrSTwe/pynmRvWx+L1hKscxEmR6uV jgqWsPkoUAKVJlHBfjw6oFEQzmgwDOfyS90TVr4gwDo/GCLX1iX5jC+KrteuAMOp V6Yiu1FAhd0TeUDPMKs4wbyE8qx3+Mn6FqGorIwyefCX9m9QCz1N/5Ut6I5uT5Vr 28Ox1Q++mZ0s5ZuFpMlzKvrfav7fxt31rgSUro7/NTNuIydHvwMcZYeVm+DT+Vfp wRZW9uoB4jiwLDxnfMOMLd72yGId+9EJ6HpOIQIDAQABAoIBAHhV/xUVfK6mN4S0 eFZTqIg5otNzK7L83mIhGQDaKwJsy4CdUEJARf4MNftVV+Svn3wuZFMjSGZiHNP0 1QL0K5lON8AfJDGIA+DelK34X4vdPg+EdTzeZBufiKIVJlqcZHF9Zn8KHyOlDABd HKCTFIuERwRSjmjRJbPizoC7J2Ina15pi+35T3x3oye4V5kQlJH3WxlMOhp0Z+iO qGlqJITWeOqxXaLfPIoOYcJ2LBPzvgv4IRVbmhi4mxphfPTEAlGnvITauDa9fx7S REBAFyt5NyU9M7dusmlLiTIHryOensBzCQOwbMseeUS0Z1rIfbusqwBM3X5oPcx8 jOP1QxECgYEA+qCU4Q84hDBhC2SLfH4D1QyOixeho5re6E0NchSposIhrxLdRDDk 04GGNTKUflgfoskaI9loe84MkTqqrjxguRWVXf5qPMAtfpXr2GYiPq+VhiEeo7ZG f1rHYqK7Ity9jzy7z/CpTLekveUT93I7/yF5iEOUYQgnIZhDg+Hr0ysCgYEAzxUu DmL7GceRauoD9w3tT2nsa23hXc+jo1QWV5I7veZyosXnz8gxpax4SgwZ/iCH8pDJ RagWTHQHO3X6DdkovzPEWYiLtLCE2APlfClzkZif5tv7kO4B3BuyuDlNleuCH894 5WlieyCHcPtXqG/60tXxVrlNrevI+ccsYRb+reMCgYBgu8AaybQnmUCrlAgeacjy 3yDZYKqbqfflM3BAGueKkWFM4HwUiMaZOAHj4Hzd8wdq3jG/qncgadwB5eHg1B8E 8Oaw27SHdClbFWRtJqaLCVwt4/SefYjiONiCIosWHprvgSKAVMQTf0IPpS46sJWl mHb++A56ERqBZfKRIY7S9wKBgCKf+flx12ZyFgB4bH1MmNdkcKFt1/bllwjiMHIo A1E3TQema6I0aQi4k8xdxaLWMaT/TIgXGNNjuynYCh1yp/uAXl5SFHn74dp0nFRs YeSATow9UAzlnu38u59OBYkBvdovyJkjS9ImmD7t57RENP43w4iqpzBjclFBWkxJ mf/dAoGBAOZa8+FmFyx2VmgwR4qPrNKg6EuhO6OgDJ8NvRnx4Mu5/0L1JoYoM0Yc 2NMKh14fqmDB/CvCbMGxCj86WF8534t+eBqhY6/AUkSolh8139KNmYHNB0ynqDYn QDm/HJ4cux2rkLUCKlbGBnS9M+PSJA/MJeh0JTIrSOu4bH7aanVl -----END RSA PRIVATE KEY----- ================================================ FILE: test_data/1.pem ================================================ -----BEGIN CERTIFICATE----- MIID2zCCAsOgAwIBAgIRAJP0pRlp9k2eiBLK2a2B7LYwDQYJKoZIhvcNAQELBQAw XjELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSswKQYDVQQLEyJNeVNTTCBU ZXN0IFJTQSAtIEZvciB0ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20w HhcNMjMwNjA4MTQxOTQzWhcNMjgwNjA2MTQxOTQzWjAiMQswCQYDVQQGEwJDTjET MBEGA1UEAxMKMTE0NTE0LmdheTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBAMq8lKUiNNU+I4D8sKbeAiGn/jDYD5/Q8R7SZrqNVQe2LJNd2bjgVX0vRyaI pG63FXqhDZEfAatb3YDC2x5pca0k8Hv6cp5kb1sfi9YSrHMRJkerlY4KlrD5KFAC lSZRwX48OqBREM5oMAzn8kvdE1a+IMA6Pxgi19Yl+Ywviq7XrgDDqVemIrtRQIXd E3lAzzCrOMG8hPKsd/jJ+hahqKyMMnnwl/ZvUAs9Tf+VLeiObk+Va9vDsdUPvpmd LOWbhaTJcyr632r+38bd9a4ElK6O/zUzbiMnR78DHGWHlZvg0/lX6cEWVvbqAeI4 sCw8Z3zDjC3e9shiHfvRCeh6TiECAwEAAaOBzzCBzDAOBgNVHQ8BAf8EBAMCBaAw HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFCiBJgXR NBo/wXMPu5PPFRw/A79/MGMGCCsGAQUFBwEBBFcwVTAhBggrBgEFBQcwAYYVaHR0 cDovL29jc3AubXlzc2wuY29tMDAGCCsGAQUFBzAChiRodHRwOi8vY2EubXlzc2wu Y29tL215c3NsdGVzdHJzYS5jcnQwFQYDVR0RBA4wDIIKMTE0NTE0LmdheTANBgkq hkiG9w0BAQsFAAOCAQEAdlvVKnB1OHojoHfgPKUVTk5+OXk9X/q++wkkrGa1rQs0 bPikKdc1TQoW/ylpX9wN3rwLLYGf/Hs9SqHr4RkAhAb6v+K1O5HUMqJARONdB7j0 /1BuKd0wx5pIqJhs6qRf+grsOGj9EdTfKXqElCljAES0t+2ZbZ2666XftwSjybtF yKg+9iS9PX5VA1SIsa7XSVTlJ8oXy91KuCm07UxnSEKovpzV4TIlxPgKOfWBUYh9 JfOhwh1rpUy6tqNjFyJdGHxBJsf7HLcO9VJe/RD55c54ovZaTT24Cy/II5DKiQ26 TUeMj1BMedu5Ou0YCH7W9QhH40fvwi/hSQrjysQAoA== -----END CERTIFICATE-----