[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug 报告\nabout: 提交项目中存在的漏洞和问题\ntitle: \"[BUG]\"\nlabels: ''\nassignees: ''\n\n---\n\n- [ ] **我确定我已经尝试多次复现此次问题，并且将会提供涉及此问题的系统和网络环境，软件及其版本。**\n\n我们建议您按照下方模板填写 Bug Report，以便我们收集更多的有效信息\n\n## 简单描述这个 Bug\n\n## 如何复现这个 Bug\n\n在此描述复现这个Bug所需要的操作步骤\n\n## 服务器和客户端环境信息\n\n在此描述你的服务器和客户端所处的网络环境，系统架构，以及其他信息\n\n## 服务端和客户端日志\n\n粘贴**故障发生时**，服务端和客户端日志\n\n## 服务端和客户端配置文件\n\n可以复现该问题的客户端和服务端的完整配置（请隐去域名和IP等隐私信息）\n\n## 服务端和客户端版本信息\n\n请执行./trojan-go -version并将输出完整粘贴在此处\n\n## 其他信息\n\n你认为对我们修复bug有帮助的任何信息都可以在这里写出来\n"
  },
  {
    "path": ".github/linters/.golangci.yml",
    "content": "run:\n  timeout: 5m\n  skip-files:\n    - \\.pb\\.go$\n\nissues:\n  new: true\n\nlinters:\n  enable:\n    - asciicheck\n    - bodyclose\n    - depguard\n    - gci\n    - gocritic\n    - gofmt\n    - gofumpt\n    - goimports\n    - govet\n    - ineffassign\n    - misspell\n    - typecheck\n    - unconvert\n    - whitespace\n  disable:\n    - deadcode\n    - errcheck\n    - goprintffuncname\n    - gosimple\n    - nilerr\n    - rowserrcheck\n    - staticcheck\n    - structcheck\n    - stylecheck\n    - unparam\n    - unused\n    - varcheck\n\nlinters-settings:\n  gci:\n    local-prefixes: github.com/p4gefau1t/trojan-go\n  goimports:\n    local-prefixes: github.com/p4gefau1t/trojan-go\n"
  },
  {
    "path": ".github/workflows/docker-build.yml",
    "content": "on:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - \"**.md\"\n      - \"docs/**\"\n    tags:\n      - \"v*.*.*\"\nname: docker-build\njobs:\n  build:\n    if: github.repository == 'p4gefau1t/trojan-go'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout the code\n        uses: actions/checkout@v2\n\n      - name: Setup QEMU\n        uses: docker/setup-qemu-action@v1\n\n      - name: Setup Docker Buildx\n        uses: docker/setup-buildx-action@v1\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Prepare\n        id: prepare\n        run: |\n          if [[ $GITHUB_REF == refs/tags/* ]]; then\n            echo ::set-output name=version::${GITHUB_REF#refs/tags/}\n            echo ::set-output name=ref::${GITHUB_REF#refs/tags/}\n          else\n            echo ::set-output name=version::snapshot\n            echo ::set-output name=ref::${{ github.sha }}\n          fi\n          echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64\n          echo ::set-output name=docker_image::${{ secrets.DOCKER_USERNAME }}/trojan-go\n\n      - name: Build and push docker image\n        run: |\n          docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \\\n          --output \"type=image,push=true\" \\\n          --tag \"${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}\" \\\n          --tag \"${{ steps.prepare.outputs.docker_image }}:latest\" \\\n          --build-arg REF=${{ steps.prepare.outputs.ref }} \\\n          --file Dockerfile .\n\n  test:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Test docker image\n        run: |\n          docker run --rm --entrypoint /usr/local/bin/trojan-go ${{ secrets.DOCKER_USERNAME }}/trojan-go -version\n"
  },
  {
    "path": ".github/workflows/docker-nightly-build.yml",
    "content": "on:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - '**.md'\n      - 'docs/**'\nname: docker-nightly-build\njobs:\n  build:\n    if: github.repository == 'p4gefau1t/trojan-go'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout the code\n        uses: actions/checkout@v2\n\n      - name: Setup QEMU\n        uses: docker/setup-qemu-action@v1\n\n      - name: Setup Docker Buildx\n        uses: docker/setup-buildx-action@v1\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Prepare\n        id: prepare\n        run: |\n          echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64\n          echo ::set-output name=docker_image::${{ secrets.DOCKER_USERNAME }}/trojan-go\n          echo ::set-output name=ref::${{ github.sha }}\n\n      - name: Build and push docker image\n        run: |\n          docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \\\n          --output \"type=image,push=true\" \\\n          --tag \"${{ steps.prepare.outputs.docker_image }}:nightly\" \\\n          --build-arg REF=${{ steps.prepare.outputs.ref }} \\\n          --file Dockerfile .\n\n  test:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Test docker image\n        run: |\n          docker run --rm --entrypoint /usr/local/bin/trojan-go ${{ secrets.DOCKER_USERNAME }}/trojan-go -version\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "on:\n  push:\n    branches:\n      - master\n    paths:\n      - \"docs/**\"\n      - \".github/workflows/gh-pages.yml\"\n  pull_request:\n    types: [opened, synchronize, reopened]\n    branches:\n      - master\n    paths:\n      - \"docs/**\"\n      - \".github/workflows/gh-pages.yml\"\nname: github-pages\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          submodules: true  # Fetch Hugo themes\n          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod\n\n      - name: Setup Hugo\n        uses: peaceiris/actions-hugo@v2\n        with:\n          hugo-version: \"0.83.1\"\n          # extended: true\n\n      - name: Build\n        run: |\n          cd docs\n          make hugo-themes\n          hugo\n\n      - name: Deploy\n        if: ${{ github.event_name == 'push' }}\n        uses: peaceiris/actions-gh-pages@v3\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./docs/public\n"
  },
  {
    "path": ".github/workflows/linter.yml",
    "content": "name: Linter\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - \"**/*.go\"\n      - \".github/workflows/linter.yml\"\n      - \".github/linters/.golangci.yml\"\n  pull_request:\n    types: [opened, synchronize, reopened]\n    branches:\n      - master\n    paths:\n      - \"**/*.go\"\n      - \".github/workflows/linter.yml\"\n      - \".github/linters/.golangci.yml\"\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout codebase\n        uses: actions/checkout@v2\n\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v2\n        with:\n          version: latest\n          args: --config=.github/linters/.golangci.yml\n          only-new-issues: true\n"
  },
  {
    "path": ".github/workflows/nightly-build.yml",
    "content": "on:\n  push:\n    branches:\n      - master\n    paths:\n      - \"**/*.go\"\n      - \"go.mod\"\n      - \"go.sum\"\n      - \"Makefile\"\n      - \".github/workflows/nightly-build.yml\"\n  pull_request:\n    types: [opened, synchronize, reopened]\n    branches:\n      - master\n    paths:\n      - \"**/*.go\"\n      - \"go.mod\"\n      - \"go.sum\"\n      - \"Makefile\"\n      - \".github/workflows/nightly-build.yml\"\nname: nightly-build\njobs:\n  build:\n    strategy:\n      fail-fast: false\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: ^1.17.1\n\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Build\n        run: |\n          make release -j$(nproc)\n"
  },
  {
    "path": ".github/workflows/release-build.yml",
    "content": "on:\n  push:\n    tags:\n      - \"v*.*.*\"\nname: release-build\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: ^1.17.1\n\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Checkout tag\n        run: |\n          git fetch --depth=1 origin +refs/tags/*:refs/tags/*\n          tag_name=\"${GITHUB_REF##*/}\"\n          echo Tag $tag_name\n          git checkout $tag_name\n          echo \"TAG_NAME=${tag_name}\" >> $GITHUB_ENV\n\n      - name: Build\n        run: |\n          make release -j$(nproc)\n\n      - name: Release\n        uses: svenstaro/upload-release-action@v2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          tag: ${{ env.TAG_NAME }}\n          file: ./trojan-go-*.zip\n          file_glob: true\n          prerelease: true\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non:\n  push:\n    branches:\n      - master\n    paths:\n      - \"**/*.go\"\n      - \"go.mod\"\n      - \"go.sum\"\n      - \".github/workflows/test.yml\"\n  pull_request:\n    types: [opened, synchronize, reopened]\n    branches:\n      - master\n    paths:\n      - \"**/*.go\"\n      - \"go.mod\"\n      - \"go.sum\"\n      - \".github/workflows/test.yml\"\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, windows-latest, macos-latest]\n    runs-on: ${{ matrix.platform }}\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: ^1.17.1\n\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Check Go modules\n        run: |\n          go mod tidy -compat=1.17\n          git diff --exit-code go.mod go.sum\n          go mod verify\n\n      - name: Test\n        run: make test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\nbuild/\n*.DS_Store\n*.zip\n*.tar.gz\n*.crt\n*.key\n*.dat\ntrojan-go\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:alpine AS builder\nWORKDIR /\nARG REF\nRUN apk add git make &&\\\n    git clone https://github.com/p4gefau1t/trojan-go.git\nRUN if [[ -z \"${REF}\" ]]; then \\\n        echo \"No specific commit provided, use the latest one.\" \\\n    ;else \\\n        echo \"Use commit ${REF}\" &&\\\n        cd trojan-go &&\\\n        git checkout ${REF} \\\n    ;fi\nRUN cd trojan-go &&\\\n    make &&\\\n    wget https://github.com/v2fly/domain-list-community/raw/release/dlc.dat -O build/geosite.dat &&\\\n    wget https://github.com/v2fly/geoip/raw/release/geoip.dat -O build/geoip.dat &&\\\n    wget https://github.com/v2fly/geoip/raw/release/geoip-only-cn-private.dat -O build/geoip-only-cn-private.dat\n\nFROM alpine\nWORKDIR /\nRUN apk add --no-cache tzdata ca-certificates\nCOPY --from=builder /trojan-go/build /usr/local/bin/\nCOPY --from=builder /trojan-go/example/server.json /etc/trojan-go/config.json\n\nENTRYPOINT [\"/usr/local/bin/trojan-go\", \"-config\"]\nCMD [\"/etc/trojan-go/config.json\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "Makefile",
    "content": "NAME := trojan-go\nPACKAGE_NAME := github.com/p4gefau1t/trojan-go\nVERSION := `git describe --dirty`\nCOMMIT := `git rev-parse HEAD`\n\nPLATFORM := linux\nBUILD_DIR := build\nVAR_SETTING := -X $(PACKAGE_NAME)/constant.Version=$(VERSION) -X $(PACKAGE_NAME)/constant.Commit=$(COMMIT)\nGOBUILD = env CGO_ENABLED=0 $(GO_DIR)go build -tags \"full\" -trimpath -ldflags=\"-s -w -buildid= $(VAR_SETTING)\" -o $(BUILD_DIR)\n\n.PHONY: trojan-go release test\nnormal: clean trojan-go\n\nclean:\n\trm -rf $(BUILD_DIR)\n\trm -f *.zip\n\trm -f *.dat\n\ngeoip.dat:\n\twget https://github.com/v2fly/geoip/raw/release/geoip.dat\n\ngeoip-only-cn-private.dat:\n\twget https://github.com/v2fly/geoip/raw/release/geoip-only-cn-private.dat\n\ngeosite.dat:\n\twget https://github.com/v2fly/domain-list-community/raw/release/dlc.dat -O geosite.dat\n\ntest:\n\t# Disable Bloomfilter when testing\n\tSHADOWSOCKS_SF_CAPACITY=\"-1\" $(GO_DIR)go test -v ./...\n\ntrojan-go:\n\tmkdir -p $(BUILD_DIR)\n\t$(GOBUILD)\n\ninstall: $(BUILD_DIR)/$(NAME) geoip.dat geoip-only-cn-private.dat geosite.dat\n\tmkdir -p /etc/$(NAME)\n\tmkdir -p /usr/share/$(NAME)\n\tcp example/*.json /etc/$(NAME)\n\tcp $(BUILD_DIR)/$(NAME) /usr/bin/$(NAME)\n\tcp example/$(NAME).service /usr/lib/systemd/system/\n\tcp example/$(NAME)@.service /usr/lib/systemd/system/\n\tsystemctl daemon-reload\n\tcp geosite.dat /usr/share/$(NAME)/geosite.dat\n\tcp geoip.dat /usr/share/$(NAME)/geoip.dat\n\tcp geoip-only-cn-private.dat /usr/share/$(NAME)/geoip-only-cn-private.dat\n\tln -fs /usr/share/$(NAME)/geoip.dat /usr/bin/\n\tln -fs /usr/share/$(NAME)/geoip-only-cn-private.dat /usr/bin/\n\tln -fs /usr/share/$(NAME)/geosite.dat /usr/bin/\n\nuninstall:\n\trm /usr/lib/systemd/system/$(NAME).service\n\trm /usr/lib/systemd/system/$(NAME)@.service\n\tsystemctl daemon-reload\n\trm /usr/bin/$(NAME)\n\trm -rd /etc/$(NAME)\n\trm -rd /usr/share/$(NAME)\n\trm /usr/bin/geoip.dat\n\trm /usr/bin/geoip-only-cn-private.dat\n\trm /usr/bin/geosite.dat\n\n%.zip: % geosite.dat geoip.dat geoip-only-cn-private.dat\n\t@zip -du $(NAME)-$@ -j $(BUILD_DIR)/$</*\n\t@zip -du $(NAME)-$@ example/*\n\t@-zip -du $(NAME)-$@ *.dat\n\t@echo \"<<< ---- $(NAME)-$@\"\n\nrelease: geosite.dat geoip.dat geoip-only-cn-private.dat darwin-amd64.zip darwin-arm64.zip linux-386.zip linux-amd64.zip \\\n\tlinux-arm.zip linux-armv5.zip linux-armv6.zip linux-armv7.zip linux-armv8.zip \\\n\tlinux-mips-softfloat.zip linux-mips-hardfloat.zip linux-mipsle-softfloat.zip linux-mipsle-hardfloat.zip \\\n\tlinux-mips64.zip linux-mips64le.zip freebsd-386.zip freebsd-amd64.zip \\\n\twindows-386.zip windows-amd64.zip windows-arm.zip windows-armv6.zip windows-armv7.zip windows-arm64.zip\n\ndarwin-amd64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=amd64 GOOS=darwin $(GOBUILD)/$@\n\ndarwin-arm64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm64 GOOS=darwin $(GOBUILD)/$@\n\nlinux-386:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=386 GOOS=linux $(GOBUILD)/$@\n\nlinux-amd64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=amd64 GOOS=linux $(GOBUILD)/$@\n\nlinux-arm:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=linux $(GOBUILD)/$@\n\nlinux-armv5:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=linux GOARM=5 $(GOBUILD)/$@\n\nlinux-armv6:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=linux GOARM=6 $(GOBUILD)/$@\n\nlinux-armv7:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=linux GOARM=7 $(GOBUILD)/$@\n\nlinux-armv8:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm64 GOOS=linux $(GOBUILD)/$@\n\nlinux-mips-softfloat:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD)/$@\n\nlinux-mips-hardfloat:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD)/$@\n\nlinux-mipsle-softfloat:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD)/$@\n\nlinux-mipsle-hardfloat:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD)/$@\n\nlinux-mips64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=mips64 GOOS=linux $(GOBUILD)/$@\n\nlinux-mips64le:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=mips64le GOOS=linux $(GOBUILD)/$@\n\nfreebsd-386:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=386 GOOS=freebsd $(GOBUILD)/$@\n\nfreebsd-amd64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=amd64 GOOS=freebsd $(GOBUILD)/$@\n\nwindows-386:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=386 GOOS=windows $(GOBUILD)/$@\n\nwindows-amd64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=amd64 GOOS=windows $(GOBUILD)/$@\n\nwindows-arm:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=windows $(GOBUILD)/$@\n\nwindows-armv6:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=windows GOARM=6 $(GOBUILD)/$@\n\nwindows-armv7:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm GOOS=windows GOARM=7 $(GOBUILD)/$@\n\nwindows-arm64:\n\tmkdir -p $(BUILD_DIR)/$@\n\tGOARCH=arm64 GOOS=windows $(GOBUILD)/$@\n"
  },
  {
    "path": "README.md",
    "content": "# Trojan-Go [![Go Report Card](https://goreportcard.com/badge/github.com/p4gefau1t/trojan-go)](https://goreportcard.com/report/github.com/p4gefau1t/trojan-go) [![Downloads](https://img.shields.io/github/downloads/p4gefau1t/trojan-go/total?label=downloads&logo=github&style=flat-square)](https://img.shields.io/github/downloads/p4gefau1t/trojan-go/total?label=downloads&logo=github&style=flat-square)\n\n使用 Go 实现的完整 Trojan 代理，兼容原版 Trojan 协议及配置文件格式。安全、高效、轻巧、易用。\n\nTrojan-Go 支持[多路复用](#多路复用)提升并发性能；使用[路由模块](#路由模块)实现国内外分流；支持 [CDN 流量中转](#Websocket)(基于 WebSocket over TLS)；支持使用 AEAD 对 Trojan 流量进行[二次加密](#aead-加密)(基于 Shadowsocks AEAD)；支持可插拔的[传输层插件](#传输层插件)，允许替换 TLS，使用其他加密隧道传输 Trojan 协议流量。\n\n预编译二进制可执行文件可在 [Release 页面](https://github.com/p4gefau1t/trojan-go/releases)下载。解压后即可直接运行，无其他组件依赖。\n\n如遇到配置和使用问题、发现 bug，或是有更好的想法，欢迎加入 [Telegram 交流反馈群](https://t.me/trojan_go_chat)。\n\n## 简介\n\n**完整介绍和配置教程，参见 [Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。**\n\nTrojan-Go 兼容原版 Trojan 的绝大多数功能，包括但不限于：\n\n- TLS 隧道传输\n- UDP 代理\n- 透明代理 (NAT 模式，iptables 设置参考[这里](https://github.com/shadowsocks/shadowsocks-libev/tree/v3.3.1#transparent-proxy))\n- 对抗 GFW 被动检测 / 主动检测的机制\n- MySQL 数据持久化方案\n- MySQL 用户权限认证\n- 用户流量统计和配额限制\n\n同时，Trojan-Go 还扩展实现了更多高效易用的功能特性：\n\n- 便于快速部署的「简易模式」\n- Socks5 / HTTP 代理自动适配\n- 基于 TProxy 的透明代理（TCP / UDP）\n- 全平台支持，无特殊依赖\n- 基于多路复用（smux）降低延迟，提升并发性能\n- 自定义路由模块，可实现国内外分流 / 广告屏蔽等功能\n- Websocket 传输支持，以实现 CDN 流量中转（基于 WebSocket over TLS）和对抗 GFW 中间人攻击\n- TLS 指纹伪造，以对抗 GFW 针对 TLS Client Hello 的特征识别\n- 基于 gRPC 的 API 支持，以实现用户管理和速度限制等\n- 可插拔传输层，可将 TLS 替换为其他协议或明文传输，同时有完整的 Shadowsocks 混淆插件支持\n- 支持对用户更友好的 YAML 配置文件格式\n\n## 图形界面客户端\n\nTrojan-Go 服务端兼容所有原 Trojan 客户端，如 Igniter、ShadowRocket 等。以下是支持 Trojan-Go 扩展特性（Websocket / Mux 等）的客户端：\n\n- [Qv2ray](https://github.com/Qv2ray/Qv2ray)：跨平台客户端，支持 Windows / macOS / Linux，使用 Trojan-Go 核心，支持所有 Trojan-Go 扩展特性。\n- [Igniter-Go](https://github.com/p4gefau1t/trojan-go-android)：Android 客户端，Fork 自 Igniter，将 Igniter 核心替换为 Trojan-Go 并做了一定修改，支持所有 Trojan-Go 扩展特性。\n\n## 使用方法\n\n1. 快速启动服务端和客户端（简易模式）\n\n    - 服务端\n\n        ```shell\n        sudo ./trojan-go -server -remote 127.0.0.1:80 -local 0.0.0.0:443 -key ./your_key.key -cert ./your_cert.crt -password your_password\n        ```\n\n    - 客户端\n\n        ```shell\n        ./trojan-go -client -remote example.com:443 -local 127.0.0.1:1080 -password your_password\n        ```\n\n2. 使用配置文件启动客户端 / 服务端 / 透明代理 / 中继（一般模式）\n\n    ```shell\n    ./trojan-go -config config.json\n    ```\n\n3. 使用 URL 启动客户端（格式参见文档）\n\n    ```shell\n    ./trojan-go -url 'trojan-go://password@cloudflare.com/?type=ws&path=%2Fpath&host=your-site.com'\n    ```\n\n4. 使用 Docker 部署\n\n    ```shell\n    docker run \\\n        --name trojan-go \\\n        -d \\\n        -v /etc/trojan-go/:/etc/trojan-go \\\n        --network host \\\n        p4gefau1t/trojan-go\n    ```\n\n   或者\n\n    ```shell\n    docker run \\\n        --name trojan-go \\\n        -d \\\n        -v /path/to/host/config:/path/in/container \\\n        --network host \\\n        p4gefau1t/trojan-go \\\n        /path/in/container/config.json\n    ```\n\n## 特性\n\n一般情况下，Trojan-Go 和 Trojan 是互相兼容的，但一旦使用下面介绍的扩展特性（如多路复用、Websocket 等），则无法兼容。\n\n### 移植性\n\n编译得到的 Trojan-Go 单个可执行文件不依赖其他组件。同时，你可以很方便地编译（或交叉编译） Trojan-Go，然后在你的服务器、PC、树莓派，甚至路由器上部署；可以方便地使用 build tag 删减模块，以缩小可执行文件体积。\n\n例如，交叉编译一个可在 mips 处理器、Linux 操作系统上运行的、只有客户端功能的 Trojan-Go，只需执行下面的命令，得到的可执行文件可以直接在目标平台运行：\n\n```shell\nCGO_ENABLED=0 GOOS=linux GOARCH=mips go build -tags \"client\" -trimpath -ldflags \"-s -w -buildid=\"\n```\n\n完整的 tag 说明参见 [Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。\n\n### 易用\n\n配置文件格式与原版 Trojan 兼容，但做了大幅简化，未指定的字段会被赋予默认值，由此可以更方便地部署服务端和客户端。以下是一个简单例子，完整的配置文件可以参见[这里](https://p4gefau1t.github.io/trojan-go)。\n\n服务端配置文件 `server.json`：\n\n```json\n{\n  \"run_type\": \"server\",\n  \"local_addr\": \"0.0.0.0\",\n  \"local_port\": 443,\n  \"remote_addr\": \"127.0.0.1\",\n  \"remote_port\": 80,\n  \"password\": [\"your_awesome_password\"],\n  \"ssl\": {\n    \"cert\": \"your_cert.crt\",\n    \"key\": \"your_key.key\",\n    \"sni\": \"www.your-awesome-domain-name.com\"\n  }\n}\n```\n\n客户端配置文件 `client.json`：\n\n```json\n{\n  \"run_type\": \"client\",\n  \"local_addr\": \"127.0.0.1\",\n  \"local_port\": 1080,\n  \"remote_addr\": \"www.your-awesome-domain-name.com\",\n  \"remote_port\": 443,\n  \"password\": [\"your_awesome_password\"]\n}\n```\n\n可以使用更简明易读的 YAML 语法进行配置。以下是一个客户端的例子，与上面的 `client.json` 等价：\n\n客户端配置文件 `client.yaml`：\n\n```yaml\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: 1080\nremote-addr: www.your-awesome-domain_name.com\nremote-port: 443\npassword:\n  - your_awesome_password\n```\n\n### WebSocket\n\nTrojan-Go 支持使用 TLS + Websocket 承载 Trojan 协议，使得利用 CDN 进行流量中转成为可能。\n\n服务端和客户端配置文件中同时添加 `websocket` 选项即可启用 Websocket 支持，例如\n\n```json\n\"websocket\": {\n    \"enabled\": true,\n    \"path\": \"/your-websocket-path\",\n    \"hostname\": \"www.your-awesome-domain-name.com\"\n}\n```\n\n完整的选项说明参见 [Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。\n\n可以省略 `hostname`, 但服务端和客户端的 `path` 必须一致。服务端开启 Websocket 支持后，可以同时支持 Websocket 和一般 Trojan 流量。未配置 Websocket 选项的客户端依然可以正常使用。\n\n由于 Trojan 并不支持 Websocket，因此，虽然开启了 Websocket 支持的 Trojan-Go 服务端可以兼容所有客户端，但如果要使用 Websocket 承载流量，请确保双方都使用 Trojan-Go。\n\n### 多路复用\n\n在很差的网络条件下，一次 TLS 握手可能会花费很多时间。Trojan-Go 支持多路复用（基于 [smux](https://github.com/xtaci/smux)），通过一条 TLS 隧道连接承载多条 TCP 连接的方式，减少 TCP 和 TLS 握手带来的延迟，以期提升高并发情景下的性能。\n\n> 启用多路复用并不能提高测速得到的链路速度，但能降低延迟、提升大量并发请求时的网络体验，例如浏览含有大量图片的网页等。\n\n你可以通过设置客户端的 `mux` 选项 `enabled` 字段启用它：\n\n```json\n\"mux\": {\n    \"enabled\": true\n}\n```\n\n只需开启客户端 mux 配置即可，服务端会自动检测是否启用多路复用并提供支持。完整的选项说明参见 [Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。\n\n### 路由模块\n\nTrojan-Go 客户端内建一个简单实用的路由模块，以方便实现国内直连、海外代理等自定义路由功能。\n\n路由策略有三种：\n\n- `Proxy` 代理：将请求通过 TLS 隧道进行代理，由 Trojan 服务端与目的地址进行连接。\n- `Bypass` 绕过：直接使用本地设备与目的地址进行连接。\n- `Block` 封锁：不发送请求，直接关闭连接。\n\n要激活路由模块，请在配置文件中添加 `router` 选项，并设置 `enabled` 字段为 `true`：\n\n```json\n\"router\": {\n    \"enabled\": true,\n    \"bypass\": [\n        \"geoip:cn\",\n        \"geoip:private\",\n        \"full:localhost\"\n    ],\n    \"block\": [\n        \"cidr:192.168.1.1/24\",\n    ],\n    \"proxy\": [\n        \"domain:google.com\",\n    ],\n    \"default_policy\": \"proxy\"\n}\n```\n\n完整的选项说明参见 [Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。\n\n### AEAD 加密\n\nTrojan-Go 支持基于 Shadowsocks AEAD 对 Trojan 协议流量进行二次加密，以保证 Websocket 传输流量无法被不可信的 CDN 识别和审查：\n\n```json\n\"shadowsocks\": {\n    \"enabled\": true,\n    \"password\": \"my-password\"\n}\n```\n\n如需开启，服务端和客户端必须同时开启并保证密码一致。\n\n### 传输层插件\n\nTrojan-Go 支持可插拔的传输层插件，并支持 Shadowsocks [SIP003](https://shadowsocks.org/en/wiki/Plugin.html) 标准的混淆插件。下面是使用 `v2ray-plugin` 的一个例子：\n\n> **此配置并不安全，仅作为演示**\n\n服务端配置：\n\n```json\n\"transport_plugin\": {\n    \"enabled\": true,\n    \"type\": \"shadowsocks\",\n    \"command\": \"./v2ray-plugin\",\n    \"arg\": [\"-server\", \"-host\", \"www.baidu.com\"]\n}\n```\n\n客户端配置：\n\n```json\n\"transport_plugin\": {\n    \"enabled\": true,\n    \"type\": \"shadowsocks\",\n    \"command\": \"./v2ray-plugin\",\n    \"arg\": [\"-host\", \"www.baidu.com\"]\n}\n```\n\n完整的选项说明参见 [Trojan-Go 文档](https://p4gefau1t.github.io/trojan-go)。\n\n## 构建\n\n> 请确保 Go 版本 >= 1.14\n\n使用 `make` 进行编译：\n\n```shell\ngit clone https://github.com/p4gefau1t/trojan-go.git\ncd trojan-go\nmake\nmake install #安装systemd服务等，可选\n```\n\n或者使用 Go 自行编译：\n\n```shell\ngo build -tags \"full\"\n```\n\nGo 支持通过设置环境变量进行交叉编译，例如：\n\n编译适用于 64 位 Windows 操作系统的可执行文件：\n\n```shell\nCGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -tags \"full\"\n```\n\n编译适用于 Apple Silicon 的可执行文件：\n\n```shell\nCGO_ENABLED=0 GOOS=macos GOARCH=arm64 go build -tags \"full\"\n```\n\n编译适用于 64 位 Linux 操作系统的可执行文件：\n\n```shell\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags \"full\"\n```\n\n## 致谢\n\n- [Trojan](https://github.com/trojan-gfw/trojan)\n- [V2Fly](https://github.com/v2fly)\n- [utls](https://github.com/refraction-networking/utls)\n- [smux](https://github.com/xtaci/smux)\n- [go-tproxy](https://github.com/LiamHaworth/go-tproxy)\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/p4gefau1t/trojan-go.svg)](https://starchart.cc/p4gefau1t/trojan-go)\n"
  },
  {
    "path": "api/api.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n)\n\ntype Handler func(ctx context.Context, auth statistic.Authenticator) error\n\nvar handlers = make(map[string]Handler)\n\nfunc RegisterHandler(name string, handler Handler) {\n\thandlers[name] = handler\n}\n\nfunc RunService(ctx context.Context, name string, auth statistic.Authenticator) error {\n\tif h, ok := handlers[name]; ok {\n\t\tlog.Debug(\"api handler found\", name)\n\t\treturn h(ctx, auth)\n\t}\n\tlog.Debug(\"api handler not found\", name)\n\treturn nil\n}\n"
  },
  {
    "path": "api/control/control.go",
    "content": "package control\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/p4gefau1t/trojan-go/api/service\"\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/option\"\n)\n\ntype apiController struct {\n\taddress *string\n\tkey     *string\n\thash    *string\n\tcert    *string\n\n\tcmd                *string\n\tpassword           *string\n\tadd                *bool\n\tdelete             *bool\n\tmodify             *bool\n\tlist               *bool\n\tuploadSpeedLimit   *int\n\tdownloadSpeedLimit *int\n\tipLimit            *int\n\tctx                context.Context\n}\n\nfunc (apiController) Name() string {\n\treturn \"api\"\n}\n\nfunc (o *apiController) listUsers(apiClient service.TrojanServerServiceClient) error {\n\tstream, err := apiClient.ListUsers(o.ctx, &service.ListUsersRequest{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer stream.CloseSend()\n\tresult := []*service.ListUsersResponse{}\n\tfor {\n\t\tresp, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tresult = append(result, resp)\n\t}\n\tdata, err := json.Marshal(result)\n\tcommon.Must(err)\n\tfmt.Println(string(data))\n\treturn nil\n}\n\nfunc (o *apiController) getUsers(apiClient service.TrojanServerServiceClient) error {\n\tstream, err := apiClient.GetUsers(o.ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer stream.CloseSend()\n\terr = stream.Send(&service.GetUsersRequest{\n\t\tUser: &service.User{\n\t\t\tPassword: *o.password,\n\t\t\tHash:     *o.hash,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := stream.Recv()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdata, err := json.Marshal(resp)\n\tcommon.Must(err)\n\tfmt.Print(string(data))\n\treturn nil\n}\n\nfunc (o *apiController) setUsers(apiClient service.TrojanServerServiceClient) error {\n\tstream, err := apiClient.SetUsers(o.ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer stream.CloseSend()\n\n\treq := &service.SetUsersRequest{\n\t\tStatus: &service.UserStatus{\n\t\t\tUser: &service.User{\n\t\t\t\tPassword: *o.password,\n\t\t\t\tHash:     *o.hash,\n\t\t\t},\n\t\t\tIpLimit: int32(*o.ipLimit),\n\t\t\tSpeedLimit: &service.Speed{\n\t\t\t\tUploadSpeed:   uint64(*o.uploadSpeedLimit),\n\t\t\t\tDownloadSpeed: uint64(*o.downloadSpeedLimit),\n\t\t\t},\n\t\t},\n\t}\n\n\tswitch {\n\tcase *o.add:\n\t\treq.Operation = service.SetUsersRequest_Add\n\tcase *o.modify:\n\t\treq.Operation = service.SetUsersRequest_Modify\n\tcase *o.delete:\n\t\treq.Operation = service.SetUsersRequest_Delete\n\tdefault:\n\t\treturn common.NewError(\"Invalid operation\")\n\t}\n\n\terr = stream.Send(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := stream.Recv()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif resp.Success {\n\t\tfmt.Println(\"Done\")\n\t} else {\n\t\tfmt.Println(\"Failed: \" + resp.Info)\n\t}\n\treturn nil\n}\n\nfunc (o *apiController) Handle() error {\n\tif *o.cmd == \"\" {\n\t\treturn common.NewError(\"\")\n\t}\n\tconn, err := grpc.Dial(*o.address, grpc.WithInsecure())\n\tif err != nil {\n\t\tlog.Error(err)\n\t\treturn nil\n\t}\n\tdefer conn.Close()\n\tapiClient := service.NewTrojanServerServiceClient(conn)\n\tswitch *o.cmd {\n\tcase \"list\":\n\t\terr := o.listUsers(apiClient)\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t}\n\tcase \"get\":\n\t\terr := o.getUsers(apiClient)\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t}\n\tcase \"set\":\n\t\terr := o.setUsers(apiClient)\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t}\n\tdefault:\n\t\tlog.Error(\"unknown command \" + *o.cmd)\n\t}\n\treturn nil\n}\n\nfunc (o *apiController) Priority() int {\n\treturn 50\n}\n\nfunc init() {\n\toption.RegisterHandler(&apiController{\n\t\tcmd:                flag.String(\"api\", \"\", \"Connect to a Trojan-Go API service. \\\"-api add/get/list\\\"\"),\n\t\taddress:            flag.String(\"api-addr\", \"127.0.0.1:10000\", \"Address of Trojan-Go API service\"),\n\t\tpassword:           flag.String(\"target-password\", \"\", \"Password of the target user\"),\n\t\thash:               flag.String(\"target-hash\", \"\", \"Hash of the target user\"),\n\t\tadd:                flag.Bool(\"add-profile\", false, \"Add a new profile with API\"),\n\t\tdelete:             flag.Bool(\"delete-profile\", false, \"Delete an existing profile with API\"),\n\t\tmodify:             flag.Bool(\"modify-profile\", false, \"Modify an existing profile with API\"),\n\t\tuploadSpeedLimit:   flag.Int(\"upload-speed-limit\", 0, \"Limit the upload speed with API\"),\n\t\tdownloadSpeedLimit: flag.Int(\"download-speed-limit\", 0, \"Limit the download speed with API\"),\n\t\tipLimit:            flag.Int(\"ip-limit\", 0, \"Limit the number of IP with API\"),\n\t\tctx:                context.Background(),\n\t})\n}\n"
  },
  {
    "path": "api/service/api.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.27.1\n// \tprotoc        v3.17.3\n// source: api.proto\n\npackage service\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype SetUsersRequest_Operation int32\n\nconst (\n\tSetUsersRequest_Add    SetUsersRequest_Operation = 0\n\tSetUsersRequest_Delete SetUsersRequest_Operation = 1\n\tSetUsersRequest_Modify SetUsersRequest_Operation = 2\n)\n\n// Enum value maps for SetUsersRequest_Operation.\nvar (\n\tSetUsersRequest_Operation_name = map[int32]string{\n\t\t0: \"Add\",\n\t\t1: \"Delete\",\n\t\t2: \"Modify\",\n\t}\n\tSetUsersRequest_Operation_value = map[string]int32{\n\t\t\"Add\":    0,\n\t\t\"Delete\": 1,\n\t\t\"Modify\": 2,\n\t}\n)\n\nfunc (x SetUsersRequest_Operation) Enum() *SetUsersRequest_Operation {\n\tp := new(SetUsersRequest_Operation)\n\t*p = x\n\treturn p\n}\n\nfunc (x SetUsersRequest_Operation) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (SetUsersRequest_Operation) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_api_proto_enumTypes[0].Descriptor()\n}\n\nfunc (SetUsersRequest_Operation) Type() protoreflect.EnumType {\n\treturn &file_api_proto_enumTypes[0]\n}\n\nfunc (x SetUsersRequest_Operation) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use SetUsersRequest_Operation.Descriptor instead.\nfunc (SetUsersRequest_Operation) EnumDescriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{10, 0}\n}\n\ntype Traffic struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUploadTraffic   uint64 `protobuf:\"varint,1,opt,name=upload_traffic,json=uploadTraffic,proto3\" json:\"upload_traffic,omitempty\"`\n\tDownloadTraffic uint64 `protobuf:\"varint,2,opt,name=download_traffic,json=downloadTraffic,proto3\" json:\"download_traffic,omitempty\"`\n}\n\nfunc (x *Traffic) Reset() {\n\t*x = Traffic{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Traffic) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Traffic) ProtoMessage() {}\n\nfunc (x *Traffic) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Traffic.ProtoReflect.Descriptor instead.\nfunc (*Traffic) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Traffic) GetUploadTraffic() uint64 {\n\tif x != nil {\n\t\treturn x.UploadTraffic\n\t}\n\treturn 0\n}\n\nfunc (x *Traffic) GetDownloadTraffic() uint64 {\n\tif x != nil {\n\t\treturn x.DownloadTraffic\n\t}\n\treturn 0\n}\n\ntype Speed struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUploadSpeed   uint64 `protobuf:\"varint,1,opt,name=upload_speed,json=uploadSpeed,proto3\" json:\"upload_speed,omitempty\"`\n\tDownloadSpeed uint64 `protobuf:\"varint,2,opt,name=download_speed,json=downloadSpeed,proto3\" json:\"download_speed,omitempty\"`\n}\n\nfunc (x *Speed) Reset() {\n\t*x = Speed{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Speed) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Speed) ProtoMessage() {}\n\nfunc (x *Speed) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Speed.ProtoReflect.Descriptor instead.\nfunc (*Speed) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Speed) GetUploadSpeed() uint64 {\n\tif x != nil {\n\t\treturn x.UploadSpeed\n\t}\n\treturn 0\n}\n\nfunc (x *Speed) GetDownloadSpeed() uint64 {\n\tif x != nil {\n\t\treturn x.DownloadSpeed\n\t}\n\treturn 0\n}\n\ntype User struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPassword string `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tHash     string `protobuf:\"bytes,2,opt,name=hash,proto3\" json:\"hash,omitempty\"`\n}\n\nfunc (x *User) Reset() {\n\t*x = User{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *User) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*User) ProtoMessage() {}\n\nfunc (x *User) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use User.ProtoReflect.Descriptor instead.\nfunc (*User) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *User) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nfunc (x *User) GetHash() string {\n\tif x != nil {\n\t\treturn x.Hash\n\t}\n\treturn \"\"\n}\n\ntype UserStatus struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUser         *User    `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\tTrafficTotal *Traffic `protobuf:\"bytes,2,opt,name=traffic_total,json=trafficTotal,proto3\" json:\"traffic_total,omitempty\"`\n\tSpeedCurrent *Speed   `protobuf:\"bytes,3,opt,name=speed_current,json=speedCurrent,proto3\" json:\"speed_current,omitempty\"`\n\tSpeedLimit   *Speed   `protobuf:\"bytes,4,opt,name=speed_limit,json=speedLimit,proto3\" json:\"speed_limit,omitempty\"`\n\tIpCurrent    int32    `protobuf:\"varint,5,opt,name=ip_current,json=ipCurrent,proto3\" json:\"ip_current,omitempty\"`\n\tIpLimit      int32    `protobuf:\"varint,6,opt,name=ip_limit,json=ipLimit,proto3\" json:\"ip_limit,omitempty\"`\n}\n\nfunc (x *UserStatus) Reset() {\n\t*x = UserStatus{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *UserStatus) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UserStatus) ProtoMessage() {}\n\nfunc (x *UserStatus) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UserStatus.ProtoReflect.Descriptor instead.\nfunc (*UserStatus) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *UserStatus) GetUser() *User {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\nfunc (x *UserStatus) GetTrafficTotal() *Traffic {\n\tif x != nil {\n\t\treturn x.TrafficTotal\n\t}\n\treturn nil\n}\n\nfunc (x *UserStatus) GetSpeedCurrent() *Speed {\n\tif x != nil {\n\t\treturn x.SpeedCurrent\n\t}\n\treturn nil\n}\n\nfunc (x *UserStatus) GetSpeedLimit() *Speed {\n\tif x != nil {\n\t\treturn x.SpeedLimit\n\t}\n\treturn nil\n}\n\nfunc (x *UserStatus) GetIpCurrent() int32 {\n\tif x != nil {\n\t\treturn x.IpCurrent\n\t}\n\treturn 0\n}\n\nfunc (x *UserStatus) GetIpLimit() int32 {\n\tif x != nil {\n\t\treturn x.IpLimit\n\t}\n\treturn 0\n}\n\ntype GetTrafficRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUser *User `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n}\n\nfunc (x *GetTrafficRequest) Reset() {\n\t*x = GetTrafficRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetTrafficRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetTrafficRequest) ProtoMessage() {}\n\nfunc (x *GetTrafficRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetTrafficRequest.ProtoReflect.Descriptor instead.\nfunc (*GetTrafficRequest) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *GetTrafficRequest) GetUser() *User {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\ntype GetTrafficResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSuccess      bool     `protobuf:\"varint,1,opt,name=success,proto3\" json:\"success,omitempty\"`\n\tInfo         string   `protobuf:\"bytes,2,opt,name=info,proto3\" json:\"info,omitempty\"`\n\tTrafficTotal *Traffic `protobuf:\"bytes,3,opt,name=traffic_total,json=trafficTotal,proto3\" json:\"traffic_total,omitempty\"`\n\tSpeedCurrent *Speed   `protobuf:\"bytes,4,opt,name=speed_current,json=speedCurrent,proto3\" json:\"speed_current,omitempty\"`\n}\n\nfunc (x *GetTrafficResponse) Reset() {\n\t*x = GetTrafficResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetTrafficResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetTrafficResponse) ProtoMessage() {}\n\nfunc (x *GetTrafficResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetTrafficResponse.ProtoReflect.Descriptor instead.\nfunc (*GetTrafficResponse) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *GetTrafficResponse) GetSuccess() bool {\n\tif x != nil {\n\t\treturn x.Success\n\t}\n\treturn false\n}\n\nfunc (x *GetTrafficResponse) GetInfo() string {\n\tif x != nil {\n\t\treturn x.Info\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetTrafficResponse) GetTrafficTotal() *Traffic {\n\tif x != nil {\n\t\treturn x.TrafficTotal\n\t}\n\treturn nil\n}\n\nfunc (x *GetTrafficResponse) GetSpeedCurrent() *Speed {\n\tif x != nil {\n\t\treturn x.SpeedCurrent\n\t}\n\treturn nil\n}\n\ntype ListUsersRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *ListUsersRequest) Reset() {\n\t*x = ListUsersRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListUsersRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListUsersRequest) ProtoMessage() {}\n\nfunc (x *ListUsersRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListUsersRequest.ProtoReflect.Descriptor instead.\nfunc (*ListUsersRequest) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{6}\n}\n\ntype ListUsersResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStatus *UserStatus `protobuf:\"bytes,1,opt,name=status,proto3\" json:\"status,omitempty\"`\n}\n\nfunc (x *ListUsersResponse) Reset() {\n\t*x = ListUsersResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ListUsersResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListUsersResponse) ProtoMessage() {}\n\nfunc (x *ListUsersResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListUsersResponse.ProtoReflect.Descriptor instead.\nfunc (*ListUsersResponse) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ListUsersResponse) GetStatus() *UserStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn nil\n}\n\ntype GetUsersRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUser *User `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n}\n\nfunc (x *GetUsersRequest) Reset() {\n\t*x = GetUsersRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetUsersRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetUsersRequest) ProtoMessage() {}\n\nfunc (x *GetUsersRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetUsersRequest.ProtoReflect.Descriptor instead.\nfunc (*GetUsersRequest) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *GetUsersRequest) GetUser() *User {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn nil\n}\n\ntype GetUsersResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSuccess bool        `protobuf:\"varint,1,opt,name=success,proto3\" json:\"success,omitempty\"`\n\tInfo    string      `protobuf:\"bytes,2,opt,name=info,proto3\" json:\"info,omitempty\"`\n\tStatus  *UserStatus `protobuf:\"bytes,3,opt,name=status,proto3\" json:\"status,omitempty\"`\n}\n\nfunc (x *GetUsersResponse) Reset() {\n\t*x = GetUsersResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GetUsersResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetUsersResponse) ProtoMessage() {}\n\nfunc (x *GetUsersResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetUsersResponse.ProtoReflect.Descriptor instead.\nfunc (*GetUsersResponse) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *GetUsersResponse) GetSuccess() bool {\n\tif x != nil {\n\t\treturn x.Success\n\t}\n\treturn false\n}\n\nfunc (x *GetUsersResponse) GetInfo() string {\n\tif x != nil {\n\t\treturn x.Info\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetUsersResponse) GetStatus() *UserStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn nil\n}\n\ntype SetUsersRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStatus    *UserStatus               `protobuf:\"bytes,1,opt,name=status,proto3\" json:\"status,omitempty\"`\n\tOperation SetUsersRequest_Operation `protobuf:\"varint,2,opt,name=operation,proto3,enum=trojan.api.SetUsersRequest_Operation\" json:\"operation,omitempty\"`\n}\n\nfunc (x *SetUsersRequest) Reset() {\n\t*x = SetUsersRequest{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SetUsersRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetUsersRequest) ProtoMessage() {}\n\nfunc (x *SetUsersRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetUsersRequest.ProtoReflect.Descriptor instead.\nfunc (*SetUsersRequest) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *SetUsersRequest) GetStatus() *UserStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn nil\n}\n\nfunc (x *SetUsersRequest) GetOperation() SetUsersRequest_Operation {\n\tif x != nil {\n\t\treturn x.Operation\n\t}\n\treturn SetUsersRequest_Add\n}\n\ntype SetUsersResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSuccess bool   `protobuf:\"varint,1,opt,name=success,proto3\" json:\"success,omitempty\"`\n\tInfo    string `protobuf:\"bytes,2,opt,name=info,proto3\" json:\"info,omitempty\"`\n}\n\nfunc (x *SetUsersResponse) Reset() {\n\t*x = SetUsersResponse{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_api_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SetUsersResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetUsersResponse) ProtoMessage() {}\n\nfunc (x *SetUsersResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetUsersResponse.ProtoReflect.Descriptor instead.\nfunc (*SetUsersResponse) Descriptor() ([]byte, []int) {\n\treturn file_api_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SetUsersResponse) GetSuccess() bool {\n\tif x != nil {\n\t\treturn x.Success\n\t}\n\treturn false\n}\n\nfunc (x *SetUsersResponse) GetInfo() string {\n\tif x != nil {\n\t\treturn x.Info\n\t}\n\treturn \"\"\n}\n\nvar File_api_proto protoreflect.FileDescriptor\n\nvar file_api_proto_rawDesc = []byte{\n\t0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x74, 0x72, 0x6f,\n\t0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x22, 0x5b, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x66, 0x66,\n\t0x69, 0x63, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x72, 0x61,\n\t0x66, 0x66, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x75, 0x70, 0x6c, 0x6f,\n\t0x61, 0x64, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x77,\n\t0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x72, 0x61,\n\t0x66, 0x66, 0x69, 0x63, 0x22, 0x51, 0x0a, 0x05, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x21, 0x0a,\n\t0x0c, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x0b, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x70, 0x65, 0x65, 0x64,\n\t0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x70, 0x65,\n\t0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,\n\t0x61, 0x64, 0x53, 0x70, 0x65, 0x65, 0x64, 0x22, 0x36, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12,\n\t0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x68,\n\t0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22,\n\t0x92, 0x02, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24,\n\t0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74,\n\t0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04,\n\t0x75, 0x73, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f,\n\t0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72,\n\t0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63,\n\t0x52, 0x0c, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x36,\n\t0x0a, 0x0d, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0c, 0x73, 0x70, 0x65, 0x65, 0x64, 0x43,\n\t0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f,\n\t0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72,\n\t0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0a,\n\t0x73, 0x70, 0x65, 0x65, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x70,\n\t0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09,\n\t0x69, 0x70, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x70, 0x5f,\n\t0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x69, 0x70, 0x4c,\n\t0x69, 0x6d, 0x69, 0x74, 0x22, 0x39, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66,\n\t0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x75, 0x73, 0x65,\n\t0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e,\n\t0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22,\n\t0xb4, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73,\n\t0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,\n\t0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x69, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x5f,\n\t0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72,\n\t0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63,\n\t0x52, 0x0c, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x36,\n\t0x0a, 0x0d, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18,\n\t0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x52, 0x0c, 0x73, 0x70, 0x65, 0x65, 0x64, 0x43,\n\t0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73,\n\t0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x11, 0x4c, 0x69,\n\t0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,\n\t0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x16, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65,\n\t0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,\n\t0x37, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x10, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73,\n\t0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x70, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x55,\n\t0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,\n\t0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73,\n\t0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74,\n\t0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x6f,\n\t0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,\n\t0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x0f, 0x53,\n\t0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e,\n\t0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,\n\t0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72,\n\t0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x43,\n\t0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0e, 0x32, 0x25, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53,\n\t0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4f,\n\t0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x12, 0x07, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x65, 0x6c,\n\t0x65, 0x74, 0x65, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x10,\n\t0x02, 0x22, 0x40, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12,\n\t0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69,\n\t0x6e, 0x66, 0x6f, 0x32, 0x64, 0x0a, 0x13, 0x54, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x43, 0x6c, 0x69,\n\t0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x47, 0x65,\n\t0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61,\n\t0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e,\n\t0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52,\n\t0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xfd, 0x01, 0x0a, 0x13, 0x54, 0x72,\n\t0x6f, 0x6a, 0x61, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x12, 0x4c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1c,\n\t0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74,\n\t0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74,\n\t0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73,\n\t0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,\n\t0x4b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x2e, 0x74, 0x72,\n\t0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72,\n\t0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61,\n\t0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x08,\n\t0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1b, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61,\n\t0x6e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74,\n\t0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x34, 0x67, 0x65, 0x66, 0x61, 0x75, 0x31,\n\t0x74, 0x2f, 0x74, 0x72, 0x6f, 0x6a, 0x61, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f,\n\t0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_api_proto_rawDescOnce sync.Once\n\tfile_api_proto_rawDescData = file_api_proto_rawDesc\n)\n\nfunc file_api_proto_rawDescGZIP() []byte {\n\tfile_api_proto_rawDescOnce.Do(func() {\n\t\tfile_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData)\n\t})\n\treturn file_api_proto_rawDescData\n}\n\nvar file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 12)\nvar file_api_proto_goTypes = []interface{}{\n\t(SetUsersRequest_Operation)(0), // 0: trojan.api.SetUsersRequest.Operation\n\t(*Traffic)(nil),                // 1: trojan.api.Traffic\n\t(*Speed)(nil),                  // 2: trojan.api.Speed\n\t(*User)(nil),                   // 3: trojan.api.User\n\t(*UserStatus)(nil),             // 4: trojan.api.UserStatus\n\t(*GetTrafficRequest)(nil),      // 5: trojan.api.GetTrafficRequest\n\t(*GetTrafficResponse)(nil),     // 6: trojan.api.GetTrafficResponse\n\t(*ListUsersRequest)(nil),       // 7: trojan.api.ListUsersRequest\n\t(*ListUsersResponse)(nil),      // 8: trojan.api.ListUsersResponse\n\t(*GetUsersRequest)(nil),        // 9: trojan.api.GetUsersRequest\n\t(*GetUsersResponse)(nil),       // 10: trojan.api.GetUsersResponse\n\t(*SetUsersRequest)(nil),        // 11: trojan.api.SetUsersRequest\n\t(*SetUsersResponse)(nil),       // 12: trojan.api.SetUsersResponse\n}\nvar file_api_proto_depIdxs = []int32{\n\t3,  // 0: trojan.api.UserStatus.user:type_name -> trojan.api.User\n\t1,  // 1: trojan.api.UserStatus.traffic_total:type_name -> trojan.api.Traffic\n\t2,  // 2: trojan.api.UserStatus.speed_current:type_name -> trojan.api.Speed\n\t2,  // 3: trojan.api.UserStatus.speed_limit:type_name -> trojan.api.Speed\n\t3,  // 4: trojan.api.GetTrafficRequest.user:type_name -> trojan.api.User\n\t1,  // 5: trojan.api.GetTrafficResponse.traffic_total:type_name -> trojan.api.Traffic\n\t2,  // 6: trojan.api.GetTrafficResponse.speed_current:type_name -> trojan.api.Speed\n\t4,  // 7: trojan.api.ListUsersResponse.status:type_name -> trojan.api.UserStatus\n\t3,  // 8: trojan.api.GetUsersRequest.user:type_name -> trojan.api.User\n\t4,  // 9: trojan.api.GetUsersResponse.status:type_name -> trojan.api.UserStatus\n\t4,  // 10: trojan.api.SetUsersRequest.status:type_name -> trojan.api.UserStatus\n\t0,  // 11: trojan.api.SetUsersRequest.operation:type_name -> trojan.api.SetUsersRequest.Operation\n\t5,  // 12: trojan.api.TrojanClientService.GetTraffic:input_type -> trojan.api.GetTrafficRequest\n\t7,  // 13: trojan.api.TrojanServerService.ListUsers:input_type -> trojan.api.ListUsersRequest\n\t9,  // 14: trojan.api.TrojanServerService.GetUsers:input_type -> trojan.api.GetUsersRequest\n\t11, // 15: trojan.api.TrojanServerService.SetUsers:input_type -> trojan.api.SetUsersRequest\n\t6,  // 16: trojan.api.TrojanClientService.GetTraffic:output_type -> trojan.api.GetTrafficResponse\n\t8,  // 17: trojan.api.TrojanServerService.ListUsers:output_type -> trojan.api.ListUsersResponse\n\t10, // 18: trojan.api.TrojanServerService.GetUsers:output_type -> trojan.api.GetUsersResponse\n\t12, // 19: trojan.api.TrojanServerService.SetUsers:output_type -> trojan.api.SetUsersResponse\n\t16, // [16:20] is the sub-list for method output_type\n\t12, // [12:16] is the sub-list for method input_type\n\t12, // [12:12] is the sub-list for extension type_name\n\t12, // [12:12] is the sub-list for extension extendee\n\t0,  // [0:12] is the sub-list for field type_name\n}\n\nfunc init() { file_api_proto_init() }\nfunc file_api_proto_init() {\n\tif File_api_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Traffic); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Speed); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*User); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*UserStatus); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetTrafficRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetTrafficResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ListUsersRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ListUsersResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetUsersRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GetUsersResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SetUsersRequest); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SetUsersResponse); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_api_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   12,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   2,\n\t\t},\n\t\tGoTypes:           file_api_proto_goTypes,\n\t\tDependencyIndexes: file_api_proto_depIdxs,\n\t\tEnumInfos:         file_api_proto_enumTypes,\n\t\tMessageInfos:      file_api_proto_msgTypes,\n\t}.Build()\n\tFile_api_proto = out.File\n\tfile_api_proto_rawDesc = nil\n\tfile_api_proto_goTypes = nil\n\tfile_api_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "api/service/api.proto",
    "content": "syntax = \"proto3\";\n\npackage trojan.api;\noption go_package = \"github.com/p4gefau1t/trojan-go/api/service\";\n\nmessage Traffic {\n    uint64 upload_traffic = 1;\n    uint64 download_traffic = 2;\n}\n\nmessage Speed {\n    uint64 upload_speed = 1;\n    uint64 download_speed = 2;\n}\n\nmessage User {\n    string password = 1;\n    string hash = 2;\n}\n\nmessage UserStatus {\n    User user = 1;\n    Traffic traffic_total = 2;\n    Speed speed_current = 3;\n    Speed speed_limit = 4;\n    int32 ip_current = 5;\n    int32 ip_limit = 6;\n}\n\nmessage GetTrafficRequest {\n    User user = 1;\n}\n\nmessage GetTrafficResponse {\n    bool success = 1;\n    string info = 2;\n    Traffic traffic_total = 3;\n    Speed speed_current = 4;\n}\n\nmessage ListUsersRequest {\n\n}\n\nmessage ListUsersResponse {\n    UserStatus status = 1;\n}\n\nmessage GetUsersRequest {\n    User user = 1;\n}\n\nmessage GetUsersResponse {\n    bool success = 1;\n    string info = 2;\n    UserStatus status = 3;\n}\n\nmessage SetUsersRequest {\n    enum Operation {\n        Add = 0;\n        Delete = 1;\n        Modify = 2;\n    }\n    UserStatus status = 1;\n    Operation operation = 2;\n}\n\nmessage SetUsersResponse {\n    bool success = 1;\n    string info = 2;\n}\n\nservice TrojanClientService {\n    rpc GetTraffic(GetTrafficRequest) returns(GetTrafficResponse){}\n}\n\nservice TrojanServerService {\n    // list all users\n    rpc ListUsers(ListUsersRequest) returns(stream ListUsersResponse){}\n    // obtain specified user's info\n    rpc GetUsers(stream GetUsersRequest) returns(stream GetUsersResponse){}\n    // setup existing users' config\n    rpc SetUsers(stream SetUsersRequest) returns(stream SetUsersResponse){}\n}\n"
  },
  {
    "path": "api/service/api_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n\npackage service\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.32.0 or later.\nconst _ = grpc.SupportPackageIsVersion7\n\n// TrojanClientServiceClient is the client API for TrojanClientService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype TrojanClientServiceClient interface {\n\tGetTraffic(ctx context.Context, in *GetTrafficRequest, opts ...grpc.CallOption) (*GetTrafficResponse, error)\n}\n\ntype trojanClientServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewTrojanClientServiceClient(cc grpc.ClientConnInterface) TrojanClientServiceClient {\n\treturn &trojanClientServiceClient{cc}\n}\n\nfunc (c *trojanClientServiceClient) GetTraffic(ctx context.Context, in *GetTrafficRequest, opts ...grpc.CallOption) (*GetTrafficResponse, error) {\n\tout := new(GetTrafficResponse)\n\terr := c.cc.Invoke(ctx, \"/trojan.api.TrojanClientService/GetTraffic\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// TrojanClientServiceServer is the server API for TrojanClientService service.\n// All implementations must embed UnimplementedTrojanClientServiceServer\n// for forward compatibility\ntype TrojanClientServiceServer interface {\n\tGetTraffic(context.Context, *GetTrafficRequest) (*GetTrafficResponse, error)\n\tmustEmbedUnimplementedTrojanClientServiceServer()\n}\n\n// UnimplementedTrojanClientServiceServer must be embedded to have forward compatible implementations.\ntype UnimplementedTrojanClientServiceServer struct {\n}\n\nfunc (UnimplementedTrojanClientServiceServer) GetTraffic(context.Context, *GetTrafficRequest) (*GetTrafficResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetTraffic not implemented\")\n}\nfunc (UnimplementedTrojanClientServiceServer) mustEmbedUnimplementedTrojanClientServiceServer() {}\n\n// UnsafeTrojanClientServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to TrojanClientServiceServer will\n// result in compilation errors.\ntype UnsafeTrojanClientServiceServer interface {\n\tmustEmbedUnimplementedTrojanClientServiceServer()\n}\n\nfunc RegisterTrojanClientServiceServer(s grpc.ServiceRegistrar, srv TrojanClientServiceServer) {\n\ts.RegisterService(&TrojanClientService_ServiceDesc, srv)\n}\n\nfunc _TrojanClientService_GetTraffic_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetTrafficRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TrojanClientServiceServer).GetTraffic(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/trojan.api.TrojanClientService/GetTraffic\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TrojanClientServiceServer).GetTraffic(ctx, req.(*GetTrafficRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// TrojanClientService_ServiceDesc is the grpc.ServiceDesc for TrojanClientService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar TrojanClientService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"trojan.api.TrojanClientService\",\n\tHandlerType: (*TrojanClientServiceServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetTraffic\",\n\t\t\tHandler:    _TrojanClientService_GetTraffic_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"api.proto\",\n}\n\n// TrojanServerServiceClient is the client API for TrojanServerService service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype TrojanServerServiceClient interface {\n\t// list all users\n\tListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (TrojanServerService_ListUsersClient, error)\n\t// obtain specified user's info\n\tGetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_GetUsersClient, error)\n\t// setup existing users' config\n\tSetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_SetUsersClient, error)\n}\n\ntype trojanServerServiceClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewTrojanServerServiceClient(cc grpc.ClientConnInterface) TrojanServerServiceClient {\n\treturn &trojanServerServiceClient{cc}\n}\n\nfunc (c *trojanServerServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (TrojanServerService_ListUsersClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &TrojanServerService_ServiceDesc.Streams[0], \"/trojan.api.TrojanServerService/ListUsers\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &trojanServerServiceListUsersClient{stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\ntype TrojanServerService_ListUsersClient interface {\n\tRecv() (*ListUsersResponse, error)\n\tgrpc.ClientStream\n}\n\ntype trojanServerServiceListUsersClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *trojanServerServiceListUsersClient) Recv() (*ListUsersResponse, error) {\n\tm := new(ListUsersResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *trojanServerServiceClient) GetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_GetUsersClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &TrojanServerService_ServiceDesc.Streams[1], \"/trojan.api.TrojanServerService/GetUsers\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &trojanServerServiceGetUsersClient{stream}\n\treturn x, nil\n}\n\ntype TrojanServerService_GetUsersClient interface {\n\tSend(*GetUsersRequest) error\n\tRecv() (*GetUsersResponse, error)\n\tgrpc.ClientStream\n}\n\ntype trojanServerServiceGetUsersClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *trojanServerServiceGetUsersClient) Send(m *GetUsersRequest) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *trojanServerServiceGetUsersClient) Recv() (*GetUsersResponse, error) {\n\tm := new(GetUsersResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (c *trojanServerServiceClient) SetUsers(ctx context.Context, opts ...grpc.CallOption) (TrojanServerService_SetUsersClient, error) {\n\tstream, err := c.cc.NewStream(ctx, &TrojanServerService_ServiceDesc.Streams[2], \"/trojan.api.TrojanServerService/SetUsers\", opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &trojanServerServiceSetUsersClient{stream}\n\treturn x, nil\n}\n\ntype TrojanServerService_SetUsersClient interface {\n\tSend(*SetUsersRequest) error\n\tRecv() (*SetUsersResponse, error)\n\tgrpc.ClientStream\n}\n\ntype trojanServerServiceSetUsersClient struct {\n\tgrpc.ClientStream\n}\n\nfunc (x *trojanServerServiceSetUsersClient) Send(m *SetUsersRequest) error {\n\treturn x.ClientStream.SendMsg(m)\n}\n\nfunc (x *trojanServerServiceSetUsersClient) Recv() (*SetUsersResponse, error) {\n\tm := new(SetUsersResponse)\n\tif err := x.ClientStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// TrojanServerServiceServer is the server API for TrojanServerService service.\n// All implementations must embed UnimplementedTrojanServerServiceServer\n// for forward compatibility\ntype TrojanServerServiceServer interface {\n\t// list all users\n\tListUsers(*ListUsersRequest, TrojanServerService_ListUsersServer) error\n\t// obtain specified user's info\n\tGetUsers(TrojanServerService_GetUsersServer) error\n\t// setup existing users' config\n\tSetUsers(TrojanServerService_SetUsersServer) error\n\tmustEmbedUnimplementedTrojanServerServiceServer()\n}\n\n// UnimplementedTrojanServerServiceServer must be embedded to have forward compatible implementations.\ntype UnimplementedTrojanServerServiceServer struct {\n}\n\nfunc (UnimplementedTrojanServerServiceServer) ListUsers(*ListUsersRequest, TrojanServerService_ListUsersServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method ListUsers not implemented\")\n}\nfunc (UnimplementedTrojanServerServiceServer) GetUsers(TrojanServerService_GetUsersServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method GetUsers not implemented\")\n}\nfunc (UnimplementedTrojanServerServiceServer) SetUsers(TrojanServerService_SetUsersServer) error {\n\treturn status.Errorf(codes.Unimplemented, \"method SetUsers not implemented\")\n}\nfunc (UnimplementedTrojanServerServiceServer) mustEmbedUnimplementedTrojanServerServiceServer() {}\n\n// UnsafeTrojanServerServiceServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to TrojanServerServiceServer will\n// result in compilation errors.\ntype UnsafeTrojanServerServiceServer interface {\n\tmustEmbedUnimplementedTrojanServerServiceServer()\n}\n\nfunc RegisterTrojanServerServiceServer(s grpc.ServiceRegistrar, srv TrojanServerServiceServer) {\n\ts.RegisterService(&TrojanServerService_ServiceDesc, srv)\n}\n\nfunc _TrojanServerService_ListUsers_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(ListUsersRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(TrojanServerServiceServer).ListUsers(m, &trojanServerServiceListUsersServer{stream})\n}\n\ntype TrojanServerService_ListUsersServer interface {\n\tSend(*ListUsersResponse) error\n\tgrpc.ServerStream\n}\n\ntype trojanServerServiceListUsersServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *trojanServerServiceListUsersServer) Send(m *ListUsersResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc _TrojanServerService_GetUsers_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(TrojanServerServiceServer).GetUsers(&trojanServerServiceGetUsersServer{stream})\n}\n\ntype TrojanServerService_GetUsersServer interface {\n\tSend(*GetUsersResponse) error\n\tRecv() (*GetUsersRequest, error)\n\tgrpc.ServerStream\n}\n\ntype trojanServerServiceGetUsersServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *trojanServerServiceGetUsersServer) Send(m *GetUsersResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *trojanServerServiceGetUsersServer) Recv() (*GetUsersRequest, error) {\n\tm := new(GetUsersRequest)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc _TrojanServerService_SetUsers_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(TrojanServerServiceServer).SetUsers(&trojanServerServiceSetUsersServer{stream})\n}\n\ntype TrojanServerService_SetUsersServer interface {\n\tSend(*SetUsersResponse) error\n\tRecv() (*SetUsersRequest, error)\n\tgrpc.ServerStream\n}\n\ntype trojanServerServiceSetUsersServer struct {\n\tgrpc.ServerStream\n}\n\nfunc (x *trojanServerServiceSetUsersServer) Send(m *SetUsersResponse) error {\n\treturn x.ServerStream.SendMsg(m)\n}\n\nfunc (x *trojanServerServiceSetUsersServer) Recv() (*SetUsersRequest, error) {\n\tm := new(SetUsersRequest)\n\tif err := x.ServerStream.RecvMsg(m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\n// TrojanServerService_ServiceDesc is the grpc.ServiceDesc for TrojanServerService service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar TrojanServerService_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"trojan.api.TrojanServerService\",\n\tHandlerType: (*TrojanServerServiceServer)(nil),\n\tMethods:     []grpc.MethodDesc{},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"ListUsers\",\n\t\t\tHandler:       _TrojanServerService_ListUsers_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"GetUsers\",\n\t\t\tHandler:       _TrojanServerService_GetUsers_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"SetUsers\",\n\t\t\tHandler:       _TrojanServerService_SetUsers_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t},\n\tMetadata: \"api.proto\",\n}\n"
  },
  {
    "path": "api/service/client.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/api\"\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n)\n\ntype ClientAPI struct {\n\tTrojanClientServiceServer\n\n\tauth          statistic.Authenticator\n\tctx           context.Context\n\tuploadSpeed   uint64\n\tdownloadSpeed uint64\n\tlastSent      uint64\n\tlastRecv      uint64\n}\n\nfunc (s *ClientAPI) GetTraffic(ctx context.Context, req *GetTrafficRequest) (*GetTrafficResponse, error) {\n\tlog.Debug(\"API: GetTraffic\")\n\tif req.User == nil {\n\t\treturn nil, common.NewError(\"User is unspecified\")\n\t}\n\tif req.User.Hash == \"\" {\n\t\treq.User.Hash = common.SHA224String(req.User.Password)\n\t}\n\tvalid, user := s.auth.AuthUser(req.User.Hash)\n\tif !valid {\n\t\treturn nil, common.NewError(\"User \" + req.User.Hash + \" not found\")\n\t}\n\tsent, recv := user.GetTraffic()\n\tsentSpeed, recvSpeed := user.GetSpeed()\n\tresp := &GetTrafficResponse{\n\t\tSuccess: true,\n\t\tTrafficTotal: &Traffic{\n\t\t\tUploadTraffic:   sent,\n\t\t\tDownloadTraffic: recv,\n\t\t},\n\t\tSpeedCurrent: &Speed{\n\t\t\tUploadSpeed:   sentSpeed,\n\t\t\tDownloadSpeed: recvSpeed,\n\t\t},\n\t}\n\treturn resp, nil\n}\n\nfunc RunClientAPI(ctx context.Context, auth statistic.Authenticator) error {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tif !cfg.API.Enabled {\n\t\treturn nil\n\t}\n\tserver, err := newAPIServer(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer server.Stop()\n\tservice := &ClientAPI{\n\t\tctx:  ctx,\n\t\tauth: auth,\n\t}\n\tRegisterTrojanClientServiceServer(server, service)\n\taddr, err := net.ResolveIPAddr(\"ip\", cfg.API.APIHost)\n\tif err != nil {\n\t\treturn common.NewError(\"api found invalid addr\").Base(err)\n\t}\n\tlistener, err := net.Listen(\"tcp\", (&net.TCPAddr{\n\t\tIP:   addr.IP,\n\t\tPort: cfg.API.APIPort,\n\t\tZone: addr.Zone,\n\t}).String())\n\tif err != nil {\n\t\treturn common.NewError(\"client api failed to listen\").Base(err)\n\t}\n\tdefer listener.Close()\n\tlog.Info(\"client-side api service is listening on\", listener.Addr().String())\n\terrChan := make(chan error, 1)\n\tgo func() {\n\t\terrChan <- server.Serve(listener)\n\t}()\n\tselect {\n\tcase err := <-errChan:\n\t\treturn err\n\tcase <-ctx.Done():\n\t\tlog.Debug(\"closed\")\n\t\treturn nil\n\t}\n}\n\nfunc init() {\n\tapi.RegisterHandler(trojan.Name+\"_CLIENT\", RunClientAPI)\n}\n"
  },
  {
    "path": "api/service/client_test.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/memory\"\n)\n\nfunc TestClientAPI(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tctx = config.WithConfig(ctx, memory.Name,\n\t\t&memory.Config{\n\t\t\tPasswords: []string{\"useless\"},\n\t\t})\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tctx = config.WithConfig(ctx, Name, &Config{\n\t\tAPIConfig{\n\t\t\tEnabled: true,\n\t\t\tAPIHost: \"127.0.0.1\",\n\t\t\tAPIPort: port,\n\t\t},\n\t})\n\tauth, err := memory.NewAuthenticator(ctx)\n\tcommon.Must(err)\n\tgo RunClientAPI(ctx, auth)\n\n\ttime.Sleep(time.Second * 3)\n\tcommon.Must(auth.AddUser(\"hash1234\"))\n\tvalid, user := auth.AuthUser(\"hash1234\")\n\tif !valid {\n\t\tt.Fail()\n\t}\n\tuser.AddTraffic(1234, 5678)\n\ttime.Sleep(time.Second)\n\tconn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", port), grpc.WithInsecure())\n\tcommon.Must(err)\n\tclient := NewTrojanClientServiceClient(conn)\n\tresp, err := client.GetTraffic(ctx, &GetTrafficRequest{User: &User{\n\t\tHash: \"hash1234\",\n\t}})\n\tcommon.Must(err)\n\tif resp.TrafficTotal.DownloadTraffic != 5678 || resp.TrafficTotal.UploadTraffic != 1234 {\n\t\tt.Fail()\n\t}\n\t_, err = client.GetTraffic(ctx, &GetTrafficRequest{})\n\tif err == nil {\n\t\tt.Fail()\n\t}\n\tcancel()\n}\n"
  },
  {
    "path": "api/service/config.go",
    "content": "package service\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\nconst Name = \"API_SERVICE\"\n\ntype SSLConfig struct {\n\tEnabled        bool     `json:\"enabled\" yaml:\"enabled\"`\n\tCertPath       string   `json:\"cert\" yaml:\"cert\"`\n\tKeyPath        string   `json:\"key\" yaml:\"key\"`\n\tVerifyClient   bool     `json:\"verify_client\" yaml:\"verify-client\"`\n\tClientCertPath []string `json:\"client_cert\" yaml:\"client-cert\"`\n}\n\ntype APIConfig struct {\n\tEnabled bool      `json:\"enabled\" yaml:\"enabled\"`\n\tAPIHost string    `json:\"api_addr\" yaml:\"api-addr\"`\n\tAPIPort int       `json:\"api_port\" yaml:\"api-port\"`\n\tSSL     SSLConfig `json:\"ssl\" yaml:\"ssl\"`\n}\n\ntype Config struct {\n\tAPI APIConfig `json:\"api\" yaml:\"api\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(Config)\n\t})\n}\n"
  },
  {
    "path": "api/service/gen.sh",
    "content": "#!/usr/bin/env bash\n\necho \"Processing...\"\n\nGOPATH=${GOPATH:-$(go env GOPATH)}\nGOBIN=${GOBIN:-$(go env GOBIN)}\n\nif [[ $GOBIN == \"\" ]]; then\n  GOBIN=${GOPATH}/bin\nfi\n\ngo install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest\ngo install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest\n\necho \"Use protoc-gen-go and protoc-gen-go-grpc in $GOBIN.\"\n\nprotoc --go_out=. \\\n--go_opt=paths=source_relative \\\n--go-grpc_out=. \\\n--go-grpc_opt=paths=source_relative \\\n--plugin=protoc-gen-go=${GOBIN}/protoc-gen-go \\\n--plugin=protoc-gen-go-grpc=${GOBIN}/protoc-gen-go-grpc \\\napi.proto\n\nif [ $? -eq 0 ]; then\n  echo \"Generated successfully.\"\nfi\n"
  },
  {
    "path": "api/service/server.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\n\t\"github.com/p4gefau1t/trojan-go/api\"\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n)\n\ntype ServerAPI struct {\n\tTrojanServerServiceServer\n\tauth statistic.Authenticator\n}\n\nfunc (s *ServerAPI) GetUsers(stream TrojanServerService_GetUsersServer) error {\n\tlog.Debug(\"API: GetUsers\")\n\tfor {\n\t\treq, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif req.User == nil {\n\t\t\treturn common.NewError(\"user is unspecified\")\n\t\t}\n\t\tif req.User.Hash == \"\" {\n\t\t\treq.User.Hash = common.SHA224String(req.User.Password)\n\t\t}\n\t\tvalid, user := s.auth.AuthUser(req.User.Hash)\n\t\tif !valid {\n\t\t\tstream.Send(&GetUsersResponse{\n\t\t\t\tSuccess: false,\n\t\t\t\tInfo:    \"invalid user: \" + req.User.Hash,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\tdownloadTraffic, uploadTraffic := user.GetTraffic()\n\t\tdownloadSpeed, uploadSpeed := user.GetSpeed()\n\t\tdownloadSpeedLimit, uploadSpeedLimit := user.GetSpeedLimit()\n\t\tipLimit := user.GetIPLimit()\n\t\tipCurrent := user.GetIP()\n\t\terr = stream.Send(&GetUsersResponse{\n\t\t\tSuccess: true,\n\t\t\tStatus: &UserStatus{\n\t\t\t\tUser: req.User,\n\t\t\t\tTrafficTotal: &Traffic{\n\t\t\t\t\tUploadTraffic:   uploadTraffic,\n\t\t\t\t\tDownloadTraffic: downloadTraffic,\n\t\t\t\t},\n\t\t\t\tSpeedCurrent: &Speed{\n\t\t\t\t\tDownloadSpeed: downloadSpeed,\n\t\t\t\t\tUploadSpeed:   uploadSpeed,\n\t\t\t\t},\n\t\t\t\tSpeedLimit: &Speed{\n\t\t\t\t\tDownloadSpeed: uint64(downloadSpeedLimit),\n\t\t\t\t\tUploadSpeed:   uint64(uploadSpeedLimit),\n\t\t\t\t},\n\t\t\t\tIpCurrent: int32(ipCurrent),\n\t\t\t\tIpLimit:   int32(ipLimit),\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\nfunc (s *ServerAPI) SetUsers(stream TrojanServerService_SetUsersServer) error {\n\tlog.Debug(\"API: SetUsers\")\n\tfor {\n\t\treq, err := stream.Recv()\n\t\tif err == io.EOF {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif req.Status == nil {\n\t\t\treturn common.NewError(\"status is unspecified\")\n\t\t}\n\t\tif req.Status.User.Hash == \"\" {\n\t\t\treq.Status.User.Hash = common.SHA224String(req.Status.User.Password)\n\t\t}\n\t\tswitch req.Operation {\n\t\tcase SetUsersRequest_Add:\n\t\t\tif err = s.auth.AddUser(req.Status.User.Hash); err != nil {\n\t\t\t\terr = common.NewError(\"failed to add new user\").Base(err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif req.Status.SpeedLimit != nil {\n\t\t\t\tvalid, user := s.auth.AuthUser(req.Status.User.Hash)\n\t\t\t\tif !valid {\n\t\t\t\t\terr = common.NewError(\"failed to auth new user\").Base(err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif req.Status.SpeedLimit != nil {\n\t\t\t\t\tuser.SetSpeedLimit(int(req.Status.SpeedLimit.DownloadSpeed), int(req.Status.SpeedLimit.UploadSpeed))\n\t\t\t\t}\n\t\t\t\tif req.Status.TrafficTotal != nil {\n\t\t\t\t\tuser.SetTraffic(req.Status.TrafficTotal.DownloadTraffic, req.Status.TrafficTotal.UploadTraffic)\n\t\t\t\t}\n\t\t\t\tuser.SetIPLimit(int(req.Status.IpLimit))\n\t\t\t}\n\t\tcase SetUsersRequest_Delete:\n\t\t\terr = s.auth.DelUser(req.Status.User.Hash)\n\t\tcase SetUsersRequest_Modify:\n\t\t\tvalid, user := s.auth.AuthUser(req.Status.User.Hash)\n\t\t\tif !valid {\n\t\t\t\terr = common.NewError(\"invalid user \" + req.Status.User.Hash)\n\t\t\t} else {\n\t\t\t\tif req.Status.SpeedLimit != nil {\n\t\t\t\t\tuser.SetSpeedLimit(int(req.Status.SpeedLimit.DownloadSpeed), int(req.Status.SpeedLimit.UploadSpeed))\n\t\t\t\t}\n\t\t\t\tif req.Status.TrafficTotal != nil {\n\t\t\t\t\tuser.SetTraffic(req.Status.TrafficTotal.DownloadTraffic, req.Status.TrafficTotal.UploadTraffic)\n\t\t\t\t}\n\t\t\t\tuser.SetIPLimit(int(req.Status.IpLimit))\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tstream.Send(&SetUsersResponse{\n\t\t\t\tSuccess: false,\n\t\t\t\tInfo:    err.Error(),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\t\tstream.Send(&SetUsersResponse{\n\t\t\tSuccess: true,\n\t\t})\n\t}\n}\n\nfunc (s *ServerAPI) ListUsers(req *ListUsersRequest, stream TrojanServerService_ListUsersServer) error {\n\tlog.Debug(\"API: ListUsers\")\n\tusers := s.auth.ListUsers()\n\tfor _, user := range users {\n\t\tdownloadTraffic, uploadTraffic := user.GetTraffic()\n\t\tdownloadSpeed, uploadSpeed := user.GetSpeed()\n\t\tdownloadSpeedLimit, uploadSpeedLimit := user.GetSpeedLimit()\n\t\tipLimit := user.GetIPLimit()\n\t\tipCurrent := user.GetIP()\n\t\terr := stream.Send(&ListUsersResponse{\n\t\t\tStatus: &UserStatus{\n\t\t\t\tUser: &User{\n\t\t\t\t\tHash: user.Hash(),\n\t\t\t\t},\n\t\t\t\tTrafficTotal: &Traffic{\n\t\t\t\t\tDownloadTraffic: downloadTraffic,\n\t\t\t\t\tUploadTraffic:   uploadTraffic,\n\t\t\t\t},\n\t\t\t\tSpeedCurrent: &Speed{\n\t\t\t\t\tDownloadSpeed: downloadSpeed,\n\t\t\t\t\tUploadSpeed:   uploadSpeed,\n\t\t\t\t},\n\t\t\t\tSpeedLimit: &Speed{\n\t\t\t\t\tDownloadSpeed: uint64(downloadSpeedLimit),\n\t\t\t\t\tUploadSpeed:   uint64(uploadSpeedLimit),\n\t\t\t\t},\n\t\t\t\tIpLimit:   int32(ipLimit),\n\t\t\t\tIpCurrent: int32(ipCurrent),\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc newAPIServer(cfg *Config) (*grpc.Server, error) {\n\tvar server *grpc.Server\n\tif cfg.API.SSL.Enabled {\n\t\tlog.Info(\"api tls enabled\")\n\t\tkeyPair, err := tls.LoadX509KeyPair(cfg.API.SSL.CertPath, cfg.API.SSL.KeyPath)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to load key pair\").Base(err)\n\t\t}\n\t\ttlsConfig := &tls.Config{\n\t\t\tCertificates: []tls.Certificate{keyPair},\n\t\t}\n\t\tif cfg.API.SSL.VerifyClient {\n\t\t\ttlsConfig.ClientAuth = tls.RequireAndVerifyClientCert\n\t\t\ttlsConfig.ClientCAs = x509.NewCertPool()\n\t\t\tfor _, path := range cfg.API.SSL.ClientCertPath {\n\t\t\t\tlog.Debug(\"loading client cert: \" + path)\n\t\t\t\tcertBytes, err := ioutil.ReadFile(path)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, common.NewError(\"failed to load cert file\").Base(err)\n\t\t\t\t}\n\t\t\t\tok := tlsConfig.ClientCAs.AppendCertsFromPEM(certBytes)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, common.NewError(\"invalid client cert\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcreds := credentials.NewTLS(tlsConfig)\n\t\tserver = grpc.NewServer(grpc.Creds(creds))\n\t} else {\n\t\tserver = grpc.NewServer()\n\t}\n\treturn server, nil\n}\n\nfunc RunServerAPI(ctx context.Context, auth statistic.Authenticator) error {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tif !cfg.API.Enabled {\n\t\treturn nil\n\t}\n\tservice := &ServerAPI{\n\t\tauth: auth,\n\t}\n\tserver, err := newAPIServer(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer server.Stop()\n\tRegisterTrojanServerServiceServer(server, service)\n\taddr, err := net.ResolveIPAddr(\"ip\", cfg.API.APIHost)\n\tif err != nil {\n\t\treturn common.NewError(\"api found invalid addr\").Base(err)\n\t}\n\tlistener, err := net.Listen(\"tcp\", (&net.TCPAddr{\n\t\tIP:   addr.IP,\n\t\tPort: cfg.API.APIPort,\n\t\tZone: addr.Zone,\n\t}).String())\n\tif err != nil {\n\t\treturn common.NewError(\"server api failed to listen\").Base(err)\n\t}\n\tdefer listener.Close()\n\tlog.Info(\"server-side api service is listening on\", listener.Addr().String())\n\terrChan := make(chan error, 1)\n\tgo func() {\n\t\terrChan <- server.Serve(listener)\n\t}()\n\tselect {\n\tcase err := <-errChan:\n\t\treturn err\n\tcase <-ctx.Done():\n\t\tlog.Debug(\"closed\")\n\t\treturn nil\n\t}\n}\n\nfunc init() {\n\tapi.RegisterHandler(trojan.Name+\"_SERVER\", RunServerAPI)\n}\n"
  },
  {
    "path": "api/service/server_test.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/memory\"\n)\n\nfunc TestServerAPI(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tctx = config.WithConfig(ctx, memory.Name,\n\t\t&memory.Config{\n\t\t\tPasswords: []string{},\n\t\t})\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tctx = config.WithConfig(ctx, Name, &Config{\n\t\tAPIConfig{\n\t\t\tEnabled: true,\n\t\t\tAPIHost: \"127.0.0.1\",\n\t\t\tAPIPort: port,\n\t\t},\n\t})\n\tauth, err := memory.NewAuthenticator(ctx)\n\tcommon.Must(err)\n\tgo RunServerAPI(ctx, auth)\n\ttime.Sleep(time.Second * 3)\n\tcommon.Must(auth.AddUser(\"hash1234\"))\n\t_, user := auth.AuthUser(\"hash1234\")\n\tconn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", port), grpc.WithInsecure())\n\tcommon.Must(err)\n\tserver := NewTrojanServerServiceClient(conn)\n\tstream1, err := server.ListUsers(ctx, &ListUsersRequest{})\n\tcommon.Must(err)\n\tfor {\n\t\tresp, err := stream1.Recv()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tfmt.Println(resp.Status.User.Hash)\n\t\tif resp.Status.User.Hash != \"hash1234\" {\n\t\t\tt.Fail()\n\t\t}\n\t\tfmt.Println(resp.Status.SpeedCurrent)\n\t\tfmt.Println(resp.Status.SpeedLimit)\n\t}\n\tstream1.CloseSend()\n\tuser.AddTraffic(1234, 5678)\n\ttime.Sleep(time.Second * 1)\n\tstream2, err := server.GetUsers(ctx)\n\tcommon.Must(err)\n\tstream2.Send(&GetUsersRequest{\n\t\tUser: &User{\n\t\t\tHash: \"hash1234\",\n\t\t},\n\t})\n\tresp2, err := stream2.Recv()\n\tcommon.Must(err)\n\tif resp2.Status.TrafficTotal.DownloadTraffic != 1234 || resp2.Status.TrafficTotal.UploadTraffic != 5678 {\n\t\tt.Fatal(\"wrong traffic\")\n\t}\n\n\tstream3, err := server.SetUsers(ctx)\n\tcommon.Must(err)\n\tstream3.Send(&SetUsersRequest{\n\t\tStatus: &UserStatus{\n\t\t\tUser: &User{\n\t\t\t\tHash: \"hash1234\",\n\t\t\t},\n\t\t},\n\t\tOperation: SetUsersRequest_Delete,\n\t})\n\tresp3, err := stream3.Recv()\n\tif err != nil || !resp3.Success {\n\t\tt.Fatal(\"user not exists\")\n\t}\n\tvalid, _ := auth.AuthUser(\"hash1234\")\n\tif valid {\n\t\tt.Fatal(\"failed to auth\")\n\t}\n\tstream3.Send(&SetUsersRequest{\n\t\tStatus: &UserStatus{\n\t\t\tUser: &User{\n\t\t\t\tHash: \"newhash\",\n\t\t\t},\n\t\t},\n\t\tOperation: SetUsersRequest_Add,\n\t})\n\tresp3, err = stream3.Recv()\n\tif err != nil || !resp3.Success {\n\t\tt.Fatal(\"failed to read\")\n\t}\n\tvalid, user = auth.AuthUser(\"newhash\")\n\tif !valid {\n\t\tt.Fatal(\"failed to auth 2\")\n\t}\n\tstream3.Send(&SetUsersRequest{\n\t\tStatus: &UserStatus{\n\t\t\tUser: &User{\n\t\t\t\tHash: \"newhash\",\n\t\t\t},\n\t\t\tSpeedLimit: &Speed{\n\t\t\t\tDownloadSpeed: 5000,\n\t\t\t\tUploadSpeed:   3000,\n\t\t\t},\n\t\t\tTrafficTotal: &Traffic{\n\t\t\t\tDownloadTraffic: 1,\n\t\t\t\tUploadTraffic:   1,\n\t\t\t},\n\t\t},\n\t\tOperation: SetUsersRequest_Modify,\n\t})\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tuser.AddTraffic(200, 0)\n\t\t}\n\t}()\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tuser.AddTraffic(0, 300)\n\t\t}\n\t}()\n\ttime.Sleep(time.Second * 3)\n\tfor i := 0; i < 3; i++ {\n\t\tstream2.Send(&GetUsersRequest{\n\t\t\tUser: &User{\n\t\t\t\tHash: \"newhash\",\n\t\t\t},\n\t\t})\n\t\tresp2, err = stream2.Recv()\n\t\tcommon.Must(err)\n\t\tfmt.Println(resp2.Status.SpeedCurrent)\n\t\tfmt.Println(resp2.Status.SpeedLimit)\n\t\ttime.Sleep(time.Second)\n\t}\n\tstream2.CloseSend()\n\tcancel()\n}\n\nfunc TestTLSRSA(t *testing.T) {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tcfg := &Config{\n\t\tAPI: APIConfig{\n\t\t\tEnabled: true,\n\t\t\tAPIHost: \"127.0.0.1\",\n\t\t\tAPIPort: port,\n\t\t\tSSL: SSLConfig{\n\t\t\t\tEnabled:        true,\n\t\t\t\tCertPath:       \"server-rsa2048.crt\",\n\t\t\t\tKeyPath:        \"server-rsa2048.key\",\n\t\t\t\tVerifyClient:   false,\n\t\t\t\tClientCertPath: []string{\"client-rsa2048.crt\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\tctx = config.WithConfig(ctx, memory.Name,\n\t\t&memory.Config{\n\t\t\tPasswords: []string{},\n\t\t})\n\n\tauth, err := memory.NewAuthenticator(ctx)\n\tcommon.Must(err)\n\tgo func() {\n\t\tcommon.Must(RunServerAPI(ctx, auth))\n\t}()\n\ttime.Sleep(time.Second)\n\tpool := x509.NewCertPool()\n\tcertBytes, err := os.ReadFile(\"server-rsa2048.crt\")\n\tcommon.Must(err)\n\tpool.AppendCertsFromPEM(certBytes)\n\n\tcertificate, err := tls.LoadX509KeyPair(\"client-rsa2048.crt\", \"client-rsa2048.key\")\n\tcommon.Must(err)\n\tcreds := credentials.NewTLS(&tls.Config{\n\t\tServerName:   \"localhost\",\n\t\tRootCAs:      pool,\n\t\tCertificates: []tls.Certificate{certificate},\n\t})\n\tconn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", port), grpc.WithTransportCredentials(creds))\n\tcommon.Must(err)\n\tserver := NewTrojanServerServiceClient(conn)\n\tstream, err := server.ListUsers(ctx, &ListUsersRequest{})\n\tcommon.Must(err)\n\tstream.CloseSend()\n\tconn.Close()\n}\n\nfunc TestTLSECC(t *testing.T) {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tcfg := &Config{\n\t\tAPI: APIConfig{\n\t\t\tEnabled: true,\n\t\t\tAPIHost: \"127.0.0.1\",\n\t\t\tAPIPort: port,\n\t\t\tSSL: SSLConfig{\n\t\t\t\tEnabled:        true,\n\t\t\t\tCertPath:       \"server-ecc.crt\",\n\t\t\t\tKeyPath:        \"server-ecc.key\",\n\t\t\t\tVerifyClient:   false,\n\t\t\t\tClientCertPath: []string{\"client-ecc.crt\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\tctx = config.WithConfig(ctx, memory.Name,\n\t\t&memory.Config{\n\t\t\tPasswords: []string{},\n\t\t})\n\n\tauth, err := memory.NewAuthenticator(ctx)\n\tcommon.Must(err)\n\tgo func() {\n\t\tcommon.Must(RunServerAPI(ctx, auth))\n\t}()\n\ttime.Sleep(time.Second)\n\tpool := x509.NewCertPool()\n\tcertBytes, err := os.ReadFile(\"server-ecc.crt\")\n\tcommon.Must(err)\n\tpool.AppendCertsFromPEM(certBytes)\n\n\tcertificate, err := tls.LoadX509KeyPair(\"client-ecc.crt\", \"client-ecc.key\")\n\tcommon.Must(err)\n\tcreds := credentials.NewTLS(&tls.Config{\n\t\tServerName:   \"localhost\",\n\t\tRootCAs:      pool,\n\t\tCertificates: []tls.Certificate{certificate},\n\t})\n\tconn, err := grpc.Dial(fmt.Sprintf(\"127.0.0.1:%d\", port), grpc.WithTransportCredentials(creds))\n\tcommon.Must(err)\n\tserver := NewTrojanServerServiceClient(conn)\n\tstream, err := server.ListUsers(ctx, &ListUsersRequest{})\n\tcommon.Must(err)\n\tstream.CloseSend()\n\tconn.Close()\n}\n\nvar serverRSA2048Cert = `\n-----BEGIN CERTIFICATE-----\nMIIC5TCCAc2gAwIBAgIJAJqNVe6g/10vMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0yMTA5MTQwNjE1MTFaFw0yNjA5MTMwNjE1MTFaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAK7bupJ8tmHM3shQ/7N730jzpRsXdNiBxq/Jxx8j+vB3AcxuP5bjXQZqS6YR\n5W5vrfLlegtq1E/mmaI3Ht0RfIlzev04Dua9PWmIQJD801nEPknbfgCLXDh+pYr2\nsfg8mUh3LjGtrxyH+nmbTjWg7iWSKohmZ8nUDcX94Llo5FxibMAz8OsAwOmUueCH\njP3XswZYHEy+OOP3K0ZEiJy0f5T6ZXk9OWYuPN4VQKJx1qrc9KzZtSPHwqVdkGUi\nase9tOPA4aMutzt0btgW7h7UrvG6C1c/Rr1BxdiYq1EQ+yypnAlyToVQSNbo67zz\nwGQk4GeruIkOgJOLdooN/HjhbHMCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo\nb3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B\nAQsFAAOCAQEASsBzHHYiWDDiBVWUEwVZAduTrslTLNOxG0QHBKsHWIlz/3QlhQil\nywb3OhfMTUR1dMGY5Iq5432QiCHO4IMCOv7tDIkgb4Bc3v/3CRlBlnurtAmUfNJ6\npTRSlK4AjWpGHAEEd/8aCaOE86hMP8WDht8MkJTRrQqpJ1HeDISoKt9nepHOIsj+\nI2zLZZtw0pg7FuR4MzWuqOt071iRS46Pupryb3ZEGIWNz5iLrDQod5Iz2ZGSRGqE\nrB8idX0mlj5AHRRanVR3PAes+eApsW9JvYG/ImuCOs+ZsukY614zQZdR+SyFm85G\n4NICyeQsmiypNHHgw+xZmGqZg65bXNGoyg==\n-----END CERTIFICATE-----\n`\n\nvar serverRSA2048Key = `\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu27qSfLZhzN7I\nUP+ze99I86UbF3TYgcavyccfI/rwdwHMbj+W410GakumEeVub63y5XoLatRP5pmi\nNx7dEXyJc3r9OA7mvT1piECQ/NNZxD5J234Ai1w4fqWK9rH4PJlIdy4xra8ch/p5\nm041oO4lkiqIZmfJ1A3F/eC5aORcYmzAM/DrAMDplLngh4z917MGWBxMvjjj9ytG\nRIictH+U+mV5PTlmLjzeFUCicdaq3PSs2bUjx8KlXZBlImrHvbTjwOGjLrc7dG7Y\nFu4e1K7xugtXP0a9QcXYmKtREPssqZwJck6FUEjW6Ou888BkJOBnq7iJDoCTi3aK\nDfx44WxzAgMBAAECggEBAKYhib/H0ZhWB4yWuHqUxG4RXtrAjHlvw5Acy5zgmHiC\n+Sh7ztrTJf0EXN9pvWwRm1ldgXj7hMBtPaaLbD1pccM9/qo66p17Sq/LjlyyeTOe\naffOHIbz4Sij2zCOdkR9fr0EztTQScF3yBhl4Aa/4cO8fcCeWxm86WEldq9x4xWJ\ns5WMR4CnrOJhDINLNPQPKX92KyxEQ/RfuBWovx3M0nl3fcUWfESY134t5g/UBFId\nIn19tZ+pGIpCkxP0U1AZWrlZRA8Q/3sO2orUpoAOdCrGk/DcCTMh0c1pMzbYZ1/i\ncYXn38MpUo8QeG4FElUhAv6kzeBIl2tRBMVzIigo+AECgYEA3No1rHdFu6Ox9vC8\nE93PTZevYVcL5J5yx6x7khCaOLKKuRXpjOX/h3Ll+hlN2DVAg5Jli/JVGCco4GeK\nkbFLSyxG1+E63JbgsVpaEOgvFT3bHHSPSRJDnIU+WkcNQ2u4Ky5ahZzbNdV+4fj2\nNO2iMgkm7hoJANrm3IqqW8epenMCgYEAyq+qdNj5DiDzBcDvLwY+4/QmMOOgDqeh\n/TzhbDRyr+m4xNT7LLS4s/3wcbkQC33zhMUI3YvOHnYq5Ze/iL/TSloj0QCp1I7L\nJ7sZeM1XimMBQIpCfOC7lf4tU76Fz0DTHAL+CmX1DgmRJdYO09843VsKkscC968R\n4cwL5oGxxgECgYAM4TTsH/CTJtLEIfn19qOWVNhHhvoMlSkAeBCkzg8Qa2knrh12\nuBsU3SCIW11s1H40rh758GICDJaXr7InGP3ZHnXrNRlnr+zeqvRBtCi6xma23B1X\nF5eV0zd1sFsXqXqOGh/xVtp54z+JEinZoForLNl2XVJVGG8KQZP50kUR/QKBgH4O\n8zzpFT0sUPlrHVdp0wODfZ06dPmoWJ9flfPuSsYN3tTMgcs0Owv3C+wu5UPAegxB\nX1oq8W8Qn21cC8vJQmgj19LNTtLcXI3BV/5B+Aghu02gr+lq/EA1bYuAG0jjUGlD\nkyx0bQzl9lhJ4b70PjGtxc2z6KyTPdPpTB143FABAoGAQDoIUdc77/IWcjzcaXeJ\n8abak5rAZA7cu2g2NVfs+Km+njsB0pbTwMnV1zGoFABdaHLdqbthLWtX7WOb1PDD\nMQ+kbiLw5uj8IY2HEqJhDGGEdXBqxbW7kyuIAN9Mw+mwKzkikNcFQdxgchWH1d1o\nlVkr92iEX+IhIeYb4DN1vQw=\n-----END PRIVATE KEY-----\n`\n\nvar clientRSA2048Cert = `\n-----BEGIN CERTIFICATE-----\nMIIC5TCCAc2gAwIBAgIJAKD1wSl+Mnk7MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0yMTA5MTQwNjE2MDBaFw0yNjA5MTMwNjE2MDBaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAJeDu630louuf2V4sw396cGiAnxmTseVRMG+m3PnZ831puAsApm3IWSEcOqI\nUMk6s1pgSLysg6GxRZhX4L/ljErjMO4+y8riZjqqR0wd0GnhuNxuXaSUsEmKdDZb\ncICqXkZeZRn4jw/7L0xgdAdM2w3LXR6aq6CwveFY3/JncEZFQHH5mnorZdpbheR6\nrhvIL6AAI0YEY9uzuQBSrzOml3f7D+x5Xll14HoMN0kCysWt8jSP/An5yP8pL5RO\npn5kNBc8Bx8lykuV1uS8ogncSM7JzmpP1SeAViOq8CqXlJtUbUqVPckMmdfMMtbI\nqIO7R5/8imrdhLMi25fAOnfmDzcCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo\nb3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B\nAQsFAAOCAQEAFu2QPE3x1Sm3SfnHzAhvdjviYkbWvM8rQziIlIevbvA9Nl+vxDBf\nN5aRR6Hpxq02J2G/w7tzrKB9IluWdMU1+tilph5bCnwx3QUh/GR4oTsFiTvTZ5br\nSNf3xfTyIsL+Hf6iLvEgSt15ziY/334wu9NmQrU0FNZ+Lcc7Mx0OgvuP9Zim+6oo\n/FW80R3pUSzUZcUQgsI4Sz7/6nJTxhsc+kqtnOXIQLPC9GA06kP8eN6XjTsavP7f\neZq/yozddOk0dqx8uwmKUOb1Rg+pS8VIhQBRv3UPb4L/07AWSTZSMZLf1+CMgzMY\nJtsxa1MLqPkB7fiAR6SFUFW7Q36gDp/Mdw==\n-----END CERTIFICATE-----\n`\n\nvar clientRSA2048Key = `\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXg7ut9JaLrn9l\neLMN/enBogJ8Zk7HlUTBvptz52fN9abgLAKZtyFkhHDqiFDJOrNaYEi8rIOhsUWY\nV+C/5YxK4zDuPsvK4mY6qkdMHdBp4bjcbl2klLBJinQ2W3CAql5GXmUZ+I8P+y9M\nYHQHTNsNy10emqugsL3hWN/yZ3BGRUBx+Zp6K2XaW4Xkeq4byC+gACNGBGPbs7kA\nUq8zppd3+w/seV5ZdeB6DDdJAsrFrfI0j/wJ+cj/KS+UTqZ+ZDQXPAcfJcpLldbk\nvKIJ3EjOyc5qT9UngFYjqvAql5SbVG1KlT3JDJnXzDLWyKiDu0ef/Ipq3YSzItuX\nwDp35g83AgMBAAECggEASOv4CjMrubKUUgwTcWqBdNY6iBDdXaVz4COSweffx/qx\nBDdqUP0Yrz4m8loFN7Ru2dJ5b4VAHTQqoLW6z+D08p4B0MicYNsyBI4rnnDC/BLN\nXBoqK6n8Zoiigf7kWKimkwufcS51/GUSUJojfdf5ndwAx1f9vmsSGEEkF5C9MrQo\nSa4eyySuXuDS9swrXuTn9FcVcbUoIblgL8GIlmX4S1Xl9NaVS/VAVt42FgpNSYy7\n2Qf9Medg3ApimjwkLiDSh0RElirlUwzSg9dx2U+hHwWQWimb2AhA85uK3bpFB/x9\nb2agS1uxTar4mk4LFppQqVUuXlpj2hW7HxTdcxGHYQKBgQDGA+Wv3b8FMeszYqqx\nBbI5+FeXmQ5AoYNdHyPfCH8f2LX1FTnnQbUvFMJ3UQZl/GGHocgyFNvfDo6YyYsg\n2XgcNO/JWMbKEw0HkfMgkaIa3Jfq/PTB386NhqBq5FHiBUrvSHzhaIzpuaokBrRk\njFlcqONK+uj77iRgci4wR59POQKBgQDD4fDZzOGQCy/AWrMX/adk8ymoxhWQj6PN\nzhy2GZo9jGwyskQr5neIDQtxRbgokMspxpyPdQG2SxbG/zygyFEaCsp/Xb3iB3aA\n2dcktkV9agx+g1WrflTpGG9quW5vQJgQv9FtlVXJdiihN9CQvXAcYRjUA/zkadIL\nGsrVF9rh7wKBgFoMLaiDU7neEJKGnQ7xgzI/kD29ebDEgkOXxK1JZN4ro9t3MqTK\nycVGUIUIELvSQNv4I107BR3ztb8fcCiZHLjfDehnecctULCPm5vE/o3uoRtYu0lr\nKLhNb6gMenwpYgFc2oV7ERG8v/WwItrSxFSR7QMNBWSD0IEXi4+jEnxpAoGAMJTS\n5VG5B76egzh7fpG8eH8Ob/tg0c+uMpbR7CABbw5qr1AjNDgeoTGLCvbdq8HtgVju\n7213lTyeU5Bt+vpzkt/mRRx8wZhUPbTJdSN3rJkmrCHql3Pnn0AeMfv3dcQxcsYA\nLQuCkUqq3QE4yw0QxxkVzU+H4yaTn4lvkNYvxSUCgYEAgD85MM3DaiNcrevQv6Wb\nvSo7jBhd8Q/NawH53V32eIlF9Kkm2mqTmPIQ2OxOtdZ3xm+JmPd20jEVg7NcPL58\nt6BoSH10pLaI0CP2NdcYfJTIiHAhuQe7PxPCA0nlHTXgSv97QR7y4qMhbf2j1umc\nhKym0tdk2QjR3qqglaD572A=\n-----END PRIVATE KEY-----\n`\n\nvar serverECCCert = `\n-----BEGIN CERTIFICATE-----\nMIICTDCCAfKgAwIBAgIQDtCrO8cNST2eY2tA/AGrsDAKBggqhkjOPQQDAjBeMQsw\nCQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRlc3Qg\nRUNDIC0gRm9yIHRlc3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTAeFw0y\nMTA5MTQwNjQ1MzNaFw0yNjA5MTMwNjQ1MzNaMCExCzAJBgNVBAYTAkNOMRIwEAYD\nVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASvYy/r7XR1\nY39lC2JpRJh582zR2CTNynbuolK9a1jsbXaZv+hpBlHkgzMHsWu7LY9Pnb/Dbp4i\n1lRASOddD/rLo4HOMIHLMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUWxGyVxD0fBhTy3tH4eKznRFXFCYw\nYwYIKwYBBQUHAQEEVzBVMCEGCCsGAQUFBzABhhVodHRwOi8vb2NzcC5teXNzbC5j\nb20wMAYIKwYBBQUHMAKGJGh0dHA6Ly9jYS5teXNzbC5jb20vbXlzc2x0ZXN0ZWNj\nLmNydDAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIgDQUa\nGEdmKstLMHUmmPMGm/P9S4vvSZV2VHsb3+AEyIUCIQCdJpbyTCz+mEyskhwrGOw/\nblh3WBONv6MBtqPpmgE1AQ==\n-----END CERTIFICATE-----\n`\n\nvar serverECCKey = `\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIB8G2suYKuBLoodNIwRMp3JPN1fcZxCt3kcOYIx4nbcPoAoGCCqGSM49\nAwEHoUQDQgAEr2Mv6+10dWN/ZQtiaUSYefNs0dgkzcp27qJSvWtY7G12mb/oaQZR\n5IMzB7Fruy2PT52/w26eItZUQEjnXQ/6yw==\n-----END EC PRIVATE KEY-----\n`\n\nvar clientECCCert = `\n-----BEGIN CERTIFICATE-----\nMIICTDCCAfKgAwIBAgIQb5FLuCggTiWFtt/dPh+2bTAKBggqhkjOPQQDAjBeMQsw\nCQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRlc3Qg\nRUNDIC0gRm9yIHRlc3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTAeFw0y\nMTA5MTQwNjQ2MTFaFw0yNjA5MTMwNjQ2MTFaMCExCzAJBgNVBAYTAkNOMRIwEAYD\nVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQME+DnYtcK\nlbcZmc33oEtoeRWH61DYdl4ei/bM+vkv01MkBB+YTZl0yofJIFJYsfU5pMFK+uyw\nD4qdcklKPGIKo4HOMIHLMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUWxGyVxD0fBhTy3tH4eKznRFXFCYw\nYwYIKwYBBQUHAQEEVzBVMCEGCCsGAQUFBzABhhVodHRwOi8vb2NzcC5teXNzbC5j\nb20wMAYIKwYBBQUHMAKGJGh0dHA6Ly9jYS5teXNzbC5jb20vbXlzc2x0ZXN0ZWNj\nLmNydDAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIgfjaU\nsCfgklPsjHzs3fUtSRGfWoRLFsRBO66RtHJSzrYCIQDAxgx0FB0mAXbflj4mXJVA\n9/SjaCI40D6MhMnJhQS7Zg==\n-----END CERTIFICATE-----\n`\n\nvar clientECCKey = `\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINgbap6WOGXm8V+ghIyporGcGWnggbjjP9xNsxhn+0sqoAoGCCqGSM49\nAwEHoUQDQgAEDBPg52LXCpW3GZnN96BLaHkVh+tQ2HZeHov2zPr5L9NTJAQfmE2Z\ndMqHySBSWLH1OaTBSvrssA+KnXJJSjxiCg==\n-----END EC PRIVATE KEY-----\n`\n\nfunc init() {\n\tos.WriteFile(\"server-rsa2048.crt\", []byte(serverRSA2048Cert), 0o777)\n\tos.WriteFile(\"server-rsa2048.key\", []byte(serverRSA2048Key), 0o777)\n\tos.WriteFile(\"client-rsa2048.crt\", []byte(clientRSA2048Cert), 0o777)\n\tos.WriteFile(\"client-rsa2048.key\", []byte(clientRSA2048Key), 0o777)\n\n\tos.WriteFile(\"server-ecc.crt\", []byte(serverECCCert), 0o777)\n\tos.WriteFile(\"server-ecc.key\", []byte(serverECCKey), 0o777)\n\tos.WriteFile(\"client-ecc.crt\", []byte(clientECCCert), 0o777)\n\tos.WriteFile(\"client-ecc.key\", []byte(clientECCKey), 0o777)\n}\n"
  },
  {
    "path": "common/common.go",
    "content": "package common\n\nimport (\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\ntype Runnable interface {\n\tRun() error\n\tClose() error\n}\n\nfunc SHA224String(password string) string {\n\thash := sha256.New224()\n\thash.Write([]byte(password))\n\tval := hash.Sum(nil)\n\tstr := \"\"\n\tfor _, v := range val {\n\t\tstr += fmt.Sprintf(\"%02x\", v)\n\t}\n\treturn str\n}\n\nfunc GetProgramDir() string {\n\tdir, err := filepath.Abs(filepath.Dir(os.Args[0]))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn dir\n}\n\nfunc GetAssetLocation(file string) string {\n\tif filepath.IsAbs(file) {\n\t\treturn file\n\t}\n\tif loc := os.Getenv(\"TROJAN_GO_LOCATION_ASSET\"); loc != \"\" {\n\t\tabsPath, err := filepath.Abs(loc)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Debugf(\"env set: TROJAN_GO_LOCATION_ASSET=%s\", absPath)\n\t\treturn filepath.Join(absPath, file)\n\t}\n\treturn filepath.Join(GetProgramDir(), file)\n}\n"
  },
  {
    "path": "common/error.go",
    "content": "package common\n\nimport (\n\t\"fmt\"\n)\n\ntype Error struct {\n\tinfo string\n}\n\nfunc (e *Error) Error() string {\n\treturn e.info\n}\n\nfunc (e *Error) Base(err error) *Error {\n\tif err != nil {\n\t\te.info += \" | \" + err.Error()\n\t}\n\treturn e\n}\n\nfunc NewError(info string) *Error {\n\treturn &Error{\n\t\tinfo: info,\n\t}\n}\n\nfunc Must(err error) {\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tpanic(err)\n\t}\n}\n\nfunc Must2(_ interface{}, err error) {\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "common/geodata/cache.go",
    "content": "package geodata\n\nimport (\n\t\"io/ioutil\"\n\t\"strings\"\n\n\tv2router \"github.com/v2fly/v2ray-core/v4/app/router\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\ntype geoipCache map[string]*v2router.GeoIP\n\nfunc (g geoipCache) Has(key string) bool {\n\treturn !(g.Get(key) == nil)\n}\n\nfunc (g geoipCache) Get(key string) *v2router.GeoIP {\n\tif g == nil {\n\t\treturn nil\n\t}\n\treturn g[key]\n}\n\nfunc (g geoipCache) Set(key string, value *v2router.GeoIP) {\n\tif g == nil {\n\t\tg = make(map[string]*v2router.GeoIP)\n\t}\n\tg[key] = value\n}\n\nfunc (g geoipCache) Unmarshal(filename, code string) (*v2router.GeoIP, error) {\n\tasset := common.GetAssetLocation(filename)\n\tidx := strings.ToLower(asset + \":\" + code)\n\tif g.Has(idx) {\n\t\tlog.Debugf(\"geoip cache HIT: %s -> %s\", code, idx)\n\t\treturn g.Get(idx), nil\n\t}\n\n\tgeoipBytes, err := Decode(asset, code)\n\tswitch err {\n\tcase nil:\n\t\tvar geoip v2router.GeoIP\n\t\tif err := proto.Unmarshal(geoipBytes, &geoip); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tg.Set(idx, &geoip)\n\t\treturn &geoip, nil\n\n\tcase ErrCodeNotFound:\n\t\treturn nil, common.NewError(\"country code \" + code + \" not found in \" + filename)\n\n\tcase ErrFailedToReadBytes, ErrFailedToReadExpectedLenBytes,\n\t\tErrInvalidGeodataFile, ErrInvalidGeodataVarintLength:\n\t\tlog.Warnf(\"failed to decode geoip file: %s, fallback to the original ReadFile method\", filename)\n\t\tgeoipBytes, err = ioutil.ReadFile(asset)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar geoipList v2router.GeoIPList\n\t\tif err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, geoip := range geoipList.GetEntry() {\n\t\t\tif strings.EqualFold(code, geoip.GetCountryCode()) {\n\t\t\t\tg.Set(idx, geoip)\n\t\t\t\treturn geoip, nil\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\treturn nil, err\n\t}\n\n\treturn nil, common.NewError(\"country code \" + code + \" not found in \" + filename)\n}\n\ntype geositeCache map[string]*v2router.GeoSite\n\nfunc (g geositeCache) Has(key string) bool {\n\treturn !(g.Get(key) == nil)\n}\n\nfunc (g geositeCache) Get(key string) *v2router.GeoSite {\n\tif g == nil {\n\t\treturn nil\n\t}\n\treturn g[key]\n}\n\nfunc (g geositeCache) Set(key string, value *v2router.GeoSite) {\n\tif g == nil {\n\t\tg = make(map[string]*v2router.GeoSite)\n\t}\n\tg[key] = value\n}\n\nfunc (g geositeCache) Unmarshal(filename, code string) (*v2router.GeoSite, error) {\n\tasset := common.GetAssetLocation(filename)\n\tidx := strings.ToLower(asset + \":\" + code)\n\tif g.Has(idx) {\n\t\tlog.Debugf(\"geosite cache HIT: %s -> %s\", code, idx)\n\t\treturn g.Get(idx), nil\n\t}\n\n\tgeositeBytes, err := Decode(asset, code)\n\tswitch err {\n\tcase nil:\n\t\tvar geosite v2router.GeoSite\n\t\tif err := proto.Unmarshal(geositeBytes, &geosite); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tg.Set(idx, &geosite)\n\t\treturn &geosite, nil\n\n\tcase ErrCodeNotFound:\n\t\treturn nil, common.NewError(\"list \" + code + \" not found in \" + filename)\n\n\tcase ErrFailedToReadBytes, ErrFailedToReadExpectedLenBytes,\n\t\tErrInvalidGeodataFile, ErrInvalidGeodataVarintLength:\n\t\tlog.Warnf(\"failed to decode geoip file: %s, fallback to the original ReadFile method\", filename)\n\t\tgeositeBytes, err = ioutil.ReadFile(asset)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar geositeList v2router.GeoSiteList\n\t\tif err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, geosite := range geositeList.GetEntry() {\n\t\t\tif strings.EqualFold(code, geosite.GetCountryCode()) {\n\t\t\t\tg.Set(idx, geosite)\n\t\t\t\treturn geosite, nil\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\treturn nil, err\n\t}\n\n\treturn nil, common.NewError(\"list \" + code + \" not found in \" + filename)\n}\n"
  },
  {
    "path": "common/geodata/decode.go",
    "content": "// Package geodata includes utilities to decode and parse the geoip & geosite dat files.\n//\n// It relies on the proto structure of GeoIP, GeoIPList, GeoSite and GeoSiteList in\n// github.com/v2fly/v2ray-core/v4/app/router/config.proto to comply with following rules:\n//\n// 1. GeoIPList and GeoSiteList cannot be changed\n// 2. The country_code in GeoIP and GeoSite must be\n//    a length-delimited `string`(wired type) and has field_number set to 1\n//\npackage geodata\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"google.golang.org/protobuf/encoding/protowire\"\n)\n\nvar (\n\tErrFailedToReadBytes            = errors.New(\"failed to read bytes\")\n\tErrFailedToReadExpectedLenBytes = errors.New(\"failed to read expected length of bytes\")\n\tErrInvalidGeodataFile           = errors.New(\"invalid geodata file\")\n\tErrInvalidGeodataVarintLength   = errors.New(\"invalid geodata varint length\")\n\tErrCodeNotFound                 = errors.New(\"code not found\")\n)\n\nfunc EmitBytes(f io.ReadSeeker, code string) ([]byte, error) {\n\tcount := 1\n\tisInner := false\n\ttempContainer := make([]byte, 0, 5)\n\n\tvar result []byte\n\tvar advancedN uint64 = 1\n\tvar geoDataVarintLength, codeVarintLength, varintLenByteLen uint64 = 0, 0, 0\n\nLoop:\n\tfor {\n\t\tcontainer := make([]byte, advancedN)\n\t\tbytesRead, err := f.Read(container)\n\t\tif err == io.EOF {\n\t\t\treturn nil, ErrCodeNotFound\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, ErrFailedToReadBytes\n\t\t}\n\t\tif bytesRead != len(container) {\n\t\t\treturn nil, ErrFailedToReadExpectedLenBytes\n\t\t}\n\n\t\tswitch count {\n\t\tcase 1, 3: // data type ((field_number << 3) | wire_type)\n\t\t\tif container[0] != 10 { // byte `0A` equals to `10` in decimal\n\t\t\t\treturn nil, ErrInvalidGeodataFile\n\t\t\t}\n\t\t\tadvancedN = 1\n\t\t\tcount++\n\t\tcase 2, 4: // data length\n\t\t\ttempContainer = append(tempContainer, container...)\n\t\t\tif container[0] > 127 { // max one-byte-length byte `7F`(0FFF FFFF) equals to `127` in decimal\n\t\t\t\tadvancedN = 1\n\t\t\t\tgoto Loop\n\t\t\t}\n\t\t\tlenVarint, n := protowire.ConsumeVarint(tempContainer)\n\t\t\tif n < 0 {\n\t\t\t\treturn nil, ErrInvalidGeodataVarintLength\n\t\t\t}\n\t\t\ttempContainer = nil\n\t\t\tif !isInner {\n\t\t\t\tisInner = true\n\t\t\t\tgeoDataVarintLength = lenVarint\n\t\t\t\tadvancedN = 1\n\t\t\t} else {\n\t\t\t\tisInner = false\n\t\t\t\tcodeVarintLength = lenVarint\n\t\t\t\tvarintLenByteLen = uint64(n)\n\t\t\t\tadvancedN = codeVarintLength\n\t\t\t}\n\t\t\tcount++\n\t\tcase 5: // data value\n\t\t\tif strings.EqualFold(string(container), code) {\n\t\t\t\tcount++\n\t\t\t\toffset := -(1 + int64(varintLenByteLen) + int64(codeVarintLength))\n\t\t\t\tf.Seek(offset, 1)               // back to the start of GeoIP or GeoSite varint\n\t\t\t\tadvancedN = geoDataVarintLength // the number of bytes to be read in next round\n\t\t\t} else {\n\t\t\t\tcount = 1\n\t\t\t\toffset := int64(geoDataVarintLength) - int64(codeVarintLength) - int64(varintLenByteLen) - 1\n\t\t\t\tf.Seek(offset, 1) // skip the unmatched GeoIP or GeoSite varint\n\t\t\t\tadvancedN = 1     // the next round will be the start of another GeoIPList or GeoSiteList\n\t\t\t}\n\t\tcase 6: // matched GeoIP or GeoSite varint\n\t\t\tresult = container\n\t\t\tbreak Loop\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\nfunc Decode(filename, code string) ([]byte, error) {\n\tf, err := os.Open(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\n\tgeoBytes, err := EmitBytes(f, code)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn geoBytes, nil\n}\n"
  },
  {
    "path": "common/geodata/decode_test.go",
    "content": "package geodata_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/common/geodata\"\n)\n\nfunc init() {\n\tconst (\n\t\tgeoipURL   = \"https://raw.githubusercontent.com/v2fly/geoip/release/geoip.dat\"\n\t\tgeositeURL = \"https://raw.githubusercontent.com/v2fly/domain-list-community/release/dlc.dat\"\n\t)\n\n\twd, err := os.Getwd()\n\tcommon.Must(err)\n\n\ttempPath := filepath.Join(wd, \"..\", \"..\", \"test\", \"temp\")\n\tos.Setenv(\"TROJAN_GO_LOCATION_ASSET\", tempPath)\n\n\tgeoipPath := common.GetAssetLocation(\"geoip.dat\")\n\tgeositePath := common.GetAssetLocation(\"geosite.dat\")\n\n\tif _, err := os.Stat(geoipPath); err != nil && errors.Is(err, fs.ErrNotExist) {\n\t\tcommon.Must(os.MkdirAll(tempPath, 0o755))\n\t\tgeoipBytes, err := common.FetchHTTPContent(geoipURL)\n\t\tcommon.Must(err)\n\t\tcommon.Must(common.WriteFile(geoipPath, geoipBytes))\n\t}\n\tif _, err := os.Stat(geositePath); err != nil && errors.Is(err, fs.ErrNotExist) {\n\t\tcommon.Must(os.MkdirAll(tempPath, 0o755))\n\t\tgeositeBytes, err := common.FetchHTTPContent(geositeURL)\n\t\tcommon.Must(err)\n\t\tcommon.Must(common.WriteFile(geositePath, geositeBytes))\n\t}\n}\n\nfunc TestDecodeGeoIP(t *testing.T) {\n\tfilename := common.GetAssetLocation(\"geoip.dat\")\n\tresult, err := geodata.Decode(filename, \"test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\texpected := []byte{10, 4, 84, 69, 83, 84, 18, 8, 10, 4, 127, 0, 0, 0, 16, 8}\n\tif !bytes.Equal(result, expected) {\n\t\tt.Errorf(\"failed to load geoip:test, expected: %v, got: %v\", expected, result)\n\t}\n}\n\nfunc TestDecodeGeoSite(t *testing.T) {\n\tfilename := common.GetAssetLocation(\"geosite.dat\")\n\tresult, err := geodata.Decode(filename, \"test\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\texpected := []byte{10, 4, 84, 69, 83, 84, 18, 20, 8, 3, 18, 16, 116, 101, 115, 116, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109}\n\tif !bytes.Equal(result, expected) {\n\t\tt.Errorf(\"failed to load geosite:test, expected: %v, got: %v\", expected, result)\n\t}\n}\n\nfunc BenchmarkLoadGeoIP(b *testing.B) {\n\tm1 := runtime.MemStats{}\n\tm2 := runtime.MemStats{}\n\n\tloader := geodata.NewGeodataLoader()\n\n\truntime.ReadMemStats(&m1)\n\tcn, _ := loader.LoadGeoIP(\"cn\")\n\tprivate, _ := loader.LoadGeoIP(\"private\")\n\truntime.KeepAlive(cn)\n\truntime.KeepAlive(private)\n\truntime.ReadMemStats(&m2)\n\n\tb.ReportMetric(float64(m2.Alloc-m1.Alloc)/1024, \"KiB(GeoIP-Alloc)\")\n\tb.ReportMetric(float64(m2.TotalAlloc-m1.TotalAlloc)/1024/1024, \"MiB(GeoIP-TotalAlloc)\")\n}\n\nfunc BenchmarkLoadGeoSite(b *testing.B) {\n\tm3 := runtime.MemStats{}\n\tm4 := runtime.MemStats{}\n\n\tloader := geodata.NewGeodataLoader()\n\n\truntime.ReadMemStats(&m3)\n\tcn, _ := loader.LoadGeoSite(\"cn\")\n\tnotcn, _ := loader.LoadGeoSite(\"geolocation-!cn\")\n\tprivate, _ := loader.LoadGeoSite(\"private\")\n\truntime.KeepAlive(cn)\n\truntime.KeepAlive(notcn)\n\truntime.KeepAlive(private)\n\truntime.ReadMemStats(&m4)\n\n\tb.ReportMetric(float64(m4.Alloc-m3.Alloc)/1024/1024, \"MiB(GeoSite-Alloc)\")\n\tb.ReportMetric(float64(m4.TotalAlloc-m3.TotalAlloc)/1024/1024, \"MiB(GeoSite-TotalAlloc)\")\n}\n"
  },
  {
    "path": "common/geodata/interface.go",
    "content": "package geodata\n\nimport v2router \"github.com/v2fly/v2ray-core/v4/app/router\"\n\ntype GeodataLoader interface {\n\tLoadIP(filename, country string) ([]*v2router.CIDR, error)\n\tLoadSite(filename, list string) ([]*v2router.Domain, error)\n\tLoadGeoIP(country string) ([]*v2router.CIDR, error)\n\tLoadGeoSite(list string) ([]*v2router.Domain, error)\n}\n"
  },
  {
    "path": "common/geodata/loader.go",
    "content": "package geodata\n\nimport (\n\t\"runtime\"\n\n\tv2router \"github.com/v2fly/v2ray-core/v4/app/router\"\n)\n\ntype geodataCache struct {\n\tgeoipCache\n\tgeositeCache\n}\n\nfunc NewGeodataLoader() GeodataLoader {\n\treturn &geodataCache{\n\t\tmake(map[string]*v2router.GeoIP),\n\t\tmake(map[string]*v2router.GeoSite),\n\t}\n}\n\nfunc (g *geodataCache) LoadIP(filename, country string) ([]*v2router.CIDR, error) {\n\tgeoip, err := g.geoipCache.Unmarshal(filename, country)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\truntime.GC()\n\treturn geoip.Cidr, nil\n}\n\nfunc (g *geodataCache) LoadSite(filename, list string) ([]*v2router.Domain, error) {\n\tgeosite, err := g.geositeCache.Unmarshal(filename, list)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\truntime.GC()\n\treturn geosite.Domain, nil\n}\n\nfunc (g *geodataCache) LoadGeoIP(country string) ([]*v2router.CIDR, error) {\n\treturn g.LoadIP(\"geoip.dat\", country)\n}\n\nfunc (g *geodataCache) LoadGeoSite(list string) ([]*v2router.Domain, error) {\n\treturn g.LoadSite(\"geosite.dat\", list)\n}\n"
  },
  {
    "path": "common/io.go",
    "content": "package common\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\ntype RewindReader struct {\n\tmu         sync.Mutex\n\trawReader  io.Reader\n\tbuf        []byte\n\tbufReadIdx int\n\trewound    bool\n\tbuffering  bool\n\tbufferSize int\n}\n\nfunc (r *RewindReader) Read(p []byte) (int, error) {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\n\tif r.rewound {\n\t\tif len(r.buf) > r.bufReadIdx {\n\t\t\tn := copy(p, r.buf[r.bufReadIdx:])\n\t\t\tr.bufReadIdx += n\n\t\t\treturn n, nil\n\t\t}\n\t\tr.rewound = false // all buffering content has been read\n\t}\n\tn, err := r.rawReader.Read(p)\n\tif r.buffering {\n\t\tr.buf = append(r.buf, p[:n]...)\n\t\tif len(r.buf) > r.bufferSize*2 {\n\t\t\tlog.Debug(\"read too many bytes!\")\n\t\t}\n\t}\n\treturn n, err\n}\n\nfunc (r *RewindReader) ReadByte() (byte, error) {\n\tbuf := [1]byte{}\n\t_, err := r.Read(buf[:])\n\treturn buf[0], err\n}\n\nfunc (r *RewindReader) Discard(n int) (int, error) {\n\tbuf := [128]byte{}\n\tif n < 128 {\n\t\treturn r.Read(buf[:n])\n\t}\n\tfor discarded := 0; discarded+128 < n; discarded += 128 {\n\t\t_, err := r.Read(buf[:])\n\t\tif err != nil {\n\t\t\treturn discarded, err\n\t\t}\n\t}\n\tif rest := n % 128; rest != 0 {\n\t\treturn r.Read(buf[:rest])\n\t}\n\treturn n, nil\n}\n\nfunc (r *RewindReader) Rewind() {\n\tr.mu.Lock()\n\tif r.bufferSize == 0 {\n\t\tpanic(\"no buffer\")\n\t}\n\tr.rewound = true\n\tr.bufReadIdx = 0\n\tr.mu.Unlock()\n}\n\nfunc (r *RewindReader) StopBuffering() {\n\tr.mu.Lock()\n\tr.buffering = false\n\tr.mu.Unlock()\n}\n\nfunc (r *RewindReader) SetBufferSize(size int) {\n\tr.mu.Lock()\n\tif size == 0 { // disable buffering\n\t\tif !r.buffering {\n\t\t\tpanic(\"reader is disabled\")\n\t\t}\n\t\tr.buffering = false\n\t\tr.buf = nil\n\t\tr.bufReadIdx = 0\n\t\tr.bufferSize = 0\n\t} else {\n\t\tif r.buffering {\n\t\t\tpanic(\"reader is buffering\")\n\t\t}\n\t\tr.buffering = true\n\t\tr.bufReadIdx = 0\n\t\tr.bufferSize = size\n\t\tr.buf = make([]byte, 0, size)\n\t}\n\tr.mu.Unlock()\n}\n\ntype RewindConn struct {\n\tnet.Conn\n\t*RewindReader\n}\n\nfunc (c *RewindConn) Read(p []byte) (int, error) {\n\treturn c.RewindReader.Read(p)\n}\n\nfunc NewRewindConn(conn net.Conn) *RewindConn {\n\treturn &RewindConn{\n\t\tConn: conn,\n\t\tRewindReader: &RewindReader{\n\t\t\trawReader: conn,\n\t\t},\n\t}\n}\n\ntype StickyWriter struct {\n\trawWriter   io.Writer\n\twriteBuffer []byte\n\tMaxBuffered int\n}\n\nfunc (w *StickyWriter) Write(p []byte) (int, error) {\n\tif w.MaxBuffered > 0 {\n\t\tw.MaxBuffered--\n\t\tw.writeBuffer = append(w.writeBuffer, p...)\n\t\tif w.MaxBuffered != 0 {\n\t\t\treturn len(p), nil\n\t\t}\n\t\tw.MaxBuffered = 0\n\t\t_, err := w.rawWriter.Write(w.writeBuffer)\n\t\tw.writeBuffer = nil\n\t\treturn len(p), err\n\t}\n\treturn w.rawWriter.Write(p)\n}\n"
  },
  {
    "path": "common/io_test.go",
    "content": "package common\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"testing\"\n\n\t\"github.com/v2fly/v2ray-core/v4/common\"\n)\n\nfunc TestBufferedReader(t *testing.T) {\n\tpayload := [1024]byte{}\n\trand.Reader.Read(payload[:])\n\trawReader := bytes.NewBuffer(payload[:])\n\tr := RewindReader{\n\t\trawReader: rawReader,\n\t}\n\tr.SetBufferSize(2048)\n\tbuf1 := make([]byte, 512)\n\tbuf2 := make([]byte, 512)\n\tcommon.Must2(r.Read(buf1))\n\tr.Rewind()\n\tcommon.Must2(r.Read(buf2))\n\tif !bytes.Equal(buf1, buf2) {\n\t\tt.Fail()\n\t}\n\tbuf3 := make([]byte, 512)\n\tcommon.Must2(r.Read(buf3))\n\tif !bytes.Equal(buf3, payload[512:]) {\n\t\tt.Fail()\n\t}\n\tr.Rewind()\n\tbuf4 := make([]byte, 1024)\n\tcommon.Must2(r.Read(buf4))\n\tif !bytes.Equal(payload[:], buf4) {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "common/net.go",
    "content": "package common\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst (\n\tKiB = 1024\n\tMiB = KiB * 1024\n\tGiB = MiB * 1024\n)\n\nfunc HumanFriendlyTraffic(bytes uint64) string {\n\tif bytes <= KiB {\n\t\treturn fmt.Sprintf(\"%d B\", bytes)\n\t}\n\tif bytes <= MiB {\n\t\treturn fmt.Sprintf(\"%.2f KiB\", float32(bytes)/KiB)\n\t}\n\tif bytes <= GiB {\n\t\treturn fmt.Sprintf(\"%.2f MiB\", float32(bytes)/MiB)\n\t}\n\treturn fmt.Sprintf(\"%.2f GiB\", float32(bytes)/GiB)\n}\n\nfunc PickPort(network string, host string) int {\n\tswitch network {\n\tcase \"tcp\":\n\t\tfor retry := 0; retry < 16; retry++ {\n\t\t\tl, err := net.Listen(\"tcp\", host+\":0\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdefer l.Close()\n\t\t\t_, port, err := net.SplitHostPort(l.Addr().String())\n\t\t\tMust(err)\n\t\t\tp, err := strconv.ParseInt(port, 10, 32)\n\t\t\tMust(err)\n\t\t\treturn int(p)\n\t\t}\n\tcase \"udp\":\n\t\tfor retry := 0; retry < 16; retry++ {\n\t\t\tconn, err := net.ListenPacket(\"udp\", host+\":0\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t\t_, port, err := net.SplitHostPort(conn.LocalAddr().String())\n\t\t\tMust(err)\n\t\t\tp, err := strconv.ParseInt(port, 10, 32)\n\t\t\tMust(err)\n\t\t\treturn int(p)\n\t\t}\n\tdefault:\n\t\treturn 0\n\t}\n\treturn 0\n}\n\nfunc WriteAllBytes(writer io.Writer, payload []byte) error {\n\tfor len(payload) > 0 {\n\t\tn, err := writer.Write(payload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpayload = payload[n:]\n\t}\n\treturn nil\n}\n\nfunc WriteFile(path string, payload []byte) error {\n\twriter, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer writer.Close()\n\n\treturn WriteAllBytes(writer, payload)\n}\n\nfunc FetchHTTPContent(target string) ([]byte, error) {\n\tparsedTarget, err := url.Parse(target)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid URL: %s\", target)\n\t}\n\n\tif s := strings.ToLower(parsedTarget.Scheme); s != \"http\" && s != \"https\" {\n\t\treturn nil, fmt.Errorf(\"invalid scheme: %s\", parsedTarget.Scheme)\n\t}\n\n\tclient := &http.Client{\n\t\tTimeout: 30 * time.Second,\n\t}\n\tresp, err := client.Do(&http.Request{\n\t\tMethod: \"GET\",\n\t\tURL:    parsedTarget,\n\t\tClose:  true,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to dial to %s\", target)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected HTTP status code: %d\", resp.StatusCode)\n\t}\n\n\tcontent, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read HTTP response\")\n\t}\n\n\treturn content, nil\n}\n"
  },
  {
    "path": "common/sync.go",
    "content": "package common\n\n// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.\ntype Notifier struct {\n\tc chan struct{}\n}\n\n// NewNotifier creates a new Notifier.\nfunc NewNotifier() *Notifier {\n\treturn &Notifier{\n\t\tc: make(chan struct{}, 1),\n\t}\n}\n\n// Signal signals a change, usually by producer. This method never blocks.\nfunc (n *Notifier) Signal() {\n\tselect {\n\tcase n.c <- struct{}{}:\n\tdefault:\n\t}\n}\n\n// Wait returns a channel for waiting for changes. The returned channel never gets closed.\nfunc (n *Notifier) Wait() <-chan struct{} {\n\treturn n.c\n}\n"
  },
  {
    "path": "component/api.go",
    "content": "//go:build api || full\n// +build api full\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/api/control\"\n\t_ \"github.com/p4gefau1t/trojan-go/api/service\"\n)\n"
  },
  {
    "path": "component/base.go",
    "content": "package build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/log/golog\"\n\t_ \"github.com/p4gefau1t/trojan-go/statistic/memory\"\n\t_ \"github.com/p4gefau1t/trojan-go/version\"\n)\n"
  },
  {
    "path": "component/client.go",
    "content": "//go:build client || full || mini\n// +build client full mini\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/client\"\n)\n"
  },
  {
    "path": "component/custom.go",
    "content": "//go:build custom || full\n// +build custom full\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/custom\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/adapter\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/dokodemo\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/http\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/mux\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/router\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/shadowsocks\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/simplesocks\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/socks\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/tls\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/tproxy\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n\t_ \"github.com/p4gefau1t/trojan-go/tunnel/websocket\"\n)\n"
  },
  {
    "path": "component/forward.go",
    "content": "//go:build forward || full || mini\n// +build forward full mini\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/forward\"\n)\n"
  },
  {
    "path": "component/mysql.go",
    "content": "//go:build mysql || full || mini\n// +build mysql full mini\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/statistic/mysql\"\n)\n"
  },
  {
    "path": "component/nat.go",
    "content": "//go:build nat || full || mini\n// +build nat full mini\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/nat\"\n)\n"
  },
  {
    "path": "component/other.go",
    "content": "//go:build other || full\n// +build other full\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/easy\"\n\t_ \"github.com/p4gefau1t/trojan-go/url\"\n)\n"
  },
  {
    "path": "component/server.go",
    "content": "//go:build server || full || mini\n// +build server full mini\n\npackage build\n\nimport (\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/server\"\n)\n"
  },
  {
    "path": "config/config.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\t\"gopkg.in/yaml.v3\"\n)\n\nvar creators = make(map[string]Creator)\n\n// Creator creates default config struct for a module\ntype Creator func() interface{}\n\n// RegisterConfigCreator registers a config struct for parsing\nfunc RegisterConfigCreator(name string, creator Creator) {\n\tname += \"_CONFIG\"\n\tcreators[name] = creator\n}\n\nfunc parseJSON(data []byte) (map[string]interface{}, error) {\n\tresult := make(map[string]interface{})\n\tfor name, creator := range creators {\n\t\tconfig := creator()\n\t\tif err := json.Unmarshal(data, config); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult[name] = config\n\t}\n\treturn result, nil\n}\n\nfunc parseYAML(data []byte) (map[string]interface{}, error) {\n\tresult := make(map[string]interface{})\n\tfor name, creator := range creators {\n\t\tconfig := creator()\n\t\tif err := yaml.Unmarshal(data, config); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult[name] = config\n\t}\n\treturn result, nil\n}\n\nfunc WithJSONConfig(ctx context.Context, data []byte) (context.Context, error) {\n\tvar configs map[string]interface{}\n\tvar err error\n\tconfigs, err = parseJSON(data)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tfor name, config := range configs {\n\t\tctx = context.WithValue(ctx, name, config)\n\t}\n\treturn ctx, nil\n}\n\nfunc WithYAMLConfig(ctx context.Context, data []byte) (context.Context, error) {\n\tvar configs map[string]interface{}\n\tvar err error\n\tconfigs, err = parseYAML(data)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tfor name, config := range configs {\n\t\tctx = context.WithValue(ctx, name, config)\n\t}\n\treturn ctx, nil\n}\n\nfunc WithConfig(ctx context.Context, name string, cfg interface{}) context.Context {\n\tname += \"_CONFIG\"\n\treturn context.WithValue(ctx, name, cfg)\n}\n\n// FromContext extracts config from a context\nfunc FromContext(ctx context.Context, name string) interface{} {\n\treturn ctx.Value(name + \"_CONFIG\")\n}\n"
  },
  {
    "path": "config/config_test.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n)\n\ntype Foo struct {\n\tField1 string `json,yaml:\"field1\"`\n\tField2 bool   `json:\"field2\" yaml:\"field2\"`\n}\n\ntype TestStruct struct {\n\tField1 string `json,yaml:\"field1\"`\n\tField2 bool   `json,yaml:\"field2\"`\n\tField3 []Foo  `json,yaml:\"field3\"`\n}\n\nfunc creator() interface{} {\n\treturn &TestStruct{}\n}\n\nfunc TestJSONConfig(t *testing.T) {\n\tRegisterConfigCreator(\"test\", creator)\n\tdata := []byte(`\n\t{\n\t\t\"field1\": \"test1\",\n\t\t\"field2\": true,\n\t\t\"field3\": [\n\t\t\t{\n\t\t\t\t\"field1\": \"aaaa\",\n\t\t\t\t\"field2\": true\n\t\t\t}\n\t\t]\n\t}\n\t`)\n\tctx, err := WithJSONConfig(context.Background(), data)\n\tcommon.Must(err)\n\tc := FromContext(ctx, \"test\").(*TestStruct)\n\tif c.Field1 != \"test1\" || c.Field2 != true {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestYAMLConfig(t *testing.T) {\n\tRegisterConfigCreator(\"test\", creator)\n\tdata := []byte(`\nfield1: 012345678\nfield2: true\nfield3:\n  - field1: test\n    field2: true\n`)\n\tctx, err := WithYAMLConfig(context.Background(), data)\n\tcommon.Must(err)\n\tc := FromContext(ctx, \"test\").(*TestStruct)\n\tif c.Field1 != \"012345678\" || c.Field2 != true || c.Field3[0].Field1 != \"test\" {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "constant/constant.go",
    "content": "package constant\n\nvar (\n\tVersion = \"Custom Version\"\n\tCommit  = \"Unknown Git Commit ID\"\n)\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "public/\nthemes/"
  },
  {
    "path": "docs/Makefile",
    "content": ".PHONY: default clean hugo hugo-build\n\ndefault: hugo\n\nclean:\n\trm -rf public/\n\nhugo-build: clean hugo-themes\n\thugo --enableGitInfo --source .\n\nhugo:\n\thugo server --disableFastRender --enableGitInfo --watch --source .\n\t# hugo server -D\n\nhugo-themes:\n\trm -rf themes\n\tmkdir themes\n\tgit clone --depth=1 https://github.com/llc1123/hugo-theme-techdoc.git themes/hugo-theme-techdoc\n\trm -rf themes/hugo-theme-techdoc/.git\n"
  },
  {
    "path": "docs/archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .Name \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n\n"
  },
  {
    "path": "docs/config.toml",
    "content": "baseURL = \"https://p4gefau1t.github.io/trojan-go\"\n\nlanguageCode = \"zh-CN\"\ntitle = \"Trojan-Go Docs\"\ntheme = \"hugo-theme-techdoc\"\n\nhasCJKLanguage = true\nmetaDataFormat = \"yaml\"\n\ndefaultContentLanguage = \"zh\"\ndefaultContentLanguageInSubdir= true\nenableMissingTranslationPlaceholders = false\n\n[params]\n\n    # Source Code repository section\n    description = \"An unidentifiable mechanism that helps you bypass GFW.\"\n    github_repository = \"https://github.com/p4gefau1t/trojan-go\"\n\n    # Documentation repository section\n    # documentation repository (set edit link to documentation repository)\n    github_doc_repository = \"https://github.com/p4gefau1t/trojan-go/docs\"\n\n    # Analytic section\n    google_analytics_id = \"\" # Your Google Analytics tracking id\n    tag_manager_container_id = \"\" # Your Google Tag Manager container id\n    google_site_verification = \"\" # Your Google Site Verification for Search Console\n\n    # Open Graph and Twitter Cards settings section\n    # Open Graph settings for each page are set on the front matter.\n    # See https://gohugo.io/templates/internal/#open-graph\n    # See https://gohugo.io/templates/internal/#twitter-cards\n    title = \"Trojan-Go Docs\"\n    images = [\"images/og-image.png\"] # Open graph images are placed in `static/images`\n\n    # Theme settings section\n    # Theme color\n    # See color value reference https://developer.mozilla.org/en-US/docs/Web/CSS/color\n    custom_font_color = \"\"\n    custom_background_color = \"\"\n\n    # Documentation Menu section\n    # Menu style settings\n    menu_style = \"open-menu\" # \"open-menu\" or \"slide-menu\"\n\n    # Date format\n    dateformat = \"\" # default \"2 Jan 2006\"\n    # See the format reference https://gohugo.io/functions/format/#hugo-date-and-time-templating-reference\n\n    # path name excluded from documentation menu\n    menu_exclusion = [\n        \"archives\",\n        \"archive\",\n        \"blog\",\n        \"entry\",\n        \"post\",\n        \"posts\",\n    ]\n\n    # Algolia site search section\n    # See https://www.algolia.com/doc/\n    algolia_search_enable = true\n    algolia_indexName = \"hugo-demo-techdoc\"\n    algolia_appId = \"7W4SAN4PLK\"\n    algolia_apiKey = \"cbf12a63ff72d9c5dc0c10c195cf9128\" # Search-Only API Key\n\n# Global menu section\n# See https://gohugo.io/content-management/menus/\n[menu]\n    [[menu.main]]\n        name = \"Home\"\n        url = \"/\"\n        weight = 1\n\n    [[menu.main]]\n        name = \"GitHub\"\n        url = \"https://github.com/p4gefau1t\"\n        weight = 2\n\n# Markup configure section\n# See https://gohugo.io/getting-started/configuration-markup/\n[markup]\n    defaultMarkdownHandler = \"goldmark\"\n    [markup.goldmark.renderer]\n        unsafe= true\n    [markup.tableOfContents]\n        startLevel = 2\n        endLevel = 4\n        ordered = false\n\n# Algolia Search configure section\n[outputFormats.Algolia]\n    baseName = \"algolia\"\n    isPlainText = true\n    mediaType = \"application/json\"\n    notAlternative = true\n\n[params.algolia]\n    vars = [\n        \"title\",\n        \"summary\",\n        \"content\",\n        \"date\",\n        \"publishdate\",\n        \"description\",\n        \"permalink\",\n        \"keywords\",\n        \"lastmod\",\n    ]\n    params = [\n        \"tags\",\n        \"categories\",\n    ]\n"
  },
  {
    "path": "docs/content/_index.md",
    "content": "---\ntitle: \"简介\"\ndraft: false\nweight: 10\n---\n\n# Trojan-Go\n\n这里是Trojan-Go的文档，你可以在左侧的导航栏中找到一些使用技巧，以及完整的配置文件说明。\n\nTrojan-Go是使用Go语言实现的完整的Trojan代理，和Trojan协议以及原版的配置文件格式兼容。支持并且兼容Trojan-GFW版本的绝大多数功能，并扩展了更多的实用功能。\n\nTrojan-Go的的首要目标是保障传输安全性和隐蔽性。在此前提下，尽可能提升传输性能和易用性。\n\n如果你遇到配置和使用方面的问题，发现了软件Bug，或是有更好的想法，欢迎加入Trojan-Go的[Telegram交流反馈群](https://t.me/trojan_go_chat)。\n\n----\n\n> Across the Great Wall, we can reach every corner in the world.\n>\n> (越过长城，走向世界。)\n"
  },
  {
    "path": "docs/content/advance/_index.md",
    "content": "---\ntitle: \"高级配置\"\ndraft: false\nweight: 30\n---\n\n这一部分内容将介绍更复杂的Trojan-Go配置方法。对于与Trojan原版不兼容的特性，将在小节中明确标明。\n"
  },
  {
    "path": "docs/content/advance/aead.md",
    "content": "---\ntitle: \"使用Shadowsocks AEAD进行二次加密\"\ndraft: false\nweight: 8\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan协议本身无加密，其安全性依赖于下层的TLS。在一般情况下，TLS安全性很好，并不需要再次加密Trojan流量。但是，某些场景下，你可能无法保证TLS隧道的安全性：\n\n- 你使用了Websocket，经过不可信的CDN进行中转（如国内CDN）\n\n- 你与服务器的连接遭到了GFW针对TLS的中间人攻击\n\n- 你的证书失效，无法验证证书有效性\n\n- 你使用了无法保证密码学安全的可插拔传输层\n\n等等。\n\nTrojan-Go支持使用Shadowsocks AEAD对Trojan-Go进行加密。其本质是在Trojan协议下方加上一层Shadowsocks AEAD加密。服务端和客户端必须同时开启，且密码和加密方式必须一致，否则无法进行通讯。\n\n要开启AEAD加密，只需添加一个```shadowsocks```选项：\n\n```json\n...\n\"shadowsocks\": {\n    \"enabled\": true,\n    \"method\": \"AES-128-GCM\",\n    \"password\": \"1234567890\"\n}\n```\n\n```method```如果省略，则默认使用AES-128-GCM。更多信息，参见“完整的配置文件”一节。\n"
  },
  {
    "path": "docs/content/advance/api.md",
    "content": "---\ntitle: \"使用API动态管理用户\"\ndraft: false\nweight: 10\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan-Go使用gRPC提供了一组API，API支持以下功能：\n\n- 用户信息增删改查\n\n- 流量统计\n\n- 速度统计\n\n- IP连接数统计\n\nTrojan-Go本身集成了API控制功能，也即可以使用一个Trojan-Go实例控制另一个Trojan-Go服务器。\n\n你需要在你需要被控制的服务端配置添加API设置，例如：\n\n```json\n{\n    ...\n    \"api\": {\n        \"enabled\": true,\n        \"api_addr\": \"127.0.0.1\",\n        \"api_port\": 10000,\n    }\n}\n```\n\n然后启动Trojan-Go服务器\n\n```shell\n./trojan-go -config ./server.json\n```\n\n然后可以使用另一个Trojan-Go连接该服务器进行管理，基本命令格式为\n\n```shell\n./trojan-go -api-addr SERVER_API_ADDRESS -api COMMAND\n```\n\n其中```SERVER_API_ADDRESS```为API地址和端口，如127.0.0.1:10000\n\n```COMMAND```为API命令，合法的命令有\n\n- list 列出所有用户\n\n- get 获取某个用户信息\n\n- set 设置某个用户信息（添加/删除/修改）\n\n下面是一些例子\n\n1. 列出所有用户信息\n\n    ```shell\n    ./trojan-go -api-addr 127.0.0.1:10000 -api list\n    ```\n\n    所有的用户信息将以json的形式导出，信息包括在线IP数量，实时速度，总上传和下载流量等。下面是一个返回的结果的例子\n\n    ```json\n    [{\"user\":{\"hash\":\"d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01\"},\"status\":{\"traffic_total\":{\"upload_traffic\":36393,\"download_traffic\":186478},\"speed_current\":{\"upload_speed\":25210,\"download_speed\":72384},\"speed_limit\":{\"upload_speed\":5242880,\"download_speed\":5242880},\"ip_limit\":50}}]\n    ```\n\n    流量单位均为字节。\n\n2. 获取一个用户信息\n\n    可以使用 -target-password 指定密码，也可以使用 -target-hash 指定目标用户密码的SHA224散列值。格式和list命令相同\n\n    ```shell\n    ./trojan-go -api-addr 127.0.0.1:10000 -api get -target-password password\n    ```\n\n    或者\n\n    ```shell\n    ./trojan-go -api-addr 127.0.0.1:10000 -api get -target-hash d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01\n    ```\n\n    以上两条命令等价，下面的例子统一使用明文密码的方式，散列值指定某个用户的方式以此类推。\n\n    该用户信息将以json的形式导出，格式与list命令类似。下面是一个返回的结果的例子\n\n    ```json\n    {\"user\":{\"hash\":\"d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01\"},\"status\":{\"traffic_total\":{\"upload_traffic\":36393,\"download_traffic\":186478},\"speed_current\":{\"upload_speed\":25210,\"download_speed\":72384},\"speed_limit\":{\"upload_speed\":5242880,\"download_speed\":5242880},\"ip_limit\":50}}\n    ```\n\n3. 添加一个用户信息\n\n    ```shell\n    ./trojan-go -api-addr 127.0.0.1:10000 -api set -add-profile -target-password password\n    ```\n\n4. 删除一个用户信息\n\n    ```shell\n    ./trojan-go -api-addr 127.0.0.1:10000 -api set -delete-profile -target-password password\n    ```\n\n5. 修改一个用户信息\n\n    ```shell\n    ./trojan-go -api-addr 127.0.0.1:10000 -api set -modify-profile -target-password password \\\n        -ip-limit 3 \\\n        -upload-speed-limit 5242880 \\\n        -download-speed-limit 5242880\n    ```\n\n    这个命令将密码为password的用户上传和下载速度限制为5MiB/s，同时连接的IP数量限制为3个，注意这里5242880的单位是字节。如果填写0或者负数，则表示不进行限制。\n"
  },
  {
    "path": "docs/content/advance/customize-protocol-stack.md",
    "content": "---\ntitle: \"自定义协议栈\"\ndraft: false\nweight: 8\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan-Go允许高级用户自定义协议栈。在自定义模式下，Trojan-Go将放弃对协议栈的控制，允许用户操作底层协议栈组合。例如\n\n- 在一层TLS上再建立一层或更多层TLS加密\n\n- 使用TLS传输Websocket流量，在Websocket层上再建立一层TLS，在第二层TLS上再使用Shadowsocks AEAD进行加密传输\n\n- 在TCP连接上，使用Shadowsocks的AEAD加密传输Trojan协议\n\n- 将一个入站Trojan的TLS流量解包后重新用TLS包装为新的出站Trojan流量\n\n等等。\n\n**如果你不了解网络相关知识，请不要尝试使用这个功能。不正确的配置可能导致Trojan-Go无法正常工作，或是导致性能和安全性方面的问题。**\n\nTrojan-Go将所有协议抽象为隧道，每个隧道可能提供客户端，负责发送；也可能提供服务端，负责接受；或者两者皆提供。自定义协议栈即自定义隧道的堆叠方式。\n\n### 在继续配置之前，请先阅读开发指南中“基本介绍”一节，确保已经理解Trojan-Go运作方式\n\n下面是Trojan-Go支持的隧道和他们的属性:\n\n| 隧道        | 需要下层提供流 | 需要下层提供包 | 向上层提供流 | 向上层提供包 | 可以作为入站 | 可以作为出站 |\n| ----------- | -------------- | -------------- | ------------ | ------------ | ------------ | ------------ |\n| transport   | n              | n              | y            | y            | y            | y            |\n| dokodemo    | n              | n              | y            | y            | y            | n            |\n| tproxy      | n              | n              | y            | y            | y            | n            |\n| tls         | y              | n              | y            | n            | y            | y            |\n| trojan      | y              | n              | y            | y            | y            | y            |\n| mux         | y              | n              | y            | n            | y            | y            |\n| simplesocks | y              | n              | y            | y            | y            | y            |\n| shadowsocks | y              | n              | y            | n            | y            | y            |\n| websocket   | y              | n              | y            | n            | y            | y            |\n| freedom     | n              | n              | y            | y            | n            | y            |\n| socks       | y              | y              | y            | y            | y            | n            |\n| http        | y              | n              | y            | n            | y            | n            |\n| router      | y              | y              | y            | y            | n            | y            |\n| adapter     | n              | n              | y            | y            | y            | n            |\n\n自定义协议栈的工作方式是，定义树/链上节点并分别它们起名（tag）并添加配置，然后使用tag组成的有向路径，描述这棵树/链。例如，对于一个典型的Trojan-Go服务器，可以如此描述：\n\n入站，一共两条路径，tls节点将自动识别trojan和websocket流量并进行分发\n\n- transport->tls->trojan\n\n- transport->tls->websocket->trojan\n\n出站，只能有一条路径\n\n- router->freedom\n\n对于入站，从根开始描述多条路径，组成一棵**多叉树**（也可以退化为一条链），不满足树性质的图将导致未定义的行为；对于出站，必须描述一条**链**。\n\n每条路径必须满足这样的条件：\n\n1. 必须以**不需要下层提供流或包**的隧道开始(transport/adapter/tproxy/dokodemo等)\n\n2. 必须以**能向上层提供包和流**的隧道终止(trojan/simplesocks/freedom等)\n\n3. 出站单链上，隧道必须都可作为出站。入站的所有路径上，隧道必须都可作为入站。\n\n要启用自定义协议栈，将```run_type```指定为custom，此时除```inbound```和```outbound```之外的其他选项将被忽略。\n\n下面是一个例子，你可以在此基础上插入或减少协议节点。配置文件为简明起见，使用YAML进行配置，你也可以使用JSON来配置，除格式不同之外，效果是等价的。\n\n客户端 client.yaml\n\n```yaml\nrun-type: custom\n\ninbound:\n  node:\n    - protocol: adapter\n      tag: adapter\n      config:\n        local-addr: 127.0.0.1\n        local-port: 1080\n    - protocol: socks\n      tag: socks\n      config:\n        local-addr: 127.0.0.1\n        local-port: 1080\n  path:\n    -\n      - adapter\n      - socks\n\noutbound:\n  node:\n    - protocol: transport\n      tag: transport\n      config:\n        remote-addr: you_server\n        remote-port: 443\n\n    - protocol: tls\n      tag: tls\n      config:\n        ssl:\n          sni: localhost\n          key: server.key\n          cert: server.crt\n\n    - protocol: trojan\n      tag: trojan\n      config:\n        password:\n          - 12345678\n\n  path:\n    -\n      - transport\n      - tls\n      - trojan\n\n```\n\n服务端 server.yaml\n\n```yaml\nrun-type: custom\n\ninbound:\n  node:\n    - protocol: websocket\n      tag: websocket\n      config:\n        websocket:\n            enabled: true\n            hostname: example.com\n            path: /ws\n\n    - protocol: transport\n      tag: transport\n      config:\n        local-addr: 0.0.0.0\n        local-port: 443\n        remote-addr: 127.0.0.1\n        remote-port: 80\n\n    - protocol: tls\n      tag: tls\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: 80\n        ssl:\n          sni: localhost\n          key: server.key\n          cert: server.crt\n\n    - protocol: trojan\n      tag: trojan1\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: 80\n        password:\n          - 12345678\n\n    - protocol: trojan\n      tag: trojan2\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: 80\n        password:\n          - 87654321\n\n  path:\n    -\n      - transport\n      - tls\n      - trojan1\n    -\n      - transport\n      - tls\n      - websocket\n      - trojan2\n\noutbound:\n  node:\n    - protocol: freedom\n      tag: freedom\n\n  path:\n    -\n      - freedom\n```\n"
  },
  {
    "path": "docs/content/advance/forward.md",
    "content": "---\ntitle: \"隧道和反向代理\"\ndraft: false\nweight: 5\n---\n\n你可以使用Trojan-Go建立隧道。一个典型的应用是，使用Trojan-Go在本地建立一个无污染的DNS服务器，下面是一个配置的例子\n\n```json\n{\n    \"run_type\": \"forward\",\n    \"local_addr\": \"127.0.0.1\",\n    \"local_port\": 53,\n    \"remote_addr\": \"your_awesome_server\",\n    \"remote_port\": 443,\n    \"target_addr\": \"8.8.8.8\",\n    \"target_port\": 53,\n    \"password\": [\n        \"your_awesome_password\"\n    ]\n}\n```\n\nforward本质上是一个客户端，不过你需要填入```target_addr```和```target_port```字段，指明反向代理的目标。\n\n使用这份配置文件后，本地53的TCP和UDP端口将被监听，所有的向本地53端口发送的TCP或者UDP数据，都会通过TLS隧道转发给远端服务器your_awesome_server，远端服务器得到回应后，数据会通过隧道返回到本地53端口。 也就是说，你可以将127.0.0.1当作一个DNS服务器，本地查询的结果和远端服务器查询的结果是一致的。你可以使用这个配置避开DNS污染。\n\n同样的原理，你可以在本地搭建一个Google的镜像\n\n```json\n{\n    \"run_type\": \"forward\",\n    \"local_addr\": \"127.0.0.1\",\n    \"local_port\": 443,\n    \"remote_addr\": \"your_awesome_server\",\n    \"remote_port\": 443,\n    \"target_addr\": \"www.google.com\",\n    \"target_port\": 443,\n    \"password\": [\n        \"your_awesome_password\"\n    ]\n}\n```\n\n访问```https://127.0.0.1```即可访问谷歌主页，但是注意这里由于谷歌服务器提供的https证书是google.com的证书，而当前域名为127.0.0.1，因此浏览器会引发一个证书错误的警告。\n\n类似的，可以使用forward传输其他代理协议。例如，使用Trojan-Go传输shadowsocks的流量，远端主机开启ss服务器，监听127.0.0.1:12345，并且远端服务器在443端口开启了正常的Trojan-Go服务器。你可以如此指定配置\n\n```json\n{\n    \"run_type\": \"forward\",\n    \"local_addr\": \"0.0.0.0\",\n    \"local_port\": 54321,\n    \"remote_addr\": \"your_awesome_server\",\n    \"remote_port\": 443,\n    \"target_addr\": \"www.google.com\",\n    \"target_port\": 12345,\n    \"password\": [\n        \"your_awesome_password\"\n    ]\n}\n```\n\n此后，任何连接本机54321端口的TCP/UDP连接，等同于连接远端12345端口。你可以使用shadowsocks客户端连接本地的54321端口，ss流量将使用trojan的隧道连接传输至远端12345端口的ss服务器。\n"
  },
  {
    "path": "docs/content/advance/mux.md",
    "content": "---\ntitle: \"启用多路复用提升网络并发性能\"\ndraft: false\nweight: 1\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan-Go支持使用多路复用提升网络并发性能。\n\nTrojan协议基于TLS。在一个TLS安全连接建立之前，连接双方需要进行密钥协商和交换等步骤确保后续通讯的安全性。这个过程即为TLS握手。\n\n目前GFW对于TLS握手存在审查和干扰，同时由于出口网络拥塞的原因，普通的线路完成TLS握手通常需要将近一秒甚至更长的时间。这可能导致浏览网页和观看视频的延迟提高。\n\nTrojan-Go使用多路复用的方式解决这一问题。每个建立的TLS连接将承载多个TCP连接。当新的代理请求到来时，不需要和服务器握手发起一个新的TLS连接，而是尽可能重复使用已有的TLS连接。以此减少频繁TLS握手和TCP握手的带来的延迟。\n\n启用多路复用不会增加你的链路速度（甚至会有所减少），而且可能会增加服务器和客户端的计算负担。可以粗略地理解为，多路复用牺牲网络吞吐和CPU功耗，换取更低的延迟。在高并发的情景下，如浏览含有大量图片的网页时，或者发送大量UDP请求时，可以提升使用体验。\n\n激活```mux```模块，只需要将```mux```选项中```enabled```字段设为true即可，下面是一个客户端的例子\n\n```json\n...\n\"mux\" :{\n    \"enabled\": true\n}\n```\n\n只需要配置客户端即可，服务端可以自动适配，无需配置```mux```选项。\n\n完整的mux配置如下\n\n```json\n\"mux\": {\n    \"enabled\": false,\n    \"concurrency\": 8,\n    \"idle_timeout\": 60\n}\n```\n\n```concurrency```是每个TLS连接最多可以承载的TCP连接数。这个数值越大，每个TLS连接被复用的程度就更高，握手导致的延迟越低。但服务器和客户端的计算负担也会越大，这有可能使你的网络吞吐量降低。如果你的线路的TLS握手极端缓慢，你可以将这个数值设置为-1，Trojan-Go将只进行一次TLS握手，只使用唯一的一条TLS连接进行传输。\n\n```idle_timeout```指的是每个TLS连接空闲多长时间后关闭。设置超时时间，**可能**有助于减少不必要的长连接存活确认(Keep Alive)流量传输引发GFW的探测。你可以将这个数值设置为-1，TLS连接在空闲时将被立即关闭。\n"
  },
  {
    "path": "docs/content/advance/nat.md",
    "content": "---\ntitle: \"透明代理\"\ndraft: false\nweight: 11\n---\n\n### 注意，Trojan不完全支持这个特性（UDP）\n\nTrojan-Go支持基于tproxy的透明TCP/UDP代理。\n\n要开启透明代理模式，将一份正确的客户端配置（配置方式参见基本配置部分）其中的```run_type```修改为```nat```，并按照需求修改本地监听端口即可。\n\n之后需要添加iptables规则。这里假定你的网关具有两个网卡，下面这份配置将其中一个网卡（局域网）的入站包转交给Trojan-Go，由Trojan-Go通过隧道，通过另一个网卡（互联网）发送到远端Trojan-Go服务器。你需要将下面的```$SERVER_IP```，```$TROJAN_GO_PORT```，```$INTERFACE```替换为自己的配置。\n\n```shell\n# 新建TROJAN_GO链\niptables -t mangle -N TROJAN_GO\n\n# 绕过Trojan-Go服务器地址\niptables -t mangle -A TROJAN_GO -d $SERVER_IP -j RETURN\n\n# 绕过私有地址\niptables -t mangle -A TROJAN_GO -d 0.0.0.0/8 -j RETURN\niptables -t mangle -A TROJAN_GO -d 10.0.0.0/8 -j RETURN\niptables -t mangle -A TROJAN_GO -d 127.0.0.0/8 -j RETURN\niptables -t mangle -A TROJAN_GO -d 169.254.0.0/16 -j RETURN\niptables -t mangle -A TROJAN_GO -d 172.16.0.0/12 -j RETURN\niptables -t mangle -A TROJAN_GO -d 192.168.0.0/16 -j RETURN\niptables -t mangle -A TROJAN_GO -d 224.0.0.0/4 -j RETURN\niptables -t mangle -A TROJAN_GO -d 240.0.0.0/4 -j RETURN\n\n# 未命中上文的规则的包，打上标记\niptables -t mangle -A TROJAN_GO -j TPROXY -p tcp --on-port $TROJAN_GO_PORT --tproxy-mark 0x01/0x01\niptables -t mangle -A TROJAN_GO -j TPROXY -p udp --on-port $TROJAN_GO_PORT --tproxy-mark 0x01/0x01\n\n# 从$INTERFACE网卡流入的所有TCP/UDP包，跳转TROJAN_GO链\niptables -t mangle -A PREROUTING -p tcp -i $INTERFACE -j TROJAN_GO\niptables -t mangle -A PREROUTING -p udp -i $INTERFACE -j TROJAN_GO\n\n# 添加路由，打上标记的包重新进入本地回环\nip route add local default dev lo table 100\nip rule add fwmark 1 lookup 100\n```\n\n配置完成后**以root权限启动**Trojan-Go客户端：\n\n```shell\nsudo trojan-go\n```\n"
  },
  {
    "path": "docs/content/advance/nginx-relay.md",
    "content": "---\ntitle: \"一种基于SNI代理的多路径分流中继方案\"\ndraft: false\nweight: 6\n---\n\n## 前言\n\nTrojan 是一种通过 TLS 封装后进行加密数据传输的工具，利用其 TLS 的特性，我们可以通过 SNI 代理实现在同一主机端口上实现不同路径的分流中继。\n\n## 所需工具及其他准备\n\n- 中继机：nginx 1.11.5 及以上版本\n- 落地机：trojan 服务端（无版本要求）\n\n## 配置方法\n\n为了便于说明，这里使用了两台中继主机和两台落地主机。  \n四台主机所绑定的域名分别为 (a/b/c/d).example.com。如图所示。  \n相互连接一共4条路径。分别为 a-c、a-d、b-c、b-d 。\n\n```text\n                        +-----------------+           +--------------------+\n                        |                 +---------->+                    |\n                        |   VPS RELAY A   |           |   VPS ENDPOINT C   |\n                  +---->+                 |   +------>+                    |\n                  |     |  a.example.com  |   |       |   c.example.com    |\n                  |     |                 +------+    |                    |\n  +----------+    |     +-----------------+   |  |    +--------------------+\n  |          |    |                           |  |\n  |  client  +----+                           |  |\n  |          |    |                           |  |\n  +----------+    |     +-----------------+   |  |    +--------------------+\n                  |     |                 |   |  |    |                    |\n                  |     |   VPS RELAY B   |   |  +--->+   VPS ENDPOINT D   |\n                  +---->+                 +---+       |                    |\n                        |  b.example.com  |           |   d.example.com    |\n                        |                 +---------->+                    |\n                        +-----------------+           +--------------------+\n```\n\n### 配置路径域名和相应的证书\n\n首先我们需要将每条路径分别分配一个域名，并使其解析到分别的入口主机上。  \n\n```text\na-c.example.com CNAME a.example.com  \na-d.example.com CNAME a.example.com  \nb-c.example.com CNAME b.example.com  \nb-d.example.com CNAME b.example.com\n```\n\n然后我们需要在落地主机上部署所有目标路径的证书  \n由于解析记录和主机 IP 不符，HTTP 验证无法通过。这里建议使用 DNS 验证方式签发证书。  \n具体 DNS 验证插件需要根据您的域名 DNS 解析托管商选择，这里使用了 AWS Route 53。  \n\n```shell\ncertbot certonly --dns-route53 -d a-c.example.com -d b-c.example.com // 主机 C 上\ncertbot certonly --dns-route53 -d a-d.example.com -d b-d.example.com // 主机 D 上\n```\n\n### 配置 SNI 代理\n\n这里我们使用 nginx 的 ssl_preread 模块实现 SNI 代理。  \n请安装 nginx 后按如下方法修过 nginx.conf 文件。  \n注意这里不是 HTTP 服务，请不要写在虚拟主机的配置中。\n\n这里给出主机 A 上的对应配置，主机 B 同理。\n\n```nginx\nstream {\n  map $ssl_preread_server_name $name {\n    a-c.example.com   c.example.com;  # 将 a-c 路径流量转发至主机 C\n    a-d.example.com   d.example.com;  # 将 a-d 路径流量转发至主机 D\n\n    # 如果此主机上需要配置其他占用 443 端口的服务 （例如 web 服务和 Trojan 服务）\n    # 请使那些服务监听在其他本地端口（这里使用了 4000）\n    # 所有不匹配上方 SNI 的 TLS 请求都会转发至此端口，如不需要可以删除此行\n    default           localhost:4000;\n  }\n\n  server {\n    listen      443; # 监听 443 端口\n    proxy_pass  $name;\n    ssl_preread on;\n  }\n}\n```\n\n### 配置落地 Trojan 服务\n\n在之前的配置中我们使用了一个证书签发了所有目标路径的域名，所以这里我们可以使用一个 Trojan 服务端处理所有目标路径的请求。  \nTrojan 的配置和通常配置方法无异，这里还是提供一份例子。无关的配置已省略。\n\n```json\n{\n    \"run_type\": \"server\",\n    \"local_addr\": \"0.0.0.0\",\n    \"local_port\": 443,\n    \"ssl\": {\n        \"cert\": \"/path/to/certificate.crt\",\n        \"key\": \"/path/to/private.key\",\n    }\n    ...\n}\n```\n\n小提示：如果需要在落地主机上对不同路径分别使用独立的 Trojan 服务端（比如需要分别接入各自的计费服务），可以在落地机上再配置一个 SNI 代理，并分别转发至本地不同的 Trojan 服务端监听端口。由于配置与前面所提到的过程基本相同，这里便不再赘述。\n\n## 总结\n\n通过以上介绍的配置方法，我们可以在单一端口上实现多入口多出口多级中继的 Trojan 流量转发。  \n对于多级中继，只需在中间节点上按相同思路配置 SNI 代理即可。\n"
  },
  {
    "path": "docs/content/advance/plugin.md",
    "content": "---\ntitle: \"使用Shadowsocks插件/可插拔传输层\"\ndraft: false\nweight: 7\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan-Go支持可插拔的传输层。原则上，Trojan-Go可以使用任何有TCP隧道功能的软件作为传输层，如v2ray、shadowsocks、kcp等。同时，Trojan-Go也兼容Shadowsocks的SIP003插件标准，如GoQuiet，v2ray-plugin等。也可以使用Tor的传输层插件，如obfs4，meek等。\n\n你可以使用这些插件，替换Trojan-Go的TLS传输层。\n\n开启可插拔传输层插件后，Trojan-Go客户端将会把**流量明文**直接传输给客户端本地的插件处理。由客户端插件负责进行加密和混淆，并将流量传输给服务端的插件。服务端的插件接收到流量，进行解密和解析，将**流量明文**传输给服务端本地的Trojan-Go服务端。\n\n你可以使用任何插件对流量进行加密和混淆，只需添加\"transport_plugin\"选项，并指定插件的可执行文件的路径，并做好配置即可。\n\n我们更建议**自行设计协议并开发相应插件**。因为目前现有的所有插件无法对接Trojan-Go的对抗主动探测的特性，而且部分插件并无加密能力。如果你对开发插件有兴趣，欢迎在\"实现细节和开发指南\"一节中查看插件设计的指南。\n\n例如，可以使用符合SIP003标准的v2ray-plugin，下面是一个例子:\n\n**这个配置中使用了websocket明文传输未经加密的trojan协议，存在安全隐患。这个配置仅作为演示使用。**\n\n**不要在任何情况下使用这个配置穿透GFW。**\n\n服务端配置：\n\n```json\n...（省略）\n\"transport_plugin\": {\n    \"enabled\": true,\n    \"type\": \"shadowsocks\",\n    \"command\": \"./v2ray-plugin\",\n    \"arg\": [\"-server\", \"-host\", \"www.baidu.com\"]\n}\n```\n\n客户端配置：\n\n```json\n...（省略）\n\"transport_plugin\": {\n    \"enabled\": true,\n    \"type\": \"shadowsocks\",\n    \"command\": \"./v2ray-plugin\",\n    \"arg\": [\"-host\", \"www.baidu.com\"]\n}\n```\n\n注意，v2ray-plugin插件需要指定```-server```参数来区分客户端和服务端。更多关于该插件详细的说明，参考v2ray-plugin的文档。\n\n启动Trojan-Go后，你可以看到v2ray-plugin启动的输出。插件将把流量伪装为Websocket流量并传输。\n\n非SIP003标准的插件可能需要不同的配置，你可以指定```type```为\"other\"，并自行指定插件地址，插件启动参数、环境变量。\n"
  },
  {
    "path": "docs/content/advance/router.md",
    "content": "---\ntitle: \"国内直连和广告屏蔽\"\ndraft: false\nweight: 3\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan-Go内建的路由模块可以帮助你实现国内直连，即客户端对于国内网站不经过代理，直接连接。\n\n路由模块在客户端可以配置三种策略(```bypass```, ```proxy```, ```block```)，在服务端只可使用```block```策略。\n\n下面是一个例子\n\n```json\n{\n    \"run_type\": \"client\",\n    \"local_addr\": \"127.0.0.1\",\n    \"local_port\": 1080,\n    \"remote_addr\": \"your_server\",\n    \"remote_port\": 443,\n    \"password\": [\n        \"your_password\"\n    ],\n    \"ssl\": {\n        \"sni\": \"your-domain-name.com\"\n    },\n    \"mux\" :{\n        \"enabled\": true\n    },\n    \"router\":{\n        \"enabled\": true,\n        \"bypass\": [\n            \"geoip:cn\",\n            \"geoip:private\",\n            \"geosite:cn\",\n            \"geosite:geolocation-cn\"\n        ],\n        \"block\": [\n            \"geosite:category-ads\"\n        ],\n        \"proxy\": [\n            \"geosite:geolocation-!cn\"\n        ]\n    }\n}\n```\n\n这个配置文件激活了router模块，使用的是白名单的模式，当匹配到中国大陆或者局域网的IP/域名时，直接连接。如果是广告运营商的域名，则直接断开连接。\n\n所需要的数据库```geoip.dat```和```geosite.dat```已经包含在release的压缩包中，直接使用即可。它们来自V2Ray的[domain-list-community](https://github.com/v2fly/domain-list-community)和[geoip](https://github.com/v2fly/geoip)。\n\n你可以使用如```geosite:cn```、```geosite:geolocation-!cn```、```geosite:category-ads-all```、```geosite:bilibili```的形式来指定某一类域名，所有可用的tag可以在[domain-list-community](https://github.com/v2fly/domain-list-community)仓库的[```data```](https://github.com/v2fly/domain-list-community/tree/master/data)目录中找到。```geosite.dat``` 更详细使用说明，参考[V2Ray/Routing路由#预定义域名列表](https://www.v2fly.org/config/routing.html#预定义域名列表)。\n\n你可以使用如```geoip:cn```、```geoip:hk```、```geoip:us```、```geoip:private```的形式来指定某一类IP。`geoip:private`为特殊项，囊括了内网IP和保留IP，其余类别囊括了各个国家/地区的IP地址段。各国家/地区的代号参考[维基百科](https://zh.wikipedia.org/wiki/%E5%9C%8B%E5%AE%B6%E5%9C%B0%E5%8D%80%E4%BB%A3%E7%A2%BC)。\n\n你也可以配置自己的路由规则。例如，想要屏蔽所有example.com域名以及其子域名，以及192.168.1.0/24，添加下面的规则。\n\n```json\n\"block\": [\n    \"domain:example.com\",\n    \"cidr:192.168.1.0/24\"\n]\n```\n\n支持的格式有\n\n- \"domain:\"，子域名匹配\n\n- \"full:\"，完全域名匹配\n\n- \"regexp:\"，正则表达式匹配\n\n- \"cidr:\"，CIDR匹配\n\n更详细的说明参考\"完整的配置文件\"一节。\n"
  },
  {
    "path": "docs/content/advance/websocket.md",
    "content": "---\ntitle: \"使用Websocket进行CDN转发和抵抗中间人攻击\"\ndraft: false\nweight: 2\n---\n\n### 注意，Trojan不支持这个特性\n\nTrojan-Go支持使用TLS+Websocket承载Trojan协议，使得利用CDN进行流量中转成为可能。\n\nTrojan协议本身不带加密，安全性依赖外层的TLS。但流量一旦经过CDN，TLS对CDN是透明的。其服务提供者可以对TLS的明文内容进行审查。**如果你使用的是不可信任的CDN（任何在中国大陆注册备案的CDN服务均应被视为不可信任），请务必开启Shadowsocks AEAD对Webosocket流量进行加密，以避免遭到识别和审查。**\n\n服务器和客户端配置文件中同时添加websocket选项，并将其```enabled```字段设置为true，并填写```path```字段和```host```字段即可启用Websocket支持。下面是一个完整的Websocket选项:\n\n```json\n\"websocket\": {\n    \"enabled\": true,\n    \"path\": \"/your-websocket-path\",\n    \"host\": \"example.com\"\n}\n```\n\n```host```是主机名，一般填写域名。客户端```host```是可选的，填写你的域名。如果留空，将会使用```remote_addr```填充。\n\n```path```指的是websocket所在的URL路径，必须以斜杠(\"/\")开始。路径并无特别要求，满足URL基本格式即可，但要保证客户端和服务端的```path```一致。```path```应当选择较长的字符串，以避免遭到GFW直接的主动探测。\n\n客户端的```host```将包含在Websocket的握手HTTP请求中，发送给CDN服务器，必须有效；服务端和客户端```path```必须一致，否则Websocket握手无法进行。\n\n下面是一个客户端配置文件的例子\n\n```json\n{\n    \"run_type\": \"client\",\n    \"local_addr\": \"127.0.0.1\",\n    \"local_port\": 1080,\n    \"remote_addr\": \"www.your_awesome_domain_name.com\",\n    \"remote_port\": 443,\n    \"password\": [\n        \"your_password\"\n    ],\n    \"websocket\": {\n        \"enabled\": true,\n        \"path\": \"/your-websocket-path\",\n        \"host\": \"example.com\"\n    },\n    \"shadowsocks\": {\n        \"enabled\": true,\n        \"password\": \"12345678\"\n    }\n}\n```\n"
  },
  {
    "path": "docs/content/basic/_index.md",
    "content": "---\ntitle: \"基本配置\"\ndraft: false\nweight: 20\n---\n\n这一部分内容将介绍如何配置基本的Trojan-Go代理服务器和客户端。\n"
  },
  {
    "path": "docs/content/basic/config.md",
    "content": "---\ntitle: \"正确配置Trojan-Go\"\ndraft: false\nweight: 22\n---\n\n下面将介绍如何正确配置Trojan-Go以完全隐藏你的代理节点特征。\n\n在开始之前，你需要\n\n- 一个服务器，且未被GFW封锁\n\n- 一个域名，可以使用免费的域名服务，如.tk等\n\n- Trojan-Go，可以从release页面下载\n\n- 证书和密钥，可以从letsencrypt等机构免费申请签发\n\n### 服务端配置\n\n我们的目标是，使得你的服务器和正常的HTTPS网站表现相同。\n\n首先你需要一个HTTP服务器，可以使用nginx，apache，caddy等配置一个本地HTTP服务器，也可以使用别人的HTTP服务器。HTTP服务器的作用是，当GFW主动探测时，向它展示一个完全正常的Web页面。\n\n**你需要在```remote_addr```和```remote_port```指定这个HTTP服务器的地址。```remote_addr```可以是IP或者域名。Trojan-Go将会测试这个HTTP服务器是否工作正常，如果不正常，Trojan-Go会拒绝启动。**\n\n下面是一份比较安全的服务器配置server.json，需要你在本地80端口配置一个HTTP服务（必要，你也可以使用其他的网站HTTP服务器，如\"remote_addr\": \"example.com\"），在1234端口配置一个HTTPS服务，或是一个展示\"400 Bad Request\"的静态HTTP网页服务。（可选，可以删除```fallback_port```字段，跳过这个步骤）\n\n```json\n{\n    \"run_type\": \"server\",\n    \"local_addr\": \"0.0.0.0\",\n    \"local_port\": 443,\n    \"remote_addr\": \"127.0.0.1\",\n    \"remote_port\": 80,\n    \"password\": [\n        \"your_awesome_password\"\n    ],\n    \"ssl\": {\n        \"cert\": \"server.crt\",\n        \"key\": \"server.key\",\n        \"fallback_port\": 1234\n    }\n}\n```\n\n这个配置文件使Trojan-Go在服务器的所有IP地址上(0.0.0.0)监听443端口，分别使用server.crt和server.key作为证书和密钥进行TLS握手。你应该使用尽可能复杂的密码，同时确保客户端和服务端```password```是一致的。注意，**Trojan-Go会检测你的HTTP服务器```http://remote_addr:remote_port```是否正常工作。如果你的HTTP服务器工作不正常，Trojan-Go将拒绝启动。**\n\n当一个客户端试图连接Trojan-Go的监听端口时，会发生下面的事情：\n\n- 如果TLS握手成功，检测到TLS的内容非Trojan协议（有可能是HTTP请求，或者来自GFW的主动探测）。Trojan-Go将TLS连接代理到本地127.0.0.1:80上的HTTP服务。这时在远端看来，Trojan-Go服务就是一个HTTPS网站。\n\n- 如果TLS握手成功，并且被确认是Trojan协议头部，并且其中的密码正确，那么服务器将解析来自客户端的请求并进行代理，否则和上一步的处理方法相同。\n\n- 如果TLS握手失败，说明对方使用的不是TLS协议进行连接。此时Trojan-Go将这个TCP连接代理到本地127.0.0.1:1234上运行的HTTPS服务（或者HTTP服务），返回一个展示400 Bad Reqeust的HTTP页面。```fallback_port```是一个可选选项，如果没有填写，Trojan-Go会直接终止连接。虽然是可选的，但是还是强烈建议填写。\n\n你可以通过使用浏览器访问你的域名```https://your-domain-name.com```来验证。如果工作正常，你的浏览器会显示一个正常的HTTPS保护的Web页面，页面内容与服务器本机80端口上的页面一致。你还可以使用```http://your-domain-name.com:443```验证```fallback_port```工作是否正常。\n\n事实上，你甚至可以将Trojan-Go当作你的HTTPS服务器，用来给你的网站提供HTTPS服务。访客可以正常地通过Trojan-Go浏览你的网站，而和代理流量互不影响。但是注意，不要在```remote_port```和```fallback_port```搭建有高实时性需求的服务，Trojan-Go识别到非Trojan协议流量时会有意增加少许延迟以抵抗GFW基于时间的检测。\n\n配置完成后，可以使用\n\n```shell\n./trojan-go -config ./server.json\n```\n\n启动服务端。\n\n### 客户端配置\n\n对应的客户端配置client.json\n\n```json\n{\n    \"run_type\": \"client\",\n    \"local_addr\": \"127.0.0.1\",\n    \"local_port\": 1080,\n    \"remote_addr\": \"your_awesome_server\",\n    \"remote_port\": 443,\n    \"password\": [\n        \"your_awesome_password\"\n    ],\n    \"ssl\": {\n        \"sni\": \"your-domain-name.com\"\n    }\n}\n```\n\n这个客户端配置使Trojan-Go开启一个监听在本地1080端口的socks5/http代理（自动识别），远端服务器为your_awesome_server:443，your_awesome_server可以是IP或者域名。\n\n如果你在```remote_addr```中填写的是域名，```sni```可以省略。如果你在```remote_addr```填写的是IP地址，```sni```字段应当填写你申请证书的对应域名，或者你自己签发的证书的Common Name，而且必须一致。注意，```sni```字段目前的在TLS协议中是**明文传送**的(目的是使服务器提供相应证书)。GFW已经被证实具有SNI探测和阻断能力，所以不要填写类似```google.com```等已经被封锁的域名，否则很有可能导致你的服务器也被遭到封锁。\n\n配置完成后，可以使用\n\n```shell\n./trojan-go -config ./client.json\n```\n\n启动客户端。\n\n更多关于配置文件的信息，可以在左侧导航栏中找到相应介绍。\n"
  },
  {
    "path": "docs/content/basic/full-config.md",
    "content": "---\ntitle: \"完整的配置文件\"\ndraft: false\nweight: 30\n---\n\n下面是一个完整的配置文件，其中的必填选项有\n\n- ```run_type```\n\n- ```local_addr```\n\n- ```local_port```\n\n- ```remote_addr```\n\n- ```remote_port```\n\n对于服务器```server```，```key```和```cert```为必填。\n\n对于客户端```client```，反向代理隧道```forward```，以及透明代理```nat```，```password```必填\n\n其余未填的选项，用下面给出的值进行填充。\n\n*Trojan-Go支持对人类更友好的YAML语法，配置文件的基本结构与JSON相同，效果等价。但是为了遵守YAML的命名习惯，你需要把下划线(\"_\")转换为横杠(\"-\")，如```remote_addr```在YAML文件中为```remote-addr```*\n\n```json\n{\n  \"run_type\": *required*,\n  \"local_addr\": *required*,\n  \"local_port\": *required*,\n  \"remote_addr\": *required*,\n  \"remote_port\": *required*,\n  \"log_level\": 1,\n  \"log_file\": \"\",\n  \"password\": [],\n  \"disable_http_check\": false,\n  \"udp_timeout\": 60,\n  \"ssl\": {\n    \"verify\": true,\n    \"verify_hostname\": true,\n    \"cert\": *required*,\n    \"key\": *required*,\n    \"key_password\": \"\",\n    \"cipher\": \"\",\n    \"curves\": \"\",\n    \"prefer_server_cipher\": false,\n    \"sni\": \"\",\n    \"alpn\": [\n      \"http/1.1\"\n    ],\n    \"session_ticket\": true,\n    \"reuse_session\": true,\n    \"plain_http_response\": \"\",\n    \"fallback_addr\": \"\",\n    \"fallback_port\": 0,\n    \"fingerprint\": \"\"\n  },\n  \"tcp\": {\n    \"no_delay\": true,\n    \"keep_alive\": true,\n    \"prefer_ipv4\": false\n  },\n  \"mux\": {\n    \"enabled\": false,\n    \"concurrency\": 8,\n    \"idle_timeout\": 60\n  },\n  \"router\": {\n    \"enabled\": false,\n    \"bypass\": [],\n    \"proxy\": [],\n    \"block\": [],\n    \"default_policy\": \"proxy\",\n    \"domain_strategy\": \"as_is\",\n    \"geoip\": \"$PROGRAM_DIR$/geoip.dat\",\n    \"geosite\": \"$PROGRAM_DIR$/geosite.dat\"\n  },\n  \"websocket\": {\n    \"enabled\": false,\n    \"path\": \"\",\n    \"host\": \"\"\n  },\n  \"shadowsocks\": {\n    \"enabled\": false,\n    \"method\": \"AES-128-GCM\",\n    \"password\": \"\"\n  },\n  \"transport_plugin\": {\n    \"enabled\": false,\n    \"type\": \"\",\n    \"command\": \"\",\n    \"option\": \"\",\n    \"arg\": [],\n    \"env\": []\n  },\n  \"forward_proxy\": {\n    \"enabled\": false,\n    \"proxy_addr\": \"\",\n    \"proxy_port\": 0,\n    \"username\": \"\",\n    \"password\": \"\"\n  },\n  \"mysql\": {\n    \"enabled\": false,\n    \"server_addr\": \"localhost\",\n    \"server_port\": 3306,\n    \"database\": \"\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"check_rate\": 60\n  },\n  \"api\": {\n    \"enabled\": false,\n    \"api_addr\": \"\",\n    \"api_port\": 0,\n    \"ssl\": {\n      \"enabled\": false,\n      \"key\": \"\",\n      \"cert\": \"\",\n      \"verify_client\": false,\n      \"client_cert\": []\n    }\n  }\n}\n```\n\n## 说明\n\n### 一般选项\n\n对于client/nat/forward，```remote_xxxx```应当填写你的trojan服务器地址和端口号，```local_xxxx```对应本地开放的socks5/http代理地址（自动适配）\n\n对于server，```local_xxxx```对应trojan服务器监听地址（强烈建议使用443端口），```remote_xxxx```填写识别到非trojan流量时代理到的HTTP服务地址，通常填写本地80端口。\n\n```log_level```指定日志等级。等级越高，输出的信息越少。合法的值有\n\n- 0 输出Debug以上日志（所有日志）\n\n- 1 输出Info及以上日志\n\n- 2 输出Warning及以上日志\n\n- 3 输出Error及以上日志\n\n- 4 输出Fatal及以上日志\n\n- 5 完全不输出日志\n\n```log_file```指定日志输出文件路径。如果未指定则使用标准输出。\n\n```password```可以填入多个密码。除了使用配置文件配置密码之外，trojan-go还支持使用mysql配置密码，参见下文。客户端的密码，只有与服务端配置文件中或者在数据库中的密码记录一致，才能通过服务端的校验，正常使用代理服务。\n\n```disable_http_check```是否禁用HTTP伪装服务器可用性检查。\n\n```udp_timeout``` UDP会话超时时间。\n\n### ```ssl```选项\n\n```verify```表示客户端(client/nat/forward)是否校验服务端提供的证书合法性，默认开启。出于安全性考虑，这个选项不应该在实际场景中选择false，否则可能遭受中间人攻击。如果使用自签名或者自签发的证书，开启```verify```会导致校验失败。这种情况下，应当保持```verify```开启，然后在```cert```中填写服务端的证书，即可正常连接。\n\n```verify_hostname```表示服务端是否校验客户端提供的SNI与服务端设置的一致性。如果服务端SNI字段留空，认证将被强制关闭。\n\n服务端必须填入```cert```和```key```，对应服务器的证书和私钥文件，请注意证书是否有效/过期。如果使用权威CA签发的证书，客户端(client/nat/forward)可以不填写```cert```。如果使用自签名或者自签发的证书，应当在的```cert```处填入服务器证书文件，否则可能导致校验失败。\n\n```sni```指的是TLS客户端请求中的服务器名字段，一般和证书的Common Name相同。如果你使用let'sencrypt等机构签发的证书，这里填入你的域名。对于客户端，如果这一项未填，将使用```remote_addr```填充。你应当指定一个有效的SNI（和远端证书CN一致），否则客户端可能无法验证远端证书有效性从而无法连接；对于服务端，若此项不填，则使用证书中Common Name作为SNI校验依据，支持通配符如*.example.com。\n\n```fingerprint```用于指定客户端TLS Client Hello指纹伪造类型，以抵抗GFW对于TLS Client Hello指纹的特征识别和阻断。trojan-go使用[utls](https://github.com/refraction-networking/utls)进行指纹伪造，默认伪造Firefox的指纹。合法的值有\n\n- \"\"，不使用指纹伪造（默认）\n\n- \"firefox\"，伪造Firefox指纹\n\n- \"chrome\"，伪造Chrome指纹\n\n- \"ios\"，伪造iOS指纹\n\n一旦指纹的值被设置，客户端的```cipher```，```curves```，```alpn```，```session_ticket```等有可能影响指纹的字段将使用该指纹的特定设置覆写。\n```alpn```为TLS的应用层协议协商指定协议。在TLS Client/Server Hello中传输，协商应用层使用的协议，仅用作指纹伪造，并无实际作用。**如果使用了CDN，错误的alpn字段可能导致与CDN协商得到错误的应用层协议**。\n\n```prefer_server_cipher```客户端是否偏好选择服务端在协商中提供的密码学套件。\n\n```cipher```TLS使用的密码学套件。```cipher13``字段与此字段合并。只有在你明确知道自己在做什么的情况下，才应该去填写此项以修改trojan-go使用的TLS密码学套件。**正常情况下，你应该将其留空或者不填**，trojan-go会根据当前硬件平台以及远端的情况，自动选择最合适的加密算法以提升性能和安全性。如果需要填写，密码学套件名用分号(\":\")分隔，按优先顺序排列。Go的TLS库中弃用了TLS1.2中部分不安全的密码学套件，并完全支持TLS1.3。默认情况下，trojan-go将优先使用更安全的TLS1.3。\n\n```curves```指定TLS在ECDHE中偏好使用的椭圆曲线。只有你明确知道自己在做什么的情况下，才应该填写此项。曲线名称用分号(\":\")分隔，按优先顺序排列。\n\n```plain_http_response```指服务端TLS握手失败时，明文发送的原始数据（原始TCP数据）。这个字段填入该文件路径。推荐使用```fallback_port```而不是该字段。\n\n```fallback_addr```和```fallback_port```指服务端TLS握手失败时，trojan-go将该连接重定向到该地址。这是trojan-go的特性，以便更好地隐蔽服务器，抵抗GFW的主动检测，使得服务器的443端口在遭遇非TLS协议的探测时，行为与正常服务器完全一致。当服务器接受了一个连接但无法进行TLS握手时，如果```fallback_port```不为空，则流量将会被代理至fallback_addr:fallback_port。如果```fallback_addr```为空，则用```remote_addr```填充。例如，你可以在本地使用nginx开启一个https服务，当你的服务器443端口被非TLS协议请求时（比如http请求），trojan-go将代理至本地https服务器，nginx将使用http协议明文返回一个400 Bad Request页面。你可以通过使用浏览器访问```http://your-domain-name.com:443```进行验证。\n\n```key_log```TLS密钥日志的文件路径。如果填写则开启密钥日志。**记录密钥将破坏TLS的安全性，此项不应该用于除调试以外的其他任何用途。**\n\n### ```mux```多路复用选项\n\n多路复用是trojan-go的特性。如果服务器和客户端都是trojan-go，可以开启mux多路复用以减少高并发情景下的延迟（只需要客户端开启此选项即可，服务端自动适配）。\n\n注意，多路复用的意义在于降低握手延迟，而不是提升链路速度。相反，它会增加客户端和服务端的CPU和内存消耗，从而可能造成速度下降。\n\n```enabled```是否开启多路复用。\n\n```concurrency```指单个TLS隧道可以承载的最大连接数，默认为8。这个数值越大，多连接并发时TLS由于握手产生的延迟就越低，但网络吞吐量可能会有所降低，填入负数或者0表示所有连接只使用一个TLS隧道承载。\n\n```idle_timeout```空闲超时时间。指TLS隧道在空闲多长时间之后关闭，单位为秒。如果数值为负值或0，则一旦TLS隧道空闲，则立即关闭。\n\n### ```router```路由选项\n\n路由功能是trojan-go的特性。trojan-go的路由策略有三种。\n\n- Proxy 代理。将请求通过TLS隧道进行代理，由trojan服务器和目的地址进行连接。\n\n- Bypass 绕过。直接在本地和目的地址进行连接。\n\n- Block 封锁。不代理请求，直接关闭连接。\n\n在```proxy```, ```bypass```, ```block```字段中填入对应列表geoip/geosite或路由规则，trojan-go即根据列表中的IP（CIDR）或域名执行相应路由策略。客户端(client)可以配置三种策略，服务端(server)只可配置block策略。\n\n```enabled```是否开启路由模块。\n\n```default_policy```指的是三个列表匹配均失败后，使用的默认策略，默认为\"proxy\"，即进行代理。合法的值有\n\n- \"proxy\"\n\n- \"bypass\"\n\n- \"block\"\n\n含义同上。\n\n```domain_strategy```域名解析策略，默认\"as_is\"。合法的值有：\n\n- \"as_is\"，只在各列表中的域名规则内进行匹配。\n\n- \"ip_if_non_match\"，先在各列表中的域名规则内进行匹配；如果不匹配，则解析为IP后，在各列表中的IP地址规则内进行匹配。该策略可能导致DNS泄漏或遭到污染。\n\n- \"ip_on_demand\"，先解析为IP，在各列表中的IP地址规则内进行匹配；如果不匹配，则在各列表中的域名规则内进行匹配。该策略可能导致DNS泄漏或遭到污染。\n\n```geoip```和```geosite```字段指geoip和geosite数据库文件路径，默认使用程序所在目录的geoip.dat和geosite.dat。也可以通过指定环境变量TROJAN_GO_LOCATION_ASSET指定工作目录。\n\n### ```websocket```选项\n\nWebsocket传输是trojan-go的特性。在**正常的直接连接代理节点**的情况下，开启这个选项不会改善你的链路速度（甚至有可能下降），也不会提升你的连接安全性。你只应该在需要利用CDN进行中转，或利用nginx等服务器根据路径分发的情况下，使用websocket。\n\n```enabled```表示是否启用Websocket承载流量，服务端开启后同时支持一般Trojan协议和基于websocket的Trojan协议，客户端开启后将只使用websocket承载所有Trojan协议流量。\n\n```path```指的是Websocket使用的URL路径，必须以斜杠(\"/\")开头，如\"/longlongwebsocketpath\"，并且服务器和客户端必须一致。\n\n```host```Websocket握手时，HTTP请求中使用的主机名。客户端如果留空则使用```remote_addr```填充。如果使用了CDN，这个选项一般填入域名。不正确的```host```可能导致CDN无法转发请求。\n\n### ``shadowsocks`` AEAD加密选项\n\n此选项用于替代弃用的混淆加密和双重TLS。如果此选项被设置启用，Trojan协议层下将插入一层Shadowsocks AEAD加密层。也即（已经加密的）TLS隧道内，所有的Trojan协议将再使用AEAD方法进行加密。注意，此选项和Websocket是否开启无关。无论Websocket是否开启，所有Trojan流量都会被再进行一次加密。\n\n注意，开启这个选项将有可能降低传输性能，你只应该在不信任承载Trojan协议的传输信道的情况下，启用这个选项。例如：\n\n- 你使用了Websocket，经过不可信的CDN进行中转（如国内CDN）\n\n- 你与服务器的连接遭到了GFW针对TLS的中间人攻击\n\n- 你的证书失效，无法验证证书有效性\n\n- 你使用了无法保证密码学安全的可插拔传输层\n\n等等。\n\n由于使用的是AEAD，trojan-go可以正确判断请求是否有效，是否遭到主动探测，并作出相应的响应。\n\n```enabled```是否启用Shadowsocks AEAD加密Trojan协议层。\n\n```method```加密方式。合法的值有：\n\n- \"CHACHA20-IETF-POLY1305\"\n\n- \"AES-128-GCM\" (默认)\n\n- \"AES-256-GCM\"\n\n```password```用于生成主密钥的密码。如果启用AEAD加密，必须确保客户端和服务端一致。\n\n### ```transport_plugin```传输层插件选项\n\n```enabled```是否启用传输层插件替代TLS传输。一旦启用传输层插件支持，trojan-go将会把**未经TLS加密的trojan协议流量明文传输给插件**，以允许用户对流量进行自定义的混淆和加密。\n\n```type```插件类型。目前支持的类型有\n\n- \"shadowsocks\"，支持符合[SIP003](https://shadowsocks.org/en/spec/Plugin.html)标准的shadowsocks混淆插件。trojan-go将在启动时按照SIP003标准替换环境变量并修改自身配置(```remote_addr/remote_port/local_addr/local_port```)，使插件与远端直接通讯，而trojan-go仅监听/连接插件。\n\n- \"plaintext\"，使用明文传输。选择此项，trojan-go不会修改任何地址配置(```remote_addr/remote_port/local_addr/local_port```)，也不会启动```command```中插件，仅移除最底层的TLS传输层并使用TCP明文传输。此选项目的为支持nginx等接管TLS并进行分流，以及高级用户进行调试测试。**请勿直接使用明文传输模式穿透防火墙。**\n\n- \"other\"，其他插件。选择此项，trojan-go不会修改任何地址配置(```remote_addr/remote_port/local_addr/local_port```)，但会启动```command```中插件并传入参数和环境变量。\n\n```command```传输层插件可执行文件的路径。trojan-go将在启动时一并执行它。\n\n```arg```传输层插件启动参数。这是一个列表，例如```[\"-config\", \"test.json\"]```。\n\n```env```传输层插件环境变量。这是一个列表，例如```[\"VAR1=foo\", \"VAR2=bar\"]```。\n\n```option```传输层插件配置（SIP003)。例如```\"obfs=http;obfs-host=www.baidu.com\"```。\n\n### ```tcp```选项\n\n```no_delay```TCP封包是否直接发出而不等待缓冲区填满。\n\n```keep_alive```是否启用TCP心跳存活检测。\n\n```prefer_ipv4```是否优先使用IPv4地址。\n\n### ```mysql```数据库选项\n\ntrojan-go兼容trojan的基于mysql的用户管理方式，但更推荐的方式是使用API。\n\n```enabled```表示是否启用mysql数据库进行用户验证。\n\n```check_rate```是trojan-go从MySQL获取用户数据并更新缓存的间隔时间，单位为秒。\n\n其他选项可以顾名思义，不再赘述。\n\nusers表结构和trojan版本定义一致，下面是一个创建users表的例子。注意这里的password指的是密码经过SHA224散列之后的值（字符串），流量download, upload, quota的单位是字节。你可以通过修改数据库users表中的用户记录的方式，添加和删除用户，或者指定用户的流量配额。trojan-go会根据所有的用户流量配额，自动更新当前有效的用户列表。如果download+upload>quota，trojan-go服务器将拒绝该用户的连接。\n\n```mysql\nCREATE TABLE users (\n    id INT UNSIGNED NOT NULL AUTO_INCREMENT,\n    username VARCHAR(64) NOT NULL,\n    password CHAR(56) NOT NULL,\n    quota BIGINT NOT NULL DEFAULT 0,\n    download BIGINT UNSIGNED NOT NULL DEFAULT 0,\n    upload BIGINT UNSIGNED NOT NULL DEFAULT 0,\n    PRIMARY KEY (id),\n    INDEX (password)\n);\n```\n\n### ```forward_proxy```前置代理选项\n\n前置代理选项允许使用其他代理承载trojan-go的流量\n\n```enabled```是否启用前置代理(socks5)。\n\n```proxy_addr```前置代理的主机地址。\n\n```proxy_port```前置代理的端口号。\n\n```username``` ```password```代理的用户和密码，如果留空则不使用认证。\n\n### ```api```选项\n\ntrojan-go基于gRPC提供了API，以支持服务端和客户端的管理和统计。可以实现客户端的流量和速度统计，服务端各用户的流量和速度统计，用户的动态增删和限速等。\n\n```enabled```是否启用API功能。\n\n```api_addr```gRPC监听的地址。\n\n```api_port```gRPC监听的端口。\n\n```ssl``` TLS相关设置。\n\n- ```enabled```是否使用TLS传输gRPC流量。\n\n- ```key```，```cert```服务器私钥和证书。\n\n- ```verify_client```是否认证客户端证书。\n\n- ```client_cert```如果开启客户端认证，此处填入认证的客户端证书列表。\n\n警告：**不要将未开启TLS双向认证的API服务直接暴露在互联网上，否则可能导致各类安全问题。**\n"
  },
  {
    "path": "docs/content/basic/trojan.md",
    "content": "---\ntitle: \"Trojan基本原理\"\ndraft: false\nweight: 21\n---\n\n这个页面将会简单讲述Trojan协议的基本工作原理。如果你对于GFW和Trojan的工作方式不感兴趣，可以跳过这一小节。但为了更好地保护你的通讯安全性和节点的隐蔽性，我还是建议你阅读。\n\n## 为什么（使用流密码的）Shadowsocks容易遭到封锁\n\n防火墙在早期仅仅只是对出境流量进行截获和审查，也即**被动检测**。Shadowsocks的加密协议设计使得传输的数据包本身几乎没有任何特征，看起来类似于完全随机的比特流，这在早期的确能有效绕过GFW。\n\n目前的GFW已经开始采用**主动探测**的方式。具体来说，当GFW发现一个可疑的无法识别的连接时（大流量，随机字节流，高位端口等特征），将会**主动连接**这个服务器端口，重放之前捕获到的流量（或者经过一些精心修改后重放）。Shadowsocks服务器检测到不正常的连接，将连接断开。这种不正常的流量和断开连接的行为被视作可疑的Shadowsocks服务器的特征，于是该服务器被加入GFW的可疑名单中。这个名单不一定立即生效，而是在某些特殊的敏感时期，可疑名单中的服务器会遭到暂时或者永久的封锁。该可疑名单是否封锁，可能由人为因素决定。\n\n如果你想了解更多，可以参考[这篇文章](https://gfw.report/blog/gfw_shadowsocks/)。\n\n## Trojan如何绕过GFW\n\n与Shadowsocks相反，Trojan不使用自定义的加密协议来隐藏自身。相反，使用特征明显的TLS协议(TLS/SSL)，使得流量看起来与正常的HTTPS网站相同。TLS是一个成熟的加密体系，HTTPS即使用TLS承载HTTP流量。使用**正确配置**的加密TLS隧道，可以保证传输的\n\n- 保密性（GFW无法得知传输的内容）\n\n- 完整性（一旦GFW试图篡改传输的密文，通讯双方都会发现）\n\n- 不可抵赖（GFW无法伪造身份冒充服务端或者客户端）\n\n- 前向安全（即使密钥泄露，GFW也无法解密先前的加密流量）\n\n对于被动检测，Trojan协议的流量与HTTPS流量的特征和行为完全一致。而HTTPS流量占据了目前互联网流量的一半以上，且TLS握手成功后流量均为密文，几乎不存在可行方法从其中分辨出Trojan协议流量。\n\n对于主动检测，当防火墙主动连接Trojan服务器进行检测时，Trojan可以正确识别非Trojan协议的流量。与Shadowsocks等代理不同的是，此时Trojan不会断开连接，而是将这个连接代理到一个正常的Web服务器。在GFW看来，该服务器的行为和一个普通的HTTPS网站行为完全相同，无法判断是否是一个Trojan代理节点。这也是Trojan推荐使用合法的域名、使用权威CA签名的HTTPS证书的原因: 这让你的服务器完全无法被GFW使用主动检测判定是一个Trojan服务器。\n\n因此，就目前的情况来看，若要识别并阻断Trojan的连接，只能使用无差别封锁（封锁某个IP段，某一类证书，某一类域名，甚至阻断全国所有出境HTTPS连接）或发动大规模的中间人攻击（劫持所有TLS流量并劫持证书，审查内容）。对于中间人攻击，可以使用Websocket的双重TLS应对，高级配置中有详细讲解。\n"
  },
  {
    "path": "docs/content/developer/_index.md",
    "content": "---\ntitle: \"实现细节和开发指南\"\ndraft: false\nweight: 40\n---\n\n这一部分介绍Trojan-Go底层实现的细节，主要面向开发者。\n"
  },
  {
    "path": "docs/content/developer/api.md",
    "content": "---\ntitle: \"API开发\"\ndraft: false\nweight: 100\n---\n\nTrojan-Go基于gRPC实现了API，使用protobuf交换数据。客户端可获取流量和速度信息；服务端可获取各用户流量，速度，在线情况，并动态增删用户和限制速度。可以通过在配置文件中添加```api```选项激活API模块。下面是一个例子，各字段含义参见“完整的配置文件”一节。\n\n```json\n...\n\"api\": {\n    \"enabled\": true,\n    \"api_addr\": \"0.0.0.0\",\n    \"api_port\": 10000,\n    \"ssl\": {\n      \"enabled\": true,\n      \"cert\": \"api_cert.crt\",\n      \"key\": \"api_key.key\",\n      \"verify_client\": true,\n      \"client_cert\": [\n          \"api_client_cert1.crt\",\n          \"api_client_cert2.crt\"\n      ]\n    },\n}\n```\n\n如果需要实现API客户端进行对接，请参考api/service/api.proto文件。\n"
  },
  {
    "path": "docs/content/developer/build.md",
    "content": "---\ntitle: \"编译和自定义Trojan-Go\"\ndraft: false\nweight: 10\n---\n\n编译需要Go版本号高于1.14.x，请在编译前确认你的编译器版本。推荐使用snap安装和更新go。\n\n编译方式非常简单，可以使用Makefile预设步骤进行编译：\n\n```shell\nmake\nmake install #安装systemd服务等，可选\n```\n\n或者直接使用Go进行编译：\n\n```shell\ngo build -tags \"full\" #编译完整版本\n```\n\n可以通过指定GOOS和GOARCH环境变量，指定交叉编译的目标操作系统和架构，例如\n\n```shell\nGOOS=windows GOARCH=386 go build -tags \"full\" #windows x86\nGOOS=linux GOARCH=arm64 go build -tags \"full\" #linux arm64\n```\n\n你可以使用release.sh进行批量的多个平台的交叉编译，release版本使用了这个脚本进行构建。\n\nTrojan-Go的大多数模块是可插拔的。在build文件夹下可以找到各个模块的导入声明。如果你不需要其中某些功能，或者需要缩小可执行文件的体积，可以使用构建标签(tags)进行模块的自定义，例如\n\n```shell\ngo build -tags \"full\" #编译所有模块\ngo build -tags \"client\" -trimpath -ldflags=\"-s -w -buildid=\" #只有客户端功能，且去除符号表缩小体积\ngo build -tags \"server mysql\" #只有服务端和mysql支持\n```\n\n使用full标签等价于\n\n```shell\ngo build -tags \"api client server forward nat other\"\n```\n"
  },
  {
    "path": "docs/content/developer/mux.md",
    "content": "---\ntitle: \"多路复用\"\ndraft: false\nweight: 30\n---\n\nTrojan-Go使用[smux](https://github.com/xtaci/smux)实现多路复用。同时实现了simplesocks协议用于进行代理传输。\n\n当启用多路复用时，客户端首先发起TLS连接，使用正常trojan协议格式，但协议Command部分填入0x7f(protocol.Mux)，标识此连接为复用连接（类似于http的upgrade），之后连接交由smux客户端管理。服务器收到请求头部后，交由smux服务器解析该连接的所有流量。在每条分离出的smux连接上，使用simplesocks协议（去除认证部分的trojan协议)标明代理目的地。自顶向下的协议栈如下：\n\n| 协议        | 备注     |\n| ----------- | -------- |\n| 真实流量    |\n| SimpleSocks |\n| smux        |\n| Trojan      | 用于鉴权 |\n| 底层协议    |          |\n"
  },
  {
    "path": "docs/content/developer/overview.md",
    "content": "---\ntitle: \"基本介绍\"\ndraft: false\nweight: 1\n---\n\nTrojan-Go的核心部分有\n\n- tunnel 各个协议具体实现\n\n- proxy 代理核心\n\n- config 配置注册和解析模块\n\n- redirector 主动检测欺骗模块\n\n- statistics 用户认证和统计模块\n\n可以在对应文件夹中找到相关源代码。\n\n## tunnel.Tunnel隧道\n\nTrojan-Go将所有协议（包括路由功能等）抽象为隧道(tunnel.Tunnel接口)，每个隧道可开启服务端（tunnel.Server接口）和客户端（tunnel.Client）。每个服务端可以从其底层隧道中，剥离并接受流（tunnel.Conn）和包（tunnel.PacketConn)。客户端可以向底层隧道，创建流和包。\n\n每个隧道并不关心其下方的隧道是什么，但是每个隧道清楚知道这个它上方的其他隧道的相关信息。\n\n所有隧道需要下层提供流或包传输支持，或两者都要求提供。所有隧道必须向上层隧道提供流传输支持，但不一定提供包传输。\n\n隧道可能只有服务端，也可能只有客户端，也可能两者皆有。两者皆有的隧道，可被用于作为Trojan-Go客户端和服务端间的传输隧道。\n\n注意，请区分Trojan-Go的服务端/客户端，和隧道的服务端/客户端的区别。下面是一个方便理解的图例。\n\n```text\n\n  入站                              GFW                                  出站\n-------->隧道A服务端->隧道B客户端 ----------------> 隧道B服务端->隧道C客户端----------->\n           (Trojan-Go客户端)                          (Trojan-Go服务端)\n\n```\n\n最底层的隧道为传输层，即不从其他隧道获取或者创建流和包的隧道，充当上图中隧道A或者C的角色。\n\n- transport，可插拔传输层\n\n- socks，socks5代理，仅隧道服务端\n  \n- tproxy，透明代理，仅隧道服务端\n\n- dokodemo，反向代理，仅隧道服务端\n\n- freedom，自由出站，仅隧道客户端\n\n这几个隧道直接从TCP/UDP Socket创建流和包，不接受为其底层添加的任何隧道。\n\n其他隧道，只要下层能满足上层对包和流传输的需求，则原则上可以任何方式，任何数量进行组合和堆叠。这些隧道在上图中充当隧道B的角色，他们有\n\n- trojan\n\n- websocket\n\n- mux\n\n- simplesocks\n\n- tls\n\n- router，路由功能，仅隧道客户端\n\n他们都不关心其下层隧道实现。但可以根据到来的流和包，将其分发给上层隧道。\n\n例如，在这张图中，是一个典型的Trojan-Go客户端和服务端，各个隧道自下往上堆叠的顺序是：\n\n- 隧道A: transport->socks\n\n- 隧道B: transport->tls->trojan\n\n- 隧道C: freedom\n\n实际上的隧道堆叠的情况会比这个更复杂一些。通常的入站的隧道是一棵多叉树的形式，而非一条链。具体解释参考下文。\n\n## proxy.Proxy代理核心\n\n代理核心的作用，是监听上述隧道进行组合堆叠并形成的协议栈，将所有的入站协议栈（多个隧道Server的终端节点，见下）中抽取的流和包，以及对应元信息，转送给出站协议栈（一个隧道Client）。\n\n注意，这里的入站协议栈可以有多个，如客户端可以同时从Socks5和HTTP协议栈中抽取流和包，服务端可以同时从Websocket承载的Trojan协议，和TLS承载的Trojan协议中抽取流和包等。但是出站协议栈只能有一个，如只使用TLS承载的Trojan协议出站。\n\n为了描述入站协议栈（隧道服务端）的组合和堆叠方式，使用一棵多叉树对所有协议栈进行描述。你可以在proxy文件夹中各组件，看到构建树的过程。\n\n而出站协议栈则比较简单，使用一个简单列表即可描述。\n\n所以实际上，对于一个典型的开启了Websocket和Mux的客户端/服务端，上图的隧道堆叠模型为：\n\n客户端\n\n- 入站（树）\n  - transport (根)\n    - adapter 能够识别HTTP和Socks流量并分发给上层协议\n      - http （终端节点）\n      - socks（终端节点）\n\n- 出站(链)\n  - transport (根)\n  - tls\n  - websocket\n  - trojan\n  - mux\n  - simplesocks\n\n服务端\n\n- 入站（树）\n  - transport (根)\n    - tls 能够识别HTTP和非HTTP流量并分发\n      - websocket\n        - trojan（终端节点）\n          - mux\n            - simplesocks （终端节点）\n      - trojan 能够识别mux和普通trojan流量并分发（终端节点）\n        - mux\n          - simplesocks （终端节点）\n\n- 出站（链）\n  - freedom\n\n注意，代理核心只从隧道构成的树的终端节点抽取流和包，并转送到唯一的出站上。多个终端节点的设计的目的，是使Trojan-Go同时兼容Websocket和Trojan协议入站连接，开启/未开启Mux的入站连接，以及HTTP/Socks5自动识别的功能。每个拥有多个儿子的树上节点，具有精确识别和分发流和包给不同的儿子节点的能力。这符合我们假定每个协议了解其上层承载协议的假设。\n"
  },
  {
    "path": "docs/content/developer/plugin.md",
    "content": "---\ntitle: \"可插拔传输层插件开发\"\ndraft: false\nweight: 150\n---\n\nTrojan-Go鼓励开发传输层插件，以丰富协议类型，增加与GFW对抗的战略纵深。\n\n传输层插件的作用，是替代tansport隧道的TLS进行传输加密和混淆。\n\n插件与Trojan-Go基于TCP Socket通讯，与Trojan-Go本身不存在任何耦合关系，你可以使用任何你喜欢的语言和设计模式进行开发。我们建议的参照[SIP003](https://shadowsocks.org/en/spec/Plugin.html)标准进行开发。如此开发的插件可以同时用于Trojan-Go和Shadowsocks。\n\nTrojan-Go开启插件功能后，仅使用TCP进行传输（明文）。你的插件只需要处理入站的TCP请求即可。你可以将这些TCP流量转换成任何你喜欢的流量格式，如QUIC，HTTP，甚至是ICMP。\n\nTrojan-Go插件设计原则，与Shadowsocks略有不同：\n\n1. 插件本身可以对传输内容进行加密，混淆和完整性校验，以及可以抵抗重放攻击。\n\n2. 插件应该伪造一种已有的、常见的服务（记做X服务），及其流量，在此基础上嵌入自己的加密内容。\n\n3. 服务端的插件，在检验到内容被篡改/遭到重放时，**必须将此连接交由Trojan-Go处理**。具体步骤是，将已读入和未读入的内容一并发送给Trojan-Go，并建立双向连接，而不是直接断开。Trojan-Go将与一个真实的X服务器建立连接，使攻击者直接与真实的X服务器进行交互。\n\n解释如下：\n\n第一点原则，是由于Trojan协议本身并不加密。将TLS替换为传输层插件后，将**完全信任插件的安全性**。\n\n第二点原则，是继承Trojan的精神。最适合隐藏一棵树的地方，是森林。\n\n第三点原则，是为了充分利用Trojan-Go的抗主动探测特性。即使GFW对你的服务器进行主动探测，你的服务器也可以表现得与X服务一致，而不存在其他特征。\n\n为了方便理解，举一个例子。\n\n1. 假设你的插件伪装的是MySQL流量。防火墙通过流量嗅探，发现你的MySQL流量大得异常，决定主动连接你的服务器进行主动探测。\n\n2. 防火墙连接到你的服务器并发送探测载荷，你的Trojan-Go服务端插件，经过校验，发现这个异常连接不是代理流量，于是将这个连接交由Trojan-Go处理。\n\n3. Trojan-Go发现这个连接异常，将这个连接重定向到一个真正的MySQL服务器上。于是，防火墙开始与一个真正的MySQL服务器进行交互，发现其行为与真实MySQL服务器无异，无法对服务器进行封禁。\n\n另外，即使你的协议协议和插件不能满足原则2和原则3，甚至不能很好满足原则1，我们同样鼓励开发。因为GFW仅仅针对流行的协议进行审计和封锁，此类协议（土制密码学/土制协议）只要不公开发表，同样能保持非常强健的生命力。\n"
  },
  {
    "path": "docs/content/developer/simplesocks.md",
    "content": "---\ntitle: \"SimpleSocks协议\"\ndraft: false\nweight: 50\n---\n\nSimpleSocks协议是无鉴权机制的简单代理协议，本质上是去除了sha224的Trojan协议。使用该协议的目的是减少多路复用时的overhead。\n\n只有启用多路复用之后，被复用的连接才会使用这个协议。也即SimpleSocks总是被SMux承载。\n\nSimpleSocks甚至比Socks5更简单，下面是头部结构。\n\n```text\n+-----+------+----------+----------+-----------+\n| CMD | ATYP | DST.ADDR | DST.PORT |  Payload  |\n+-----+------+----------+----------+-----------+\n|  1  |  1   | Variable |    2     |  Variable |\n+-----+------+----------+----------+-----------+\n```\n\n各字段定义与Trojan协议相同，不再赘述。\n"
  },
  {
    "path": "docs/content/developer/trojan.md",
    "content": "---\ntitle: \"Trojan协议\"\ndraft: false\nweight: 20\n---\n\nTrojan-Go遵循原始的trojan协议，具体格式可以参考[Trojan文档](https://trojan-gfw.github.io/trojan/protocol)，这里不再赘述。\n\n默认情况下，trojan协议使用TLS来承载，协议栈如下：\n\n| 协议     |\n| -------- |\n| 真实流量 |\n| Trojan   |\n| TLS      |\n| TCP      |\n"
  },
  {
    "path": "docs/content/developer/url.md",
    "content": "---\ntitle: \"URL方案（草案）\"\ndraft: false\nweight: 200\n---\n\n## Changelog\n\n- encryption 格式修改为 ss;method:password\n\n## 概述\n\n感谢 @DuckSoft @StudentMain @phlinhng 对 Trojan-Go URL 方案的讨论和贡献。**目前 URL 方案为草案，需要更多的实践和讨论。**\n\nTrojan-Go**客户端**可以接受URL，以定位服务器资源。原则如下:\n\n- 遵守 URL 格式规范\n\n- 保证人类可读，对机器友好\n\n- URL 的作用，是定位 Trojan-Go 节点资源，方便资源分享\n\n需要注意，基于人类可读性的考虑，禁止将 base64 等编码数据嵌入 URL 中。首先， base64 编码不能保证传输安全，其意义在于在 ASCII 信道传输非 ASCII 数据。其次，如果需要保证分享 URL 时的传输安全，请对明文 URL 进行加密，而不是修改 URL 格式。\n\n## 格式\n\n基本格式如下，`$()` 代表此处需要 `encodeURIComponent`。\n\n```text\ntrojan-go://\n    $(trojan-password)\n    @\n    trojan-host\n    :\n    port\n/?\n    sni=$(tls-sni.com)&\n    type=$(original|ws|h2|h2+ws)&\n        host=$(websocket-host.com)&\n        path=$(/websocket/path)&\n    encryption=$(ss;aes-256-gcm;ss-password)&\n    plugin=$(...)\n#$(descriptive-text)\n```\n\n例如\n\n```text\ntrojan-go://password1234@google.com/?sni=microsoft.com&type=ws&host=youtube.com&path=%2Fgo&encryption=ss%3Baes-256-gcm%3Afuckgfw\n```\n\n由于 Trojan-Go 兼容 Trojan，所以对于 Trojan 的 URL 方案\n\n```text\ntrojan://password@remote_host:remote_port\n```\n\n可以兼容接受。它等价于\n\n```text\ntrojan-go://password@remote_host:remote_port\n```\n\n需要注意的是，一旦服务器使用了非Trojan兼容的功能，必须使用```trojan-go://```定位服务器。这样设计的目的是使得 Trojan-Go 的 URL 不会被 Trojan 错误接受，避免污染 Trojan 用户的 URL 分享。同时，Trojan-Go 确保可以兼容接受 Trojan 的 URL。\n\n## 详述\n\n注意：所有参数名和常数字符串均区分大小写。\n\n### `trojan-password`\n\nTrojan 的密码。\n不可省略，不能为空字符串，不建议含有非 ASCII 可打印字符。\n必须使用 `encodeURIComponent` 编码。\n\n### `trojan-host`\n\n节点 IP / 域名。\n不可省略，不能为空字符串。\nIPv6 地址必须扩方括号。\nIDN 域名（如“百度.cn”）必须使用 `xn--xxxxxx` 格式。\n\n### `port`\n\n节点端口。\n省略时默认为 `443`。\n必须取 `[1,65535]` 中的整数。\n\n### `tls`或`allowInsecure`\n\n没有这个字段。\nTLS 默认一直启用，除非有传输插件禁用它。\nTLS 认证必须开启。无法使用根CA校验服务器身份的节点，不适合分享。\n\n### `sni`\n\n自定义 TLS 的 SNI。\n省略时默认与 `trojan-host` 同值。不得为空字符串。\n\n必须使用 `encodeURIComponent` 编码。\n\n### `type`\n\n传输类型。\n省略时默认为 `original`，但不可为空字符串。\n目前可选值只有 `original` 和 `ws`，未来可能会有 `h2`、`h2+ws` 等取值。\n\n当取值为 `original` 时，使用原始 Trojan 传输方式，无法方便通过 CDN。\n当取值为 `ws` 时，使用 Websocket over TLS 传输。\n\n### `host`\n\n自定义 HTTP `Host` 头。\n可以省略，省略时值同 `trojan-host`。\n可以为空字符串，但可能带来非预期情形。\n\n警告：若你的端口非标准端口（不是 80 / 443），RFC 标准规定 `Host` 应在主机名后附上端口号，例如 `example.com:44333`。至于是否遵守，请自行斟酌。\n\n必须使用 `encodeURIComponent` 编码。\n\n### `path`\n\n当传输类型 `type` 取 `ws`、`h2`、`h2+ws` 时，此项有效。\n不可省略，不可为空。\n必须以 `/` 开头。\n可以使用 URL 中的 `&` `#` `?` 等字符，但应当是合法的 URL 路径。\n\n必须使用 `encodeURIComponent` 编码。\n\n### `mux`\n\n没有这个字段。\n当前服务器默认一直支持 `mux`。\n启用 `mux` 与否各有利弊，应由客户端决定自己是否启用。URL的作用，是定位服务器资源，而不是规定用户使用偏好。\n\n### `encryption`\n\n用于保证 Trojan 流量密码学安全的加密层。\n可省略，默认为 `none`，即不使用加密。\n不可以为空字符串。\n\n必须使用 encodeURIComponent 编码。\n\n使用 Shadowsocks 算法进行流量加密时，其格式为：\n\n```text\nss;method:password\n```\n\n其中 ss 是固定内容，method 是加密方法，必须为下列之一：\n\n- `aes-128-gcm`\n- `aes-256-gcm`\n- `chacha20-ietf-poly1305`\n\n其中的 `password` 是 Shadowsocks 的密码，不得为空字符串。\n`password` 中若包含分号，不需要进行转义。\n`password` 应为英文可打印 ASCII 字符。\n\n其他加密方案待定。\n\n### `plugin`\n\n额外的插件选项。本字段保留。\n可省略，但不可以为空字符串。\n\n### URL Fragment (# 后内容)\n\n节点说明。\n不建议省略，不建议为空字符串。\n\n必须使用 `encodeURIComponent` 编码。\n"
  },
  {
    "path": "docs/content/developer/websocket.md",
    "content": "---\ntitle: \"Websocket\"\ndraft: false\nweight: 40\n---\n\n由于使用CDN中转时，HTTPS对CDN透明，CDN可以审查Websocket传输内容。而Trojan协议本身是明文传输，因此为保证安全性，可添加一层Shadowsocks AEAD加密层以混淆流量特征并保证安全性。\n\n**如果你使用的是中国境内运营商提供的CDN，请务必开启AEAD加密**\n\n开启AEAD加密后，Websocket承载的流量将被Shadowsocks AEAD加密，头部具体格式参见Shadowsocks白皮书。\n\n开启Websocket支持后，协议栈如下：\n\n| 协议        | 备注             |\n| ----------- | ---------------- |\n| 真实流量    |                  |\n| SimpleSocks | 如果开启多路复用 |\n| smux        | 如果开启多路复用 |\n| Trojan      |                  |\n| Shadowsocks | 如果开启加密     |\n| Websocket   |                  |\n| 传输层协议  |                  |\n"
  },
  {
    "path": "easy/easy.go",
    "content": "package easy\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/option\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n)\n\ntype easy struct {\n\tserver   *bool\n\tclient   *bool\n\tpassword *string\n\tlocal    *string\n\tremote   *string\n\tcert     *string\n\tkey      *string\n}\n\ntype ClientConfig struct {\n\tRunType    string   `json:\"run_type\"`\n\tLocalAddr  string   `json:\"local_addr\"`\n\tLocalPort  int      `json:\"local_port\"`\n\tRemoteAddr string   `json:\"remote_addr\"`\n\tRemotePort int      `json:\"remote_port\"`\n\tPassword   []string `json:\"password\"`\n}\n\ntype TLS struct {\n\tSNI  string `json:\"sni\"`\n\tCert string `json:\"cert\"`\n\tKey  string `json:\"key\"`\n}\n\ntype ServerConfig struct {\n\tRunType    string   `json:\"run_type\"`\n\tLocalAddr  string   `json:\"local_addr\"`\n\tLocalPort  int      `json:\"local_port\"`\n\tRemoteAddr string   `json:\"remote_addr\"`\n\tRemotePort int      `json:\"remote_port\"`\n\tPassword   []string `json:\"password\"`\n\tTLS        `json:\"ssl\"`\n}\n\nfunc (o *easy) Name() string {\n\treturn \"easy\"\n}\n\nfunc (o *easy) Handle() error {\n\tif !*o.server && !*o.client {\n\t\treturn common.NewError(\"empty\")\n\t}\n\tif *o.password == \"\" {\n\t\tlog.Fatal(\"empty password is not allowed\")\n\t}\n\tlog.Info(\"easy mode enabled, trojan-go will NOT use the config file\")\n\tif *o.client {\n\t\tif *o.local == \"\" {\n\t\t\tlog.Warn(\"client local addr is unspecified, using 127.0.0.1:1080\")\n\t\t\t*o.local = \"127.0.0.1:1080\"\n\t\t}\n\t\tlocalHost, localPortStr, err := net.SplitHostPort(*o.local)\n\t\tif err != nil {\n\t\t\tlog.Fatal(common.NewError(\"invalid local addr format:\" + *o.local).Base(err))\n\t\t}\n\t\tremoteHost, remotePortStr, err := net.SplitHostPort(*o.remote)\n\t\tif err != nil {\n\t\t\tlog.Fatal(common.NewError(\"invalid remote addr format:\" + *o.remote).Base(err))\n\t\t}\n\t\tlocalPort, err := strconv.Atoi(localPortStr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tremotePort, err := strconv.Atoi(remotePortStr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tclientConfig := ClientConfig{\n\t\t\tRunType:    \"client\",\n\t\t\tLocalAddr:  localHost,\n\t\t\tLocalPort:  localPort,\n\t\t\tRemoteAddr: remoteHost,\n\t\t\tRemotePort: remotePort,\n\t\t\tPassword: []string{\n\t\t\t\t*o.password,\n\t\t\t},\n\t\t}\n\t\tclientConfigJSON, err := json.Marshal(&clientConfig)\n\t\tcommon.Must(err)\n\t\tlog.Info(\"generated config:\")\n\t\tlog.Info(string(clientConfigJSON))\n\t\tproxy, err := proxy.NewProxyFromConfigData(clientConfigJSON, true)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif err := proxy.Run(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t} else if *o.server {\n\t\tif *o.remote == \"\" {\n\t\t\tlog.Warn(\"server remote addr is unspecified, using 127.0.0.1:80\")\n\t\t\t*o.remote = \"127.0.0.1:80\"\n\t\t}\n\t\tif *o.local == \"\" {\n\t\t\tlog.Warn(\"server local addr is unspecified, using 0.0.0.0:443\")\n\t\t\t*o.local = \"0.0.0.0:443\"\n\t\t}\n\t\tlocalHost, localPortStr, err := net.SplitHostPort(*o.local)\n\t\tif err != nil {\n\t\t\tlog.Fatal(common.NewError(\"invalid local addr format:\" + *o.local).Base(err))\n\t\t}\n\t\tremoteHost, remotePortStr, err := net.SplitHostPort(*o.remote)\n\t\tif err != nil {\n\t\t\tlog.Fatal(common.NewError(\"invalid remote addr format:\" + *o.remote).Base(err))\n\t\t}\n\t\tlocalPort, err := strconv.Atoi(localPortStr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tremotePort, err := strconv.Atoi(remotePortStr)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tserverConfig := ServerConfig{\n\t\t\tRunType:    \"server\",\n\t\t\tLocalAddr:  localHost,\n\t\t\tLocalPort:  localPort,\n\t\t\tRemoteAddr: remoteHost,\n\t\t\tRemotePort: remotePort,\n\t\t\tPassword: []string{\n\t\t\t\t*o.password,\n\t\t\t},\n\t\t\tTLS: TLS{\n\t\t\t\tCert: *o.cert,\n\t\t\t\tKey:  *o.key,\n\t\t\t},\n\t\t}\n\t\tserverConfigJSON, err := json.Marshal(&serverConfig)\n\t\tcommon.Must(err)\n\t\tlog.Info(\"generated json config:\")\n\t\tlog.Info(string(serverConfigJSON))\n\t\tproxy, err := proxy.NewProxyFromConfigData(serverConfigJSON, true)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif err := proxy.Run(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *easy) Priority() int {\n\treturn 50\n}\n\nfunc init() {\n\toption.RegisterHandler(&easy{\n\t\tserver:   flag.Bool(\"server\", false, \"Run a trojan-go server\"),\n\t\tclient:   flag.Bool(\"client\", false, \"Run a trojan-go client\"),\n\t\tpassword: flag.String(\"password\", \"\", \"Password for authentication\"),\n\t\tremote:   flag.String(\"remote\", \"\", \"Remote address, e.g. 127.0.0.1:12345\"),\n\t\tlocal:    flag.String(\"local\", \"\", \"Local address, e.g. 127.0.0.1:12345\"),\n\t\tkey:      flag.String(\"key\", \"server.key\", \"Key of the server\"),\n\t\tcert:     flag.String(\"cert\", \"server.crt\", \"Certificates of the server\"),\n\t})\n}\n"
  },
  {
    "path": "example/client.json",
    "content": "{\n    \"run_type\": \"client\",\n    \"local_addr\": \"127.0.0.1\",\n    \"local_port\": 1080,\n    \"remote_addr\": \"your_server\",\n    \"remote_port\": 443,\n    \"password\": [\n        \"your_password\"\n    ],\n    \"ssl\": {\n        \"sni\": \"your-domain-name.com\"\n    },\n    \"mux\": {\n        \"enabled\": true\n    },\n    \"router\": {\n        \"enabled\": true,\n        \"bypass\": [\n            \"geoip:cn\",\n            \"geoip:private\",\n            \"geosite:cn\",\n            \"geosite:private\"\n        ],\n        \"block\": [\n            \"geosite:category-ads\"\n        ],\n        \"proxy\": [\n            \"geosite:geolocation-!cn\"\n        ],\n        \"default_policy\": \"proxy\",\n        \"geoip\": \"/usr/share/trojan-go/geoip.dat\",\n        \"geosite\": \"/usr/share/trojan-go/geosite.dat\"\n    }\n}\n"
  },
  {
    "path": "example/client.yaml",
    "content": "run-type: client\nlocal-addr: 127.0.0.1\nlocal-port: 1080\nremote-addr: your_server\nremote-port: 443\npassword:\n    - your_password\nssl:\n    sni: your-domain-name.com\nmux:\n    enabled: true\nrouter:\n    enabled: true\n    bypass: ['geoip:cn', 'geoip:private', 'geosite:cn', 'geosite:private']\n    block: ['geosite:category-ads']\n    proxy: ['geosite:geolocation-!cn']\n    default_policy: proxy\n    geoip: /usr/share/trojan-go/geoip.dat\n    geosite: /usr/share/trojan-go/geosite.dat\n"
  },
  {
    "path": "example/server.json",
    "content": "{\n    \"run_type\": \"server\",\n    \"local_addr\": \"0.0.0.0\",\n    \"local_port\": 443,\n    \"remote_addr\": \"127.0.0.1\",\n    \"remote_port\": 80,\n    \"password\": [\n        \"your_password\"\n    ],\n    \"ssl\": {\n        \"cert\": \"your_cert.crt\",\n        \"key\": \"your_key.key\",\n        \"sni\": \"your-domain-name.com\"\n    },\n    \"router\": {\n        \"enabled\": true,\n        \"block\": [\n            \"geoip:private\"\n        ],\n        \"geoip\": \"/usr/share/trojan-go/geoip.dat\",\n        \"geosite\": \"/usr/share/trojan-go/geosite.dat\"\n    }\n}"
  },
  {
    "path": "example/server.yaml",
    "content": "run-type: server\nlocal-addr: 0.0.0.0\nlocal-port: 443\nremote-addr: 127.0.0.1\nremote-port: 80\npassword:\n  - your_password\nssl:\n  cert: your_cert.crt\n  key: your_key.key\n  sni: your-domain-name.com\nrouter:\n  enabled: true\n  block:\n    - 'geoip:private'\n  geoip: /usr/share/trojan-go/geoip.dat\n  geosite: /usr/share/trojan-go/geosite.dat\n"
  },
  {
    "path": "example/trojan-go.service",
    "content": "[Unit]\nDescription=Trojan-Go - An unidentifiable mechanism that helps you bypass GFW\nDocumentation=https://p4gefau1t.github.io/trojan-go/\nAfter=network.target nss-lookup.target\n\n[Service]\nUser=nobody\nCapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE\nAmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE\nNoNewPrivileges=true\nExecStart=/usr/bin/trojan-go -config /etc/trojan-go/config.json\nRestart=on-failure\nRestartSec=10s\nLimitNOFILE=infinity\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "example/trojan-go@.service",
    "content": "[Unit]\nDescription=Trojan-Go - An unidentifiable mechanism that helps you bypass GFW\nDocumentation=https://p4gefau1t.github.io/trojan-go/\nAfter=network.target nss-lookup.target\n\n[Service]\nUser=nobody\nCapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE\nAmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE\nNoNewPrivileges=true\nExecStart=/usr/bin/trojan-go -config /etc/trojan-go/%i.json\nRestart=on-failure\nRestartSec=10s\nLimitNOFILE=infinity\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/p4gefau1t/trojan-go\n\ngo 1.17\n\nrequire (\n\tgithub.com/go-sql-driver/mysql v1.6.0\n\tgithub.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4\n\tgithub.com/shadowsocks/go-shadowsocks2 v0.1.5\n\tgithub.com/smartystreets/goconvey v1.6.4\n\tgithub.com/stretchr/testify v1.7.0\n\tgithub.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da\n\tgithub.com/v2fly/v2ray-core/v4 v4.42.1\n\tgithub.com/xtaci/smux v1.5.15\n\tgolang.org/x/net v0.0.0-20210913180222-943fd674d43e\n\tgolang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b\n\tgolang.org/x/time v0.0.0-20210723032227-1f47c861a9ac\n\tgoogle.golang.org/grpc v1.40.0\n\tgoogle.golang.org/protobuf v1.27.1\n\tgopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect\n\tgithub.com/jtolds/gls v4.20.0+incompatible // indirect\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible // indirect\n\tgithub.com/pires/go-proxyproto v0.6.0 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect\n\tgithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect\n\tgithub.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect\n\tgithub.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect\n\tgo.starlark.net v0.0.0-20210602144842-1cdb82c9e17a // indirect\n\tgolang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect\n\tgolang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect\n\tgolang.org/x/text v0.3.6 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=\ngithub.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/lucas-clemente/quic-go v0.23.0 h1:5vFnKtZ6nHDFsc/F3uuiF4T3y/AXaQdxjUqiVw26GZE=\ngithub.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=\ngithub.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=\ngithub.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=\ngithub.com/pires/go-proxyproto v0.6.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4 h1:n9NMHJusHylTmtaJ0Qe0VV9dkTZLiwAxHmrI/l98GeE=\ngithub.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=\ngithub.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=\ngithub.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3MQWYrza7YZXOXgl0Nf0QGFqrOC0BKae1UNAA=\ngithub.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=\ngithub.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=\ngithub.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=\ngithub.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da h1:7x8pJcBTdKTBpQbRjZZc9o6CDquXBbvm9UIrR6ZSRJ4=\ngithub.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=\ngithub.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=\ngithub.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=\ngithub.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc=\ngithub.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre66h7g1BjRKUnLv0HfmmRoz7w=\ngithub.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=\ngithub.com/v2fly/v2ray-core/v4 v4.42.1 h1:oQwW7dSpzx+nY3SrWw25OXVOcXNtwLFZ6BanqMufYdk=\ngithub.com/v2fly/v2ray-core/v4 v4.42.1/go.mod h1:YrWpRau9RYPHZLJXJIoVHjSXwL5DhGuIlTnA1i9FG98=\ngithub.com/xtaci/smux v1.5.15 h1:6hMiXswcleXj5oNfcJc+DXS8Vj36XX2LaX98udog6Kc=\ngithub.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.starlark.net v0.0.0-20210602144842-1cdb82c9e17a h1:wDtSCWGrX9tusypq2Qq9xzaA3Tf/+4D2KaWO+HQvGZE=\ngo.starlark.net v0.0.0-20210602144842-1cdb82c9e17a/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=\ngolang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=\ngolang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=\ngolang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=\ngolang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=\ngolang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "log/golog/buffer/buffer.go",
    "content": "// Buffer-like byte slice\n// Copyright (c) 2017 Fadhli Dzil Ikram\n\npackage buffer\n\n// Buffer type wrap up byte slice built-in type\ntype Buffer []byte\n\n// Reset buffer position to start\nfunc (b *Buffer) Reset() {\n\t*b = Buffer([]byte(*b)[:0])\n}\n\n// Append byte slice to buffer\nfunc (b *Buffer) Append(data []byte) {\n\t*b = append(*b, data...)\n}\n\n// AppendByte to buffer\nfunc (b *Buffer) AppendByte(data byte) {\n\t*b = append(*b, data)\n}\n\n// AppendInt to buffer\nfunc (b *Buffer) AppendInt(val int, width int) {\n\tvar repr [8]byte\n\treprCount := len(repr) - 1\n\tfor val >= 10 || width > 1 {\n\t\treminder := val / 10\n\t\trepr[reprCount] = byte('0' + val - reminder*10)\n\t\tval = reminder\n\t\treprCount--\n\t\twidth--\n\t}\n\trepr[reprCount] = byte('0' + val)\n\tb.Append(repr[reprCount:])\n}\n\n// Bytes return underlying slice data\nfunc (b Buffer) Bytes() []byte {\n\treturn []byte(b)\n}\n"
  },
  {
    "path": "log/golog/buffer/buffer_test.go",
    "content": "// Buffer-like byte slice\n// Copyright (c) 2017 Fadhli Dzil Ikram\n//\n// Test file for buffer\n\npackage buffer\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/smartystreets/goconvey/convey\"\n)\n\nfunc TestBufferAllocation(t *testing.T) {\n\tConvey(\"Given new unallocated buffer\", t, func() {\n\t\tvar buf Buffer\n\n\t\tConvey(\"When appended with data\", func() {\n\t\t\tdata := []byte(\"Hello\")\n\t\t\tbuf.Append(data)\n\n\t\t\tConvey(\"It should have same content as the original data\", func() {\n\t\t\t\tSo(buf.Bytes(), ShouldResemble, data)\n\t\t\t})\n\t\t})\n\n\t\tConvey(\"When appended with single byte\", func() {\n\t\t\tdata := byte('H')\n\t\t\tbuf.AppendByte(data)\n\n\t\t\tConvey(\"It should have 1 byte length\", func() {\n\t\t\t\tSo(len(buf), ShouldEqual, 1)\n\t\t\t})\n\n\t\t\tConvey(\"It should have same content\", func() {\n\t\t\t\tSo(buf.Bytes()[0], ShouldEqual, data)\n\t\t\t})\n\t\t})\n\n\t\tConvey(\"When appended with integer\", func() {\n\t\t\tdata := 12345\n\t\t\trepr := []byte(\"012345\")\n\t\t\tbuf.AppendInt(data, len(repr))\n\n\t\t\tConvey(\"Should have same content with the integer representation\", func() {\n\t\t\t\tSo(buf.Bytes(), ShouldResemble, repr)\n\t\t\t})\n\t\t})\n\t})\n}\n\nfunc TestBufferReset(t *testing.T) {\n\tConvey(\"Given allocated buffer\", t, func() {\n\t\tvar buf Buffer\n\t\tdata := []byte(\"Hello\")\n\t\treplace := []byte(\"World\")\n\t\tbuf.Append(data)\n\n\t\tConvey(\"When buffer reset\", func() {\n\t\t\tbuf.Reset()\n\n\t\t\tConvey(\"It should have zero length\", func() {\n\t\t\t\tSo(len(buf), ShouldEqual, 0)\n\t\t\t})\n\t\t})\n\n\t\tConvey(\"When buffer reset and replaced with another append\", func() {\n\t\t\tbuf.Reset()\n\t\t\tbuf.Append(replace)\n\n\t\t\tConvey(\"It should have same content with the replaced data\", func() {\n\t\t\t\tSo(buf.Bytes(), ShouldResemble, replace)\n\t\t\t})\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "log/golog/colorful/colorful.go",
    "content": "// The color engine for the go-log library\n// Copyright (c) 2017 Fadhli Dzil Ikram\n\npackage colorful\n\nimport (\n\t\"runtime\"\n\n\t\"github.com/p4gefau1t/trojan-go/log/golog/buffer\"\n)\n\n// ColorBuffer add color option to buffer append\ntype ColorBuffer struct {\n\tbuffer.Buffer\n}\n\n// color palette map\nvar (\n\tcolorOff    = []byte(\"\\033[0m\")\n\tcolorRed    = []byte(\"\\033[0;31m\")\n\tcolorGreen  = []byte(\"\\033[0;32m\")\n\tcolorOrange = []byte(\"\\033[0;33m\")\n\tcolorBlue   = []byte(\"\\033[0;34m\")\n\tcolorPurple = []byte(\"\\033[0;35m\")\n\tcolorCyan   = []byte(\"\\033[0;36m\")\n\tcolorGray   = []byte(\"\\033[0;37m\")\n)\n\nfunc init() {\n\tif runtime.GOOS != \"linux\" {\n\t\tcolorOff = []byte(\"\")\n\t\tcolorRed = []byte(\"\")\n\t\tcolorGreen = []byte(\"\")\n\t\tcolorOrange = []byte(\"\")\n\t\tcolorBlue = []byte(\"\")\n\t\tcolorPurple = []byte(\"\")\n\t\tcolorCyan = []byte(\"\")\n\t\tcolorGray = []byte(\"\")\n\t}\n}\n\n// Off apply no color to the data\nfunc (cb *ColorBuffer) Off() {\n\tcb.Append(colorOff)\n}\n\n// Red apply red color to the data\nfunc (cb *ColorBuffer) Red() {\n\tcb.Append(colorRed)\n}\n\n// Green apply green color to the data\nfunc (cb *ColorBuffer) Green() {\n\tcb.Append(colorGreen)\n}\n\n// Orange apply orange color to the data\nfunc (cb *ColorBuffer) Orange() {\n\tcb.Append(colorOrange)\n}\n\n// Blue apply blue color to the data\nfunc (cb *ColorBuffer) Blue() {\n\tcb.Append(colorBlue)\n}\n\n// Purple apply purple color to the data\nfunc (cb *ColorBuffer) Purple() {\n\tcb.Append(colorPurple)\n}\n\n// Cyan apply cyan color to the data\nfunc (cb *ColorBuffer) Cyan() {\n\tcb.Append(colorCyan)\n}\n\n// Gray apply gray color to the data\nfunc (cb *ColorBuffer) Gray() {\n\tcb.Append(colorGray)\n}\n\n// mixer mix the color on and off byte with the actual data\nfunc mixer(data []byte, color []byte) []byte {\n\tvar result []byte\n\treturn append(append(append(result, color...), data...), colorOff...)\n}\n\n// Red apply red color to the data\nfunc Red(data []byte) []byte {\n\treturn mixer(data, colorRed)\n}\n\n// Green apply green color to the data\nfunc Green(data []byte) []byte {\n\treturn mixer(data, colorGreen)\n}\n\n// Orange apply orange color to the data\nfunc Orange(data []byte) []byte {\n\treturn mixer(data, colorOrange)\n}\n\n// Blue apply blue color to the data\nfunc Blue(data []byte) []byte {\n\treturn mixer(data, colorBlue)\n}\n\n// Purple apply purple color to the data\nfunc Purple(data []byte) []byte {\n\treturn mixer(data, colorPurple)\n}\n\n// Cyan apply cyan color to the data\nfunc Cyan(data []byte) []byte {\n\treturn mixer(data, colorCyan)\n}\n\n// Gray apply gray color to the data\nfunc Gray(data []byte) []byte {\n\treturn mixer(data, colorGray)\n}\n"
  },
  {
    "path": "log/golog/colorful/colorful_test.go",
    "content": "// The color engine for the go-log library\n// Copyright (c) 2017 Fadhli Dzil Ikram\n//\n// Test file\n\npackage colorful\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/smartystreets/goconvey/convey\"\n\n\t\"github.com/p4gefau1t/trojan-go/log/golog/buffer\"\n)\n\nfunc TestColorBuffer(t *testing.T) {\n\tConvey(\"Given empty color buffer and test data\", t, func() {\n\t\tvar cb ColorBuffer\n\t\tvar result buffer.Buffer\n\n\t\t// Add color to the result buffer\n\t\tresult.Append(colorRed)\n\t\tresult.Append(colorGreen)\n\t\tresult.Append(colorOrange)\n\t\tresult.Append(colorBlue)\n\t\tresult.Append(colorPurple)\n\t\tresult.Append(colorCyan)\n\t\tresult.Append(colorGray)\n\t\tresult.Append(colorOff)\n\n\t\tConvey(\"When appended with color\", func() {\n\t\t\tcb.Red()\n\t\t\tcb.Green()\n\t\t\tcb.Orange()\n\t\t\tcb.Blue()\n\t\t\tcb.Purple()\n\t\t\tcb.Cyan()\n\t\t\tcb.Gray()\n\t\t\tcb.Off()\n\n\t\t\tConvey(\"It should have same content with the test data\", func() {\n\t\t\t\tSo(result.Bytes(), ShouldResemble, cb.Bytes())\n\t\t\t})\n\t\t})\n\t})\n}\n\nfunc TestColorMixer(t *testing.T) {\n\tConvey(\"Given mixer test result data\", t, func() {\n\t\tvar (\n\t\t\tdata         = []byte(\"Hello\")\n\t\t\tresultRed    buffer.Buffer\n\t\t\tresultGreen  buffer.Buffer\n\t\t\tresultOrange buffer.Buffer\n\t\t\tresultBlue   buffer.Buffer\n\t\t\tresultPurple buffer.Buffer\n\t\t\tresultCyan   buffer.Buffer\n\t\t\tresultGray   buffer.Buffer\n\t\t)\n\n\t\t// Add result to buffer\n\t\tresultRed.Append(colorRed)\n\t\tresultRed.Append(data)\n\t\tresultRed.Append(colorOff)\n\n\t\tresultGreen.Append(colorGreen)\n\t\tresultGreen.Append(data)\n\t\tresultGreen.Append(colorOff)\n\n\t\tresultOrange.Append(colorOrange)\n\t\tresultOrange.Append(data)\n\t\tresultOrange.Append(colorOff)\n\n\t\tresultBlue.Append(colorBlue)\n\t\tresultBlue.Append(data)\n\t\tresultBlue.Append(colorOff)\n\n\t\tresultPurple.Append(colorPurple)\n\t\tresultPurple.Append(data)\n\t\tresultPurple.Append(colorOff)\n\n\t\tresultCyan.Append(colorCyan)\n\t\tresultCyan.Append(data)\n\t\tresultCyan.Append(colorOff)\n\n\t\tresultGray.Append(colorGray)\n\t\tresultGray.Append(data)\n\t\tresultGray.Append(colorOff)\n\n\t\tConvey(\"It should have same result when data appended with Red color\", func() {\n\t\t\tSo(Red(data), ShouldResemble, resultRed.Bytes())\n\t\t})\n\n\t\tConvey(\"It should have same result when data appended with Green color\", func() {\n\t\t\tSo(Green(data), ShouldResemble, resultGreen.Bytes())\n\t\t})\n\n\t\tConvey(\"It should have same result when data appended with Orange color\", func() {\n\t\t\tSo(Orange(data), ShouldResemble, resultOrange.Bytes())\n\t\t})\n\n\t\tConvey(\"It should have same result when data appended with Blue color\", func() {\n\t\t\tSo(Blue(data), ShouldResemble, resultBlue.Bytes())\n\t\t})\n\n\t\tConvey(\"It should have same result when data appended with Purple color\", func() {\n\t\t\tSo(Purple(data), ShouldResemble, resultPurple.Bytes())\n\t\t})\n\n\t\tConvey(\"It should have same result when data appended with Cyan color\", func() {\n\t\t\tSo(Cyan(data), ShouldResemble, resultCyan.Bytes())\n\t\t})\n\n\t\tConvey(\"It should have same result when data appended with Gray color\", func() {\n\t\t\tSo(Gray(data), ShouldResemble, resultGray.Bytes())\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "log/golog/golog.go",
    "content": "// The colorful and simple logging library\n// Copyright (c) 2017 Fadhli Dzil Ikram\n\npackage golog\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tterminal \"golang.org/x/term\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/log/golog/colorful\"\n)\n\nfunc init() {\n\tlog.RegisterLogger(New(os.Stdout))\n}\n\n// FdWriter interface extends existing io.Writer with file descriptor function\n// support\ntype FdWriter interface {\n\tio.Writer\n\tFd() uintptr\n}\n\n// Logger struct define the underlying storage for single logger\ntype Logger struct {\n\tmu        sync.RWMutex\n\tcolor     bool\n\tout       io.Writer\n\tdebug     bool\n\ttimestamp bool\n\tquiet     bool\n\tbuf       colorful.ColorBuffer\n\tlogLevel  int32\n}\n\n// Prefix struct define plain and color byte\ntype Prefix struct {\n\tPlain []byte\n\tColor []byte\n\tFile  bool\n}\n\nvar (\n\t// Plain prefix template\n\tplainFatal = []byte(\"[FATAL] \")\n\tplainError = []byte(\"[ERROR] \")\n\tplainWarn  = []byte(\"[WARN]  \")\n\tplainInfo  = []byte(\"[INFO]  \")\n\tplainDebug = []byte(\"[DEBUG] \")\n\tplainTrace = []byte(\"[TRACE] \")\n\n\t// FatalPrefix show fatal prefix\n\tFatalPrefix = Prefix{\n\t\tPlain: plainFatal,\n\t\tColor: colorful.Red(plainFatal),\n\t\tFile:  true,\n\t}\n\n\t// ErrorPrefix show error prefix\n\tErrorPrefix = Prefix{\n\t\tPlain: plainError,\n\t\tColor: colorful.Red(plainError),\n\t\tFile:  true,\n\t}\n\n\t// WarnPrefix show warn prefix\n\tWarnPrefix = Prefix{\n\t\tPlain: plainWarn,\n\t\tColor: colorful.Orange(plainWarn),\n\t}\n\n\t// InfoPrefix show info prefix\n\tInfoPrefix = Prefix{\n\t\tPlain: plainInfo,\n\t\tColor: colorful.Green(plainInfo),\n\t}\n\n\t// DebugPrefix show info prefix\n\tDebugPrefix = Prefix{\n\t\tPlain: plainDebug,\n\t\tColor: colorful.Purple(plainDebug),\n\t\tFile:  true,\n\t}\n\n\t// TracePrefix show info prefix\n\tTracePrefix = Prefix{\n\t\tPlain: plainTrace,\n\t\tColor: colorful.Cyan(plainTrace),\n\t}\n)\n\n// New returns new Logger instance with predefined writer output and\n// automatically detect terminal coloring support\nfunc New(out FdWriter) *Logger {\n\treturn &Logger{\n\t\tcolor:     terminal.IsTerminal(int(out.Fd())),\n\t\tout:       out,\n\t\ttimestamp: true,\n\t}\n}\n\nfunc (l *Logger) SetLogLevel(level log.LogLevel) {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tatomic.StoreInt32(&l.logLevel, int32(level))\n}\n\nfunc (l *Logger) SetOutput(w io.Writer) {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.color = false\n\tif fdw, ok := w.(FdWriter); ok {\n\t\tl.color = terminal.IsTerminal(int(fdw.Fd()))\n\t}\n\tl.out = w\n}\n\n// WithColor explicitly turn on colorful features on the log\nfunc (l *Logger) WithColor() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.color = true\n\treturn l\n}\n\n// WithoutColor explicitly turn off colorful features on the log\nfunc (l *Logger) WithoutColor() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.color = false\n\treturn l\n}\n\n// WithDebug turn on debugging output on the log to reveal debug and trace level\nfunc (l *Logger) WithDebug() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.debug = true\n\treturn l\n}\n\n// WithoutDebug turn off debugging output on the log\nfunc (l *Logger) WithoutDebug() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.debug = false\n\treturn l\n}\n\n// IsDebug check the state of debugging output\nfunc (l *Logger) IsDebug() bool {\n\tl.mu.RLock()\n\tdefer l.mu.RUnlock()\n\treturn l.debug\n}\n\n// WithTimestamp turn on timestamp output on the log\nfunc (l *Logger) WithTimestamp() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.timestamp = true\n\treturn l\n}\n\n// WithoutTimestamp turn off timestamp output on the log\nfunc (l *Logger) WithoutTimestamp() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.timestamp = false\n\treturn l\n}\n\n// Quiet turn off all log output\nfunc (l *Logger) Quiet() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.quiet = true\n\treturn l\n}\n\n// NoQuiet turn on all log output\nfunc (l *Logger) NoQuiet() *Logger {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\tl.quiet = false\n\treturn l\n}\n\n// IsQuiet check for quiet state\nfunc (l *Logger) IsQuiet() bool {\n\tl.mu.RLock()\n\tdefer l.mu.RUnlock()\n\treturn l.quiet\n}\n\n// Output print the actual value\nfunc (l *Logger) Output(depth int, prefix Prefix, data string) error {\n\t// Check if quiet is requested, and try to return no error and be quiet\n\tif l.IsQuiet() {\n\t\treturn nil\n\t}\n\t// Get current time\n\tnow := time.Now()\n\t// Temporary storage for file and line tracing\n\tvar file string\n\tvar line int\n\tvar fn string\n\t// Check if the specified prefix needs to be included with file logging\n\tif prefix.File {\n\t\tvar ok bool\n\t\tvar pc uintptr\n\n\t\t// Get the caller filename and line\n\t\tif pc, file, line, ok = runtime.Caller(depth + 2); !ok {\n\t\t\tfile = \"<unknown file>\"\n\t\t\tfn = \"<unknown function>\"\n\t\t\tline = 0\n\t\t} else {\n\t\t\tfile = filepath.Base(file)\n\t\t\tfn = runtime.FuncForPC(pc).Name()\n\t\t}\n\t}\n\t// Acquire exclusive access to the shared buffer\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\t// Reset buffer so it start from the beginning\n\tl.buf.Reset()\n\t// Write prefix to the buffer\n\tif l.color {\n\t\tl.buf.Append(prefix.Color)\n\t} else {\n\t\tl.buf.Append(prefix.Plain)\n\t}\n\t// Check if the log require timestamping\n\tif l.timestamp {\n\t\t// Print timestamp color if color enabled\n\t\tif l.color {\n\t\t\tl.buf.Blue()\n\t\t}\n\t\t// Print date and time\n\t\tyear, month, day := now.Date()\n\t\tl.buf.AppendInt(year, 4)\n\t\tl.buf.AppendByte('/')\n\t\tl.buf.AppendInt(int(month), 2)\n\t\tl.buf.AppendByte('/')\n\t\tl.buf.AppendInt(day, 2)\n\t\tl.buf.AppendByte(' ')\n\t\thour, min, sec := now.Clock()\n\t\tl.buf.AppendInt(hour, 2)\n\t\tl.buf.AppendByte(':')\n\t\tl.buf.AppendInt(min, 2)\n\t\tl.buf.AppendByte(':')\n\t\tl.buf.AppendInt(sec, 2)\n\t\tl.buf.AppendByte(' ')\n\t\t// Print reset color if color enabled\n\t\tif l.color {\n\t\t\tl.buf.Off()\n\t\t}\n\t}\n\t// Add caller filename and line if enabled\n\tif prefix.File {\n\t\t// Print color start if enabled\n\t\tif l.color {\n\t\t\tl.buf.Orange()\n\t\t}\n\t\t// Print filename and line\n\t\tl.buf.Append([]byte(fn))\n\t\tl.buf.AppendByte(':')\n\t\tl.buf.Append([]byte(file))\n\t\tl.buf.AppendByte(':')\n\t\tl.buf.AppendInt(line, 0)\n\t\tl.buf.AppendByte(' ')\n\t\t// Print color stop\n\t\tif l.color {\n\t\t\tl.buf.Off()\n\t\t}\n\t}\n\t// Print the actual string data from caller\n\tl.buf.Append([]byte(data))\n\tif len(data) == 0 || data[len(data)-1] != '\\n' {\n\t\tl.buf.AppendByte('\\n')\n\t}\n\t// Flush buffer to output\n\t_, err := l.out.Write(l.buf.Buffer)\n\treturn err\n}\n\n// Fatal print fatal message to output and quit the application with status 1\nfunc (l *Logger) Fatal(v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 4 {\n\t\tl.Output(1, FatalPrefix, fmt.Sprintln(v...))\n\t}\n\tos.Exit(1)\n}\n\n// Fatalf print formatted fatal message to output and quit the application\n// with status 1\nfunc (l *Logger) Fatalf(format string, v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 4 {\n\t\tl.Output(1, FatalPrefix, fmt.Sprintf(format, v...))\n\t}\n\tos.Exit(1)\n}\n\n// Error print error message to output\nfunc (l *Logger) Error(v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 3 {\n\t\tl.Output(1, ErrorPrefix, fmt.Sprintln(v...))\n\t}\n}\n\n// Errorf print formatted error message to output\nfunc (l *Logger) Errorf(format string, v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 3 {\n\t\tl.Output(1, ErrorPrefix, fmt.Sprintf(format, v...))\n\t}\n}\n\n// Warn print warning message to output\nfunc (l *Logger) Warn(v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 2 {\n\t\tl.Output(1, WarnPrefix, fmt.Sprintln(v...))\n\t}\n}\n\n// Warnf print formatted warning message to output\nfunc (l *Logger) Warnf(format string, v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 2 {\n\t\tl.Output(1, WarnPrefix, fmt.Sprintf(format, v...))\n\t}\n}\n\n// Info print informational message to output\nfunc (l *Logger) Info(v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 1 {\n\t\tl.Output(1, InfoPrefix, fmt.Sprintln(v...))\n\t}\n}\n\n// Infof print formatted informational message to output\nfunc (l *Logger) Infof(format string, v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) <= 1 {\n\t\tl.Output(1, InfoPrefix, fmt.Sprintf(format, v...))\n\t}\n}\n\n// Debug print debug message to output if debug output enabled\nfunc (l *Logger) Debug(v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) == 0 {\n\t\tl.Output(1, DebugPrefix, fmt.Sprintln(v...))\n\t}\n}\n\n// Debugf print formatted debug message to output if debug output enabled\nfunc (l *Logger) Debugf(format string, v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) == 0 {\n\t\tl.Output(1, DebugPrefix, fmt.Sprintf(format, v...))\n\t}\n}\n\n// Trace print trace message to output if debug output enabled\nfunc (l *Logger) Trace(v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) == 0 {\n\t\tl.Output(1, TracePrefix, fmt.Sprintln(v...))\n\t}\n}\n\n// Tracef print formatted trace message to output if debug output enabled\nfunc (l *Logger) Tracef(format string, v ...interface{}) {\n\tif atomic.LoadInt32(&l.logLevel) == 0 {\n\t\tl.Output(1, TracePrefix, fmt.Sprintf(format, v...))\n\t}\n}\n"
  },
  {
    "path": "log/log.go",
    "content": "package log\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\n// LogLevel how much log to dump\n// 0: ALL; 1: INFO; 2: WARN; 3: ERROR; 4: FATAL; 5: OFF\ntype LogLevel int\n\nconst (\n\tAllLevel   LogLevel = 0\n\tInfoLevel  LogLevel = 1\n\tWarnLevel  LogLevel = 2\n\tErrorLevel LogLevel = 3\n\tFatalLevel LogLevel = 4\n\tOffLevel   LogLevel = 5\n)\n\ntype Logger interface {\n\tFatal(v ...interface{})\n\tFatalf(format string, v ...interface{})\n\tError(v ...interface{})\n\tErrorf(format string, v ...interface{})\n\tWarn(v ...interface{})\n\tWarnf(format string, v ...interface{})\n\tInfo(v ...interface{})\n\tInfof(format string, v ...interface{})\n\tDebug(v ...interface{})\n\tDebugf(format string, v ...interface{})\n\tTrace(v ...interface{})\n\tTracef(format string, v ...interface{})\n\tSetLogLevel(level LogLevel)\n\tSetOutput(io.Writer)\n}\n\nvar logger Logger = &EmptyLogger{}\n\ntype EmptyLogger struct{}\n\nfunc (l *EmptyLogger) SetLogLevel(LogLevel) {}\n\nfunc (l *EmptyLogger) Fatal(v ...interface{}) { os.Exit(1) }\n\nfunc (l *EmptyLogger) Fatalf(format string, v ...interface{}) { os.Exit(1) }\n\nfunc (l *EmptyLogger) Error(v ...interface{}) {}\n\nfunc (l *EmptyLogger) Errorf(format string, v ...interface{}) {}\n\nfunc (l *EmptyLogger) Warn(v ...interface{}) {}\n\nfunc (l *EmptyLogger) Warnf(format string, v ...interface{}) {}\n\nfunc (l *EmptyLogger) Info(v ...interface{}) {}\n\nfunc (l *EmptyLogger) Infof(format string, v ...interface{}) {}\n\nfunc (l *EmptyLogger) Debug(v ...interface{}) {}\n\nfunc (l *EmptyLogger) Debugf(format string, v ...interface{}) {}\n\nfunc (l *EmptyLogger) Trace(v ...interface{}) {}\n\nfunc (l *EmptyLogger) Tracef(format string, v ...interface{}) {}\n\nfunc (l *EmptyLogger) SetOutput(w io.Writer) {}\n\nfunc Error(v ...interface{}) {\n\tlogger.Error(v...)\n}\n\nfunc Errorf(format string, v ...interface{}) {\n\tlogger.Errorf(format, v...)\n}\n\nfunc Warn(v ...interface{}) {\n\tlogger.Warn(v...)\n}\n\nfunc Warnf(format string, v ...interface{}) {\n\tlogger.Warnf(format, v...)\n}\n\nfunc Info(v ...interface{}) {\n\tlogger.Info(v...)\n}\n\nfunc Infof(format string, v ...interface{}) {\n\tlogger.Infof(format, v...)\n}\n\nfunc Debug(v ...interface{}) {\n\tlogger.Debug(v...)\n}\n\nfunc Debugf(format string, v ...interface{}) {\n\tlogger.Debugf(format, v...)\n}\n\nfunc Trace(v ...interface{}) {\n\tlogger.Trace(v...)\n}\n\nfunc Tracef(format string, v ...interface{}) {\n\tlogger.Tracef(format, v...)\n}\n\nfunc Fatal(v ...interface{}) {\n\tlogger.Fatal(v...)\n}\n\nfunc Fatalf(format string, v ...interface{}) {\n\tlogger.Fatalf(format, v...)\n}\n\nfunc SetLogLevel(level LogLevel) {\n\tlogger.SetLogLevel(level)\n}\n\nfunc SetOutput(w io.Writer) {\n\tlogger.SetOutput(w)\n}\n\nfunc RegisterLogger(l Logger) {\n\tlogger = l\n}\n"
  },
  {
    "path": "log/simplelog/simplelog.go",
    "content": "package simplelog\n\nimport (\n\t\"io\"\n\tgolog \"log\"\n\t\"os\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\nfunc init() {\n\tlog.RegisterLogger(&SimpleLogger{})\n}\n\ntype SimpleLogger struct {\n\tlogLevel log.LogLevel\n}\n\nfunc (l *SimpleLogger) SetLogLevel(level log.LogLevel) {\n\tl.logLevel = level\n}\n\nfunc (l *SimpleLogger) Fatal(v ...interface{}) {\n\tif l.logLevel <= log.FatalLevel {\n\t\tgolog.Fatal(v...)\n\t}\n\tos.Exit(1)\n}\n\nfunc (l *SimpleLogger) Fatalf(format string, v ...interface{}) {\n\tif l.logLevel <= log.FatalLevel {\n\t\tgolog.Fatalf(format, v...)\n\t}\n\tos.Exit(1)\n}\n\nfunc (l *SimpleLogger) Error(v ...interface{}) {\n\tif l.logLevel <= log.ErrorLevel {\n\t\tgolog.Println(v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Errorf(format string, v ...interface{}) {\n\tif l.logLevel <= log.ErrorLevel {\n\t\tgolog.Printf(format, v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Warn(v ...interface{}) {\n\tif l.logLevel <= log.WarnLevel {\n\t\tgolog.Println(v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Warnf(format string, v ...interface{}) {\n\tif l.logLevel <= log.WarnLevel {\n\t\tgolog.Printf(format, v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Info(v ...interface{}) {\n\tif l.logLevel <= log.InfoLevel {\n\t\tgolog.Println(v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Infof(format string, v ...interface{}) {\n\tif l.logLevel <= log.InfoLevel {\n\t\tgolog.Printf(format, v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Debug(v ...interface{}) {\n\tif l.logLevel <= log.AllLevel {\n\t\tgolog.Println(v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Debugf(format string, v ...interface{}) {\n\tif l.logLevel <= log.AllLevel {\n\t\tgolog.Printf(format, v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Trace(v ...interface{}) {\n\tif l.logLevel <= log.AllLevel {\n\t\tgolog.Println(v...)\n\t}\n}\n\nfunc (l *SimpleLogger) Tracef(format string, v ...interface{}) {\n\tif l.logLevel <= log.AllLevel {\n\t\tgolog.Printf(format, v...)\n\t}\n}\n\nfunc (l *SimpleLogger) SetOutput(io.Writer) {\n\t// do nothing\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t_ \"github.com/p4gefau1t/trojan-go/component\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/option\"\n)\n\nfunc main() {\n\tflag.Parse()\n\tfor {\n\t\th, err := option.PopOptionHandler()\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"invalid options\")\n\t\t}\n\t\terr = h.Handle()\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "option/option.go",
    "content": "package option\n\nimport \"github.com/p4gefau1t/trojan-go/common\"\n\ntype Handler interface {\n\tName() string\n\tHandle() error\n\tPriority() int\n}\n\nvar handlers = make(map[string]Handler)\n\nfunc RegisterHandler(h Handler) {\n\thandlers[h.Name()] = h\n}\n\nfunc PopOptionHandler() (Handler, error) {\n\tvar maxHandler Handler = nil\n\tfor _, h := range handlers {\n\t\tif maxHandler == nil || maxHandler.Priority() < h.Priority() {\n\t\t\tmaxHandler = h\n\t\t}\n\t}\n\tif maxHandler == nil {\n\t\treturn nil, common.NewError(\"no option left\")\n\t}\n\tdelete(handlers, maxHandler.Name())\n\treturn maxHandler, nil\n}\n"
  },
  {
    "path": "proxy/client/client.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/adapter\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/http\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/mux\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/router\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/shadowsocks\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/simplesocks\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/socks\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/tls\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/websocket\"\n)\n\nconst Name = \"CLIENT\"\n\n// GenerateClientTree generate general outbound protocol stack\nfunc GenerateClientTree(transportPlugin bool, muxEnabled bool, wsEnabled bool, ssEnabled bool, routerEnabled bool) []string {\n\tclientStack := []string{transport.Name}\n\tif !transportPlugin {\n\t\tclientStack = append(clientStack, tls.Name)\n\t}\n\tif wsEnabled {\n\t\tclientStack = append(clientStack, websocket.Name)\n\t}\n\tif ssEnabled {\n\t\tclientStack = append(clientStack, shadowsocks.Name)\n\t}\n\tclientStack = append(clientStack, trojan.Name)\n\tif muxEnabled {\n\t\tclientStack = append(clientStack, []string{mux.Name, simplesocks.Name}...)\n\t}\n\tif routerEnabled {\n\t\tclientStack = append(clientStack, router.Name)\n\t}\n\treturn clientStack\n}\n\nfunc init() {\n\tproxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {\n\t\tcfg := config.FromContext(ctx, Name).(*Config)\n\t\tadapterServer, err := adapter.NewServer(ctx, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tctx, cancel := context.WithCancel(ctx)\n\n\t\troot := &proxy.Node{\n\t\t\tName:       adapter.Name,\n\t\t\tNext:       make(map[string]*proxy.Node),\n\t\t\tIsEndpoint: false,\n\t\t\tContext:    ctx,\n\t\t\tServer:     adapterServer,\n\t\t}\n\n\t\troot.BuildNext(http.Name).IsEndpoint = true\n\t\troot.BuildNext(socks.Name).IsEndpoint = true\n\n\t\tclientStack := GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, cfg.Router.Enabled)\n\t\tc, err := proxy.CreateClientStack(ctx, clientStack)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\ts := proxy.FindAllEndpoints(root)\n\t\treturn proxy.NewProxy(ctx, cancel, s, c), nil\n\t})\n}\n"
  },
  {
    "path": "proxy/client/config.go",
    "content": "package client\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype MuxConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype WebsocketConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype RouterConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype ShadowsocksConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype TransportPluginConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype Config struct {\n\tMux             MuxConfig             `json:\"mux\" yaml:\"mux\"`\n\tWebsocket       WebsocketConfig       `json:\"websocket\" yaml:\"websocket\"`\n\tRouter          RouterConfig          `json:\"router\" yaml:\"router\"`\n\tShadowsocks     ShadowsocksConfig     `json:\"shadowsocks\" yaml:\"shadowsocks\"`\n\tTransportPlugin TransportPluginConfig `json:\"transport_plugin\" yaml:\"transport-plugin\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(Config)\n\t})\n}\n"
  },
  {
    "path": "proxy/config.go",
    "content": "package proxy\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tRunType  string `json:\"run_type\" yaml:\"run-type\"`\n\tLogLevel int    `json:\"log_level\" yaml:\"log-level\"`\n\tLogFile  string `json:\"log_file\" yaml:\"log-file\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tLogLevel: 1,\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "proxy/custom/config.go",
    "content": "package custom\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\nconst Name = \"CUSTOM\"\n\ntype NodeConfig struct {\n\tProtocol string      `json:\"protocol\" yaml:\"protocol\"`\n\tTag      string      `json:\"tag\" yaml:\"tag\"`\n\tConfig   interface{} `json:\"config\" yaml:\"config\"`\n}\n\ntype StackConfig struct {\n\tPath [][]string   `json:\"path\" yaml:\"path\"`\n\tNode []NodeConfig `json:\"node\" yaml:\"node\"`\n}\n\ntype Config struct {\n\tInbound  StackConfig `json:\"inbound\" yaml:\"inbound\"`\n\tOutbound StackConfig `json:\"outbound\" yaml:\"outbound\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(Config)\n\t})\n}\n"
  },
  {
    "path": "proxy/custom/custom.go",
    "content": "package custom\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"gopkg.in/yaml.v3\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nfunc convert(i interface{}) interface{} {\n\tswitch x := i.(type) {\n\tcase map[interface{}]interface{}:\n\t\tm2 := map[string]interface{}{}\n\t\tfor k, v := range x {\n\t\t\tm2[k.(string)] = convert(v)\n\t\t}\n\t\treturn m2\n\tcase []interface{}:\n\t\tfor i, v := range x {\n\t\t\tx[i] = convert(v)\n\t\t}\n\t}\n\treturn i\n}\n\nfunc buildNodes(ctx context.Context, nodeConfigList []NodeConfig) (map[string]*proxy.Node, error) {\n\tnodes := make(map[string]*proxy.Node)\n\tfor _, nodeCfg := range nodeConfigList {\n\t\tnodeCfg.Protocol = strings.ToUpper(nodeCfg.Protocol)\n\t\tif _, err := tunnel.GetTunnel(nodeCfg.Protocol); err != nil {\n\t\t\treturn nil, common.NewError(\"invalid protocol name:\" + nodeCfg.Protocol)\n\t\t}\n\t\tdata, err := yaml.Marshal(nodeCfg.Config)\n\t\tcommon.Must(err)\n\t\tnodeContext, err := config.WithYAMLConfig(ctx, data)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to parse config data for \" + nodeCfg.Tag + \" with protocol\" + nodeCfg.Protocol).Base(err)\n\t\t}\n\t\tnode := &proxy.Node{\n\t\t\tName:    nodeCfg.Protocol,\n\t\t\tNext:    make(map[string]*proxy.Node),\n\t\t\tContext: nodeContext,\n\t\t}\n\t\tnodes[nodeCfg.Tag] = node\n\t}\n\treturn nodes, nil\n}\n\nfunc init() {\n\tproxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {\n\t\tcfg := config.FromContext(ctx, Name).(*Config)\n\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tsuccess := false\n\t\tdefer func() {\n\t\t\tif !success {\n\t\t\t\tcancel()\n\t\t\t}\n\t\t}()\n\t\t// inbound\n\t\tnodes, err := buildNodes(ctx, cfg.Inbound.Node)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar root *proxy.Node\n\t\t// build server tree\n\t\tfor _, path := range cfg.Inbound.Path {\n\t\t\tvar lastNode *proxy.Node\n\t\t\tfor _, tag := range path {\n\t\t\t\tif _, found := nodes[tag]; !found {\n\t\t\t\t\treturn nil, common.NewError(\"invalid node tag: \" + tag)\n\t\t\t\t}\n\t\t\t\tif lastNode == nil {\n\t\t\t\t\tif root == nil {\n\t\t\t\t\t\tlastNode = nodes[tag]\n\t\t\t\t\t\troot = lastNode\n\t\t\t\t\t\tt, err := tunnel.GetTunnel(root.Name)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, common.NewError(\"failed to find root tunnel\").Base(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\ts, err := t.NewServer(root.Context, nil)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, common.NewError(\"failed to init root server\").Base(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\troot.Server = s\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlastNode = root\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlastNode = lastNode.LinkNextNode(nodes[tag])\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastNode.IsEndpoint = true\n\t\t}\n\n\t\tservers := proxy.FindAllEndpoints(root)\n\n\t\tif len(cfg.Outbound.Path) != 1 {\n\t\t\treturn nil, common.NewError(\"there must be only 1 path for outbound protocol stack\")\n\t\t}\n\n\t\t// outbound\n\t\tnodes, err = buildNodes(ctx, cfg.Outbound.Node)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// build client stack\n\t\tvar client tunnel.Client\n\t\tfor _, tag := range cfg.Outbound.Path[0] {\n\t\t\tif _, found := nodes[tag]; !found {\n\t\t\t\treturn nil, common.NewError(\"invalid node tag: \" + tag)\n\t\t\t}\n\t\t\tt, err := tunnel.GetTunnel(nodes[tag].Name)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, common.NewError(\"invalid tunnel name\").Base(err)\n\t\t\t}\n\t\t\tclient, err = t.NewClient(nodes[tag].Context, client)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, common.NewError(\"failed to create client\").Base(err)\n\t\t\t}\n\t\t}\n\n\t\tsuccess = true\n\t\treturn proxy.NewProxy(ctx, cancel, servers, client), nil\n\t})\n}\n"
  },
  {
    "path": "proxy/forward/forward.go",
    "content": "package forward\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n\t\"github.com/p4gefau1t/trojan-go/proxy/client\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/dokodemo\"\n)\n\nconst Name = \"FORWARD\"\n\nfunc init() {\n\tproxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {\n\t\tcfg := config.FromContext(ctx, Name).(*client.Config)\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tserverStack := []string{dokodemo.Name}\n\t\tclientStack := client.GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, cfg.Router.Enabled)\n\t\tc, err := proxy.CreateClientStack(ctx, clientStack)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\ts, err := proxy.CreateServerStack(ctx, serverStack)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\treturn proxy.NewProxy(ctx, cancel, []tunnel.Server{s}, c), nil\n\t})\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(client.Config)\n\t})\n}\n"
  },
  {
    "path": "proxy/nat/nat.go",
    "content": "//go:build linux\n// +build linux\n\npackage nat\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n\t\"github.com/p4gefau1t/trojan-go/proxy/client\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/tproxy\"\n)\n\nconst Name = \"NAT\"\n\nfunc init() {\n\tproxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {\n\t\tcfg := config.FromContext(ctx, Name).(*client.Config)\n\t\tif cfg.Router.Enabled {\n\t\t\treturn nil, common.NewError(\"router is not allowed in nat mode\")\n\t\t}\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tserverStack := []string{tproxy.Name}\n\t\tclientStack := client.GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, false)\n\t\tc, err := proxy.CreateClientStack(ctx, clientStack)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\ts, err := proxy.CreateServerStack(ctx, serverStack)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\treturn proxy.NewProxy(ctx, cancel, []tunnel.Server{s}, c), nil\n\t})\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(client.Config)\n\t})\n}\n"
  },
  {
    "path": "proxy/nat/nat_stub.go",
    "content": "package nat\n"
  },
  {
    "path": "proxy/option.go",
    "content": "package proxy\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/constant\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/option\"\n)\n\ntype Option struct {\n\tpath *string\n}\n\nfunc (o *Option) Name() string {\n\treturn Name\n}\n\nfunc detectAndReadConfig(file string) ([]byte, bool, error) {\n\tisJSON := false\n\tswitch {\n\tcase strings.HasSuffix(file, \".json\"):\n\t\tisJSON = true\n\tcase strings.HasSuffix(file, \".yaml\"), strings.HasSuffix(file, \".yml\"):\n\t\tisJSON = false\n\tdefault:\n\t\tlog.Fatalf(\"unsupported config format: %s. use .yaml or .json instead.\", file)\n\t}\n\n\tdata, err := ioutil.ReadFile(file)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\treturn data, isJSON, nil\n}\n\nfunc (o *Option) Handle() error {\n\tdefaultConfigPath := []string{\n\t\t\"config.json\",\n\t\t\"config.yml\",\n\t\t\"config.yaml\",\n\t}\n\n\tisJSON := false\n\tvar data []byte\n\tvar err error\n\n\tswitch *o.path {\n\tcase \"\":\n\t\tlog.Warn(\"no specified config file, use default path to detect config file\")\n\t\tfor _, file := range defaultConfigPath {\n\t\t\tlog.Warn(\"try to load config from default path:\", file)\n\t\t\tdata, isJSON, err = detectAndReadConfig(file)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\tdefault:\n\t\tdata, isJSON, err = detectAndReadConfig(*o.path)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\tif data != nil {\n\t\tlog.Info(\"trojan-go\", constant.Version, \"initializing\")\n\t\tproxy, err := NewProxyFromConfigData(data, isJSON)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\terr = proxy.Run()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\tlog.Fatal(\"no valid config\")\n\treturn nil\n}\n\nfunc (o *Option) Priority() int {\n\treturn -1\n}\n\nfunc init() {\n\toption.RegisterHandler(&Option{\n\t\tpath: flag.String(\"config\", \"\", \"Trojan-Go config filename (.yaml/.yml/.json)\"),\n\t})\n\toption.RegisterHandler(&StdinOption{\n\t\tformat:       flag.String(\"stdin-format\", \"disabled\", \"Read from standard input (yaml/json)\"),\n\t\tsuppressHint: flag.Bool(\"stdin-suppress-hint\", false, \"Suppress hint text\"),\n\t})\n}\n\ntype StdinOption struct {\n\tformat       *string\n\tsuppressHint *bool\n}\n\nfunc (o *StdinOption) Name() string {\n\treturn Name + \"_STDIN\"\n}\n\nfunc (o *StdinOption) Handle() error {\n\tisJSON, e := o.isFormatJson()\n\tif e != nil {\n\t\treturn e\n\t}\n\n\tif o.suppressHint == nil || !*o.suppressHint {\n\t\tfmt.Printf(\"Trojan-Go %s (%s/%s)\\n\", constant.Version, runtime.GOOS, runtime.GOARCH)\n\t\tif isJSON {\n\t\t\tfmt.Println(\"Reading JSON configuration from stdin.\")\n\t\t} else {\n\t\t\tfmt.Println(\"Reading YAML configuration from stdin.\")\n\t\t}\n\t}\n\n\tdata, e := ioutil.ReadAll(bufio.NewReader(os.Stdin))\n\tif e != nil {\n\t\tlog.Fatalf(\"Failed to read from stdin: %s\", e.Error())\n\t}\n\n\tproxy, err := NewProxyFromConfigData(data, isJSON)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\terr = proxy.Run()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\treturn nil\n}\n\nfunc (o *StdinOption) Priority() int {\n\treturn 0\n}\n\nfunc (o *StdinOption) isFormatJson() (isJson bool, e error) {\n\tif o.format == nil {\n\t\treturn false, common.NewError(\"format specifier is nil\")\n\t}\n\tif *o.format == \"disabled\" {\n\t\treturn false, common.NewError(\"reading from stdin is disabled\")\n\t}\n\treturn strings.ToLower(*o.format) == \"json\", nil\n}\n"
  },
  {
    "path": "proxy/proxy.go",
    "content": "package proxy\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"PROXY\"\n\nconst (\n\tMaxPacketSize = 1024 * 8\n)\n\n// Proxy relay connections and packets\ntype Proxy struct {\n\tsources []tunnel.Server\n\tsink    tunnel.Client\n\tctx     context.Context\n\tcancel  context.CancelFunc\n}\n\nfunc (p *Proxy) Run() error {\n\tp.relayConnLoop()\n\tp.relayPacketLoop()\n\t<-p.ctx.Done()\n\treturn nil\n}\n\nfunc (p *Proxy) Close() error {\n\tp.cancel()\n\tp.sink.Close()\n\tfor _, source := range p.sources {\n\t\tsource.Close()\n\t}\n\treturn nil\n}\n\nfunc (p *Proxy) relayConnLoop() {\n\tfor _, source := range p.sources {\n\t\tgo func(source tunnel.Server) {\n\t\t\tfor {\n\t\t\t\tinbound, err := source.AcceptConn(nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-p.ctx.Done():\n\t\t\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t\tlog.Error(common.NewError(\"failed to accept connection\").Base(err))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgo func(inbound tunnel.Conn) {\n\t\t\t\t\tdefer inbound.Close()\n\t\t\t\t\toutbound, err := p.sink.DialConn(inbound.Metadata().Address, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"proxy failed to dial connection\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tdefer outbound.Close()\n\t\t\t\t\terrChan := make(chan error, 2)\n\t\t\t\t\tcopyConn := func(a, b net.Conn) {\n\t\t\t\t\t\t_, err := io.Copy(a, b)\n\t\t\t\t\t\terrChan <- err\n\t\t\t\t\t}\n\t\t\t\t\tgo copyConn(inbound, outbound)\n\t\t\t\t\tgo copyConn(outbound, inbound)\n\t\t\t\t\tselect {\n\t\t\t\t\tcase err = <-errChan:\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Error(err)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-p.ctx.Done():\n\t\t\t\t\t\tlog.Debug(\"shutting down conn relay\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlog.Debug(\"conn relay ends\")\n\t\t\t\t}(inbound)\n\t\t\t}\n\t\t}(source)\n\t}\n}\n\nfunc (p *Proxy) relayPacketLoop() {\n\tfor _, source := range p.sources {\n\t\tgo func(source tunnel.Server) {\n\t\t\tfor {\n\t\t\t\tinbound, err := source.AcceptPacket(nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-p.ctx.Done():\n\t\t\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t\tlog.Error(common.NewError(\"failed to accept packet\").Base(err))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgo func(inbound tunnel.PacketConn) {\n\t\t\t\t\tdefer inbound.Close()\n\t\t\t\t\toutbound, err := p.sink.DialPacket(nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"proxy failed to dial packet\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tdefer outbound.Close()\n\t\t\t\t\terrChan := make(chan error, 2)\n\t\t\t\t\tcopyPacket := func(a, b tunnel.PacketConn) {\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\tbuf := make([]byte, MaxPacketSize)\n\t\t\t\t\t\t\tn, metadata, err := a.ReadWithMetadata(buf)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\terrChan <- err\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif n == 0 {\n\t\t\t\t\t\t\t\terrChan <- nil\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_, err = b.WriteWithMetadata(buf[:n], metadata)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\terrChan <- err\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tgo copyPacket(inbound, outbound)\n\t\t\t\t\tgo copyPacket(outbound, inbound)\n\t\t\t\t\tselect {\n\t\t\t\t\tcase err = <-errChan:\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Error(err)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-p.ctx.Done():\n\t\t\t\t\t\tlog.Debug(\"shutting down packet relay\")\n\t\t\t\t\t}\n\t\t\t\t\tlog.Debug(\"packet relay ends\")\n\t\t\t\t}(inbound)\n\t\t\t}\n\t\t}(source)\n\t}\n}\n\nfunc NewProxy(ctx context.Context, cancel context.CancelFunc, sources []tunnel.Server, sink tunnel.Client) *Proxy {\n\treturn &Proxy{\n\t\tsources: sources,\n\t\tsink:    sink,\n\t\tctx:     ctx,\n\t\tcancel:  cancel,\n\t}\n}\n\ntype Creator func(ctx context.Context) (*Proxy, error)\n\nvar creators = make(map[string]Creator)\n\nfunc RegisterProxyCreator(name string, creator Creator) {\n\tcreators[name] = creator\n}\n\nfunc NewProxyFromConfigData(data []byte, isJSON bool) (*Proxy, error) {\n\t// create a unique context for each proxy instance to avoid duplicated authenticator\n\tctx := context.WithValue(context.Background(), Name+\"_ID\", rand.Int())\n\tvar err error\n\tif isJSON {\n\t\tctx, err = config.WithJSONConfig(ctx, data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tctx, err = config.WithYAMLConfig(ctx, data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tcreate, ok := creators[strings.ToUpper(cfg.RunType)]\n\tif !ok {\n\t\treturn nil, common.NewError(\"unknown proxy type: \" + cfg.RunType)\n\t}\n\tlog.SetLogLevel(log.LogLevel(cfg.LogLevel))\n\tif cfg.LogFile != \"\" {\n\t\tfile, err := os.OpenFile(cfg.LogFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to open log file\").Base(err)\n\t\t}\n\t\tlog.SetOutput(file)\n\t}\n\treturn create(ctx)\n}\n"
  },
  {
    "path": "proxy/server/config.go",
    "content": "package server\n\nimport (\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/proxy/client\"\n)\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(client.Config)\n\t})\n}\n"
  },
  {
    "path": "proxy/server/server.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n\t\"github.com/p4gefau1t/trojan-go/proxy/client\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/mux\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/router\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/shadowsocks\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/simplesocks\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/tls\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/websocket\"\n)\n\nconst Name = \"SERVER\"\n\nfunc init() {\n\tproxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) {\n\t\tcfg := config.FromContext(ctx, Name).(*client.Config)\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\ttransportServer, err := transport.NewServer(ctx, nil)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\tclientStack := []string{freedom.Name}\n\t\tif cfg.Router.Enabled {\n\t\t\tclientStack = []string{freedom.Name, router.Name}\n\t\t}\n\n\t\troot := &proxy.Node{\n\t\t\tName:       transport.Name,\n\t\t\tNext:       make(map[string]*proxy.Node),\n\t\t\tIsEndpoint: false,\n\t\t\tContext:    ctx,\n\t\t\tServer:     transportServer,\n\t\t}\n\n\t\tif !cfg.TransportPlugin.Enabled {\n\t\t\troot = root.BuildNext(tls.Name)\n\t\t}\n\n\t\ttrojanSubTree := root\n\t\tif cfg.Shadowsocks.Enabled {\n\t\t\ttrojanSubTree = trojanSubTree.BuildNext(shadowsocks.Name)\n\t\t}\n\t\ttrojanSubTree.BuildNext(trojan.Name).BuildNext(mux.Name).BuildNext(simplesocks.Name).IsEndpoint = true\n\t\ttrojanSubTree.BuildNext(trojan.Name).IsEndpoint = true\n\n\t\twsSubTree := root.BuildNext(websocket.Name)\n\t\tif cfg.Shadowsocks.Enabled {\n\t\t\twsSubTree = wsSubTree.BuildNext(shadowsocks.Name)\n\t\t}\n\t\twsSubTree.BuildNext(trojan.Name).BuildNext(mux.Name).BuildNext(simplesocks.Name).IsEndpoint = true\n\t\twsSubTree.BuildNext(trojan.Name).IsEndpoint = true\n\n\t\tserverList := proxy.FindAllEndpoints(root)\n\t\tclientList, err := proxy.CreateClientStack(ctx, clientStack)\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t\treturn proxy.NewProxy(ctx, cancel, serverList, clientList), nil\n\t})\n}\n"
  },
  {
    "path": "proxy/stack.go",
    "content": "package proxy\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Node struct {\n\tName       string\n\tNext       map[string]*Node\n\tIsEndpoint bool\n\tcontext.Context\n\ttunnel.Server\n\ttunnel.Client\n}\n\nfunc (n *Node) BuildNext(name string) *Node {\n\tif next, found := n.Next[name]; found {\n\t\treturn next\n\t}\n\tt, err := tunnel.GetTunnel(name)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ts, err := t.NewServer(n.Context, n.Server)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tnewNode := &Node{\n\t\tName:    name,\n\t\tNext:    make(map[string]*Node),\n\t\tContext: n.Context,\n\t\tServer:  s,\n\t}\n\tn.Next[name] = newNode\n\treturn newNode\n}\n\nfunc (n *Node) LinkNextNode(next *Node) *Node {\n\tif next, found := n.Next[next.Name]; found {\n\t\treturn next\n\t}\n\tn.Next[next.Name] = next\n\tt, err := tunnel.GetTunnel(next.Name)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ts, err := t.NewServer(next.Context, n.Server) // context of the child nodes have been initialized\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tnext.Server = s\n\treturn next\n}\n\nfunc FindAllEndpoints(root *Node) []tunnel.Server {\n\tlist := make([]tunnel.Server, 0)\n\tif root.IsEndpoint || len(root.Next) == 0 {\n\t\tlist = append(list, root.Server)\n\t}\n\tfor _, next := range root.Next {\n\t\tlist = append(list, FindAllEndpoints(next)...)\n\t}\n\treturn list\n}\n\n// CreateClientStack create client tunnel stacks from lists\nfunc CreateClientStack(ctx context.Context, clientStack []string) (tunnel.Client, error) {\n\tvar client tunnel.Client\n\tfor _, name := range clientStack {\n\t\tt, err := tunnel.GetTunnel(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclient, err = t.NewClient(ctx, client)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn client, nil\n}\n\n// CreateServerStack create server tunnel stack from list\nfunc CreateServerStack(ctx context.Context, serverStack []string) (tunnel.Server, error) {\n\tvar server tunnel.Server\n\tfor _, name := range serverStack {\n\t\tt, err := tunnel.GetTunnel(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tserver, err = t.NewServer(ctx, server)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn server, nil\n}\n"
  },
  {
    "path": "redirector/redirector.go",
    "content": "package redirector\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"reflect\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\ntype Dial func(net.Addr) (net.Conn, error)\n\nfunc defaultDial(addr net.Addr) (net.Conn, error) {\n\treturn net.Dial(\"tcp\", addr.String())\n}\n\ntype Redirection struct {\n\tDial\n\tRedirectTo  net.Addr\n\tInboundConn net.Conn\n}\n\ntype Redirector struct {\n\tctx             context.Context\n\tredirectionChan chan *Redirection\n}\n\nfunc (r *Redirector) Redirect(redirection *Redirection) {\n\tselect {\n\tcase r.redirectionChan <- redirection:\n\t\tlog.Debug(\"redirect request\")\n\tcase <-r.ctx.Done():\n\t\tlog.Debug(\"exiting\")\n\t}\n}\n\nfunc (r *Redirector) worker() {\n\tfor {\n\t\tselect {\n\t\tcase redirection := <-r.redirectionChan:\n\t\t\thandle := func(redirection *Redirection) {\n\t\t\t\tif redirection.InboundConn == nil || reflect.ValueOf(redirection.InboundConn).IsNil() {\n\t\t\t\t\tlog.Error(\"nil inbound conn\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer redirection.InboundConn.Close()\n\t\t\t\tif redirection.RedirectTo == nil || reflect.ValueOf(redirection.RedirectTo).IsNil() {\n\t\t\t\t\tlog.Error(\"nil redirection addr\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif redirection.Dial == nil {\n\t\t\t\t\tredirection.Dial = defaultDial\n\t\t\t\t}\n\t\t\t\tlog.Warn(\"redirecting connection from\", redirection.InboundConn.RemoteAddr(), \"to\", redirection.RedirectTo.String())\n\t\t\t\toutboundConn, err := redirection.Dial(redirection.RedirectTo)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Error(common.NewError(\"failed to redirect to target address\").Base(err))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer outboundConn.Close()\n\t\t\t\terrChan := make(chan error, 2)\n\t\t\t\tcopyConn := func(a, b net.Conn) {\n\t\t\t\t\t_, err := io.Copy(a, b)\n\t\t\t\t\terrChan <- err\n\t\t\t\t}\n\t\t\t\tgo copyConn(outboundConn, redirection.InboundConn)\n\t\t\t\tgo copyConn(redirection.InboundConn, outboundConn)\n\t\t\t\tselect {\n\t\t\t\tcase err := <-errChan:\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"failed to redirect\").Base(err))\n\t\t\t\t\t}\n\t\t\t\t\tlog.Info(\"redirection done\")\n\t\t\t\tcase <-r.ctx.Done():\n\t\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tgo handle(redirection)\n\t\tcase <-r.ctx.Done():\n\t\t\tlog.Debug(\"shutting down redirector\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc NewRedirector(ctx context.Context) *Redirector {\n\tr := &Redirector{\n\t\tctx:             ctx,\n\t\tredirectionChan: make(chan *Redirection, 64),\n\t}\n\tgo r.worker()\n\treturn r\n}\n"
  },
  {
    "path": "redirector/redirector_test.go",
    "content": "package redirector\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n)\n\nfunc TestRedirector(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tredir := NewRedirector(ctx)\n\tredir.Redirect(&Redirection{\n\t\tDial:        nil,\n\t\tRedirectTo:  nil,\n\t\tInboundConn: nil,\n\t})\n\tvar fakeAddr net.Addr\n\tvar fakeConn net.Conn\n\tredir.Redirect(&Redirection{\n\t\tDial:        nil,\n\t\tRedirectTo:  fakeAddr,\n\t\tInboundConn: fakeConn,\n\t})\n\tredir.Redirect(&Redirection{\n\t\tDial:        nil,\n\t\tRedirectTo:  nil,\n\t\tInboundConn: fakeConn,\n\t})\n\tredir.Redirect(&Redirection{\n\t\tDial:        nil,\n\t\tRedirectTo:  fakeAddr,\n\t\tInboundConn: nil,\n\t})\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tcommon.Must(err)\n\tconn1, err := net.Dial(\"tcp\", l.Addr().String())\n\tcommon.Must(err)\n\tconn2, err := l.Accept()\n\tcommon.Must(err)\n\tredirAddr, err := net.ResolveTCPAddr(\"tcp\", util.HTTPAddr)\n\tcommon.Must(err)\n\tredir.Redirect(&Redirection{\n\t\tDial:        nil,\n\t\tRedirectTo:  redirAddr,\n\t\tInboundConn: conn2,\n\t})\n\ttime.Sleep(time.Second)\n\treq, err := http.NewRequest(\"GET\", \"http://localhost/\", nil)\n\tcommon.Must(err)\n\treq.Write(conn1)\n\tbuf := make([]byte, 1024)\n\tconn1.Read(buf)\n\tfmt.Println(string(buf))\n\tif !strings.HasPrefix(string(buf), \"HTTP/1.1 200 OK\") {\n\t\tt.Fail()\n\t}\n\tcancel()\n\tconn1.Close()\n\tconn2.Close()\n}\n"
  },
  {
    "path": "statistic/memory/config.go",
    "content": "package memory\n\nimport (\n\t\"github.com/p4gefau1t/trojan-go/config\"\n)\n\ntype Config struct {\n\tPasswords []string `json:\"password\" yaml:\"password\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{}\n\t})\n}\n"
  },
  {
    "path": "statistic/memory/memory.go",
    "content": "package memory\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"golang.org/x/time/rate\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n)\n\nconst Name = \"MEMORY\"\n\ntype User struct {\n\t// WARNING: do not change the order of these fields.\n\t// 64-bit fields that use `sync/atomic` package functions\n\t// must be 64-bit aligned on 32-bit systems.\n\t// Reference: https://github.com/golang/go/issues/599\n\t// Solution: https://github.com/golang/go/issues/11891#issuecomment-433623786\n\tsent      uint64\n\trecv      uint64\n\tlastSent  uint64\n\tlastRecv  uint64\n\tsendSpeed uint64\n\trecvSpeed uint64\n\n\thash        string\n\tipTable     sync.Map\n\tipNum       int32\n\tmaxIPNum    int\n\tlimiterLock sync.RWMutex\n\tsendLimiter *rate.Limiter\n\trecvLimiter *rate.Limiter\n\tctx         context.Context\n\tcancel      context.CancelFunc\n}\n\nfunc (u *User) Close() error {\n\tu.ResetTraffic()\n\tu.cancel()\n\treturn nil\n}\n\nfunc (u *User) AddIP(ip string) bool {\n\tif u.maxIPNum <= 0 {\n\t\treturn true\n\t}\n\t_, found := u.ipTable.Load(ip)\n\tif found {\n\t\treturn true\n\t}\n\tif int(u.ipNum)+1 > u.maxIPNum {\n\t\treturn false\n\t}\n\tu.ipTable.Store(ip, true)\n\tatomic.AddInt32(&u.ipNum, 1)\n\treturn true\n}\n\nfunc (u *User) DelIP(ip string) bool {\n\tif u.maxIPNum <= 0 {\n\t\treturn true\n\t}\n\t_, found := u.ipTable.Load(ip)\n\tif !found {\n\t\treturn false\n\t}\n\tu.ipTable.Delete(ip)\n\tatomic.AddInt32(&u.ipNum, -1)\n\treturn true\n}\n\nfunc (u *User) GetIP() int {\n\treturn int(u.ipNum)\n}\n\nfunc (u *User) SetIPLimit(n int) {\n\tu.maxIPNum = n\n}\n\nfunc (u *User) GetIPLimit() int {\n\treturn u.maxIPNum\n}\n\nfunc (u *User) AddTraffic(sent, recv int) {\n\tu.limiterLock.RLock()\n\tdefer u.limiterLock.RUnlock()\n\n\tif u.sendLimiter != nil && sent >= 0 {\n\t\tu.sendLimiter.WaitN(u.ctx, sent)\n\t} else if u.recvLimiter != nil && recv >= 0 {\n\t\tu.recvLimiter.WaitN(u.ctx, recv)\n\t}\n\tatomic.AddUint64(&u.sent, uint64(sent))\n\tatomic.AddUint64(&u.recv, uint64(recv))\n}\n\nfunc (u *User) SetSpeedLimit(send, recv int) {\n\tu.limiterLock.Lock()\n\tdefer u.limiterLock.Unlock()\n\n\tif send <= 0 {\n\t\tu.sendLimiter = nil\n\t} else {\n\t\tu.sendLimiter = rate.NewLimiter(rate.Limit(send), send*2)\n\t}\n\tif recv <= 0 {\n\t\tu.recvLimiter = nil\n\t} else {\n\t\tu.recvLimiter = rate.NewLimiter(rate.Limit(recv), recv*2)\n\t}\n}\n\nfunc (u *User) GetSpeedLimit() (send, recv int) {\n\tu.limiterLock.RLock()\n\tdefer u.limiterLock.RUnlock()\n\n\tif u.sendLimiter != nil {\n\t\tsend = int(u.sendLimiter.Limit())\n\t}\n\tif u.recvLimiter != nil {\n\t\trecv = int(u.recvLimiter.Limit())\n\t}\n\treturn\n}\n\nfunc (u *User) Hash() string {\n\treturn u.hash\n}\n\nfunc (u *User) SetTraffic(send, recv uint64) {\n\tatomic.StoreUint64(&u.sent, send)\n\tatomic.StoreUint64(&u.recv, recv)\n}\n\nfunc (u *User) GetTraffic() (uint64, uint64) {\n\treturn atomic.LoadUint64(&u.sent), atomic.LoadUint64(&u.recv)\n}\n\nfunc (u *User) ResetTraffic() (uint64, uint64) {\n\tsent := atomic.SwapUint64(&u.sent, 0)\n\trecv := atomic.SwapUint64(&u.recv, 0)\n\tatomic.StoreUint64(&u.lastSent, 0)\n\tatomic.StoreUint64(&u.lastRecv, 0)\n\treturn sent, recv\n}\n\nfunc (u *User) speedUpdater() {\n\tticker := time.NewTicker(time.Second)\n\tfor {\n\t\tselect {\n\t\tcase <-u.ctx.Done():\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tsent, recv := u.GetTraffic()\n\t\t\tatomic.StoreUint64(&u.sendSpeed, sent-u.lastSent)\n\t\t\tatomic.StoreUint64(&u.recvSpeed, recv-u.lastRecv)\n\t\t\tatomic.StoreUint64(&u.lastSent, sent)\n\t\t\tatomic.StoreUint64(&u.lastRecv, recv)\n\t\t}\n\t}\n}\n\nfunc (u *User) GetSpeed() (uint64, uint64) {\n\treturn atomic.LoadUint64(&u.sendSpeed), atomic.LoadUint64(&u.recvSpeed)\n}\n\ntype Authenticator struct {\n\tusers sync.Map\n\tctx   context.Context\n}\n\nfunc (a *Authenticator) AuthUser(hash string) (bool, statistic.User) {\n\tif user, found := a.users.Load(hash); found {\n\t\treturn true, user.(*User)\n\t}\n\treturn false, nil\n}\n\nfunc (a *Authenticator) AddUser(hash string) error {\n\tif _, found := a.users.Load(hash); found {\n\t\treturn common.NewError(\"hash \" + hash + \" is already exist\")\n\t}\n\tctx, cancel := context.WithCancel(a.ctx)\n\tmeter := &User{\n\t\thash:   hash,\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t}\n\tgo meter.speedUpdater()\n\ta.users.Store(hash, meter)\n\treturn nil\n}\n\nfunc (a *Authenticator) DelUser(hash string) error {\n\tmeter, found := a.users.Load(hash)\n\tif !found {\n\t\treturn common.NewError(\"hash \" + hash + \" not found\")\n\t}\n\tmeter.(*User).Close()\n\ta.users.Delete(hash)\n\treturn nil\n}\n\nfunc (a *Authenticator) ListUsers() []statistic.User {\n\tresult := make([]statistic.User, 0)\n\ta.users.Range(func(k, v interface{}) bool {\n\t\tresult = append(result, v.(*User))\n\t\treturn true\n\t})\n\treturn result\n}\n\nfunc (a *Authenticator) Close() error {\n\treturn nil\n}\n\nfunc NewAuthenticator(ctx context.Context) (statistic.Authenticator, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tu := &Authenticator{\n\t\tctx: ctx,\n\t}\n\tfor _, password := range cfg.Passwords {\n\t\thash := common.SHA224String(password)\n\t\tu.AddUser(hash)\n\t}\n\tlog.Debug(\"memory authenticator created\")\n\treturn u, nil\n}\n\nfunc init() {\n\tstatistic.RegisterAuthenticatorCreator(Name, NewAuthenticator)\n}\n"
  },
  {
    "path": "statistic/memory/memory_test.go",
    "content": "package memory\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n)\n\nfunc TestMemoryAuth(t *testing.T) {\n\tcfg := &Config{\n\t\tPasswords: nil,\n\t}\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\tauth, err := NewAuthenticator(ctx)\n\tcommon.Must(err)\n\tauth.AddUser(\"user1\")\n\tvalid, user := auth.AuthUser(\"user1\")\n\tif !valid {\n\t\tt.Fatal(\"add, auth\")\n\t}\n\tif user.Hash() != \"user1\" {\n\t\tt.Fatal(\"Hash\")\n\t}\n\tuser.AddTraffic(100, 200)\n\tsent, recv := user.GetTraffic()\n\tif sent != 100 || recv != 200 {\n\t\tt.Fatal(\"traffic\")\n\t}\n\tsent, recv = user.ResetTraffic()\n\tif sent != 100 || recv != 200 {\n\t\tt.Fatal(\"ResetTraffic\")\n\t}\n\tsent, recv = user.GetTraffic()\n\tif sent != 0 || recv != 0 {\n\t\tt.Fatal(\"ResetTraffic\")\n\t}\n\n\tuser.AddIP(\"1234\")\n\tuser.AddIP(\"5678\")\n\tif user.GetIP() != 0 {\n\t\tt.Fatal(\"GetIP\")\n\t}\n\n\tuser.SetIPLimit(2)\n\tuser.AddIP(\"1234\")\n\tuser.AddIP(\"5678\")\n\tuser.DelIP(\"1234\")\n\tif user.GetIP() != 1 {\n\t\tt.Fatal(\"DelIP\")\n\t}\n\tuser.DelIP(\"5678\")\n\n\tuser.SetIPLimit(2)\n\tif !user.AddIP(\"1\") || !user.AddIP(\"2\") {\n\t\tt.Fatal(\"AddIP\")\n\t}\n\tif user.AddIP(\"3\") {\n\t\tt.Fatal(\"AddIP\")\n\t}\n\tif !user.AddIP(\"2\") {\n\t\tt.Fatal(\"AddIP\")\n\t}\n\n\tuser.SetTraffic(1234, 4321)\n\tif a, b := user.GetTraffic(); a != 1234 || b != 4321 {\n\t\tt.Fatal(\"SetTraffic\")\n\t}\n\n\tuser.ResetTraffic()\n\tgo func() {\n\t\tfor {\n\t\t\tk := 100\n\t\t\ttime.Sleep(time.Second / time.Duration(k))\n\t\t\tuser.AddTraffic(2000/k, 1000/k)\n\t\t}\n\t}()\n\ttime.Sleep(time.Second * 4)\n\tif sent, recv := user.GetSpeed(); sent > 3000 || sent < 1000 || recv > 1500 || recv < 500 {\n\t\tt.Error(\"GetSpeed\", sent, recv)\n\t} else {\n\t\tt.Log(\"GetSpeed\", sent, recv)\n\t}\n\n\tuser.SetSpeedLimit(30, 20)\n\ttime.Sleep(time.Second * 4)\n\tif sent, recv := user.GetSpeed(); sent > 60 || recv > 40 {\n\t\tt.Error(\"SetSpeedLimit\", sent, recv)\n\t} else {\n\t\tt.Log(\"SetSpeedLimit\", sent, recv)\n\t}\n\n\tuser.SetSpeedLimit(0, 0)\n\ttime.Sleep(time.Second * 4)\n\tif sent, recv := user.GetSpeed(); sent < 30 || recv < 20 {\n\t\tt.Error(\"SetSpeedLimit\", sent, recv)\n\t} else {\n\t\tt.Log(\"SetSpeedLimit\", sent, recv)\n\t}\n\n\tauth.AddUser(\"user2\")\n\tvalid, _ = auth.AuthUser(\"user2\")\n\tif !valid {\n\t\tt.Fatal()\n\t}\n\tauth.DelUser(\"user2\")\n\tvalid, _ = auth.AuthUser(\"user2\")\n\tif valid {\n\t\tt.Fatal()\n\t}\n\tauth.AddUser(\"user3\")\n\tusers := auth.ListUsers()\n\tif len(users) != 2 {\n\t\tt.Fatal()\n\t}\n\tuser.Close()\n\tauth.Close()\n}\n\nfunc BenchmarkMemoryUsage(b *testing.B) {\n\tcfg := &Config{\n\t\tPasswords: nil,\n\t}\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\tauth, err := NewAuthenticator(ctx)\n\tcommon.Must(err)\n\n\tm1 := runtime.MemStats{}\n\tm2 := runtime.MemStats{}\n\truntime.ReadMemStats(&m1)\n\tfor i := 0; i < b.N; i++ {\n\t\tcommon.Must(auth.AddUser(common.SHA224String(\"hash\" + strconv.Itoa(i))))\n\t}\n\truntime.ReadMemStats(&m2)\n\n\tb.ReportMetric(float64(m2.Alloc-m1.Alloc)/1024/1024, \"MiB(Alloc)\")\n\tb.ReportMetric(float64(m2.TotalAlloc-m1.TotalAlloc)/1024/1024, \"MiB(TotalAlloc)\")\n}\n"
  },
  {
    "path": "statistic/mysql/config.go",
    "content": "package mysql\n\nimport (\n\t\"github.com/p4gefau1t/trojan-go/config\"\n)\n\ntype MySQLConfig struct {\n\tEnabled    bool   `json:\"enabled\" yaml:\"enabled\"`\n\tServerHost string `json:\"server_addr\" yaml:\"server-addr\"`\n\tServerPort int    `json:\"server_port\" yaml:\"server-port\"`\n\tDatabase   string `json:\"database\" yaml:\"database\"`\n\tUsername   string `json:\"username\" yaml:\"username\"`\n\tPassword   string `json:\"password\" yaml:\"password\"`\n\tCheckRate  int    `json:\"check_rate\" yaml:\"check-rate\"`\n}\n\ntype Config struct {\n\tMySQL MySQLConfig `json:\"mysql\" yaml:\"mysql\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tMySQL: MySQLConfig{\n\t\t\t\tServerPort: 3306,\n\t\t\t\tCheckRate:  30,\n\t\t\t},\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "statistic/mysql/mysql.go",
    "content": "package mysql\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t// MySQL Driver\n\t_ \"github.com/go-sql-driver/mysql\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/memory\"\n)\n\nconst Name = \"MYSQL\"\n\ntype Authenticator struct {\n\t*memory.Authenticator\n\tdb             *sql.DB\n\tupdateDuration time.Duration\n\tctx            context.Context\n}\n\nfunc (a *Authenticator) updater() {\n\tfor {\n\t\tfor _, user := range a.ListUsers() {\n\t\t\t// swap upload and download for users\n\t\t\thash := user.Hash()\n\t\t\tsent, recv := user.ResetTraffic()\n\n\t\t\ts, err := a.db.Exec(\"UPDATE `users` SET `upload`=`upload`+?, `download`=`download`+? WHERE `password`=?;\", recv, sent, hash)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(common.NewError(\"failed to update data to user table\").Base(err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif r, err := s.RowsAffected(); err != nil {\n\t\t\t\tif r == 0 {\n\t\t\t\t\ta.DelUser(hash)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlog.Info(\"buffered data has been written into the database\")\n\n\t\t// update memory\n\t\trows, err := a.db.Query(\"SELECT password,quota,download,upload FROM users\")\n\t\tif err != nil || rows.Err() != nil {\n\t\t\tlog.Error(common.NewError(\"failed to pull data from the database\").Base(err))\n\t\t\ttime.Sleep(a.updateDuration)\n\t\t\tcontinue\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tvar hash string\n\t\t\tvar quota, download, upload int64\n\t\t\terr := rows.Scan(&hash, &quota, &download, &upload)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(common.NewError(\"failed to obtain data from the query result\").Base(err))\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif download+upload < quota || quota < 0 {\n\t\t\t\ta.AddUser(hash)\n\t\t\t} else {\n\t\t\t\ta.DelUser(hash)\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase <-time.After(a.updateDuration):\n\t\tcase <-a.ctx.Done():\n\t\t\tlog.Debug(\"MySQL daemon exiting...\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc connectDatabase(driverName, username, password, ip string, port int, dbName string) (*sql.DB, error) {\n\tpath := strings.Join([]string{username, \":\", password, \"@tcp(\", ip, \":\", fmt.Sprintf(\"%d\", port), \")/\", dbName, \"?charset=utf8\"}, \"\")\n\treturn sql.Open(driverName, path)\n}\n\nfunc NewAuthenticator(ctx context.Context) (statistic.Authenticator, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tdb, err := connectDatabase(\n\t\t\"mysql\",\n\t\tcfg.MySQL.Username,\n\t\tcfg.MySQL.Password,\n\t\tcfg.MySQL.ServerHost,\n\t\tcfg.MySQL.ServerPort,\n\t\tcfg.MySQL.Database,\n\t)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"Failed to connect to database server\").Base(err)\n\t}\n\tmemoryAuth, err := memory.NewAuthenticator(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ta := &Authenticator{\n\t\tdb:             db,\n\t\tctx:            ctx,\n\t\tupdateDuration: time.Duration(cfg.MySQL.CheckRate) * time.Second,\n\t\tAuthenticator:  memoryAuth.(*memory.Authenticator),\n\t}\n\tgo a.updater()\n\tlog.Debug(\"mysql authenticator created\")\n\treturn a, nil\n}\n\nfunc init() {\n\tstatistic.RegisterAuthenticatorCreator(Name, NewAuthenticator)\n}\n"
  },
  {
    "path": "statistic/statistics.go",
    "content": "package statistic\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\ntype TrafficMeter interface {\n\tio.Closer\n\tHash() string\n\tAddTraffic(sent, recv int)\n\tGetTraffic() (sent, recv uint64)\n\tSetTraffic(sent, recv uint64)\n\tResetTraffic() (sent, recv uint64)\n\tGetSpeed() (sent, recv uint64)\n\tGetSpeedLimit() (sent, recv int)\n\tSetSpeedLimit(sent, recv int)\n}\n\ntype IPRecorder interface {\n\tAddIP(string) bool\n\tDelIP(string) bool\n\tGetIP() int\n\tSetIPLimit(int)\n\tGetIPLimit() int\n}\n\ntype User interface {\n\tTrafficMeter\n\tIPRecorder\n}\n\ntype Authenticator interface {\n\tio.Closer\n\tAuthUser(hash string) (valid bool, user User)\n\tAddUser(hash string) error\n\tDelUser(hash string) error\n\tListUsers() []User\n}\n\ntype Creator func(ctx context.Context) (Authenticator, error)\n\nvar (\n\tcreatedAuthLock sync.Mutex\n\tauthCreators    = make(map[string]Creator)\n\tcreatedAuth     = make(map[context.Context]Authenticator)\n)\n\nfunc RegisterAuthenticatorCreator(name string, creator Creator) {\n\tauthCreators[name] = creator\n}\n\nfunc NewAuthenticator(ctx context.Context, name string) (Authenticator, error) {\n\t// allocate a unique authenticator for each context\n\tcreatedAuthLock.Lock() // avoid concurrent map read/write\n\tdefer createdAuthLock.Unlock()\n\tif auth, found := createdAuth[ctx]; found {\n\t\tlog.Debug(\"authenticator has been created:\", name)\n\t\treturn auth, nil\n\t}\n\tcreator, found := authCreators[strings.ToUpper(name)]\n\tif !found {\n\t\treturn nil, common.NewError(\"auth driver name \" + name + \" not found\")\n\t}\n\tauth, err := creator(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcreatedAuth[ctx] = auth\n\treturn auth, err\n}\n"
  },
  {
    "path": "test/scenario/custom_test.go",
    "content": "package scenario\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/custom\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n)\n\nfunc TestCustom1(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientData := fmt.Sprintf(`\nrun-type: custom\n\ninbound:\n  node:\n    - protocol: adapter\n      tag: adapter\n      config:\n        local-addr: 127.0.0.1\n        local-port: %d\n    - protocol: socks\n      tag: socks\n      config:\n        local-addr: 127.0.0.1\n        local-port: %d\n  path:\n    -\n      - adapter\n      - socks\n\noutbound:\n  node:\n    - protocol: transport\n      tag: transport\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: %d\n\n    - protocol: tls\n      tag: tls\n      config:\n        ssl:\n          sni: localhost\n          key: server.key\n          cert: server.crt\n\n    - protocol: trojan\n      tag: trojan\n      config:\n        password:\n          - \"12345678\"\n\n  path:\n    - \n      - transport\n      - tls\n      - trojan\n\n`, socksPort, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: custom\n\ninbound:\n  node:\n    - protocol: transport\n      tag: transport\n      config:\n        local-addr: 127.0.0.1\n        local-port: %d\n        remote-addr: 127.0.0.1\n        remote-port: %s\n\n    - protocol: tls\n      tag: tls\n      config:\n        ssl:\n          sni: localhost\n          key: server.key\n          cert: server.crt\n\n    - protocol: trojan\n      tag: trojan\n      config:\n        disable-http-check: true\n        password:\n          - \"12345678\"\n\n    - protocol: mux\n      tag: mux\n\n    - protocol: simplesocks\n      tag: simplesocks\n     \n\n  path:\n    - \n      - transport\n      - tls\n      - trojan\n    - \n      - transport\n      - tls\n      - trojan\n      - mux\n      - simplesocks\n\noutbound:\n  node:\n    - protocol: freedom\n      tag: freedom\n\n  path:\n    - \n      - freedom\n\n`, serverPort, util.HTTPPort)\n\n\tif !CheckClientServer(clientData, serverData, socksPort) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestCustom2(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientData := fmt.Sprintf(`\nrun-type: custom\nlog-level: 0\n\ninbound:\n  node:\n    - protocol: adapter\n      tag: adapter\n      config:\n        local-addr: 127.0.0.1\n        local-port: %d\n    - protocol: socks\n      tag: socks\n      config:\n        local-addr: 127.0.0.1\n        local-port: %d\n  path:\n    -\n      - adapter\n      - socks\n\noutbound:\n  node:\n    - protocol: transport\n      tag: transport\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: %d\n\n    - protocol: tls\n      tag: tls\n      config:\n        ssl:\n          sni: localhost\n          key: server.key\n          cert: server.crt\n\n    - protocol: trojan\n      tag: trojan\n      config:\n        password:\n          - \"12345678\"\n\n    - protocol: shadowsocks\n      tag: shadowsocks\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: 80\n        shadowsocks:\n          enabled: true\n          password: \"12345678\"\n\n    - protocol: websocket\n      tag: websocket\n      config:\n        websocket:\n          host: localhost\n          path: /ws\n\n  path:\n    - \n      - transport\n      - tls\n      - websocket\n      - shadowsocks \n      - trojan\n\n`, socksPort, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: custom\nlog-level: 0\n\ninbound:\n  node:\n    - protocol: transport\n      tag: transport\n      config:\n        local-addr: 127.0.0.1\n        local-port: %d\n        remote-addr: 127.0.0.1\n        remote-port: %s\n\n    - protocol: tls\n      tag: tls\n      config:\n        ssl:\n          sni: localhost\n          key: server.key\n          cert: server.crt\n\n    - protocol: trojan\n      tag: trojan\n      config:\n        disable-http-check: true\n        password:\n          - \"12345678\"\n\n    - protocol: trojan\n      tag: trojan2\n      config:\n        disable-http-check: true\n        password:\n          - \"12345678\"\n\n    - protocol: websocket\n      tag: websocket\n      config:\n        websocket:\n          enabled: true\n          host: localhost\n          path: /ws\n\n    - protocol: mux\n      tag: mux\n\n    - protocol: simplesocks\n      tag: simplesocks\n\n    - protocol: shadowsocks\n      tag: shadowsocks\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: 80\n        shadowsocks:\n          enabled: true\n          password: \"12345678\"\n\n    - protocol: shadowsocks\n      tag: shadowsocks2\n      config:\n        remote-addr: 127.0.0.1\n        remote-port: 80\n        shadowsocks:\n          enabled: true\n          password: \"12345678\"\n     \n  path:\n    - \n      - transport\n      - tls\n      - shadowsocks \n      - trojan\n    - \n      - transport\n      - tls\n      - websocket\n      - shadowsocks2\n      - trojan2\n    - \n      - transport\n      - tls\n      - shadowsocks\n      - trojan\n      - mux\n      - simplesocks\n\noutbound:\n  node:\n    - protocol: freedom\n      tag: freedom\n\n  path:\n    - \n      - freedom\n\n`, serverPort, util.HTTPPort)\n\n\tif !CheckClientServer(clientData, serverData, socksPort) {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "test/scenario/proxy_test.go",
    "content": "package scenario\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tnetproxy \"golang.org/x/net/proxy\"\n\n\t_ \"github.com/p4gefau1t/trojan-go/api\"\n\t_ \"github.com/p4gefau1t/trojan-go/api/service\"\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t_ \"github.com/p4gefau1t/trojan-go/log/golog\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/client\"\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/forward\"\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/nat\"\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/server\"\n\t_ \"github.com/p4gefau1t/trojan-go/statistic/memory\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n)\n\n// test key and cert\n\nvar cert = `\n-----BEGIN CERTIFICATE-----\nMIIC5TCCAc2gAwIBAgIJAJqNVe6g/10vMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0yMTA5MTQwNjE1MTFaFw0yNjA5MTMwNjE1MTFaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAK7bupJ8tmHM3shQ/7N730jzpRsXdNiBxq/Jxx8j+vB3AcxuP5bjXQZqS6YR\n5W5vrfLlegtq1E/mmaI3Ht0RfIlzev04Dua9PWmIQJD801nEPknbfgCLXDh+pYr2\nsfg8mUh3LjGtrxyH+nmbTjWg7iWSKohmZ8nUDcX94Llo5FxibMAz8OsAwOmUueCH\njP3XswZYHEy+OOP3K0ZEiJy0f5T6ZXk9OWYuPN4VQKJx1qrc9KzZtSPHwqVdkGUi\nase9tOPA4aMutzt0btgW7h7UrvG6C1c/Rr1BxdiYq1EQ+yypnAlyToVQSNbo67zz\nwGQk4GeruIkOgJOLdooN/HjhbHMCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo\nb3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B\nAQsFAAOCAQEASsBzHHYiWDDiBVWUEwVZAduTrslTLNOxG0QHBKsHWIlz/3QlhQil\nywb3OhfMTUR1dMGY5Iq5432QiCHO4IMCOv7tDIkgb4Bc3v/3CRlBlnurtAmUfNJ6\npTRSlK4AjWpGHAEEd/8aCaOE86hMP8WDht8MkJTRrQqpJ1HeDISoKt9nepHOIsj+\nI2zLZZtw0pg7FuR4MzWuqOt071iRS46Pupryb3ZEGIWNz5iLrDQod5Iz2ZGSRGqE\nrB8idX0mlj5AHRRanVR3PAes+eApsW9JvYG/ImuCOs+ZsukY614zQZdR+SyFm85G\n4NICyeQsmiypNHHgw+xZmGqZg65bXNGoyg==\n-----END CERTIFICATE-----\n`\n\nvar key = `\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu27qSfLZhzN7I\nUP+ze99I86UbF3TYgcavyccfI/rwdwHMbj+W410GakumEeVub63y5XoLatRP5pmi\nNx7dEXyJc3r9OA7mvT1piECQ/NNZxD5J234Ai1w4fqWK9rH4PJlIdy4xra8ch/p5\nm041oO4lkiqIZmfJ1A3F/eC5aORcYmzAM/DrAMDplLngh4z917MGWBxMvjjj9ytG\nRIictH+U+mV5PTlmLjzeFUCicdaq3PSs2bUjx8KlXZBlImrHvbTjwOGjLrc7dG7Y\nFu4e1K7xugtXP0a9QcXYmKtREPssqZwJck6FUEjW6Ou888BkJOBnq7iJDoCTi3aK\nDfx44WxzAgMBAAECggEBAKYhib/H0ZhWB4yWuHqUxG4RXtrAjHlvw5Acy5zgmHiC\n+Sh7ztrTJf0EXN9pvWwRm1ldgXj7hMBtPaaLbD1pccM9/qo66p17Sq/LjlyyeTOe\naffOHIbz4Sij2zCOdkR9fr0EztTQScF3yBhl4Aa/4cO8fcCeWxm86WEldq9x4xWJ\ns5WMR4CnrOJhDINLNPQPKX92KyxEQ/RfuBWovx3M0nl3fcUWfESY134t5g/UBFId\nIn19tZ+pGIpCkxP0U1AZWrlZRA8Q/3sO2orUpoAOdCrGk/DcCTMh0c1pMzbYZ1/i\ncYXn38MpUo8QeG4FElUhAv6kzeBIl2tRBMVzIigo+AECgYEA3No1rHdFu6Ox9vC8\nE93PTZevYVcL5J5yx6x7khCaOLKKuRXpjOX/h3Ll+hlN2DVAg5Jli/JVGCco4GeK\nkbFLSyxG1+E63JbgsVpaEOgvFT3bHHSPSRJDnIU+WkcNQ2u4Ky5ahZzbNdV+4fj2\nNO2iMgkm7hoJANrm3IqqW8epenMCgYEAyq+qdNj5DiDzBcDvLwY+4/QmMOOgDqeh\n/TzhbDRyr+m4xNT7LLS4s/3wcbkQC33zhMUI3YvOHnYq5Ze/iL/TSloj0QCp1I7L\nJ7sZeM1XimMBQIpCfOC7lf4tU76Fz0DTHAL+CmX1DgmRJdYO09843VsKkscC968R\n4cwL5oGxxgECgYAM4TTsH/CTJtLEIfn19qOWVNhHhvoMlSkAeBCkzg8Qa2knrh12\nuBsU3SCIW11s1H40rh758GICDJaXr7InGP3ZHnXrNRlnr+zeqvRBtCi6xma23B1X\nF5eV0zd1sFsXqXqOGh/xVtp54z+JEinZoForLNl2XVJVGG8KQZP50kUR/QKBgH4O\n8zzpFT0sUPlrHVdp0wODfZ06dPmoWJ9flfPuSsYN3tTMgcs0Owv3C+wu5UPAegxB\nX1oq8W8Qn21cC8vJQmgj19LNTtLcXI3BV/5B+Aghu02gr+lq/EA1bYuAG0jjUGlD\nkyx0bQzl9lhJ4b70PjGtxc2z6KyTPdPpTB143FABAoGAQDoIUdc77/IWcjzcaXeJ\n8abak5rAZA7cu2g2NVfs+Km+njsB0pbTwMnV1zGoFABdaHLdqbthLWtX7WOb1PDD\nMQ+kbiLw5uj8IY2HEqJhDGGEdXBqxbW7kyuIAN9Mw+mwKzkikNcFQdxgchWH1d1o\nlVkr92iEX+IhIeYb4DN1vQw=\n-----END PRIVATE KEY-----\n`\n\nfunc init() {\n\tos.WriteFile(\"server.crt\", []byte(cert), 0o777)\n\tos.WriteFile(\"server.key\", []byte(key), 0o777)\n}\n\nfunc CheckClientServer(clientData, serverData string, socksPort int) (ok bool) {\n\tserver, err := proxy.NewProxyFromConfigData([]byte(serverData), false)\n\tcommon.Must(err)\n\tgo server.Run()\n\n\tclient, err := proxy.NewProxyFromConfigData([]byte(clientData), false)\n\tcommon.Must(err)\n\tgo client.Run()\n\n\ttime.Sleep(time.Second * 2)\n\tdialer, err := netproxy.SOCKS5(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", socksPort), nil, netproxy.Direct)\n\tcommon.Must(err)\n\n\tok = true\n\tconst num = 100\n\twg := sync.WaitGroup{}\n\twg.Add(num)\n\tfor i := 0; i < num; i++ {\n\t\tgo func() {\n\t\t\tconst payloadSize = 1024\n\t\t\tpayload := util.GeneratePayload(payloadSize)\n\t\t\tbuf := [payloadSize]byte{}\n\n\t\t\tconn, err := dialer.Dial(\"tcp\", util.EchoAddr)\n\t\t\tcommon.Must(err)\n\n\t\t\tcommon.Must2(conn.Write(payload))\n\t\t\tcommon.Must2(conn.Read(buf[:]))\n\n\t\t\tif !bytes.Equal(payload, buf[:]) {\n\t\t\t\tok = false\n\t\t\t}\n\t\t\tconn.Close()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n\tclient.Close()\n\tserver.Close()\n\treturn\n}\n\nfunc TestClientServerWebsocketSubTree(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientData := fmt.Sprintf(`\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\npassword:\n    - password\nssl:\n    verify: false\n    fingerprint: firefox\n    sni: localhost\nwebsocket:\n    enabled: true\n    path: /ws\n    host: somedomainname.com\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nmux:\n    enabled: true\n`, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: server\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %s\ndisable-http-check: true\npassword:\n    - password\nssl:\n    verify-hostname: false\n    key: server.key\n    cert: server.crt\n    sni: localhost\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nwebsocket:\n    enabled: true\n    path: /ws\n    host: 127.0.0.1\n`, serverPort, util.HTTPPort)\n\n\tif !CheckClientServer(clientData, serverData, socksPort) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestClientServerTrojanSubTree(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientData := fmt.Sprintf(`\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\npassword:\n    - password\nssl:\n    verify: false\n    fingerprint: firefox\n    sni: localhost\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nmux:\n    enabled: true\n`, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: server\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %s\ndisable-http-check: true\npassword:\n    - password\nssl:\n    verify-hostname: false\n    key: server.key\n    cert: server.crt\n    sni: localhost\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\n`, serverPort, util.HTTPPort)\n\n\tif !CheckClientServer(clientData, serverData, socksPort) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestWebsocketDetection(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\n\tclientData := fmt.Sprintf(`\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\npassword:\n    - password\nssl:\n    verify: false\n    fingerprint: firefox\n    sni: localhost\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nmux:\n    enabled: true\n`, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: server\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %s\ndisable-http-check: true\npassword:\n    - password\nssl:\n    verify-hostname: false\n    key: server.key\n    cert: server.crt\n    sni: localhost\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nwebsocket:\n    enabled: true\n    path: /ws\n    hostname: 127.0.0.1\n`, serverPort, util.HTTPPort)\n\n\tif !CheckClientServer(clientData, serverData, socksPort) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestPluginWebsocket(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\n\tclientData := fmt.Sprintf(`\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\npassword:\n    - password\ntransport-plugin:\n    enabled: true\n    type: plaintext\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nmux:\n    enabled: true\nwebsocket:\n    enabled: true\n    path: /ws\n    hostname: 127.0.0.1\n`, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: server\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %s\ndisable-http-check: true\npassword:\n    - password\ntransport-plugin:\n    enabled: true\n    type: plaintext\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nwebsocket:\n    enabled: true\n    path: /ws\n    hostname: 127.0.0.1\n`, serverPort, util.HTTPPort)\n\n\tif !CheckClientServer(clientData, serverData, socksPort) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestForward(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\t_, targetPort, _ := net.SplitHostPort(util.EchoAddr)\n\tclientData := fmt.Sprintf(`\nrun-type: forward\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\ntarget-addr: 127.0.0.1\ntarget-port: %s\npassword:\n    - password\nssl:\n    verify: false\n    fingerprint: firefox\n    sni: localhost\nwebsocket:\n    enabled: true\n    path: /ws\n    hostname: 127.0.0.1\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nmux:\n    enabled: true\n`, clientPort, serverPort, targetPort)\n\tgo func() {\n\t\tproxy, err := proxy.NewProxyFromConfigData([]byte(clientData), false)\n\t\tcommon.Must(err)\n\t\tcommon.Must(proxy.Run())\n\t}()\n\n\tserverData := fmt.Sprintf(`\nrun-type: server\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %s\ndisable-http-check: true\npassword:\n    - password\nssl:\n    verify-hostname: false\n    key: server.key\n    cert: server.crt\n    sni: \"localhost\"\nwebsocket:\n    enabled: true\n    path: /ws\n    hostname: 127.0.0.1\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\n`, serverPort, util.HTTPPort)\n\tgo func() {\n\t\tproxy, err := proxy.NewProxyFromConfigData([]byte(serverData), false)\n\t\tcommon.Must(err)\n\t\tcommon.Must(proxy.Run())\n\t}()\n\n\ttime.Sleep(time.Second * 2)\n\n\tpayload := util.GeneratePayload(1024)\n\tbuf := [1024]byte{}\n\n\tconn, err := net.Dial(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", clientPort))\n\tcommon.Must(err)\n\n\tcommon.Must2(conn.Write(payload))\n\tcommon.Must2(conn.Read(buf[:]))\n\n\tif !bytes.Equal(payload, buf[:]) {\n\t\tt.Fail()\n\t}\n\n\tpacket, err := net.ListenPacket(\"udp\", \"\")\n\tcommon.Must(err)\n\tcommon.Must2(packet.WriteTo(payload, &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"127.0.0.1\"),\n\t\tPort: clientPort,\n\t}))\n\t_, _, err = packet.ReadFrom(buf[:])\n\tcommon.Must(err)\n\tif !bytes.Equal(payload, buf[:]) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestLeak(t *testing.T) {\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientData := fmt.Sprintf(`\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\nlog-level: 0\npassword:\n    - password\nssl:\n    verify: false\n    fingerprint: firefox\n    sni: localhost\nshadowsocks:\n    enabled: true\n    method: AEAD_CHACHA20_POLY1305\n    password: 12345678\nmux:\n    enabled: true\napi:\n    enabled: true\n    api-port: 0\n`, socksPort, serverPort)\n\tclient, err := proxy.NewProxyFromConfigData([]byte(clientData), false)\n\tcommon.Must(err)\n\tgo client.Run()\n\ttime.Sleep(time.Second * 3)\n\tclient.Close()\n\ttime.Sleep(time.Second * 3)\n\t// http.ListenAndServe(\"localhost:6060\", nil)\n}\n\nfunc SingleThreadBenchmark(clientData, serverData string, socksPort int) {\n\tserver, err := proxy.NewProxyFromConfigData([]byte(clientData), false)\n\tcommon.Must(err)\n\tgo server.Run()\n\n\tclient, err := proxy.NewProxyFromConfigData([]byte(serverData), false)\n\tcommon.Must(err)\n\tgo client.Run()\n\n\ttime.Sleep(time.Second * 2)\n\tdialer, err := netproxy.SOCKS5(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", socksPort), nil, netproxy.Direct)\n\tcommon.Must(err)\n\n\tconst num = 100\n\twg := sync.WaitGroup{}\n\twg.Add(num)\n\tconst payloadSize = 1024 * 1024 * 1024\n\tpayload := util.GeneratePayload(payloadSize)\n\n\tfor i := 0; i < 100; i++ {\n\t\tconn, err := dialer.Dial(\"tcp\", util.BlackHoleAddr)\n\t\tcommon.Must(err)\n\n\t\tt1 := time.Now()\n\t\tcommon.Must2(conn.Write(payload))\n\t\tt2 := time.Now()\n\n\t\tspeed := float64(payloadSize) / (float64(t2.Sub(t1).Nanoseconds()) / float64(time.Second))\n\t\tfmt.Printf(\"speed: %f Gbps\\n\", speed/1024/1024/1024)\n\n\t\tconn.Close()\n\t}\n\tclient.Close()\n\tserver.Close()\n}\n\nfunc BenchmarkClientServer(b *testing.B) {\n\tgo func() {\n\t\tfmt.Println(http.ListenAndServe(\"localhost:6060\", nil))\n\t}()\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tsocksPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tclientData := fmt.Sprintf(`\nrun-type: client\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %d\nlog-level: 0\npassword:\n    - password\nssl:\n    verify: false\n    fingerprint: firefox\n    sni: localhost\n`, socksPort, serverPort)\n\tserverData := fmt.Sprintf(`\nrun-type: server\nlocal-addr: 127.0.0.1\nlocal-port: %d\nremote-addr: 127.0.0.1\nremote-port: %s\nlog-level: 0\ndisable-http-check: true\npassword:\n    - password\nssl:\n    verify-hostname: false\n    key: server.key\n    cert: server.crt\n    sni: localhost\n`, serverPort, util.HTTPPort)\n\n\tSingleThreadBenchmark(clientData, serverData, socksPort)\n}\n"
  },
  {
    "path": "test/util/target.go",
    "content": "package util\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/net/websocket\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\nvar (\n\tHTTPAddr string\n\tHTTPPort string\n)\n\nfunc runHelloHTTPServer() {\n\thttpHello := func(w http.ResponseWriter, req *http.Request) {\n\t\tw.Write([]byte(\"HelloWorld\"))\n\t}\n\n\twsConfig, err := websocket.NewConfig(\"wss://127.0.0.1/websocket\", \"https://127.0.0.1\")\n\tcommon.Must(err)\n\twsServer := websocket.Server{\n\t\tConfig: *wsConfig,\n\t\tHandler: func(conn *websocket.Conn) {\n\t\t\tconn.Write([]byte(\"HelloWorld\"))\n\t\t},\n\t\tHandshake: func(wsConfig *websocket.Config, httpRequest *http.Request) error {\n\t\t\tlog.Debug(\"websocket url\", httpRequest.URL, \"origin\", httpRequest.Header.Get(\"Origin\"))\n\t\t\treturn nil\n\t\t},\n\t}\n\tmux := &http.ServeMux{}\n\tmux.HandleFunc(\"/\", httpHello)\n\tmux.HandleFunc(\"/websocket\", wsServer.ServeHTTP)\n\tHTTPAddr = GetTestAddr()\n\t_, HTTPPort, _ = net.SplitHostPort(HTTPAddr)\n\tserver := http.Server{\n\t\tAddr:    HTTPAddr,\n\t\tHandler: mux,\n\t}\n\tgo server.ListenAndServe()\n\ttime.Sleep(time.Second * 1) // wait for http server\n\tfmt.Println(\"http test server listening on\", HTTPAddr)\n\twg.Done()\n}\n\nvar (\n\tEchoAddr string\n\tEchoPort int\n)\n\nfunc runTCPEchoServer() {\n\tlistener, err := net.Listen(\"tcp\", EchoAddr)\n\tcommon.Must(err)\n\twg.Done()\n\tgo func() {\n\t\tdefer listener.Close()\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgo func(conn net.Conn) {\n\t\t\t\tdefer conn.Close()\n\t\t\t\tfor {\n\t\t\t\t\tbuf := make([]byte, 2048)\n\t\t\t\t\tconn.SetDeadline(time.Now().Add(time.Second * 5))\n\t\t\t\t\tn, err := conn.Read(buf)\n\t\t\t\t\tconn.SetDeadline(time.Time{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t_, err = conn.Write(buf[0:n])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\t\t}\n\t}()\n}\n\nfunc runUDPEchoServer() {\n\tconn, err := net.ListenPacket(\"udp\", EchoAddr)\n\tcommon.Must(err)\n\twg.Done()\n\tgo func() {\n\t\tfor {\n\t\t\tbuf := make([]byte, 1024*8)\n\t\t\tn, addr, err := conn.ReadFrom(buf)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Info(\"Echo from\", addr)\n\t\t\tconn.WriteTo(buf[0:n], addr)\n\t\t}\n\t}()\n}\n\nfunc GeneratePayload(length int) []byte {\n\tbuf := make([]byte, length)\n\tio.ReadFull(rand.Reader, buf)\n\treturn buf\n}\n\nvar (\n\tBlackHoleAddr string\n\tBlackHolePort int\n)\n\nfunc runTCPBlackHoleServer() {\n\tlistener, err := net.Listen(\"tcp\", BlackHoleAddr)\n\tcommon.Must(err)\n\twg.Done()\n\tgo func() {\n\t\tdefer listener.Close()\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgo func(conn net.Conn) {\n\t\t\t\tio.Copy(ioutil.Discard, conn)\n\t\t\t\tconn.Close()\n\t\t\t}(conn)\n\t\t}\n\t}()\n}\n\nfunc runUDPBlackHoleServer() {\n\tconn, err := net.ListenPacket(\"udp\", BlackHoleAddr)\n\tcommon.Must(err)\n\twg.Done()\n\tgo func() {\n\t\tdefer conn.Close()\n\t\tbuf := make([]byte, 1024*8)\n\t\tfor {\n\t\t\t_, _, err := conn.ReadFrom(buf)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nvar wg = sync.WaitGroup{}\n\nfunc init() {\n\twg.Add(5)\n\trunHelloHTTPServer()\n\n\tEchoPort = common.PickPort(\"tcp\", \"127.0.0.1\")\n\tEchoAddr = fmt.Sprintf(\"127.0.0.1:%d\", EchoPort)\n\n\tBlackHolePort = common.PickPort(\"tcp\", \"127.0.0.1\")\n\tBlackHoleAddr = fmt.Sprintf(\"127.0.0.1:%d\", BlackHolePort)\n\n\trunTCPEchoServer()\n\trunUDPEchoServer()\n\n\trunTCPBlackHoleServer()\n\trunUDPBlackHoleServer()\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "test/util/util.go",
    "content": "package util\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n)\n\n// CheckConn checks if two netConn were connected and work properly\nfunc CheckConn(a net.Conn, b net.Conn) bool {\n\tpayload1 := make([]byte, 1024)\n\tpayload2 := make([]byte, 1024)\n\n\tresult1 := make([]byte, 1024)\n\tresult2 := make([]byte, 1024)\n\n\trand.Reader.Read(payload1)\n\trand.Reader.Read(payload2)\n\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\n\tgo func() {\n\t\ta.Write(payload1)\n\t\ta.Read(result2)\n\t\twg.Done()\n\t}()\n\n\tgo func() {\n\t\tb.Read(result1)\n\t\tb.Write(payload2)\n\t\twg.Done()\n\t}()\n\n\twg.Wait()\n\n\treturn bytes.Equal(payload1, result1) && bytes.Equal(payload2, result2)\n}\n\n// CheckPacketOverConn checks if two PacketConn streaming over a connection work properly\nfunc CheckPacketOverConn(a, b net.PacketConn) bool {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\taddr := &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"127.0.0.1\"),\n\t\tPort: port,\n\t}\n\n\tpayload1 := make([]byte, 1024)\n\tpayload2 := make([]byte, 1024)\n\n\tresult1 := make([]byte, 1024)\n\tresult2 := make([]byte, 1024)\n\n\trand.Reader.Read(payload1)\n\trand.Reader.Read(payload2)\n\n\tcommon.Must2(a.WriteTo(payload1, addr))\n\t_, addr1, err := b.ReadFrom(result1)\n\tcommon.Must(err)\n\tif addr1.String() != addr.String() {\n\t\treturn false\n\t}\n\n\tcommon.Must2(a.WriteTo(payload2, addr))\n\t_, addr2, err := b.ReadFrom(result2)\n\tcommon.Must(err)\n\tif addr2.String() != addr.String() {\n\t\treturn false\n\t}\n\n\treturn bytes.Equal(payload1, result1) && bytes.Equal(payload2, result2)\n}\n\nfunc CheckPacket(a, b net.PacketConn) bool {\n\tpayload1 := make([]byte, 1024)\n\tpayload2 := make([]byte, 1024)\n\n\tresult1 := make([]byte, 1024)\n\tresult2 := make([]byte, 1024)\n\n\trand.Reader.Read(payload1)\n\trand.Reader.Read(payload2)\n\n\t_, err := a.WriteTo(payload1, b.LocalAddr())\n\tcommon.Must(err)\n\t_, _, err = b.ReadFrom(result1)\n\tcommon.Must(err)\n\n\t_, err = b.WriteTo(payload2, a.LocalAddr())\n\tcommon.Must(err)\n\t_, _, err = a.ReadFrom(result2)\n\tcommon.Must(err)\n\n\treturn bytes.Equal(payload1, result1) && bytes.Equal(payload2, result2)\n}\n\nfunc GetTestAddr() string {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\treturn fmt.Sprintf(\"127.0.0.1:%d\", port)\n}\n"
  },
  {
    "path": "tunnel/adapter/config.go",
    "content": "package adapter\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tLocalHost string `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort int    `json:\"local_port\" yaml:\"local-port\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(Config)\n\t})\n}\n"
  },
  {
    "path": "tunnel/adapter/server.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/http\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/socks\"\n)\n\ntype Server struct {\n\ttcpListener net.Listener\n\tudpListener net.PacketConn\n\tsocksConn   chan tunnel.Conn\n\thttpConn    chan tunnel.Conn\n\tsocksLock   sync.RWMutex\n\tnextSocks   bool\n\tctx         context.Context\n\tcancel      context.CancelFunc\n}\n\nfunc (s *Server) acceptConnLoop() {\n\tfor {\n\t\tconn, err := s.tcpListener.Accept()\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\trewindConn := common.NewRewindConn(conn)\n\t\trewindConn.SetBufferSize(16)\n\t\tbuf := [3]byte{}\n\t\t_, err = rewindConn.Read(buf[:])\n\t\trewindConn.Rewind()\n\t\trewindConn.StopBuffering()\n\t\tif err != nil {\n\t\t\tlog.Error(common.NewError(\"failed to detect proxy protocol type\").Base(err))\n\t\t\tcontinue\n\t\t}\n\t\ts.socksLock.RLock()\n\t\tif buf[0] == 5 && s.nextSocks {\n\t\t\ts.socksLock.RUnlock()\n\t\t\tlog.Debug(\"socks5 connection\")\n\t\t\ts.socksConn <- &freedom.Conn{\n\t\t\t\tConn: rewindConn,\n\t\t\t}\n\t\t} else {\n\t\t\ts.socksLock.RUnlock()\n\t\t\tlog.Debug(\"http connection\")\n\t\t\ts.httpConn <- &freedom.Conn{\n\t\t\t\tConn: rewindConn,\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\tif _, ok := overlay.(*http.Tunnel); ok {\n\t\tselect {\n\t\tcase conn := <-s.httpConn:\n\t\t\treturn conn, nil\n\t\tcase <-s.ctx.Done():\n\t\t\treturn nil, common.NewError(\"adapter closed\")\n\t\t}\n\t} else if _, ok := overlay.(*socks.Tunnel); ok {\n\t\ts.socksLock.Lock()\n\t\ts.nextSocks = true\n\t\ts.socksLock.Unlock()\n\t\tselect {\n\t\tcase conn := <-s.socksConn:\n\t\t\treturn conn, nil\n\t\tcase <-s.ctx.Done():\n\t\t\treturn nil, common.NewError(\"adapter closed\")\n\t\t}\n\t} else {\n\t\tpanic(\"invalid overlay\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\treturn &freedom.PacketConn{\n\t\tUDPConn: s.udpListener.(*net.UDPConn),\n\t}, nil\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\ts.tcpListener.Close()\n\treturn s.udpListener.Close()\n}\n\nfunc NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tvar cancel context.CancelFunc\n\tctx, cancel = context.WithCancel(ctx)\n\n\taddr := tunnel.NewAddressFromHostPort(\"tcp\", cfg.LocalHost, cfg.LocalPort)\n\ttcpListener, err := net.Listen(\"tcp\", addr.String())\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"adapter failed to create tcp listener\").Base(err)\n\t}\n\tudpListener, err := net.ListenPacket(\"udp\", addr.String())\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"adapter failed to create tcp listener\").Base(err)\n\t}\n\tserver := &Server{\n\t\ttcpListener: tcpListener,\n\t\tudpListener: udpListener,\n\t\tsocksConn:   make(chan tunnel.Conn, 32),\n\t\thttpConn:    make(chan tunnel.Conn, 32),\n\t\tctx:         ctx,\n\t\tcancel:      cancel,\n\t}\n\tlog.Info(\"adapter listening on tcp/udp:\", addr)\n\tgo server.acceptConnLoop()\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/adapter/tunnel.go",
    "content": "package adapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"ADAPTER\"\n\ntype Tunnel struct{}\n\nfunc (t *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/dokodemo/config.go",
    "content": "package dokodemo\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tLocalHost  string `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort  int    `json:\"local_port\" yaml:\"local-port\"`\n\tTargetHost string `json:\"target_addr\" yaml:\"target-addr\"`\n\tTargetPort int    `json:\"target_port\" yaml:\"target-port\"`\n\tUDPTimeout int    `json:\"udp_timeout\" yaml:\"udp-timeout\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tUDPTimeout: 60,\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/dokodemo/conn.go",
    "content": "package dokodemo\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst MaxPacketSize = 1024 * 8\n\ntype Conn struct {\n\tnet.Conn\n\tsrc            *tunnel.Address\n\ttargetMetadata *tunnel.Metadata\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn c.targetMetadata\n}\n\n// PacketConn receive packet info from the packet dispatcher\ntype PacketConn struct {\n\tnet.PacketConn\n\tmetadata *tunnel.Metadata\n\tinput    chan []byte\n\toutput   chan []byte\n\tsrc      net.Addr\n\tctx      context.Context\n\tcancel   context.CancelFunc\n}\n\nfunc (c *PacketConn) Close() error {\n\tc.cancel()\n\t// don't close the underlying udp socket\n\treturn nil\n}\n\nfunc (c *PacketConn) ReadFrom(p []byte) (int, net.Addr, error) {\n\treturn c.ReadWithMetadata(p)\n}\n\nfunc (c *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {\n\taddress, err := tunnel.NewAddressFromAddr(\"udp\", addr.String())\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn c.WriteWithMetadata(p, &tunnel.Metadata{\n\t\tAddress: address,\n\t})\n}\n\nfunc (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {\n\tselect {\n\tcase payload := <-c.input:\n\t\tn := copy(p, payload)\n\t\treturn n, c.metadata, nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, nil, common.NewError(\"dokodemo packet conn closed\")\n\t}\n}\n\nfunc (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {\n\tselect {\n\tcase c.output <- p:\n\t\treturn len(p), nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, common.NewError(\"dokodemo packet conn failed to write\")\n\t}\n}\n"
  },
  {
    "path": "tunnel/dokodemo/dokodemo_test.go",
    "content": "package dokodemo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n)\n\nfunc TestDokodemo(t *testing.T) {\n\tcfg := &Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t\tTargetHost: \"127.0.0.1\",\n\t\tTargetPort: common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t\tUDPTimeout: 30,\n\t}\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\ts, err := NewServer(ctx, nil)\n\tcommon.Must(err)\n\tconn1, err := net.Dial(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", cfg.LocalPort))\n\tcommon.Must(err)\n\tconn2, err := s.AcceptConn(nil)\n\tcommon.Must(err)\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\tconn1.Close()\n\tconn2.Close()\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\n\tpacket1, err := net.ListenPacket(\"udp\", \"\")\n\tcommon.Must(err)\n\tcommon.Must2(packet1.(*net.UDPConn).WriteToUDP([]byte(\"hello1\"), &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"127.0.0.1\"),\n\t\tPort: cfg.LocalPort,\n\t}))\n\tpacket2, err := s.AcceptPacket(nil)\n\tcommon.Must(err)\n\tbuf := [100]byte{}\n\tn, m, err := packet2.ReadWithMetadata(buf[:])\n\tcommon.Must(err)\n\tif m.Address.Port != cfg.TargetPort {\n\t\tt.Fail()\n\t}\n\tif string(buf[:n]) != \"hello1\" {\n\t\tt.Fail()\n\t}\n\tfmt.Println(n, m, string(buf[:n]))\n\n\tif !util.CheckPacket(packet1, packet2) {\n\t\tt.Fail()\n\t}\n\n\tpacket3, err := net.ListenPacket(\"udp\", \"\")\n\tcommon.Must(err)\n\tcommon.Must2(packet3.(*net.UDPConn).WriteToUDP([]byte(\"hello2\"), &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"127.0.0.1\"),\n\t\tPort: cfg.LocalPort,\n\t}))\n\tpacket4, err := s.AcceptPacket(nil)\n\tcommon.Must(err)\n\tn, m, err = packet4.ReadWithMetadata(buf[:])\n\tcommon.Must(err)\n\tif m.Address.Port != cfg.TargetPort {\n\t\tt.Fail()\n\t}\n\tif string(buf[:n]) != \"hello2\" {\n\t\tt.Fail()\n\t}\n\tfmt.Println(n, m, string(buf[:n]))\n\n\twg = sync.WaitGroup{}\n\twg.Add(2)\n\tgo func() {\n\t\tif !util.CheckPacket(packet3, packet4) {\n\t\t\tt.Fail()\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tif !util.CheckPacket(packet1, packet2) {\n\t\t\tt.Fail()\n\t\t}\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\ts.Close()\n}\n"
  },
  {
    "path": "tunnel/dokodemo/server.go",
    "content": "package dokodemo\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Server struct {\n\ttunnel.Server\n\ttcpListener net.Listener\n\tudpListener net.PacketConn\n\tpacketChan  chan tunnel.PacketConn\n\ttimeout     time.Duration\n\ttargetAddr  *tunnel.Address\n\tmappingLock sync.Mutex\n\tmapping     map[string]*PacketConn\n\tctx         context.Context\n\tcancel      context.CancelFunc\n}\n\nfunc (s *Server) dispatchLoop() {\n\tfixedMetadata := &tunnel.Metadata{\n\t\tAddress: s.targetAddr,\n\t}\n\tfor {\n\t\tbuf := make([]byte, MaxPacketSize)\n\t\tn, addr, err := s.udpListener.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\tdefault:\n\t\t\t\tlog.Fatal(common.NewError(\"dokodemo failed to read from udp socket\").Base(err))\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tlog.Debug(\"udp packet from\", addr)\n\t\ts.mappingLock.Lock()\n\t\tif conn, found := s.mapping[addr.String()]; found {\n\t\t\tconn.input <- buf[:n]\n\t\t\ts.mappingLock.Unlock()\n\t\t\tcontinue\n\t\t}\n\t\tctx, cancel := context.WithCancel(s.ctx)\n\t\tconn := &PacketConn{\n\t\t\tinput:      make(chan []byte, 16),\n\t\t\toutput:     make(chan []byte, 16),\n\t\t\tmetadata:   fixedMetadata,\n\t\t\tsrc:        addr,\n\t\t\tPacketConn: s.udpListener,\n\t\t\tctx:        ctx,\n\t\t\tcancel:     cancel,\n\t\t}\n\t\ts.mapping[addr.String()] = conn\n\t\ts.mappingLock.Unlock()\n\n\t\tconn.input <- buf[:n]\n\t\ts.packetChan <- conn\n\n\t\tgo func(conn *PacketConn) {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase payload := <-conn.output:\n\t\t\t\t\t// \"Multiple goroutines may invoke methods on a Conn simultaneously.\"\n\t\t\t\t\t_, err := s.udpListener.WriteTo(payload, conn.src)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"dokodemo udp write error\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\tcase <-s.ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tcase <-time.After(s.timeout):\n\t\t\t\t\ts.mappingLock.Lock()\n\t\t\t\t\tdelete(s.mapping, conn.src.String())\n\t\t\t\t\ts.mappingLock.Unlock()\n\t\t\t\t\tconn.Close()\n\t\t\t\t\tlog.Debug(\"closing timeout packetConn\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}(conn)\n\t}\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := s.tcpListener.Accept()\n\tif err != nil {\n\t\tlog.Fatal(common.NewError(\"dokodemo failed to accept connection\").Base(err))\n\t}\n\treturn &Conn{\n\t\tConn: conn,\n\t\ttargetMetadata: &tunnel.Metadata{\n\t\t\tAddress: s.targetAddr,\n\t\t},\n\t}, nil\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tselect {\n\tcase conn := <-s.packetChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"dokodemo server closed\")\n\t}\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\ts.tcpListener.Close()\n\ts.udpListener.Close()\n\treturn nil\n}\n\nfunc NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\ttargetAddr := tunnel.NewAddressFromHostPort(\"tcp\", cfg.TargetHost, cfg.TargetPort)\n\tlistenAddr := tunnel.NewAddressFromHostPort(\"tcp\", cfg.LocalHost, cfg.LocalPort)\n\n\ttcpListener, err := net.Listen(\"tcp\", listenAddr.String())\n\tif err != nil {\n\t\treturn nil, common.NewError(\"failed to listen tcp\").Base(err)\n\t}\n\tudpListener, err := net.ListenPacket(\"udp\", listenAddr.String())\n\tif err != nil {\n\t\treturn nil, common.NewError(\"failed to listen udp\").Base(err)\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\ttcpListener: tcpListener,\n\t\tudpListener: udpListener,\n\t\ttargetAddr:  targetAddr,\n\t\tmapping:     make(map[string]*PacketConn),\n\t\tpacketChan:  make(chan tunnel.PacketConn, 32),\n\t\ttimeout:     time.Second * time.Duration(cfg.UDPTimeout),\n\t\tctx:         ctx,\n\t\tcancel:      cancel,\n\t}\n\tgo server.dispatchLoop()\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/dokodemo/tunnel.go",
    "content": "package dokodemo\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"DOKODEMO\"\n\ntype Tunnel struct{ tunnel.Tunnel }\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, underlay)\n}\n\nfunc (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) {\n\treturn nil, common.NewError(\"not supported\")\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/freedom/client.go",
    "content": "package freedom\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/txthinking/socks5\"\n\t\"golang.org/x/net/proxy\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Client struct {\n\tpreferIPv4   bool\n\tnoDelay      bool\n\tkeepAlive    bool\n\tctx          context.Context\n\tcancel       context.CancelFunc\n\tforwardProxy bool\n\tproxyAddr    *tunnel.Address\n\tusername     string\n\tpassword     string\n}\n\nfunc (c *Client) DialConn(addr *tunnel.Address, _ tunnel.Tunnel) (tunnel.Conn, error) {\n\t// forward proxy\n\tif c.forwardProxy {\n\t\tvar auth *proxy.Auth\n\t\tif c.username != \"\" {\n\t\t\tauth = &proxy.Auth{\n\t\t\t\tUser:     c.username,\n\t\t\t\tPassword: c.password,\n\t\t\t}\n\t\t}\n\t\tdialer, err := proxy.SOCKS5(\"tcp\", c.proxyAddr.String(), auth, proxy.Direct)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"freedom failed to init socks dialer\")\n\t\t}\n\t\tconn, err := dialer.Dial(\"tcp\", addr.String())\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"freedom failed to dial target address via socks proxy \" + addr.String()).Base(err)\n\t\t}\n\t\treturn &Conn{\n\t\t\tConn: conn,\n\t\t}, nil\n\t}\n\tnetwork := \"tcp\"\n\tif c.preferIPv4 {\n\t\tnetwork = \"tcp4\"\n\t}\n\tdialer := new(net.Dialer)\n\ttcpConn, err := dialer.DialContext(c.ctx, network, addr.String())\n\tif err != nil {\n\t\treturn nil, common.NewError(\"freedom failed to dial \" + addr.String()).Base(err)\n\t}\n\n\ttcpConn.(*net.TCPConn).SetKeepAlive(c.keepAlive)\n\ttcpConn.(*net.TCPConn).SetNoDelay(c.noDelay)\n\treturn &Conn{\n\t\tConn: tcpConn,\n\t}, nil\n}\n\nfunc (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tif c.forwardProxy {\n\t\tsocksClient, err := socks5.NewClient(c.proxyAddr.String(), c.username, c.password, 0, 0)\n\t\tcommon.Must(err)\n\t\tif err := socksClient.Negotiate(&net.TCPAddr{}); err != nil {\n\t\t\treturn nil, common.NewError(\"freedom failed to negotiate socks\").Base(err)\n\t\t}\n\t\ta, addr, port, err := socks5.ParseAddress(\"1.1.1.1:53\") // useless address\n\t\tcommon.Must(err)\n\t\tresp, err := socksClient.Request(socks5.NewRequest(socks5.CmdUDP, a, addr, port))\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"freedom failed to dial udp to socks\").Base(err)\n\t\t}\n\t\t// TODO fix hardcoded localhost\n\t\tpacketConn, err := net.ListenPacket(\"udp\", \"127.0.0.1:0\")\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"freedom failed to listen udp\").Base(err)\n\t\t}\n\t\tsocksAddr, err := net.ResolveUDPAddr(\"udp\", resp.Address())\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"freedom recv invalid socks bind addr\").Base(err)\n\t\t}\n\t\treturn &SocksPacketConn{\n\t\t\tPacketConn:  packetConn,\n\t\t\tsocksAddr:   socksAddr,\n\t\t\tsocksClient: socksClient,\n\t\t}, nil\n\t}\n\tnetwork := \"udp\"\n\tif c.preferIPv4 {\n\t\tnetwork = \"udp4\"\n\t}\n\tudpConn, err := net.ListenPacket(network, \"\")\n\tif err != nil {\n\t\treturn nil, common.NewError(\"freedom failed to listen udp socket\").Base(err)\n\t}\n\treturn &PacketConn{\n\t\tUDPConn: udpConn.(*net.UDPConn),\n\t}, nil\n}\n\nfunc (c *Client) Close() error {\n\tc.cancel()\n\treturn nil\n}\n\nfunc NewClient(ctx context.Context, _ tunnel.Client) (*Client, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\taddr := tunnel.NewAddressFromHostPort(\"tcp\", cfg.ForwardProxy.ProxyHost, cfg.ForwardProxy.ProxyPort)\n\tctx, cancel := context.WithCancel(ctx)\n\treturn &Client{\n\t\tctx:          ctx,\n\t\tcancel:       cancel,\n\t\tnoDelay:      cfg.TCP.NoDelay,\n\t\tkeepAlive:    cfg.TCP.KeepAlive,\n\t\tpreferIPv4:   cfg.TCP.PreferIPV4,\n\t\tforwardProxy: cfg.ForwardProxy.Enabled,\n\t\tproxyAddr:    addr,\n\t\tusername:     cfg.ForwardProxy.Username,\n\t\tpassword:     cfg.ForwardProxy.Password,\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/freedom/config.go",
    "content": "package freedom\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tLocalHost    string             `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort    int                `json:\"local_port\" yaml:\"local-port\"`\n\tTCP          TCPConfig          `json:\"tcp\" yaml:\"tcp\"`\n\tForwardProxy ForwardProxyConfig `json:\"forward_proxy\" yaml:\"forward-proxy\"`\n}\n\ntype TCPConfig struct {\n\tPreferIPV4 bool `json:\"prefer_ipv4\" yaml:\"prefer-ipv4\"`\n\tKeepAlive  bool `json:\"keep_alive\" yaml:\"keep-alive\"`\n\tNoDelay    bool `json:\"no_delay\" yaml:\"no-delay\"`\n}\n\ntype ForwardProxyConfig struct {\n\tEnabled   bool   `json:\"enabled\" yaml:\"enabled\"`\n\tProxyHost string `json:\"proxy_addr\" yaml:\"proxy-addr\"`\n\tProxyPort int    `json:\"proxy_port\" yaml:\"proxy-port\"`\n\tUsername  string `json:\"username\" yaml:\"username\"`\n\tPassword  string `json:\"password\" yaml:\"password\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tTCP: TCPConfig{\n\t\t\t\tPreferIPV4: false,\n\t\t\t\tNoDelay:    true,\n\t\t\t\tKeepAlive:  true,\n\t\t\t},\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/freedom/conn.go",
    "content": "package freedom\n\nimport (\n\t\"bytes\"\n\t\"net\"\n\n\t\"github.com/txthinking/socks5\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst MaxPacketSize = 1024 * 8\n\ntype Conn struct {\n\tnet.Conn\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn nil\n}\n\ntype PacketConn struct {\n\t*net.UDPConn\n}\n\nfunc (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {\n\treturn c.WriteTo(p, m.Address)\n}\n\nfunc (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {\n\tn, addr, err := c.ReadFrom(p)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\taddress, err := tunnel.NewAddressFromAddr(\"udp\", addr.String())\n\tcommon.Must(err)\n\tmetadata := &tunnel.Metadata{\n\t\tAddress: address,\n\t}\n\treturn n, metadata, nil\n}\n\nfunc (c *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {\n\tif udpAddr, ok := addr.(*net.UDPAddr); ok {\n\t\treturn c.WriteToUDP(p, udpAddr)\n\t}\n\tip, err := addr.(*tunnel.Address).ResolveIP()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tudpAddr := &net.UDPAddr{\n\t\tIP:   ip,\n\t\tPort: addr.(*tunnel.Address).Port,\n\t}\n\treturn c.WriteToUDP(p, udpAddr)\n}\n\ntype SocksPacketConn struct {\n\tnet.PacketConn\n\tsocksAddr   *net.UDPAddr\n\tsocksClient *socks5.Client\n}\n\nfunc (c *SocksPacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata) (int, error) {\n\tbuf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize))\n\tbuf.Write([]byte{0, 0, 0}) // RSV, FRAG\n\tcommon.Must(metadata.Address.WriteTo(buf))\n\tbuf.Write(payload)\n\t_, err := c.PacketConn.WriteTo(buf.Bytes(), c.socksAddr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tlog.Debug(\"sent udp packet to \" + c.socksAddr.String() + \" with metadata \" + metadata.String())\n\treturn len(payload), nil\n}\n\nfunc (c *SocksPacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, error) {\n\tbuf := make([]byte, MaxPacketSize)\n\tn, from, err := c.PacketConn.ReadFrom(buf)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\tlog.Debug(\"recv udp packet from \" + from.String())\n\taddr := new(tunnel.Address)\n\tr := bytes.NewBuffer(buf[3:n])\n\tif err := addr.ReadFrom(r); err != nil {\n\t\treturn 0, nil, common.NewError(\"socks5 failed to parse addr in the packet\").Base(err)\n\t}\n\tlength, err := r.Read(payload)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\treturn length, &tunnel.Metadata{\n\t\tAddress: addr,\n\t}, nil\n}\n\nfunc (c *SocksPacketConn) Close() error {\n\tc.socksClient.Close()\n\treturn c.PacketConn.Close()\n}\n"
  },
  {
    "path": "tunnel/freedom/freedom_test.go",
    "content": "package freedom\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/txthinking/socks5\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nfunc TestConn(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tclient := &Client{\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t}\n\taddr, err := tunnel.NewAddressFromAddr(\"tcp\", util.EchoAddr)\n\tcommon.Must(err)\n\tconn1, err := client.DialConn(addr, nil)\n\tcommon.Must(err)\n\n\tsendBuf := util.GeneratePayload(1024)\n\trecvBuf := [1024]byte{}\n\n\tcommon.Must2(conn1.Write(sendBuf))\n\tcommon.Must2(conn1.Read(recvBuf[:]))\n\n\tif !bytes.Equal(sendBuf, recvBuf[:]) {\n\t\tt.Fail()\n\t}\n\tclient.Close()\n}\n\nfunc TestPacket(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tclient := &Client{\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t}\n\taddr, err := tunnel.NewAddressFromAddr(\"udp\", util.EchoAddr)\n\tcommon.Must(err)\n\tconn1, err := client.DialPacket(nil)\n\tcommon.Must(err)\n\n\tsendBuf := util.GeneratePayload(1024)\n\trecvBuf := [1024]byte{}\n\n\tcommon.Must2(conn1.WriteTo(sendBuf, addr))\n\t_, _, err = conn1.ReadFrom(recvBuf[:])\n\tcommon.Must(err)\n\n\tif !bytes.Equal(sendBuf, recvBuf[:]) {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestSocks(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tsocksAddr := tunnel.NewAddressFromHostPort(\"udp\", \"127.0.0.1\", common.PickPort(\"udp\", \"127.0.0.1\"))\n\tclient := &Client{\n\t\tctx:          ctx,\n\t\tcancel:       cancel,\n\t\tproxyAddr:    socksAddr,\n\t\tforwardProxy: true,\n\t\tnoDelay:      true,\n\t}\n\ttarget, err := tunnel.NewAddressFromAddr(\"tcp\", util.EchoAddr)\n\tcommon.Must(err)\n\ts, _ := socks5.NewClassicServer(socksAddr.String(), \"127.0.0.1\", \"\", \"\", 0, 0)\n\ts.Handle = &socks5.DefaultHandle{}\n\tgo s.RunTCPServer()\n\tgo s.RunUDPServer()\n\n\ttime.Sleep(time.Second * 2)\n\tconn, err := client.DialConn(target, nil)\n\tcommon.Must(err)\n\tpayload := util.GeneratePayload(1024)\n\tcommon.Must2(conn.Write(payload))\n\n\trecvBuf := [1024]byte{}\n\tconn.Read(recvBuf[:])\n\tif !bytes.Equal(recvBuf[:], payload) {\n\t\tt.Fail()\n\t}\n\tconn.Close()\n\n\tpacket, err := client.DialPacket(nil)\n\tcommon.Must(err)\n\tcommon.Must2(packet.WriteWithMetadata(payload, &tunnel.Metadata{\n\t\tAddress: target,\n\t}))\n\n\trecvBuf = [1024]byte{}\n\tn, m, err := packet.ReadWithMetadata(recvBuf[:])\n\tcommon.Must(err)\n\n\tif n != 1024 || !bytes.Equal(recvBuf[:], payload) {\n\t\tt.Fail()\n\t}\n\n\tfmt.Println(m)\n\tpacket.Close()\n\tclient.Close()\n}\n"
  },
  {
    "path": "tunnel/freedom/tunnel.go",
    "content": "package freedom\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"FREEDOM\"\n\ntype Tunnel struct{}\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, client tunnel.Server) (tunnel.Server, error) {\n\tpanic(\"not supported\")\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/http/http_test.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nfunc TestHTTP(t *testing.T) {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tctx := config.WithConfig(context.Background(), transport.Name, &transport.Config{\n\t\tLocalHost: \"127.0.0.1\",\n\t\tLocalPort: port,\n\t})\n\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\ts, err := NewServer(ctx, tcpServer)\n\tcommon.Must(err)\n\n\tfor i := 0; i < 10; i++ {\n\t\tgo func() {\n\t\t\tresp, err := http.Get(fmt.Sprintf(\"http://127.0.0.1:%d\", port))\n\t\t\tcommon.Must(err)\n\t\t\tdefer resp.Body.Close()\n\t\t}()\n\t\ttime.Sleep(time.Microsecond * 10)\n\t\tconn, err := s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\tbufReader := bufio.NewReader(bufio.NewReader(conn))\n\t\treq, err := http.ReadRequest(bufReader)\n\t\tcommon.Must(err)\n\t\tfmt.Println(req)\n\t\tioutil.ReadAll(req.Body)\n\t\treq.Body.Close()\n\t\tresp, err := http.Get(\"http://127.0.0.1:\" + util.HTTPPort)\n\t\tcommon.Must(err)\n\t\tdefer resp.Body.Close()\n\t\terr = resp.Write(conn)\n\t\tcommon.Must(err)\n\t\tbuf := [100]byte{}\n\t\t_, err = conn.Read(buf[:])\n\t\tif err == nil {\n\t\t\tt.Fail()\n\t\t}\n\t\tconn.Close()\n\t}\n\n\treq, err := http.NewRequest(http.MethodConnect, \"https://google.com:443\", nil)\n\tcommon.Must(err)\n\tconn1, err := net.Dial(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", port))\n\tcommon.Must(err)\n\tgo func() {\n\t\tcommon.Must(req.Write(conn1))\n\t}()\n\n\tconn2, err := s.AcceptConn(nil)\n\tcommon.Must(err)\n\n\tif conn2.Metadata().Port != 443 || conn2.Metadata().DomainName != \"google.com\" {\n\t\tt.Fail()\n\t}\n\n\tconnResp := \"HTTP/1.1 200 Connection established\\r\\n\\r\\n\"\n\tbuf := make([]byte, len(connResp))\n\t_, err = conn1.Read(buf)\n\tcommon.Must(err)\n\tif string(buf) != connResp {\n\t\tt.Fail()\n\t}\n\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\n\tconn1.Close()\n\tconn2.Close()\n\ts.Close()\n}\n"
  },
  {
    "path": "tunnel/http/server.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype ConnectConn struct {\n\tnet.Conn\n\tmetadata *tunnel.Metadata\n}\n\nfunc (c *ConnectConn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\ntype OtherConn struct {\n\tnet.Conn\n\tmetadata   *tunnel.Metadata // fixed\n\treqReader  *io.PipeReader\n\trespWriter *io.PipeWriter\n\tctx        context.Context\n\tcancel     context.CancelFunc\n}\n\nfunc (c *OtherConn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\nfunc (c *OtherConn) Read(p []byte) (int, error) {\n\tn, err := c.reqReader.Read(p)\n\tif err == io.EOF {\n\t\tif n != 0 {\n\t\t\tpanic(\"non zero\")\n\t\t}\n\t\tfor range c.ctx.Done() {\n\t\t\treturn 0, common.NewError(\"http conn closed\")\n\t\t}\n\t}\n\treturn n, err\n}\n\nfunc (c *OtherConn) Write(p []byte) (int, error) {\n\treturn c.respWriter.Write(p)\n}\n\nfunc (c *OtherConn) Close() error {\n\tc.cancel()\n\tc.reqReader.Close()\n\tc.respWriter.Close()\n\treturn nil\n}\n\ntype Server struct {\n\tunderlay tunnel.Server\n\tconnChan chan tunnel.Conn\n\tctx      context.Context\n\tcancel   context.CancelFunc\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\tlog.Error(common.NewError(\"http closed\"))\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tlog.Error(common.NewError(\"http failed to accept connection\").Base(err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tgo func(conn net.Conn) {\n\t\t\treqBufReader := bufio.NewReader(ioutil.NopCloser(conn))\n\t\t\treq, err := http.ReadRequest(reqBufReader)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(common.NewError(\"not a valid http request\").Base(err))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif strings.ToUpper(req.Method) == \"CONNECT\" { // CONNECT\n\t\t\t\taddr, err := tunnel.NewAddressFromAddr(\"tcp\", req.Host)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Error(common.NewError(\"invalid http dest address\").Base(err))\n\t\t\t\t\tconn.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tresp := fmt.Sprintf(\"HTTP/%d.%d 200 Connection established\\r\\n\\r\\n\", req.ProtoMajor, req.ProtoMinor)\n\t\t\t\t_, err = conn.Write([]byte(resp))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Error(\"http failed to respond connect request\")\n\t\t\t\t\tconn.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ts.connChan <- &ConnectConn{\n\t\t\t\t\tConn: conn,\n\t\t\t\t\tmetadata: &tunnel.Metadata{\n\t\t\t\t\t\tAddress: addr,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t} else { // GET, POST, PUT...\n\t\t\t\tdefer conn.Close()\n\t\t\t\tfor {\n\t\t\t\t\treqReader, reqWriter := io.Pipe()\n\t\t\t\t\trespReader, respWriter := io.Pipe()\n\t\t\t\t\tvar addr *tunnel.Address\n\t\t\t\t\tif addr, err = tunnel.NewAddressFromAddr(\"tcp\", req.Host); err != nil {\n\t\t\t\t\t\taddr = tunnel.NewAddressFromHostPort(\"tcp\", req.Host, 80)\n\t\t\t\t\t}\n\t\t\t\t\tlog.Debug(\"http dest\", addr)\n\n\t\t\t\t\tctx, cancel := context.WithCancel(s.ctx)\n\t\t\t\t\tnewConn := &OtherConn{\n\t\t\t\t\t\tConn: conn,\n\t\t\t\t\t\tmetadata: &tunnel.Metadata{\n\t\t\t\t\t\t\tAddress: addr,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tctx:        ctx,\n\t\t\t\t\t\tcancel:     cancel,\n\t\t\t\t\t\treqReader:  reqReader,\n\t\t\t\t\t\trespWriter: respWriter,\n\t\t\t\t\t}\n\t\t\t\t\ts.connChan <- newConn // pass this http session connection to proxy.RelayConn\n\n\t\t\t\t\terr = req.Write(reqWriter) // write request to the remote\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"http failed to write http request\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\trespBufReader := bufio.NewReader(ioutil.NopCloser(respReader)) // read response from the remote\n\t\t\t\t\tresp, err := http.ReadResponse(respBufReader, req)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"http failed to read http response\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\terr = resp.Write(conn) // send the response back to the local\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"http failed to write the response back\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tnewConn.Close()\n\t\t\t\t\treq.Body.Close()\n\t\t\t\t\tresp.Body.Close()\n\n\t\t\t\t\treq, err = http.ReadRequest(reqBufReader) // read the next http request from local\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(common.NewError(\"http failed to the read request from local\").Base(err))\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}(conn)\n\t}\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tselect {\n\tcase conn := <-s.connChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"http server closed\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\t<-s.ctx.Done()\n\treturn nil, common.NewError(\"http server closed\")\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\treturn s.underlay.Close()\n}\n\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\tunderlay: underlay,\n\t\tconnChan: make(chan tunnel.Conn, 32),\n\t\tctx:      ctx,\n\t\tcancel:   cancel,\n\t}\n\tgo server.acceptLoop()\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/http/tunnel.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"HTTP\"\n\ntype Tunnel struct{}\n\nfunc (t *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/metadata.go",
    "content": "package tunnel\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n)\n\ntype Command byte\n\ntype Metadata struct {\n\tCommand\n\t*Address\n}\n\nfunc (r *Metadata) ReadFrom(rr io.Reader) error {\n\tbyteBuf := [1]byte{}\n\t_, err := io.ReadFull(rr, byteBuf[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Command = Command(byteBuf[0])\n\tr.Address = new(Address)\n\terr = r.Address.ReadFrom(rr)\n\tif err != nil {\n\t\treturn common.NewError(\"failed to marshal address\").Base(err)\n\t}\n\treturn nil\n}\n\nfunc (r *Metadata) WriteTo(w io.Writer) error {\n\tbuf := bytes.NewBuffer(make([]byte, 0, 64))\n\tbuf.WriteByte(byte(r.Command))\n\tif err := r.Address.WriteTo(buf); err != nil {\n\t\treturn err\n\t}\n\t// use tcp by default\n\tr.Address.NetworkType = \"tcp\"\n\t_, err := w.Write(buf.Bytes())\n\treturn err\n}\n\nfunc (r *Metadata) Network() string {\n\treturn r.Address.Network()\n}\n\nfunc (r *Metadata) String() string {\n\treturn r.Address.String()\n}\n\ntype AddressType byte\n\nconst (\n\tIPv4       AddressType = 1\n\tDomainName AddressType = 3\n\tIPv6       AddressType = 4\n)\n\ntype Address struct {\n\tDomainName  string\n\tPort        int\n\tNetworkType string\n\tnet.IP\n\tAddressType\n}\n\nfunc (a *Address) String() string {\n\tswitch a.AddressType {\n\tcase IPv4:\n\t\treturn fmt.Sprintf(\"%s:%d\", a.IP.String(), a.Port)\n\tcase IPv6:\n\t\treturn fmt.Sprintf(\"[%s]:%d\", a.IP.String(), a.Port)\n\tcase DomainName:\n\t\treturn fmt.Sprintf(\"%s:%d\", a.DomainName, a.Port)\n\tdefault:\n\t\treturn \"INVALID_ADDRESS_TYPE\"\n\t}\n}\n\nfunc (a *Address) Network() string {\n\treturn a.NetworkType\n}\n\nfunc (a *Address) ResolveIP() (net.IP, error) {\n\tif a.AddressType == IPv4 || a.AddressType == IPv6 {\n\t\treturn a.IP, nil\n\t}\n\tif a.IP != nil {\n\t\treturn a.IP, nil\n\t}\n\taddr, err := net.ResolveIPAddr(\"ip\", a.DomainName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ta.IP = addr.IP\n\treturn addr.IP, nil\n}\n\nfunc NewAddressFromAddr(network string, addr string) (*Address, error) {\n\thost, portStr, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tport, err := strconv.ParseInt(portStr, 10, 32)\n\tcommon.Must(err)\n\treturn NewAddressFromHostPort(network, host, int(port)), nil\n}\n\nfunc NewAddressFromHostPort(network string, host string, port int) *Address {\n\tif ip := net.ParseIP(host); ip != nil {\n\t\tif ip.To4() != nil {\n\t\t\treturn &Address{\n\t\t\t\tIP:          ip,\n\t\t\t\tPort:        port,\n\t\t\t\tAddressType: IPv4,\n\t\t\t\tNetworkType: network,\n\t\t\t}\n\t\t}\n\t\treturn &Address{\n\t\t\tIP:          ip,\n\t\t\tPort:        port,\n\t\t\tAddressType: IPv6,\n\t\t\tNetworkType: network,\n\t\t}\n\t}\n\treturn &Address{\n\t\tDomainName:  host,\n\t\tPort:        port,\n\t\tAddressType: DomainName,\n\t\tNetworkType: network,\n\t}\n}\n\nfunc (a *Address) ReadFrom(r io.Reader) error {\n\tbyteBuf := [1]byte{}\n\t_, err := io.ReadFull(r, byteBuf[:])\n\tif err != nil {\n\t\treturn common.NewError(\"unable to read ATYP\").Base(err)\n\t}\n\ta.AddressType = AddressType(byteBuf[0])\n\tswitch a.AddressType {\n\tcase IPv4:\n\t\tvar buf [6]byte\n\t\t_, err := io.ReadFull(r, buf[:])\n\t\tif err != nil {\n\t\t\treturn common.NewError(\"failed to read IPv4\").Base(err)\n\t\t}\n\t\ta.IP = buf[0:4]\n\t\ta.Port = int(binary.BigEndian.Uint16(buf[4:6]))\n\tcase IPv6:\n\t\tvar buf [18]byte\n\t\t_, err := io.ReadFull(r, buf[:])\n\t\tif err != nil {\n\t\t\treturn common.NewError(\"failed to read IPv6\").Base(err)\n\t\t}\n\t\ta.IP = buf[0:16]\n\t\ta.Port = int(binary.BigEndian.Uint16(buf[16:18]))\n\tcase DomainName:\n\t\t_, err := io.ReadFull(r, byteBuf[:])\n\t\tlength := byteBuf[0]\n\t\tif err != nil {\n\t\t\treturn common.NewError(\"failed to read domain name length\")\n\t\t}\n\t\tbuf := make([]byte, length+2)\n\t\t_, err = io.ReadFull(r, buf)\n\t\tif err != nil {\n\t\t\treturn common.NewError(\"failed to read domain name\")\n\t\t}\n\t\t// the fucking browser uses IP as a domain name sometimes\n\t\thost := buf[0:length]\n\t\tif ip := net.ParseIP(string(host)); ip != nil {\n\t\t\ta.IP = ip\n\t\t\tif ip.To4() != nil {\n\t\t\t\ta.AddressType = IPv4\n\t\t\t} else {\n\t\t\t\ta.AddressType = IPv6\n\t\t\t}\n\t\t} else {\n\t\t\ta.DomainName = string(host)\n\t\t}\n\t\ta.Port = int(binary.BigEndian.Uint16(buf[length : length+2]))\n\tdefault:\n\t\treturn common.NewError(\"invalid ATYP \" + strconv.FormatInt(int64(a.AddressType), 10))\n\t}\n\treturn nil\n}\n\nfunc (a *Address) WriteTo(w io.Writer) error {\n\t_, err := w.Write([]byte{byte(a.AddressType)})\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch a.AddressType {\n\tcase DomainName:\n\t\tw.Write([]byte{byte(len(a.DomainName))})\n\t\t_, err = w.Write([]byte(a.DomainName))\n\tcase IPv4:\n\t\t_, err = w.Write(a.IP.To4())\n\tcase IPv6:\n\t\t_, err = w.Write(a.IP.To16())\n\tdefault:\n\t\treturn common.NewError(\"invalid ATYP \" + strconv.FormatInt(int64(a.AddressType), 10))\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tport := [2]byte{}\n\tbinary.BigEndian.PutUint16(port[:], uint16(a.Port))\n\t_, err = w.Write(port[:])\n\treturn err\n}\n"
  },
  {
    "path": "tunnel/mux/client.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/xtaci/smux\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype muxID uint32\n\nfunc generateMuxID() muxID {\n\treturn muxID(rand.Uint32())\n}\n\ntype smuxClientInfo struct {\n\tid             muxID\n\tclient         *smux.Session\n\tlastActiveTime time.Time\n\tunderlayConn   tunnel.Conn\n}\n\n// Client is a smux client\ntype Client struct {\n\tclientPoolLock sync.Mutex\n\tclientPool     map[muxID]*smuxClientInfo\n\tunderlay       tunnel.Client\n\tconcurrency    int\n\ttimeout        time.Duration\n\tctx            context.Context\n\tcancel         context.CancelFunc\n}\n\nfunc (c *Client) Close() error {\n\tc.cancel()\n\tc.clientPoolLock.Lock()\n\tdefer c.clientPoolLock.Unlock()\n\tfor id, info := range c.clientPool {\n\t\tinfo.client.Close()\n\t\tlog.Debug(\"mux client\", id, \"closed\")\n\t}\n\treturn nil\n}\n\nfunc (c *Client) cleanLoop() {\n\tvar checkDuration time.Duration\n\tif c.timeout <= 0 {\n\t\tcheckDuration = time.Second * 10\n\t\tlog.Warn(\"negative mux timeout\")\n\t} else {\n\t\tcheckDuration = c.timeout / 4\n\t}\n\tlog.Debug(\"check duration:\", checkDuration.Seconds(), \"s\")\n\tfor {\n\t\tselect {\n\t\tcase <-time.After(checkDuration):\n\t\t\tc.clientPoolLock.Lock()\n\t\t\tfor id, info := range c.clientPool {\n\t\t\t\tif info.client.IsClosed() {\n\t\t\t\t\tinfo.client.Close()\n\t\t\t\t\tinfo.underlayConn.Close()\n\t\t\t\t\tdelete(c.clientPool, id)\n\t\t\t\t\tlog.Info(\"mux client\", id, \"is dead\")\n\t\t\t\t} else if info.client.NumStreams() == 0 && time.Since(info.lastActiveTime) > c.timeout {\n\t\t\t\t\tinfo.client.Close()\n\t\t\t\t\tinfo.underlayConn.Close()\n\t\t\t\t\tdelete(c.clientPool, id)\n\t\t\t\t\tlog.Info(\"mux client\", id, \"is closed due to inactivity\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tlog.Debug(\"current mux clients: \", len(c.clientPool))\n\t\t\tfor id, info := range c.clientPool {\n\t\t\t\tlog.Debug(fmt.Sprintf(\"  - %x: %d/%d\", id, info.client.NumStreams(), c.concurrency))\n\t\t\t}\n\t\t\tc.clientPoolLock.Unlock()\n\t\tcase <-c.ctx.Done():\n\t\t\tlog.Debug(\"shutting down mux cleaner..\")\n\t\t\tc.clientPoolLock.Lock()\n\t\t\tfor id, info := range c.clientPool {\n\t\t\t\tinfo.client.Close()\n\t\t\t\tinfo.underlayConn.Close()\n\t\t\t\tdelete(c.clientPool, id)\n\t\t\t\tlog.Debug(\"mux client\", id, \"closed\")\n\t\t\t}\n\t\t\tc.clientPoolLock.Unlock()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *Client) newMuxClient() (*smuxClientInfo, error) {\n\t// The mutex should be locked when this function is called\n\tid := generateMuxID()\n\tif _, found := c.clientPool[id]; found {\n\t\treturn nil, common.NewError(\"duplicated id\")\n\t}\n\n\tfakeAddr := &tunnel.Address{\n\t\tDomainName:  \"MUX_CONN\",\n\t\tAddressType: tunnel.DomainName,\n\t}\n\tconn, err := c.underlay.DialConn(fakeAddr, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"mux failed to dial\").Base(err)\n\t}\n\tconn = newStickyConn(conn)\n\n\tsmuxConfig := smux.DefaultConfig()\n\t// smuxConfig.KeepAliveDisabled = true\n\tclient, _ := smux.Client(conn, smuxConfig)\n\tinfo := &smuxClientInfo{\n\t\tclient:         client,\n\t\tunderlayConn:   conn,\n\t\tid:             id,\n\t\tlastActiveTime: time.Now(),\n\t}\n\tc.clientPool[id] = info\n\treturn info, nil\n}\n\nfunc (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) {\n\tcreateNewConn := func(info *smuxClientInfo) (tunnel.Conn, error) {\n\t\trwc, err := info.client.Open()\n\t\tinfo.lastActiveTime = time.Now()\n\t\tif err != nil {\n\t\t\tinfo.underlayConn.Close()\n\t\t\tinfo.client.Close()\n\t\t\tdelete(c.clientPool, info.id)\n\t\t\treturn nil, common.NewError(\"mux failed to open stream from client\").Base(err)\n\t\t}\n\t\treturn &Conn{\n\t\t\trwc:  rwc,\n\t\t\tConn: info.underlayConn,\n\t\t}, nil\n\t}\n\n\tc.clientPoolLock.Lock()\n\tdefer c.clientPoolLock.Unlock()\n\tfor _, info := range c.clientPool {\n\t\tif info.client.IsClosed() {\n\t\t\tdelete(c.clientPool, info.id)\n\t\t\tlog.Info(fmt.Sprintf(\"Mux client %x is closed\", info.id))\n\t\t\tcontinue\n\t\t}\n\t\tif info.client.NumStreams() < c.concurrency || c.concurrency <= 0 {\n\t\t\treturn createNewConn(info)\n\t\t}\n\t}\n\n\tinfo, err := c.newMuxClient()\n\tif err != nil {\n\t\treturn nil, common.NewError(\"no available mux client found\").Base(err)\n\t}\n\treturn createNewConn(info)\n}\n\nfunc (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\nfunc NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {\n\tclientConfig := config.FromContext(ctx, Name).(*Config)\n\tctx, cancel := context.WithCancel(ctx)\n\tclient := &Client{\n\t\tunderlay:    underlay,\n\t\tconcurrency: clientConfig.Mux.Concurrency,\n\t\ttimeout:     time.Duration(clientConfig.Mux.IdleTimeout) * time.Second,\n\t\tctx:         ctx,\n\t\tcancel:      cancel,\n\t\tclientPool:  make(map[muxID]*smuxClientInfo),\n\t}\n\tgo client.cleanLoop()\n\tlog.Debug(\"mux client created\")\n\treturn client, nil\n}\n"
  },
  {
    "path": "tunnel/mux/config.go",
    "content": "package mux\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype MuxConfig struct {\n\tEnabled     bool `json:\"enabled\" yaml:\"enabled\"`\n\tIdleTimeout int  `json:\"idle_timeout\" yaml:\"idle-timeout\"`\n\tConcurrency int  `json:\"concurrency\" yaml:\"concurrency\"`\n}\n\ntype Config struct {\n\tMux MuxConfig `json:\"mux\" yaml:\"mux\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tMux: MuxConfig{\n\t\t\t\tEnabled:     false,\n\t\t\t\tIdleTimeout: 30,\n\t\t\t\tConcurrency: 8,\n\t\t\t},\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/mux/conn.go",
    "content": "package mux\n\nimport (\n\t\"io\"\n\t\"math/rand\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype stickyConn struct {\n\ttunnel.Conn\n\tsynQueue chan []byte\n\tfinQueue chan []byte\n}\n\nfunc (c *stickyConn) stickToPayload(p []byte) []byte {\n\tbuf := make([]byte, 0, len(p)+16)\n\tfor {\n\t\tselect {\n\t\tcase header := <-c.synQueue:\n\t\t\tbuf = append(buf, header...)\n\t\tdefault:\n\t\t\tgoto stick1\n\t\t}\n\t}\nstick1:\n\tbuf = append(buf, p...)\n\tfor {\n\t\tselect {\n\t\tcase header := <-c.finQueue:\n\t\t\tbuf = append(buf, header...)\n\t\tdefault:\n\t\t\tgoto stick2\n\t\t}\n\t}\nstick2:\n\treturn buf\n}\n\nfunc (c *stickyConn) Close() error {\n\tconst maxPaddingLength = 512\n\tpadding := [maxPaddingLength + 8]byte{'A', 'B', 'C', 'D', 'E', 'F'} // for debugging\n\tbuf := c.stickToPayload(nil)\n\tc.Write(append(buf, padding[:rand.Intn(maxPaddingLength)]...))\n\treturn c.Conn.Close()\n}\n\nfunc (c *stickyConn) Write(p []byte) (int, error) {\n\tif len(p) == 8 {\n\t\tif p[0] == 1 || p[0] == 2 { // smux 8 bytes header\n\t\t\tswitch p[1] {\n\t\t\t// THE CONTENT OF THE BUFFER MIGHT CHANGE\n\t\t\t// NEVER STORE THE POINTER TO HEADER, COPY THE HEADER INSTEAD\n\t\t\tcase 0:\n\t\t\t\t// cmdSYN\n\t\t\t\theader := make([]byte, 8)\n\t\t\t\tcopy(header, p)\n\t\t\t\tc.synQueue <- header\n\t\t\t\treturn 8, nil\n\t\t\tcase 1:\n\t\t\t\t// cmdFIN\n\t\t\t\theader := make([]byte, 8)\n\t\t\t\tcopy(header, p)\n\t\t\t\tc.finQueue <- header\n\t\t\t\treturn 8, nil\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Debug(\"other 8 bytes header\")\n\t\t}\n\t}\n\t_, err := c.Conn.Write(c.stickToPayload(p))\n\treturn len(p), err\n}\n\nfunc newStickyConn(conn tunnel.Conn) *stickyConn {\n\treturn &stickyConn{\n\t\tConn:     conn,\n\t\tsynQueue: make(chan []byte, 128),\n\t\tfinQueue: make(chan []byte, 128),\n\t}\n}\n\ntype Conn struct {\n\trwc io.ReadWriteCloser\n\ttunnel.Conn\n}\n\nfunc (c *Conn) Read(p []byte) (int, error) {\n\treturn c.rwc.Read(p)\n}\n\nfunc (c *Conn) Write(p []byte) (int, error) {\n\treturn c.rwc.Write(p)\n}\n\nfunc (c *Conn) Close() error {\n\treturn c.rwc.Close()\n}\n"
  },
  {
    "path": "tunnel/mux/mux_test.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nfunc TestMux(t *testing.T) {\n\tmuxCfg := &Config{\n\t\tMux: MuxConfig{\n\t\t\tEnabled:     true,\n\t\t\tConcurrency: 8,\n\t\t\tIdleTimeout: 60,\n\t\t},\n\t}\n\tctx := config.WithConfig(context.Background(), Name, muxCfg)\n\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tctx = config.WithConfig(ctx, transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\n\tcommon.Must(err)\n\n\tmuxTunnel := Tunnel{}\n\tmuxClient, _ := muxTunnel.NewClient(ctx, tcpClient)\n\tmuxServer, _ := muxTunnel.NewServer(ctx, tcpServer)\n\n\tconn1, err := muxClient.DialConn(nil, nil)\n\tcommon.Must2(conn1.Write(util.GeneratePayload(1024)))\n\tcommon.Must(err)\n\tbuf := [1024]byte{}\n\tconn2, err := muxServer.AcceptConn(nil)\n\tcommon.Must(err)\n\tcommon.Must2(conn2.Read(buf[:]))\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\tconn1.Close()\n\tconn2.Close()\n\tmuxClient.Close()\n\tmuxServer.Close()\n}\n"
  },
  {
    "path": "tunnel/mux/server.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtaci/smux\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\n// Server is a smux server\ntype Server struct {\n\tunderlay tunnel.Server\n\tconnChan chan tunnel.Conn\n\tctx      context.Context\n\tcancel   context.CancelFunc\n}\n\nfunc (s *Server) acceptConnWorker() {\n\tfor {\n\t\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\t\tif err != nil {\n\t\t\tlog.Debug(err)\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tgo func(conn tunnel.Conn) {\n\t\t\tsmuxConfig := smux.DefaultConfig()\n\t\t\t// smuxConfig.KeepAliveDisabled = true\n\t\t\tsmuxSession, err := smux.Server(conn, smuxConfig)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgo func(session *smux.Session, conn tunnel.Conn) {\n\t\t\t\tdefer session.Close()\n\t\t\t\tdefer conn.Close()\n\t\t\t\tfor {\n\t\t\t\t\tstream, err := session.AcceptStream()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Error(err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tselect {\n\t\t\t\t\tcase s.connChan <- &Conn{\n\t\t\t\t\t\trwc:  stream,\n\t\t\t\t\t\tConn: conn,\n\t\t\t\t\t}:\n\t\t\t\t\tcase <-s.ctx.Done():\n\t\t\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(smuxSession, conn)\n\t\t}(conn)\n\t}\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tselect {\n\tcase conn := <-s.connChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"mux server closed\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\treturn s.underlay.Close()\n}\n\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\tunderlay: underlay,\n\t\tctx:      ctx,\n\t\tcancel:   cancel,\n\t\tconnChan: make(chan tunnel.Conn, 32),\n\t}\n\tgo server.acceptConnWorker()\n\tlog.Debug(\"mux server created\")\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/mux/tunnel.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"MUX\"\n\ntype Tunnel struct{}\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/router/client.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\tv2router \"github.com/v2fly/v2ray-core/v4/app/router\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/common/geodata\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nconst (\n\tBlock  = 0\n\tBypass = 1\n\tProxy  = 2\n)\n\nconst (\n\tAsIs         = 0\n\tIPIfNonMatch = 1\n\tIPOnDemand   = 2\n)\n\nconst MaxPacketSize = 1024 * 8\n\nfunc matchDomain(list []*v2router.Domain, target string) bool {\n\tfor _, d := range list {\n\t\tswitch d.GetType() {\n\t\tcase v2router.Domain_Full:\n\t\t\tdomain := d.GetValue()\n\t\t\tif domain == target {\n\t\t\t\tlog.Tracef(\"domain %s hit domain(full) rule: %s\", target, domain)\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase v2router.Domain_Domain:\n\t\t\tdomain := d.GetValue()\n\t\t\tif strings.HasSuffix(target, domain) {\n\t\t\t\tidx := strings.Index(target, domain)\n\t\t\t\tif idx == 0 || target[idx-1] == '.' {\n\t\t\t\t\tlog.Tracef(\"domain %s hit domain rule: %s\", target, domain)\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\tcase v2router.Domain_Plain:\n\t\t\t// keyword\n\t\t\tif strings.Contains(target, d.GetValue()) {\n\t\t\t\tlog.Tracef(\"domain %s hit keyword rule: %s\", target, d.GetValue())\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase v2router.Domain_Regex:\n\t\t\tmatched, err := regexp.Match(d.GetValue(), []byte(target))\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(\"invalid regex\", d.GetValue())\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif matched {\n\t\t\t\tlog.Tracef(\"domain %s hit regex rule: %s\", target, d.GetValue())\n\t\t\t\treturn true\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Debug(\"unknown rule type:\", d.GetType().String())\n\t\t}\n\t}\n\treturn false\n}\n\nfunc matchIP(list []*v2router.CIDR, target net.IP) bool {\n\tisIPv6 := true\n\tlen := net.IPv6len\n\tif target.To4() != nil {\n\t\tlen = net.IPv4len\n\t\tisIPv6 = false\n\t}\n\tfor _, c := range list {\n\t\tn := int(c.GetPrefix())\n\t\tmask := net.CIDRMask(n, 8*len)\n\t\tcidrIP := net.IP(c.GetIp())\n\t\tif cidrIP.To4() != nil { // IPv4 CIDR\n\t\t\tif isIPv6 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else { // IPv6 CIDR\n\t\t\tif !isIPv6 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tsubnet := &net.IPNet{IP: cidrIP.Mask(mask), Mask: mask}\n\t\tif subnet.Contains(target) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc newIPAddress(address *tunnel.Address) (*tunnel.Address, error) {\n\tip, err := address.ResolveIP()\n\tif err != nil {\n\t\treturn nil, common.NewError(\"router failed to resolve ip\").Base(err)\n\t}\n\tnewAddress := &tunnel.Address{\n\t\tIP:   ip,\n\t\tPort: address.Port,\n\t}\n\tif ip.To4() != nil {\n\t\tnewAddress.AddressType = tunnel.IPv4\n\t} else {\n\t\tnewAddress.AddressType = tunnel.IPv6\n\t}\n\treturn newAddress, nil\n}\n\ntype Client struct {\n\tdomains        [3][]*v2router.Domain\n\tcidrs          [3][]*v2router.CIDR\n\tdefaultPolicy  int\n\tdomainStrategy int\n\tunderlay       tunnel.Client\n\tdirect         *freedom.Client\n\tctx            context.Context\n\tcancel         context.CancelFunc\n}\n\nfunc (c *Client) Route(address *tunnel.Address) int {\n\tif address.AddressType == tunnel.DomainName {\n\t\tif c.domainStrategy == IPOnDemand {\n\t\t\tresolvedIP, err := newIPAddress(address)\n\t\t\tif err == nil {\n\t\t\t\tfor i := Block; i <= Proxy; i++ {\n\t\t\t\t\tif matchIP(c.cidrs[i], resolvedIP.IP) {\n\t\t\t\t\t\treturn i\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor i := Block; i <= Proxy; i++ {\n\t\t\tif matchDomain(c.domains[i], address.DomainName) {\n\t\t\t\treturn i\n\t\t\t}\n\t\t}\n\t\tif c.domainStrategy == IPIfNonMatch {\n\t\t\tresolvedIP, err := newIPAddress(address)\n\t\t\tif err == nil {\n\t\t\t\tfor i := Block; i <= Proxy; i++ {\n\t\t\t\t\tif matchIP(c.cidrs[i], resolvedIP.IP) {\n\t\t\t\t\t\treturn i\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor i := Block; i <= Proxy; i++ {\n\t\t\tif matchIP(c.cidrs[i], address.IP) {\n\t\t\t\treturn i\n\t\t\t}\n\t\t}\n\t}\n\treturn c.defaultPolicy\n}\n\nfunc (c *Client) DialConn(address *tunnel.Address, overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\tpolicy := c.Route(address)\n\tswitch policy {\n\tcase Proxy:\n\t\treturn c.underlay.DialConn(address, overlay)\n\tcase Block:\n\t\treturn nil, common.NewError(\"router blocked address: \" + address.String())\n\tcase Bypass:\n\t\tconn, err := c.direct.DialConn(address, &Tunnel{})\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"router dial error\").Base(err)\n\t\t}\n\t\treturn &transport.Conn{\n\t\t\tConn: conn,\n\t\t}, nil\n\t}\n\tpanic(\"unknown policy\")\n}\n\nfunc (c *Client) DialPacket(overlay tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tdirectConn, err := net.ListenPacket(\"udp\", \"\")\n\tif err != nil {\n\t\treturn nil, common.NewError(\"router failed to dial udp (direct)\").Base(err)\n\t}\n\tproxy, err := c.underlay.DialPacket(overlay)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"router failed to dial udp (proxy)\").Base(err)\n\t}\n\tctx, cancel := context.WithCancel(c.ctx)\n\tconn := &PacketConn{\n\t\tClient:     c,\n\t\tPacketConn: directConn,\n\t\tproxy:      proxy,\n\t\tcancel:     cancel,\n\t\tctx:        ctx,\n\t\tpacketChan: make(chan *packetInfo, 16),\n\t}\n\tgo conn.packetLoop()\n\treturn conn, nil\n}\n\nfunc (c *Client) Close() error {\n\tc.cancel()\n\treturn c.underlay.Close()\n}\n\ntype codeInfo struct {\n\tcode     string\n\tstrategy int\n}\n\nfunc loadCode(cfg *Config, prefix string) []codeInfo {\n\tcodes := []codeInfo{}\n\tfor _, s := range cfg.Router.Proxy {\n\t\tif strings.HasPrefix(s, prefix) {\n\t\t\tif left := s[len(prefix):]; len(left) > 0 {\n\t\t\t\tcodes = append(codes, codeInfo{\n\t\t\t\t\tcode:     left,\n\t\t\t\t\tstrategy: Proxy,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlog.Warn(\"invalid empty rule:\", s)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, s := range cfg.Router.Bypass {\n\t\tif strings.HasPrefix(s, prefix) {\n\t\t\tif left := s[len(prefix):]; len(left) > 0 {\n\t\t\t\tcodes = append(codes, codeInfo{\n\t\t\t\t\tcode:     left,\n\t\t\t\t\tstrategy: Bypass,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlog.Warn(\"invalid empty rule:\", s)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, s := range cfg.Router.Block {\n\t\tif strings.HasPrefix(s, prefix) {\n\t\t\tif left := s[len(prefix):]; len(left) > 0 {\n\t\t\t\tcodes = append(codes, codeInfo{\n\t\t\t\t\tcode:     left,\n\t\t\t\t\tstrategy: Block,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tlog.Warn(\"invalid empty rule:\", s)\n\t\t\t}\n\t\t}\n\t}\n\treturn codes\n}\n\nfunc NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {\n\tm1 := runtime.MemStats{}\n\tm2 := runtime.MemStats{}\n\tm3 := runtime.MemStats{}\n\tm4 := runtime.MemStats{}\n\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tvar cancel context.CancelFunc\n\tctx, cancel = context.WithCancel(ctx)\n\n\tdirect, err := freedom.NewClient(ctx, nil)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"router failed to initialize raw client\").Base(err)\n\t}\n\n\tclient := &Client{\n\t\tdomains:  [3][]*v2router.Domain{},\n\t\tcidrs:    [3][]*v2router.CIDR{},\n\t\tunderlay: underlay,\n\t\tdirect:   direct,\n\t\tctx:      ctx,\n\t\tcancel:   cancel,\n\t}\n\tswitch strings.ToLower(cfg.Router.DomainStrategy) {\n\tcase \"as_is\", \"as-is\", \"asis\":\n\t\tclient.domainStrategy = AsIs\n\tcase \"ip_if_non_match\", \"ip-if-non-match\", \"ipifnonmatch\":\n\t\tclient.domainStrategy = IPIfNonMatch\n\tcase \"ip_on_demand\", \"ip-on-demand\", \"ipondemand\":\n\t\tclient.domainStrategy = IPOnDemand\n\tdefault:\n\t\treturn nil, common.NewError(\"unknown strategy: \" + cfg.Router.DomainStrategy)\n\t}\n\n\tswitch strings.ToLower(cfg.Router.DefaultPolicy) {\n\tcase \"proxy\":\n\t\tclient.defaultPolicy = Proxy\n\tcase \"bypass\":\n\t\tclient.defaultPolicy = Bypass\n\tcase \"block\":\n\t\tclient.defaultPolicy = Block\n\tdefault:\n\t\treturn nil, common.NewError(\"unknown strategy: \" + cfg.Router.DomainStrategy)\n\t}\n\n\truntime.ReadMemStats(&m1)\n\n\tgeodataLoader := geodata.NewGeodataLoader()\n\n\tipCode := loadCode(cfg, \"geoip:\")\n\tfor _, c := range ipCode {\n\t\tcode := c.code\n\t\tcidrs, err := geodataLoader.LoadIP(cfg.Router.GeoIPFilename, code)\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t} else {\n\t\t\tlog.Infof(\"geoip:%s loaded\", code)\n\t\t\tclient.cidrs[c.strategy] = append(client.cidrs[c.strategy], cidrs...)\n\t\t}\n\t}\n\n\truntime.ReadMemStats(&m2)\n\n\tsiteCode := loadCode(cfg, \"geosite:\")\n\tfor _, c := range siteCode {\n\t\tcode := c.code\n\t\tattrWanted := \"\"\n\t\t// Test if user wants domains that have an attribute\n\t\tif attrIdx := strings.Index(code, \"@\"); attrIdx > 0 {\n\t\t\tif !strings.HasSuffix(code, \"@\") {\n\t\t\t\tcode = c.code[:attrIdx]\n\t\t\t\tattrWanted = c.code[attrIdx+1:]\n\t\t\t} else { // \"geosite:google@\" is invalid\n\t\t\t\tlog.Warnf(\"geosite:%s invalid\", code)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if attrIdx == 0 { // \"geosite:@cn\" is invalid\n\t\t\tlog.Warnf(\"geosite:%s invalid\", code)\n\t\t\tcontinue\n\t\t}\n\n\t\tdomainList, err := geodataLoader.LoadSite(cfg.Router.GeoSiteFilename, code)\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t} else {\n\t\t\tfound := false\n\t\t\tif attrWanted != \"\" {\n\t\t\t\tfor _, domain := range domainList {\n\t\t\t\t\tfor _, attr := range domain.GetAttribute() {\n\t\t\t\t\t\tif strings.EqualFold(attrWanted, attr.GetKey()) {\n\t\t\t\t\t\t\tclient.domains[c.strategy] = append(client.domains[c.strategy], domain)\n\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tclient.domains[c.strategy] = append(client.domains[c.strategy], domainList...)\n\t\t\t\tfound = true\n\t\t\t}\n\t\t\tif found {\n\t\t\t\tlog.Infof(\"geosite:%s loaded\", c.code)\n\t\t\t} else {\n\t\t\t\tlog.Errorf(\"geosite:%s not found\", c.code)\n\t\t\t}\n\t\t}\n\t}\n\n\truntime.ReadMemStats(&m3)\n\n\tdomainInfo := loadCode(cfg, \"domain:\")\n\tfor _, info := range domainInfo {\n\t\tclient.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{\n\t\t\tType:      v2router.Domain_Domain,\n\t\t\tValue:     strings.ToLower(info.code),\n\t\t\tAttribute: nil,\n\t\t})\n\t}\n\n\tkeywordInfo := loadCode(cfg, \"keyword:\")\n\tfor _, info := range keywordInfo {\n\t\tclient.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{\n\t\t\tType:      v2router.Domain_Plain,\n\t\t\tValue:     strings.ToLower(info.code),\n\t\t\tAttribute: nil,\n\t\t})\n\t}\n\n\tregexInfo := loadCode(cfg, \"regex:\")\n\tfor _, info := range regexInfo {\n\t\tif _, err := regexp.Compile(info.code); err != nil {\n\t\t\treturn nil, common.NewError(\"invalid regular expression: \" + info.code).Base(err)\n\t\t}\n\t\tclient.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{\n\t\t\tType:      v2router.Domain_Regex,\n\t\t\tValue:     info.code,\n\t\t\tAttribute: nil,\n\t\t})\n\t}\n\n\t// Just for compatibility with V2Ray rule type `regexp`\n\tregexpInfo := loadCode(cfg, \"regexp:\")\n\tfor _, info := range regexpInfo {\n\t\tif _, err := regexp.Compile(info.code); err != nil {\n\t\t\treturn nil, common.NewError(\"invalid regular expression: \" + info.code).Base(err)\n\t\t}\n\t\tclient.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{\n\t\t\tType:      v2router.Domain_Regex,\n\t\t\tValue:     info.code,\n\t\t\tAttribute: nil,\n\t\t})\n\t}\n\n\tfullInfo := loadCode(cfg, \"full:\")\n\tfor _, info := range fullInfo {\n\t\tclient.domains[info.strategy] = append(client.domains[info.strategy], &v2router.Domain{\n\t\t\tType:      v2router.Domain_Full,\n\t\t\tValue:     strings.ToLower(info.code),\n\t\t\tAttribute: nil,\n\t\t})\n\t}\n\n\tcidrInfo := loadCode(cfg, \"cidr:\")\n\tfor _, info := range cidrInfo {\n\t\ttmp := strings.Split(info.code, \"/\")\n\t\tif len(tmp) != 2 {\n\t\t\treturn nil, common.NewError(\"invalid cidr: \" + info.code)\n\t\t}\n\t\tip := net.ParseIP(tmp[0])\n\t\tif ip == nil {\n\t\t\treturn nil, common.NewError(\"invalid cidr ip: \" + info.code)\n\t\t}\n\t\tprefix, err := strconv.ParseInt(tmp[1], 10, 32)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"invalid prefix\").Base(err)\n\t\t}\n\t\tclient.cidrs[info.strategy] = append(client.cidrs[info.strategy], &v2router.CIDR{\n\t\t\tIp:     ip,\n\t\t\tPrefix: uint32(prefix),\n\t\t})\n\t}\n\n\tlog.Info(\"router client created\")\n\n\truntime.ReadMemStats(&m4)\n\n\tlog.Debugf(\"GeoIP rules -> Alloc: %s; TotalAlloc: %s\", common.HumanFriendlyTraffic(m2.Alloc-m1.Alloc), common.HumanFriendlyTraffic(m2.TotalAlloc-m1.TotalAlloc))\n\tlog.Debugf(\"GeoSite rules -> Alloc: %s; TotalAlloc: %s\", common.HumanFriendlyTraffic(m3.Alloc-m2.Alloc), common.HumanFriendlyTraffic(m3.TotalAlloc-m2.TotalAlloc))\n\tlog.Debugf(\"Plaintext rules -> Alloc: %s; TotalAlloc: %s\", common.HumanFriendlyTraffic(m4.Alloc-m3.Alloc), common.HumanFriendlyTraffic(m4.TotalAlloc-m3.TotalAlloc))\n\tlog.Debugf(\"Total(router) -> Alloc: %s; TotalAlloc: %s\", common.HumanFriendlyTraffic(m4.Alloc-m1.Alloc), common.HumanFriendlyTraffic(m4.TotalAlloc-m1.TotalAlloc))\n\n\treturn client, nil\n}\n"
  },
  {
    "path": "tunnel/router/config.go",
    "content": "package router\n\nimport (\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n)\n\ntype Config struct {\n\tRouter RouterConfig `json:\"router\" yaml:\"router\"`\n}\n\ntype RouterConfig struct {\n\tEnabled         bool     `json:\"enabled\" yaml:\"enabled\"`\n\tBypass          []string `json:\"bypass\" yaml:\"bypass\"`\n\tProxy           []string `json:\"proxy\" yaml:\"proxy\"`\n\tBlock           []string `json:\"block\" yaml:\"block\"`\n\tDomainStrategy  string   `json:\"domain_strategy\" yaml:\"domain-strategy\"`\n\tDefaultPolicy   string   `json:\"default_policy\" yaml:\"default-policy\"`\n\tGeoIPFilename   string   `json:\"geoip\" yaml:\"geoip\"`\n\tGeoSiteFilename string   `json:\"geosite\" yaml:\"geosite\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\tcfg := &Config{\n\t\t\tRouter: RouterConfig{\n\t\t\t\tDefaultPolicy:   \"proxy\",\n\t\t\t\tDomainStrategy:  \"as_is\",\n\t\t\t\tGeoIPFilename:   common.GetAssetLocation(\"geoip.dat\"),\n\t\t\t\tGeoSiteFilename: common.GetAssetLocation(\"geosite.dat\"),\n\t\t\t},\n\t\t}\n\t\treturn cfg\n\t})\n}\n"
  },
  {
    "path": "tunnel/router/conn.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype packetInfo struct {\n\tsrc     *tunnel.Metadata\n\tpayload []byte\n}\n\ntype PacketConn struct {\n\tproxy tunnel.PacketConn\n\tnet.PacketConn\n\tpacketChan chan *packetInfo\n\t*Client\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc (c *PacketConn) packetLoop() {\n\tgo func() {\n\t\tfor {\n\t\t\tbuf := make([]byte, MaxPacketSize)\n\t\t\tn, addr, err := c.proxy.ReadWithMetadata(buf)\n\t\t\tif err != nil {\n\t\t\t\tselect {\n\t\t\t\tcase <-c.ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Error(\"router packetConn error\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tc.packetChan <- &packetInfo{\n\t\t\t\tsrc:     addr,\n\t\t\t\tpayload: buf[:n],\n\t\t\t}\n\t\t}\n\t}()\n\tfor {\n\t\tbuf := make([]byte, MaxPacketSize)\n\t\tn, addr, err := c.PacketConn.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-c.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tlog.Error(\"router packetConn error\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\taddress, _ := tunnel.NewAddressFromAddr(\"udp\", addr.String())\n\t\tc.packetChan <- &packetInfo{\n\t\t\tsrc: &tunnel.Metadata{\n\t\t\t\tAddress: address,\n\t\t\t},\n\t\t\tpayload: buf[:n],\n\t\t}\n\t}\n}\n\nfunc (c *PacketConn) Close() error {\n\tc.cancel()\n\tc.proxy.Close()\n\treturn c.PacketConn.Close()\n}\n\nfunc (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {\n\tpolicy := c.Route(m.Address)\n\tswitch policy {\n\tcase Proxy:\n\t\treturn c.proxy.WriteWithMetadata(p, m)\n\tcase Block:\n\t\treturn 0, common.NewError(\"router blocked address (udp): \" + m.Address.String())\n\tcase Bypass:\n\t\tip, err := m.Address.ResolveIP()\n\t\tif err != nil {\n\t\t\treturn 0, common.NewError(\"router failed to resolve udp address\").Base(err)\n\t\t}\n\t\treturn c.PacketConn.WriteTo(p, &net.UDPAddr{\n\t\t\tIP:   ip,\n\t\t\tPort: m.Address.Port,\n\t\t})\n\tdefault:\n\t\tpanic(\"unknown policy\")\n\t}\n}\n\nfunc (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {\n\tselect {\n\tcase info := <-c.packetChan:\n\t\tn := copy(p, info.payload)\n\t\treturn n, info.src, nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, nil, io.EOF\n\t}\n}\n"
  },
  {
    "path": "tunnel/router/data.go",
    "content": "package router\n"
  },
  {
    "path": "tunnel/router/router_test.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype MockClient struct{}\n\nfunc (m *MockClient) DialConn(address *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) {\n\treturn nil, common.NewError(\"mockproxy\")\n}\n\nfunc (m *MockClient) DialPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) {\n\treturn MockPacketConn{}, nil\n}\n\nfunc (m MockClient) Close() error {\n\treturn nil\n}\n\ntype MockPacketConn struct{}\n\nfunc (m MockPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) Close() error {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) LocalAddr() net.Addr {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) SetDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) SetReadDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) SetWriteDeadline(t time.Time) error {\n\tpanic(\"implement me\")\n}\n\nfunc (m MockPacketConn) WriteWithMetadata(bytes []byte, metadata *tunnel.Metadata) (int, error) {\n\treturn 0, common.NewError(\"mockproxy\")\n}\n\nfunc (m MockPacketConn) ReadWithMetadata(bytes []byte) (int, *tunnel.Metadata, error) {\n\treturn 0, nil, common.NewError(\"mockproxy\")\n}\n\nfunc TestRouter(t *testing.T) {\n\tdata := `\nrouter:\n    enabled: true\n    bypass: \n    - \"regex:bypassreg(.*)\"\n    - \"full:bypassfull\"\n    - \"full:localhost\"\n    - \"domain:bypass.com\"\n    block:\n    - \"regexp:blockreg(.*)\"\n    - \"full:blockfull\"\n    - \"domain:block.com\"\n    proxy:\n    - \"regexp:proxyreg(.*)\"\n    - \"full:proxyfull\"\n    - \"domain:proxy.com\"\n    - \"cidr:192.168.1.1/16\"\n`\n\tctx, err := config.WithYAMLConfig(context.Background(), []byte(data))\n\tcommon.Must(err)\n\tclient, err := NewClient(ctx, &MockClient{})\n\tcommon.Must(err)\n\t_, err = client.DialConn(&tunnel.Address{\n\t\tAddressType: tunnel.DomainName,\n\t\tDomainName:  \"proxy.com\",\n\t\tPort:        80,\n\t}, nil)\n\tif err.Error() != \"mockproxy\" {\n\t\tt.Fatal(err)\n\t}\n\t_, err = client.DialConn(&tunnel.Address{\n\t\tAddressType: tunnel.DomainName,\n\t\tDomainName:  \"proxyreg123456\",\n\t\tPort:        80,\n\t}, nil)\n\tif err.Error() != \"mockproxy\" {\n\t\tt.Fatal(err)\n\t}\n\t_, err = client.DialConn(&tunnel.Address{\n\t\tAddressType: tunnel.DomainName,\n\t\tDomainName:  \"proxyfull\",\n\t\tPort:        80,\n\t}, nil)\n\tif err.Error() != \"mockproxy\" {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = client.DialConn(&tunnel.Address{\n\t\tAddressType: tunnel.IPv4,\n\t\tIP:          net.ParseIP(\"192.168.123.123\"),\n\t\tPort:        80,\n\t}, nil)\n\tif err.Error() != \"mockproxy\" {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = client.DialConn(&tunnel.Address{\n\t\tAddressType: tunnel.DomainName,\n\t\tDomainName:  \"block.com\",\n\t\tPort:        80,\n\t}, nil)\n\tif !strings.Contains(err.Error(), \"block\") {\n\t\tt.Fatal(\"block??\")\n\t}\n\tport, err := strconv.Atoi(util.HTTPPort)\n\tcommon.Must(err)\n\n\t_, err = client.DialConn(&tunnel.Address{\n\t\tAddressType: tunnel.DomainName,\n\t\tDomainName:  \"localhost\",\n\t\tPort:        port,\n\t}, nil)\n\tif err != nil {\n\t\tt.Fatal(\"dial http failed\", err)\n\t}\n\n\tpacket, err := client.DialPacket(nil)\n\tcommon.Must(err)\n\tbuf := [10]byte{}\n\t_, err = packet.WriteWithMetadata(buf[:], &tunnel.Metadata{\n\t\tAddress: &tunnel.Address{\n\t\t\tAddressType: tunnel.DomainName,\n\t\t\tDomainName:  \"proxyfull\",\n\t\t\tPort:        port,\n\t\t},\n\t})\n\tif err.Error() != \"mockproxy\" {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "tunnel/router/tunnel.go",
    "content": "package router\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"ROUTER\"\n\ntype Tunnel struct{}\n\nfunc (t *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\tpanic(\"not supported\")\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/shadowsocks/client.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/shadowsocks/go-shadowsocks2/core\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Client struct {\n\tunderlay tunnel.Client\n\tcore.Cipher\n}\n\nfunc (c *Client) DialConn(address *tunnel.Address, tunnel tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := c.underlay.DialConn(address, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Conn{\n\t\taeadConn: c.Cipher.StreamConn(conn),\n\t\tConn:     conn,\n\t}, nil\n}\n\nfunc (c *Client) DialPacket(tunnel tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (c *Client) Close() error {\n\treturn c.underlay.Close()\n}\n\nfunc NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tcipher, err := core.PickCipher(cfg.Shadowsocks.Method, nil, cfg.Shadowsocks.Password)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"invalid shadowsocks cipher\").Base(err)\n\t}\n\tlog.Debug(\"shadowsocks client created\")\n\treturn &Client{\n\t\tunderlay: underlay,\n\t\tCipher:   cipher,\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/shadowsocks/config.go",
    "content": "package shadowsocks\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype ShadowsocksConfig struct {\n\tEnabled  bool   `json:\"enabled\" yaml:\"enabled\"`\n\tMethod   string `json:\"method\" yaml:\"method\"`\n\tPassword string `json:\"password\" yaml:\"password\"`\n}\n\ntype Config struct {\n\tRemoteHost  string            `json:\"remote_addr\" yaml:\"remote-addr\"`\n\tRemotePort  int               `json:\"remote_port\" yaml:\"remote-port\"`\n\tShadowsocks ShadowsocksConfig `json:\"shadowsocks\" yaml:\"shadowsocks\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tShadowsocks: ShadowsocksConfig{\n\t\t\t\tMethod: \"AES-128-GCM\",\n\t\t\t},\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/shadowsocks/conn.go",
    "content": "package shadowsocks\n\nimport (\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Conn struct {\n\taeadConn net.Conn\n\ttunnel.Conn\n}\n\nfunc (c *Conn) Read(p []byte) (n int, err error) {\n\treturn c.aeadConn.Read(p)\n}\n\nfunc (c *Conn) Write(p []byte) (n int, err error) {\n\treturn c.aeadConn.Write(p)\n}\n\nfunc (c *Conn) Close() error {\n\tc.Conn.Close()\n\treturn c.aeadConn.Close()\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn c.Conn.Metadata()\n}\n"
  },
  {
    "path": "tunnel/shadowsocks/server.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/shadowsocks/go-shadowsocks2/core\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/redirector\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Server struct {\n\tcore.Cipher\n\t*redirector.Redirector\n\tunderlay  tunnel.Server\n\tredirAddr net.Addr\n}\n\nfunc (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"shadowsocks failed to accept connection from underlying tunnel\").Base(err)\n\t}\n\trewindConn := common.NewRewindConn(conn)\n\trewindConn.SetBufferSize(1024)\n\tdefer rewindConn.StopBuffering()\n\n\t// try to read something from this connection\n\tbuf := [1024]byte{}\n\ttestConn := s.Cipher.StreamConn(rewindConn)\n\tif _, err := testConn.Read(buf[:]); err != nil {\n\t\t// we are under attack\n\t\tlog.Error(common.NewError(\"shadowsocks failed to decrypt\").Base(err))\n\t\trewindConn.Rewind()\n\t\trewindConn.StopBuffering()\n\t\ts.Redirect(&redirector.Redirection{\n\t\t\tRedirectTo:  s.redirAddr,\n\t\t\tInboundConn: rewindConn,\n\t\t})\n\t\treturn nil, common.NewError(\"invalid aead payload\")\n\t}\n\trewindConn.Rewind()\n\trewindConn.StopBuffering()\n\n\treturn &Conn{\n\t\taeadConn: s.Cipher.StreamConn(rewindConn),\n\t\tConn:     conn,\n\t}, nil\n}\n\nfunc (s *Server) AcceptPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (s *Server) Close() error {\n\treturn s.underlay.Close()\n}\n\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tcipher, err := core.PickCipher(cfg.Shadowsocks.Method, nil, cfg.Shadowsocks.Password)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"invalid shadowsocks cipher\").Base(err)\n\t}\n\tif cfg.RemoteHost == \"\" {\n\t\treturn nil, common.NewError(\"invalid shadowsocks redirection address\")\n\t}\n\tif cfg.RemotePort == 0 {\n\t\treturn nil, common.NewError(\"invalid shadowsocks redirection port\")\n\t}\n\tlog.Debug(\"shadowsocks client created\")\n\treturn &Server{\n\t\tunderlay:   underlay,\n\t\tCipher:     cipher,\n\t\tRedirector: redirector.NewRedirector(ctx),\n\t\tredirAddr:  tunnel.NewAddressFromHostPort(\"tcp\", cfg.RemoteHost, cfg.RemotePort),\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/shadowsocks/shadowsocks_test.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nfunc TestShadowsocks(t *testing.T) {\n\tp, err := strconv.ParseInt(util.HTTPPort, 10, 32)\n\tcommon.Must(err)\n\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tctx := config.WithConfig(context.Background(), transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\n\tcfg := &Config{\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: int(p),\n\t\tShadowsocks: ShadowsocksConfig{\n\t\t\tEnabled:  true,\n\t\t\tMethod:   \"AES-128-GCM\",\n\t\t\tPassword: \"password\",\n\t\t},\n\t}\n\tctx = config.WithConfig(ctx, Name, cfg)\n\n\tc, err := NewClient(ctx, tcpClient)\n\tcommon.Must(err)\n\ts, err := NewServer(ctx, tcpServer)\n\tcommon.Must(err)\n\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\tvar conn1, conn2 net.Conn\n\tgo func() {\n\t\tvar err error\n\t\tconn1, err = c.DialConn(nil, nil)\n\t\tcommon.Must(err)\n\t\tconn1.Write(util.GeneratePayload(1024))\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tvar err error\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\tbuf := [1024]byte{}\n\t\tconn2.Read(buf[:])\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\n\tgo func() {\n\t\tvar err error\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tif err == nil {\n\t\t\tt.Fail()\n\t\t}\n\t}()\n\n\t// test redirection\n\tconn3, err := tcpClient.DialConn(nil, nil)\n\tcommon.Must(err)\n\tn, err := conn3.Write(util.GeneratePayload(1024))\n\tcommon.Must(err)\n\tfmt.Println(\"write:\", n)\n\tbuf := [1024]byte{}\n\tn, err = conn3.Read(buf[:])\n\tcommon.Must(err)\n\tfmt.Println(\"read:\", n)\n\tif !strings.Contains(string(buf[:n]), \"Bad Request\") {\n\t\tt.Fail()\n\t}\n\tconn1.Close()\n\tconn3.Close()\n\tc.Close()\n\ts.Close()\n}\n"
  },
  {
    "path": "tunnel/shadowsocks/tunnel.go",
    "content": "package shadowsocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"SHADOWSOCKS\"\n\ntype Tunnel struct{}\n\nfunc (t *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/simplesocks/client.go",
    "content": "package simplesocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n)\n\nconst (\n\tConnect   tunnel.Command = 1\n\tAssociate tunnel.Command = 3\n)\n\ntype Client struct {\n\tunderlay tunnel.Client\n}\n\nfunc (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := c.underlay.DialConn(nil, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"simplesocks failed to dial using underlying tunnel\").Base(err)\n\t}\n\treturn &Conn{\n\t\tConn:       conn,\n\t\tisOutbound: true,\n\t\tmetadata: &tunnel.Metadata{\n\t\t\tCommand: Connect,\n\t\t\tAddress: addr,\n\t\t},\n\t}, nil\n}\n\nfunc (c *Client) DialPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tconn, err := c.underlay.DialConn(nil, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"simplesocks failed to dial using underlying tunnel\").Base(err)\n\t}\n\tmetadata := &tunnel.Metadata{\n\t\tCommand: Associate,\n\t\tAddress: &tunnel.Address{\n\t\t\tDomainName:  \"UDP_CONN\",\n\t\t\tAddressType: tunnel.DomainName,\n\t\t},\n\t}\n\tif err := metadata.WriteTo(conn); err != nil {\n\t\treturn nil, common.NewError(\"simplesocks failed to write udp associate\").Base(err)\n\t}\n\treturn &PacketConn{\n\t\tPacketConn: trojan.PacketConn{\n\t\t\tConn: conn,\n\t\t},\n\t}, nil\n}\n\nfunc (c *Client) Close() error {\n\treturn c.underlay.Close()\n}\n\nfunc NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {\n\tlog.Debug(\"simplesocks client created\")\n\treturn &Client{\n\t\tunderlay: underlay,\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/simplesocks/conn.go",
    "content": "package simplesocks\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n)\n\n// Conn is a simplesocks connection\ntype Conn struct {\n\ttunnel.Conn\n\tmetadata      *tunnel.Metadata\n\tisOutbound    bool\n\theaderWritten bool\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\nfunc (c *Conn) Write(payload []byte) (int, error) {\n\tif c.isOutbound && !c.headerWritten {\n\t\tbuf := bytes.NewBuffer(make([]byte, 0, 4096))\n\t\tc.metadata.WriteTo(buf)\n\t\tbuf.Write(payload)\n\t\t_, err := c.Conn.Write(buf.Bytes())\n\t\tif err != nil {\n\t\t\treturn 0, common.NewError(\"failed to write simplesocks header\").Base(err)\n\t\t}\n\t\tc.headerWritten = true\n\t\treturn len(payload), nil\n\t}\n\treturn c.Conn.Write(payload)\n}\n\n// PacketConn is a simplesocks packet connection\n// The header syntax is the same as trojan's\ntype PacketConn struct {\n\ttrojan.PacketConn\n}\n"
  },
  {
    "path": "tunnel/simplesocks/server.go",
    "content": "package simplesocks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/trojan\"\n)\n\n// Server is a simplesocks server\ntype Server struct {\n\tunderlay   tunnel.Server\n\tconnChan   chan tunnel.Conn\n\tpacketChan chan tunnel.PacketConn\n\tctx        context.Context\n\tcancel     context.CancelFunc\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\treturn s.underlay.Close()\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\t\tif err != nil {\n\t\t\tlog.Error(common.NewError(\"simplesocks failed to accept connection from underlying tunnel\").Base(err))\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tmetadata := new(tunnel.Metadata)\n\t\tif err := metadata.ReadFrom(conn); err != nil {\n\t\t\tlog.Error(common.NewError(\"simplesocks server faield to read header\").Base(err))\n\t\t\tconn.Close()\n\t\t\tcontinue\n\t\t}\n\t\tswitch metadata.Command {\n\t\tcase Connect:\n\t\t\ts.connChan <- &Conn{\n\t\t\t\tmetadata: metadata,\n\t\t\t\tConn:     conn,\n\t\t\t}\n\t\tcase Associate:\n\t\t\ts.packetChan <- &PacketConn{\n\t\t\t\tPacketConn: trojan.PacketConn{\n\t\t\t\t\tConn: conn,\n\t\t\t\t},\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Error(common.NewError(fmt.Sprintf(\"simplesocks unknown command %d\", metadata.Command)))\n\t\t\tconn.Close()\n\t\t}\n\t}\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tselect {\n\tcase conn := <-s.connChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"simplesocks server closed\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tselect {\n\tcase packetConn := <-s.packetChan:\n\t\treturn packetConn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"simplesocks server closed\")\n\t}\n}\n\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\tunderlay:   underlay,\n\t\tctx:        ctx,\n\t\tconnChan:   make(chan tunnel.Conn, 32),\n\t\tpacketChan: make(chan tunnel.PacketConn, 32),\n\t\tcancel:     cancel,\n\t}\n\tgo server.acceptLoop()\n\tlog.Debug(\"simplesocks server created\")\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/simplesocks/simplesocks_test.go",
    "content": "package simplesocks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nfunc TestSimpleSocks(t *testing.T) {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tctx := config.WithConfig(context.Background(), transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\n\tc, err := NewClient(ctx, tcpClient)\n\tcommon.Must(err)\n\ts, err := NewServer(ctx, tcpServer)\n\tcommon.Must(err)\n\n\tconn1, err := c.DialConn(&tunnel.Address{\n\t\tDomainName:  \"www.baidu.com\",\n\t\tAddressType: tunnel.DomainName,\n\t\tPort:        443,\n\t}, nil)\n\tcommon.Must(err)\n\tdefer conn1.Close()\n\tconn1.Write(util.GeneratePayload(1024))\n\tconn2, err := s.AcceptConn(nil)\n\tcommon.Must(err)\n\tdefer conn2.Close()\n\tbuf := [1024]byte{}\n\tcommon.Must2(conn2.Read(buf[:]))\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\n\tpacket1, err := c.DialPacket(nil)\n\tcommon.Must(err)\n\tpacket1.WriteWithMetadata([]byte(\"12345678\"), &tunnel.Metadata{\n\t\tAddress: &tunnel.Address{\n\t\t\tDomainName:  \"test.com\",\n\t\t\tAddressType: tunnel.DomainName,\n\t\t\tPort:        443,\n\t\t},\n\t})\n\tdefer packet1.Close()\n\tpacket2, err := s.AcceptPacket(nil)\n\tcommon.Must(err)\n\tdefer packet2.Close()\n\t_, m, err := packet2.ReadWithMetadata(buf[:])\n\tcommon.Must(err)\n\tfmt.Println(m)\n\n\tif !util.CheckPacketOverConn(packet1, packet2) {\n\t\tt.Fail()\n\t}\n\ts.Close()\n\tc.Close()\n}\n"
  },
  {
    "path": "tunnel/simplesocks/tunnel.go",
    "content": "package simplesocks\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"SIMPLESOCKS\"\n\ntype Tunnel struct{}\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, underlay)\n}\n\nfunc (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, underlay)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/socks/config.go",
    "content": "package socks\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tLocalHost  string `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort  int    `json:\"local_port\" yaml:\"local-port\"`\n\tUDPTimeout int    `json:\"udp_timeout\" yaml:\"udp-timeout\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tUDPTimeout: 60,\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/socks/conn.go",
    "content": "package socks\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Conn struct {\n\tnet.Conn\n\tmetadata *tunnel.Metadata\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\ntype packetInfo struct {\n\tmetadata *tunnel.Metadata\n\tpayload  []byte\n}\n\ntype PacketConn struct {\n\tnet.PacketConn\n\tinput  chan *packetInfo\n\toutput chan *packetInfo\n\tsrc    net.Addr\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (c *PacketConn) Close() error {\n\tc.cancel()\n\treturn nil\n}\n\nfunc (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {\n\tselect {\n\tcase c.output <- &packetInfo{\n\t\tmetadata: m,\n\t\tpayload:  p,\n\t}:\n\t\treturn len(p), nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, common.NewError(\"socks packet conn closed\")\n\t}\n}\n\nfunc (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {\n\tselect {\n\tcase info := <-c.input:\n\t\tn := copy(p, info.payload)\n\t\treturn n, info.metadata, nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, nil, common.NewError(\"socks packet conn closed\")\n\t}\n}\n"
  },
  {
    "path": "tunnel/socks/server.go",
    "content": "package socks\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst (\n\tConnect   tunnel.Command = 1\n\tAssociate tunnel.Command = 3\n)\n\nconst (\n\tMaxPacketSize = 1024 * 8\n)\n\ntype Server struct {\n\tconnChan         chan tunnel.Conn\n\tpacketChan       chan tunnel.PacketConn\n\tunderlay         tunnel.Server\n\tlocalHost        string\n\tlocalPort        int\n\ttimeout          time.Duration\n\tlistenPacketConn tunnel.PacketConn\n\tmapping          map[string]*PacketConn\n\tmappingLock      sync.RWMutex\n\tctx              context.Context\n\tcancel           context.CancelFunc\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tselect {\n\tcase conn := <-s.connChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"socks server closed\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tselect {\n\tcase conn := <-s.packetChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"socks server closed\")\n\t}\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\treturn s.underlay.Close()\n}\n\nfunc (s *Server) handshake(conn net.Conn) (*Conn, error) {\n\tversion := [1]byte{}\n\tif _, err := conn.Read(version[:]); err != nil {\n\t\treturn nil, common.NewError(\"failed to read socks version\").Base(err)\n\t}\n\tif version[0] != 5 {\n\t\treturn nil, common.NewError(fmt.Sprintf(\"invalid socks version %d\", version[0]))\n\t}\n\tnmethods := [1]byte{}\n\tif _, err := conn.Read(nmethods[:]); err != nil {\n\t\treturn nil, common.NewError(\"failed to read NMETHODS\")\n\t}\n\tif _, err := io.CopyN(ioutil.Discard, conn, int64(nmethods[0])); err != nil {\n\t\treturn nil, common.NewError(\"socks failed to read methods\").Base(err)\n\t}\n\tif _, err := conn.Write([]byte{0x5, 0x0}); err != nil {\n\t\treturn nil, common.NewError(\"failed to respond auth\").Base(err)\n\t}\n\n\tbuf := [3]byte{}\n\tif _, err := conn.Read(buf[:]); err != nil {\n\t\treturn nil, common.NewError(\"failed to read command\")\n\t}\n\n\taddr := new(tunnel.Address)\n\tif err := addr.ReadFrom(conn); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Conn{\n\t\tmetadata: &tunnel.Metadata{\n\t\t\tCommand: tunnel.Command(buf[1]),\n\t\t\tAddress: addr,\n\t\t},\n\t\tConn: conn,\n\t}, nil\n}\n\nfunc (s *Server) connect(conn net.Conn) error {\n\t_, err := conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})\n\treturn err\n}\n\nfunc (s *Server) associate(conn net.Conn, addr *tunnel.Address) error {\n\tbuf := bytes.NewBuffer([]byte{0x05, 0x00, 0x00})\n\tcommon.Must(addr.WriteTo(buf))\n\t_, err := conn.Write(buf.Bytes())\n\treturn err\n}\n\nfunc (s *Server) packetDispatchLoop() {\n\tfor {\n\t\tbuf := make([]byte, MaxPacketSize)\n\t\tn, src, err := s.listenPacketConn.ReadFrom(buf)\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tlog.Debug(\"socks recv udp packet from\", src)\n\t\ts.mappingLock.RLock()\n\t\tconn, found := s.mapping[src.String()]\n\t\ts.mappingLock.RUnlock()\n\t\tif !found {\n\t\t\tctx, cancel := context.WithCancel(s.ctx)\n\t\t\tconn = &PacketConn{\n\t\t\t\tinput:      make(chan *packetInfo, 128),\n\t\t\t\toutput:     make(chan *packetInfo, 128),\n\t\t\t\tctx:        ctx,\n\t\t\t\tcancel:     cancel,\n\t\t\t\tPacketConn: s.listenPacketConn,\n\t\t\t\tsrc:        src,\n\t\t\t}\n\t\t\tgo func(conn *PacketConn) {\n\t\t\t\tdefer conn.Close()\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase info := <-conn.output:\n\t\t\t\t\t\tbuf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize))\n\t\t\t\t\t\tbuf.Write([]byte{0, 0, 0}) // RSV, FRAG\n\t\t\t\t\t\tcommon.Must(info.metadata.Address.WriteTo(buf))\n\t\t\t\t\t\tbuf.Write(info.payload)\n\t\t\t\t\t\t_, err := s.listenPacketConn.WriteTo(buf.Bytes(), conn.src)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Error(\"socks failed to respond packet to\", src)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlog.Debug(\"socks respond udp packet to\", src, \"metadata\", info.metadata)\n\t\t\t\t\tcase <-time.After(time.Second * 5):\n\t\t\t\t\t\tlog.Info(\"socks udp session timeout, closed\")\n\t\t\t\t\t\ts.mappingLock.Lock()\n\t\t\t\t\t\tdelete(s.mapping, src.String())\n\t\t\t\t\t\ts.mappingLock.Unlock()\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase <-conn.ctx.Done():\n\t\t\t\t\t\tlog.Info(\"socks udp session closed\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\n\t\t\ts.mappingLock.Lock()\n\t\t\ts.mapping[src.String()] = conn\n\t\t\ts.mappingLock.Unlock()\n\n\t\t\ts.packetChan <- conn\n\t\t\tlog.Info(\"socks new udp session from\", src)\n\t\t}\n\t\tr := bytes.NewBuffer(buf[3:n])\n\t\taddress := new(tunnel.Address)\n\t\tif err := address.ReadFrom(r); err != nil {\n\t\t\tlog.Error(common.NewError(\"socks failed to parse incoming packet\").Base(err))\n\t\t\tcontinue\n\t\t}\n\t\tpayload := make([]byte, MaxPacketSize)\n\t\tlength, _ := r.Read(payload)\n\t\tselect {\n\t\tcase conn.input <- &packetInfo{\n\t\t\tmetadata: &tunnel.Metadata{\n\t\t\t\tAddress: address,\n\t\t\t},\n\t\t\tpayload: payload[:length],\n\t\t}:\n\t\tdefault:\n\t\t\tlog.Warn(\"socks udp queue full\")\n\t\t}\n\t}\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\t\tif err != nil {\n\t\t\tlog.Error(common.NewError(\"socks accept err\").Base(err))\n\t\t\treturn\n\t\t}\n\t\tgo func(conn net.Conn) {\n\t\t\tnewConn, err := s.handshake(conn)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(common.NewError(\"socks failed to handshake with client\").Base(err))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Info(\"socks connection from\", conn.RemoteAddr(), \"metadata\", newConn.metadata.String())\n\t\t\tswitch newConn.metadata.Command {\n\t\t\tcase Connect:\n\t\t\t\tif err := s.connect(newConn); err != nil {\n\t\t\t\t\tlog.Error(common.NewError(\"socks failed to respond CONNECT\").Base(err))\n\t\t\t\t\tnewConn.Close()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ts.connChan <- newConn\n\t\t\t\treturn\n\t\t\tcase Associate:\n\t\t\t\tdefer newConn.Close()\n\t\t\t\tassociateAddr := tunnel.NewAddressFromHostPort(\"udp\", s.localHost, s.localPort)\n\t\t\t\tif err := s.associate(newConn, associateAddr); err != nil {\n\t\t\t\t\tlog.Error(common.NewError(\"socks failed to respond to associate request\").Base(err))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbuf := [16]byte{}\n\t\t\t\tnewConn.Read(buf[:])\n\t\t\t\tlog.Debug(\"socks udp session ends\")\n\t\t\tdefault:\n\t\t\t\tlog.Error(common.NewError(fmt.Sprintf(\"unknown socks command %d\", newConn.metadata.Command)))\n\t\t\t\tnewConn.Close()\n\t\t\t}\n\t\t}(conn)\n\t}\n}\n\n// NewServer create a socks server\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tlistenPacketConn, err := underlay.AcceptPacket(&Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"socks failed to listen packet from underlying server\")\n\t}\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\tunderlay:         underlay,\n\t\tctx:              ctx,\n\t\tcancel:           cancel,\n\t\tconnChan:         make(chan tunnel.Conn, 32),\n\t\tpacketChan:       make(chan tunnel.PacketConn, 32),\n\t\tlocalHost:        cfg.LocalHost,\n\t\tlocalPort:        cfg.LocalPort,\n\t\ttimeout:          time.Duration(cfg.UDPTimeout) * time.Second,\n\t\tlistenPacketConn: listenPacketConn,\n\t\tmapping:          make(map[string]*PacketConn),\n\t}\n\tgo server.acceptLoop()\n\tgo server.packetDispatchLoop()\n\tlog.Debug(\"socks server created\")\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/socks/socks_test.go",
    "content": "package socks_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/txthinking/socks5\"\n\t\"golang.org/x/net/proxy\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/adapter\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/socks\"\n)\n\nfunc TestSocks(t *testing.T) {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tctx := config.WithConfig(context.Background(), adapter.Name, &adapter.Config{\n\t\tLocalHost: \"127.0.0.1\",\n\t\tLocalPort: port,\n\t})\n\tctx = config.WithConfig(ctx, socks.Name, &socks.Config{\n\t\tLocalHost: \"127.0.0.1\",\n\t\tLocalPort: port,\n\t})\n\ttcpServer, err := adapter.NewServer(ctx, nil)\n\tcommon.Must(err)\n\taddr := tunnel.NewAddressFromHostPort(\"tcp\", \"127.0.0.1\", port)\n\ts, err := socks.NewServer(ctx, tcpServer)\n\tcommon.Must(err)\n\tsocksClient, err := proxy.SOCKS5(\"tcp\", addr.String(), nil, proxy.Direct)\n\tcommon.Must(err)\n\tvar conn1, conn2 net.Conn\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\n\ttime.Sleep(time.Second * 2)\n\tgo func() {\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\twg.Done()\n\t}()\n\n\ttime.Sleep(time.Second * 1)\n\tgo func() {\n\t\tconn1, err = socksClient.Dial(\"tcp\", util.EchoAddr)\n\t\tcommon.Must(err)\n\t\twg.Done()\n\t}()\n\n\twg.Wait()\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\tfmt.Println(conn2.(tunnel.Conn).Metadata())\n\n\tudpConn, err := net.ListenPacket(\"udp\", \":0\")\n\tcommon.Must(err)\n\n\taddr = &tunnel.Address{\n\t\tAddressType: tunnel.DomainName,\n\t\tDomainName:  \"google.com\",\n\t\tPort:        12345,\n\t}\n\n\tpayload := util.GeneratePayload(1024)\n\tbuf := bytes.NewBuffer(make([]byte, 0, 4096))\n\tbuf.Write([]byte{0, 0, 0}) // RSV, FRAG\n\tcommon.Must(addr.WriteTo(buf))\n\tbuf.Write(payload)\n\n\tudpConn.WriteTo(buf.Bytes(), &net.UDPAddr{\n\t\tIP:   net.ParseIP(\"127.0.0.1\"),\n\t\tPort: port,\n\t})\n\n\tpacket, err := s.AcceptPacket(nil)\n\tcommon.Must(err)\n\trecvBuf := make([]byte, 4096)\n\tn, m, err := packet.ReadWithMetadata(recvBuf)\n\tcommon.Must(err)\n\tif m.DomainName != \"google.com\" || m.Port != 12345 || n != 1024 || !(bytes.Equal(recvBuf[:n], payload)) {\n\t\tt.Fail()\n\t}\n\n\tpayload = util.GeneratePayload(1024)\n\t_, err = packet.WriteWithMetadata(payload, &tunnel.Metadata{\n\t\tAddress: &tunnel.Address{\n\t\t\tAddressType: tunnel.IPv4,\n\t\t\tIP:          net.ParseIP(\"123.123.234.234\"),\n\t\t\tPort:        12345,\n\t\t},\n\t})\n\tcommon.Must(err)\n\n\t_, _, err = udpConn.ReadFrom(recvBuf)\n\tcommon.Must(err)\n\n\tr := bytes.NewReader(recvBuf)\n\theader := [3]byte{}\n\tr.Read(header[:])\n\taddr = new(tunnel.Address)\n\tcommon.Must(addr.ReadFrom(r))\n\tif addr.IP.String() != \"123.123.234.234\" || addr.Port != 12345 {\n\t\tt.Fail()\n\t}\n\n\trecvBuf, err = ioutil.ReadAll(r)\n\tcommon.Must(err)\n\n\tif bytes.Equal(recvBuf, payload) {\n\t\tt.Fail()\n\t}\n\tpacket.Close()\n\tudpConn.Close()\n\n\tc, _ := socks5.NewClient(fmt.Sprintf(\"127.0.0.1:%d\", port), \"\", \"\", 0, 0)\n\n\tconn, err := c.Dial(\"udp\", util.EchoAddr)\n\tcommon.Must(err)\n\n\tpayload = util.GeneratePayload(4096)\n\trecvBuf = make([]byte, 4096)\n\n\tconn.Write(payload)\n\n\tnewPacket, err := s.AcceptPacket(nil)\n\tcommon.Must(err)\n\n\t_, m, err = newPacket.ReadWithMetadata(recvBuf)\n\tcommon.Must(err)\n\tif m.String() != util.EchoAddr || !bytes.Equal(recvBuf, payload) {\n\t\tt.Fail()\n\t}\n\n\ts.Close()\n}\n"
  },
  {
    "path": "tunnel/socks/tunnel.go",
    "content": "package socks\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"SOCKS\"\n\ntype Tunnel struct{}\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewClient(context.Context, tunnel.Client) (tunnel.Client, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/tls/client.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"strings\"\n\n\tutls \"github.com/refraction-networking/utls\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/tls/fingerprint\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\n// Client is a tls client\ntype Client struct {\n\tverify        bool\n\tsni           string\n\tca            *x509.CertPool\n\tcipher        []uint16\n\tsessionTicket bool\n\treuseSession  bool\n\tfingerprint   string\n\thelloID       utls.ClientHelloID\n\tkeyLogger     io.WriteCloser\n\tunderlay      tunnel.Client\n}\n\nfunc (c *Client) Close() error {\n\tif c.keyLogger != nil {\n\t\tc.keyLogger.Close()\n\t}\n\treturn c.underlay.Close()\n}\n\nfunc (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (c *Client) DialConn(_ *tunnel.Address, overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := c.underlay.DialConn(nil, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"tls failed to dial conn\").Base(err)\n\t}\n\n\tif c.fingerprint != \"\" {\n\t\t// utls fingerprint\n\t\ttlsConn := utls.UClient(conn, &utls.Config{\n\t\t\tRootCAs:            c.ca,\n\t\t\tServerName:         c.sni,\n\t\t\tInsecureSkipVerify: !c.verify,\n\t\t\tKeyLogWriter:       c.keyLogger,\n\t\t}, c.helloID)\n\t\tif err := tlsConn.Handshake(); err != nil {\n\t\t\treturn nil, common.NewError(\"tls failed to handshake with remote server\").Base(err)\n\t\t}\n\t\treturn &transport.Conn{\n\t\t\tConn: tlsConn,\n\t\t}, nil\n\t}\n\t// golang default tls library\n\ttlsConn := tls.Client(conn, &tls.Config{\n\t\tInsecureSkipVerify:     !c.verify,\n\t\tServerName:             c.sni,\n\t\tRootCAs:                c.ca,\n\t\tKeyLogWriter:           c.keyLogger,\n\t\tCipherSuites:           c.cipher,\n\t\tSessionTicketsDisabled: !c.sessionTicket,\n\t})\n\terr = tlsConn.Handshake()\n\tif err != nil {\n\t\treturn nil, common.NewError(\"tls failed to handshake with remote server\").Base(err)\n\t}\n\treturn &transport.Conn{\n\t\tConn: tlsConn,\n\t}, nil\n}\n\n// NewClient creates a tls client\nfunc NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\n\thelloID := utls.ClientHelloID{}\n\tif cfg.TLS.Fingerprint != \"\" {\n\t\tswitch cfg.TLS.Fingerprint {\n\t\tcase \"firefox\":\n\t\t\thelloID = utls.HelloFirefox_Auto\n\t\tcase \"chrome\":\n\t\t\thelloID = utls.HelloChrome_Auto\n\t\tcase \"ios\":\n\t\t\thelloID = utls.HelloIOS_Auto\n\t\tdefault:\n\t\t\treturn nil, common.NewError(\"invalid fingerprint \" + cfg.TLS.Fingerprint)\n\t\t}\n\t\tlog.Info(\"tls fingerprint\", cfg.TLS.Fingerprint, \"applied\")\n\t}\n\n\tif cfg.TLS.SNI == \"\" {\n\t\tcfg.TLS.SNI = cfg.RemoteHost\n\t\tlog.Warn(\"tls sni is unspecified\")\n\t}\n\n\tclient := &Client{\n\t\tunderlay:      underlay,\n\t\tverify:        cfg.TLS.Verify,\n\t\tsni:           cfg.TLS.SNI,\n\t\tcipher:        fingerprint.ParseCipher(strings.Split(cfg.TLS.Cipher, \":\")),\n\t\tsessionTicket: cfg.TLS.ReuseSession,\n\t\tfingerprint:   cfg.TLS.Fingerprint,\n\t\thelloID:       helloID,\n\t}\n\n\tif cfg.TLS.CertPath != \"\" {\n\t\tcaCertByte, err := ioutil.ReadFile(cfg.TLS.CertPath)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to load cert file\").Base(err)\n\t\t}\n\t\tclient.ca = x509.NewCertPool()\n\t\tok := client.ca.AppendCertsFromPEM(caCertByte)\n\t\tif !ok {\n\t\t\tlog.Warn(\"invalid cert list\")\n\t\t}\n\t\tlog.Info(\"using custom cert\")\n\n\t\t// print cert info\n\t\tpemCerts := caCertByte\n\t\tfor len(pemCerts) > 0 {\n\t\t\tvar block *pem.Block\n\t\t\tblock, pemCerts = pem.Decode(pemCerts)\n\t\t\tif block == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif block.Type != \"CERTIFICATE\" || len(block.Headers) != 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcert, err := x509.ParseCertificate(block.Bytes)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlog.Trace(\"issuer:\", cert.Issuer, \"subject:\", cert.Subject)\n\t\t}\n\t}\n\n\tif cfg.TLS.CertPath == \"\" {\n\t\tlog.Info(\"cert is unspecified, using default ca list\")\n\t}\n\n\tlog.Debug(\"tls client created\")\n\treturn client, nil\n}\n"
  },
  {
    "path": "tunnel/tls/config.go",
    "content": "package tls\n\nimport (\n\t\"github.com/p4gefau1t/trojan-go/config\"\n)\n\ntype Config struct {\n\tRemoteHost string          `json:\"remote_addr\" yaml:\"remote-addr\"`\n\tRemotePort int             `json:\"remote_port\" yaml:\"remote-port\"`\n\tTLS        TLSConfig       `json:\"ssl\" yaml:\"ssl\"`\n\tWebsocket  WebsocketConfig `json:\"websocket\" yaml:\"websocket\"`\n}\n\ntype WebsocketConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype TLSConfig struct {\n\tVerify               bool     `json:\"verify\" yaml:\"verify\"`\n\tVerifyHostName       bool     `json:\"verify_hostname\" yaml:\"verify-hostname\"`\n\tCertPath             string   `json:\"cert\" yaml:\"cert\"`\n\tKeyPath              string   `json:\"key\" yaml:\"key\"`\n\tKeyPassword          string   `json:\"key_password\" yaml:\"key-password\"`\n\tCipher               string   `json:\"cipher\" yaml:\"cipher\"`\n\tPreferServerCipher   bool     `json:\"prefer_server_cipher\" yaml:\"prefer-server-cipher\"`\n\tSNI                  string   `json:\"sni\" yaml:\"sni\"`\n\tHTTPResponseFileName string   `json:\"plain_http_response\" yaml:\"plain-http-response\"`\n\tFallbackHost         string   `json:\"fallback_addr\" yaml:\"fallback-addr\"`\n\tFallbackPort         int      `json:\"fallback_port\" yaml:\"fallback-port\"`\n\tReuseSession         bool     `json:\"reuse_session\" yaml:\"reuse-session\"`\n\tALPN                 []string `json:\"alpn\" yaml:\"alpn\"`\n\tCurves               string   `json:\"curves\" yaml:\"curves\"`\n\tFingerprint          string   `json:\"fingerprint\" yaml:\"fingerprint\"`\n\tKeyLogPath           string   `json:\"key_log\" yaml:\"key-log\"`\n\tCertCheckRate        int      `json:\"cert_check_rate\" yaml:\"cert-check-rate\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tTLS: TLSConfig{\n\t\t\t\tVerify:         true,\n\t\t\t\tVerifyHostName: true,\n\t\t\t\tFingerprint:    \"\",\n\t\t\t\tALPN:           []string{\"http/1.1\"},\n\t\t\t},\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/tls/fingerprint/tls.go",
    "content": "package fingerprint\n\nimport (\n\t\"crypto/tls\"\n\n\t\"github.com/p4gefau1t/trojan-go/log\"\n)\n\nfunc ParseCipher(s []string) []uint16 {\n\tall := tls.CipherSuites()\n\tvar result []uint16\n\tfor _, p := range s {\n\t\tfound := true\n\t\tfor _, q := range all {\n\t\t\tif q.Name == p {\n\t\t\t\tresult = append(result, q.ID)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tlog.Warn(\"invalid cipher suite\", p, \"skipped\")\n\t\t\t}\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "tunnel/tls/server.go",
    "content": "package tls\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/redirector\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/tls/fingerprint\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/websocket\"\n)\n\n// Server is a tls server\ntype Server struct {\n\tfallbackAddress    *tunnel.Address\n\tverifySNI          bool\n\tsni                string\n\talpn               []string\n\tPreferServerCipher bool\n\tkeyPair            []tls.Certificate\n\tkeyPairLock        sync.RWMutex\n\thttpResp           []byte\n\tcipherSuite        []uint16\n\tsessionTicket      bool\n\tcurve              []tls.CurveID\n\tkeyLogger          io.WriteCloser\n\tconnChan           chan tunnel.Conn\n\twsChan             chan tunnel.Conn\n\tredir              *redirector.Redirector\n\tctx                context.Context\n\tcancel             context.CancelFunc\n\tunderlay           tunnel.Server\n\tnextHTTP           int32\n\tportOverrider      map[string]int\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\tif s.keyLogger != nil {\n\t\ts.keyLogger.Close()\n\t}\n\treturn s.underlay.Close()\n}\n\nfunc isDomainNameMatched(pattern string, domainName string) bool {\n\tif strings.HasPrefix(pattern, \"*.\") {\n\t\tsuffix := pattern[2:]\n\t\tdomainPrefixLen := len(domainName) - len(suffix) - 1\n\t\treturn strings.HasSuffix(domainName, suffix) && domainPrefixLen > 0 && !strings.Contains(domainName[:domainPrefixLen], \".\")\n\t}\n\treturn pattern == domainName\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\tdefault:\n\t\t\t\tlog.Fatal(common.NewError(\"transport accept error\" + err.Error()))\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tgo func(conn net.Conn) {\n\t\t\ttlsConfig := &tls.Config{\n\t\t\t\tCipherSuites:             s.cipherSuite,\n\t\t\t\tPreferServerCipherSuites: s.PreferServerCipher,\n\t\t\t\tSessionTicketsDisabled:   !s.sessionTicket,\n\t\t\t\tNextProtos:               s.alpn,\n\t\t\t\tKeyLogWriter:             s.keyLogger,\n\t\t\t\tGetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\t\t\t\t\ts.keyPairLock.RLock()\n\t\t\t\t\tdefer s.keyPairLock.RUnlock()\n\t\t\t\t\tsni := s.keyPair[0].Leaf.Subject.CommonName\n\t\t\t\t\tdnsNames := s.keyPair[0].Leaf.DNSNames\n\t\t\t\t\tif s.sni != \"\" {\n\t\t\t\t\t\tsni = s.sni\n\t\t\t\t\t}\n\t\t\t\t\tmatched := isDomainNameMatched(sni, hello.ServerName)\n\t\t\t\t\tfor _, name := range dnsNames {\n\t\t\t\t\t\tif isDomainNameMatched(name, hello.ServerName) {\n\t\t\t\t\t\t\tmatched = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif s.verifySNI && !matched {\n\t\t\t\t\t\treturn nil, common.NewError(\"sni mismatched: \" + hello.ServerName + \", expected: \" + s.sni)\n\t\t\t\t\t}\n\t\t\t\t\treturn &s.keyPair[0], nil\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// ------------------------ WAR ZONE ----------------------------\n\n\t\t\thandshakeRewindConn := common.NewRewindConn(conn)\n\t\t\thandshakeRewindConn.SetBufferSize(2048)\n\n\t\t\ttlsConn := tls.Server(handshakeRewindConn, tlsConfig)\n\t\t\terr = tlsConn.Handshake()\n\t\t\thandshakeRewindConn.StopBuffering()\n\n\t\t\tif err != nil {\n\t\t\t\tif strings.Contains(err.Error(), \"first record does not look like a TLS handshake\") {\n\t\t\t\t\t// not a valid tls client hello\n\t\t\t\t\thandshakeRewindConn.Rewind()\n\t\t\t\t\tlog.Error(common.NewError(\"failed to perform tls handshake with \" + tlsConn.RemoteAddr().String() + \", redirecting\").Base(err))\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase s.fallbackAddress != nil:\n\t\t\t\t\t\ts.redir.Redirect(&redirector.Redirection{\n\t\t\t\t\t\t\tInboundConn: handshakeRewindConn,\n\t\t\t\t\t\t\tRedirectTo:  s.fallbackAddress,\n\t\t\t\t\t\t})\n\t\t\t\t\tcase s.httpResp != nil:\n\t\t\t\t\t\thandshakeRewindConn.Write(s.httpResp)\n\t\t\t\t\t\thandshakeRewindConn.Close()\n\t\t\t\t\tdefault:\n\t\t\t\t\t\thandshakeRewindConn.Close()\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// in other cases, simply close it\n\t\t\t\t\ttlsConn.Close()\n\t\t\t\t\tlog.Error(common.NewError(\"tls handshake failed\").Base(err))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlog.Info(\"tls connection from\", conn.RemoteAddr())\n\t\t\tstate := tlsConn.ConnectionState()\n\t\t\tlog.Trace(\"tls handshake\", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol)\n\n\t\t\t// we use a real http header parser to mimic a real http server\n\t\t\trewindConn := common.NewRewindConn(tlsConn)\n\t\t\trewindConn.SetBufferSize(1024)\n\t\t\tr := bufio.NewReader(rewindConn)\n\t\t\thttpReq, err := http.ReadRequest(r)\n\t\t\trewindConn.Rewind()\n\t\t\trewindConn.StopBuffering()\n\t\t\tif err != nil {\n\t\t\t\t// this is not a http request. pass it to trojan protocol layer for further inspection\n\t\t\t\ts.connChan <- &transport.Conn{\n\t\t\t\t\tConn: rewindConn,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif atomic.LoadInt32(&s.nextHTTP) != 1 {\n\t\t\t\t\t// there is no websocket layer waiting for connections, redirect it\n\t\t\t\t\tlog.Error(\"incoming http request, but no websocket server is listening\")\n\t\t\t\t\ts.redir.Redirect(&redirector.Redirection{\n\t\t\t\t\t\tInboundConn: rewindConn,\n\t\t\t\t\t\tRedirectTo:  s.fallbackAddress,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// this is a http request, pass it to websocket protocol layer\n\t\t\t\tlog.Debug(\"http req: \", httpReq)\n\t\t\t\ts.wsChan <- &transport.Conn{\n\t\t\t\t\tConn: rewindConn,\n\t\t\t\t}\n\t\t\t}\n\t\t}(conn)\n\t}\n}\n\nfunc (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\tif _, ok := overlay.(*websocket.Tunnel); ok {\n\t\tatomic.StoreInt32(&s.nextHTTP, 1)\n\t\tlog.Debug(\"next proto http\")\n\t\t// websocket overlay\n\t\tselect {\n\t\tcase conn := <-s.wsChan:\n\t\t\treturn conn, nil\n\t\tcase <-s.ctx.Done():\n\t\t\treturn nil, common.NewError(\"transport server closed\")\n\t\t}\n\t}\n\t// trojan overlay\n\tselect {\n\tcase conn := <-s.connChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"transport server closed\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (s *Server) checkKeyPairLoop(checkRate time.Duration, keyPath string, certPath string, password string) {\n\tvar lastKeyBytes, lastCertBytes []byte\n\tticker := time.NewTicker(checkRate)\n\n\tfor {\n\t\tlog.Debug(\"checking cert...\")\n\t\tkeyBytes, err := ioutil.ReadFile(keyPath)\n\t\tif err != nil {\n\t\t\tlog.Error(common.NewError(\"tls failed to check key\").Base(err))\n\t\t\tcontinue\n\t\t}\n\t\tcertBytes, err := ioutil.ReadFile(certPath)\n\t\tif err != nil {\n\t\t\tlog.Error(common.NewError(\"tls failed to check cert\").Base(err))\n\t\t\tcontinue\n\t\t}\n\t\tif !bytes.Equal(keyBytes, lastKeyBytes) || !bytes.Equal(lastCertBytes, certBytes) {\n\t\t\tlog.Info(\"new key pair detected\")\n\t\t\tkeyPair, err := loadKeyPair(keyPath, certPath, password)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(common.NewError(\"tls failed to load new key pair\").Base(err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.keyPairLock.Lock()\n\t\t\ts.keyPair = []tls.Certificate{*keyPair}\n\t\t\ts.keyPairLock.Unlock()\n\t\t\tlastKeyBytes = keyBytes\n\t\t\tlastCertBytes = certBytes\n\t\t}\n\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tcontinue\n\t\tcase <-s.ctx.Done():\n\t\t\tlog.Debug(\"exiting\")\n\t\t\tticker.Stop()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc loadKeyPair(keyPath string, certPath string, password string) (*tls.Certificate, error) {\n\tif password != \"\" {\n\t\tkeyFile, err := ioutil.ReadFile(keyPath)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to load key file\").Base(err)\n\t\t}\n\t\tkeyBlock, _ := pem.Decode(keyFile)\n\t\tif keyBlock == nil {\n\t\t\treturn nil, common.NewError(\"failed to decode key file\").Base(err)\n\t\t}\n\t\tdecryptedKey, err := x509.DecryptPEMBlock(keyBlock, []byte(password))\n\t\tif err == nil {\n\t\t\treturn nil, common.NewError(\"failed to decrypt key\").Base(err)\n\t\t}\n\n\t\tcertFile, err := ioutil.ReadFile(certPath)\n\t\tcertBlock, _ := pem.Decode(certFile)\n\t\tif certBlock == nil {\n\t\t\treturn nil, common.NewError(\"failed to decode cert file\").Base(err)\n\t\t}\n\n\t\tkeyPair, err := tls.X509KeyPair(certBlock.Bytes, decryptedKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkeyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to parse leaf certificate\").Base(err)\n\t\t}\n\n\t\treturn &keyPair, nil\n\t}\n\tkeyPair, err := tls.LoadX509KeyPair(certPath, keyPath)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"failed to load key pair\").Base(err)\n\t}\n\tkeyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])\n\tif err != nil {\n\t\treturn nil, common.NewError(\"failed to parse leaf certificate\").Base(err)\n\t}\n\treturn &keyPair, nil\n}\n\n// NewServer creates a tls layer server\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\n\tvar fallbackAddress *tunnel.Address\n\tvar httpResp []byte\n\tif cfg.TLS.FallbackPort != 0 {\n\t\tif cfg.TLS.FallbackHost == \"\" {\n\t\t\tcfg.TLS.FallbackHost = cfg.RemoteHost\n\t\t\tlog.Warn(\"empty tls fallback address\")\n\t\t}\n\t\tfallbackAddress = tunnel.NewAddressFromHostPort(\"tcp\", cfg.TLS.FallbackHost, cfg.TLS.FallbackPort)\n\t\tfallbackConn, err := net.Dial(\"tcp\", fallbackAddress.String())\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"invalid fallback address\").Base(err)\n\t\t}\n\t\tfallbackConn.Close()\n\t} else {\n\t\tlog.Warn(\"empty tls fallback port\")\n\t\tif cfg.TLS.HTTPResponseFileName != \"\" {\n\t\t\thttpRespBody, err := ioutil.ReadFile(cfg.TLS.HTTPResponseFileName)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, common.NewError(\"invalid response file\").Base(err)\n\t\t\t}\n\t\t\thttpResp = httpRespBody\n\t\t} else {\n\t\t\tlog.Warn(\"empty tls http response\")\n\t\t}\n\t}\n\n\tkeyPair, err := loadKeyPair(cfg.TLS.KeyPath, cfg.TLS.CertPath, cfg.TLS.KeyPassword)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"tls failed to load key pair\")\n\t}\n\n\tvar keyLogger io.WriteCloser\n\tif cfg.TLS.KeyLogPath != \"\" {\n\t\tlog.Warn(\"tls key logging activated. USE OF KEY LOGGING COMPROMISES SECURITY. IT SHOULD ONLY BE USED FOR DEBUGGING.\")\n\t\tfile, err := os.OpenFile(cfg.TLS.KeyLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)\n\t\tif err != nil {\n\t\t\treturn nil, common.NewError(\"failed to open key log file\").Base(err)\n\t\t}\n\t\tkeyLogger = file\n\t}\n\n\tvar cipherSuite []uint16\n\tif len(cfg.TLS.Cipher) != 0 {\n\t\tcipherSuite = fingerprint.ParseCipher(strings.Split(cfg.TLS.Cipher, \":\"))\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\tunderlay:           underlay,\n\t\tfallbackAddress:    fallbackAddress,\n\t\thttpResp:           httpResp,\n\t\tverifySNI:          cfg.TLS.VerifyHostName,\n\t\tsni:                cfg.TLS.SNI,\n\t\talpn:               cfg.TLS.ALPN,\n\t\tPreferServerCipher: cfg.TLS.PreferServerCipher,\n\t\tsessionTicket:      cfg.TLS.ReuseSession,\n\t\tconnChan:           make(chan tunnel.Conn, 32),\n\t\twsChan:             make(chan tunnel.Conn, 32),\n\t\tredir:              redirector.NewRedirector(ctx),\n\t\tkeyPair:            []tls.Certificate{*keyPair},\n\t\tkeyLogger:          keyLogger,\n\t\tcipherSuite:        cipherSuite,\n\t\tctx:                ctx,\n\t\tcancel:             cancel,\n\t}\n\n\tgo server.acceptLoop()\n\tif cfg.TLS.CertCheckRate > 0 {\n\t\tgo server.checkKeyPairLoop(\n\t\t\ttime.Second*time.Duration(cfg.TLS.CertCheckRate),\n\t\t\tcfg.TLS.KeyPath,\n\t\t\tcfg.TLS.CertPath,\n\t\t\tcfg.TLS.KeyPassword,\n\t\t)\n\t}\n\n\tlog.Debug(\"tls server created\")\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/tls/tls_test.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nvar rsa2048Cert = `\n-----BEGIN CERTIFICATE-----\nMIIC5TCCAc2gAwIBAgIJAJqNVe6g/10vMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0yMTA5MTQwNjE1MTFaFw0yNjA5MTMwNjE1MTFaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAK7bupJ8tmHM3shQ/7N730jzpRsXdNiBxq/Jxx8j+vB3AcxuP5bjXQZqS6YR\n5W5vrfLlegtq1E/mmaI3Ht0RfIlzev04Dua9PWmIQJD801nEPknbfgCLXDh+pYr2\nsfg8mUh3LjGtrxyH+nmbTjWg7iWSKohmZ8nUDcX94Llo5FxibMAz8OsAwOmUueCH\njP3XswZYHEy+OOP3K0ZEiJy0f5T6ZXk9OWYuPN4VQKJx1qrc9KzZtSPHwqVdkGUi\nase9tOPA4aMutzt0btgW7h7UrvG6C1c/Rr1BxdiYq1EQ+yypnAlyToVQSNbo67zz\nwGQk4GeruIkOgJOLdooN/HjhbHMCAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo\nb3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B\nAQsFAAOCAQEASsBzHHYiWDDiBVWUEwVZAduTrslTLNOxG0QHBKsHWIlz/3QlhQil\nywb3OhfMTUR1dMGY5Iq5432QiCHO4IMCOv7tDIkgb4Bc3v/3CRlBlnurtAmUfNJ6\npTRSlK4AjWpGHAEEd/8aCaOE86hMP8WDht8MkJTRrQqpJ1HeDISoKt9nepHOIsj+\nI2zLZZtw0pg7FuR4MzWuqOt071iRS46Pupryb3ZEGIWNz5iLrDQod5Iz2ZGSRGqE\nrB8idX0mlj5AHRRanVR3PAes+eApsW9JvYG/ImuCOs+ZsukY614zQZdR+SyFm85G\n4NICyeQsmiypNHHgw+xZmGqZg65bXNGoyg==\n-----END CERTIFICATE-----\n`\n\nvar rsa2048Key = `\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu27qSfLZhzN7I\nUP+ze99I86UbF3TYgcavyccfI/rwdwHMbj+W410GakumEeVub63y5XoLatRP5pmi\nNx7dEXyJc3r9OA7mvT1piECQ/NNZxD5J234Ai1w4fqWK9rH4PJlIdy4xra8ch/p5\nm041oO4lkiqIZmfJ1A3F/eC5aORcYmzAM/DrAMDplLngh4z917MGWBxMvjjj9ytG\nRIictH+U+mV5PTlmLjzeFUCicdaq3PSs2bUjx8KlXZBlImrHvbTjwOGjLrc7dG7Y\nFu4e1K7xugtXP0a9QcXYmKtREPssqZwJck6FUEjW6Ou888BkJOBnq7iJDoCTi3aK\nDfx44WxzAgMBAAECggEBAKYhib/H0ZhWB4yWuHqUxG4RXtrAjHlvw5Acy5zgmHiC\n+Sh7ztrTJf0EXN9pvWwRm1ldgXj7hMBtPaaLbD1pccM9/qo66p17Sq/LjlyyeTOe\naffOHIbz4Sij2zCOdkR9fr0EztTQScF3yBhl4Aa/4cO8fcCeWxm86WEldq9x4xWJ\ns5WMR4CnrOJhDINLNPQPKX92KyxEQ/RfuBWovx3M0nl3fcUWfESY134t5g/UBFId\nIn19tZ+pGIpCkxP0U1AZWrlZRA8Q/3sO2orUpoAOdCrGk/DcCTMh0c1pMzbYZ1/i\ncYXn38MpUo8QeG4FElUhAv6kzeBIl2tRBMVzIigo+AECgYEA3No1rHdFu6Ox9vC8\nE93PTZevYVcL5J5yx6x7khCaOLKKuRXpjOX/h3Ll+hlN2DVAg5Jli/JVGCco4GeK\nkbFLSyxG1+E63JbgsVpaEOgvFT3bHHSPSRJDnIU+WkcNQ2u4Ky5ahZzbNdV+4fj2\nNO2iMgkm7hoJANrm3IqqW8epenMCgYEAyq+qdNj5DiDzBcDvLwY+4/QmMOOgDqeh\n/TzhbDRyr+m4xNT7LLS4s/3wcbkQC33zhMUI3YvOHnYq5Ze/iL/TSloj0QCp1I7L\nJ7sZeM1XimMBQIpCfOC7lf4tU76Fz0DTHAL+CmX1DgmRJdYO09843VsKkscC968R\n4cwL5oGxxgECgYAM4TTsH/CTJtLEIfn19qOWVNhHhvoMlSkAeBCkzg8Qa2knrh12\nuBsU3SCIW11s1H40rh758GICDJaXr7InGP3ZHnXrNRlnr+zeqvRBtCi6xma23B1X\nF5eV0zd1sFsXqXqOGh/xVtp54z+JEinZoForLNl2XVJVGG8KQZP50kUR/QKBgH4O\n8zzpFT0sUPlrHVdp0wODfZ06dPmoWJ9flfPuSsYN3tTMgcs0Owv3C+wu5UPAegxB\nX1oq8W8Qn21cC8vJQmgj19LNTtLcXI3BV/5B+Aghu02gr+lq/EA1bYuAG0jjUGlD\nkyx0bQzl9lhJ4b70PjGtxc2z6KyTPdPpTB143FABAoGAQDoIUdc77/IWcjzcaXeJ\n8abak5rAZA7cu2g2NVfs+Km+njsB0pbTwMnV1zGoFABdaHLdqbthLWtX7WOb1PDD\nMQ+kbiLw5uj8IY2HEqJhDGGEdXBqxbW7kyuIAN9Mw+mwKzkikNcFQdxgchWH1d1o\nlVkr92iEX+IhIeYb4DN1vQw=\n-----END PRIVATE KEY-----\n`\n\nvar eccCert = `\n-----BEGIN CERTIFICATE-----\nMIICTDCCAfKgAwIBAgIQDtCrO8cNST2eY2tA/AGrsDAKBggqhkjOPQQDAjBeMQsw\nCQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRlc3Qg\nRUNDIC0gRm9yIHRlc3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTAeFw0y\nMTA5MTQwNjQ1MzNaFw0yNjA5MTMwNjQ1MzNaMCExCzAJBgNVBAYTAkNOMRIwEAYD\nVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASvYy/r7XR1\nY39lC2JpRJh582zR2CTNynbuolK9a1jsbXaZv+hpBlHkgzMHsWu7LY9Pnb/Dbp4i\n1lRASOddD/rLo4HOMIHLMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUWxGyVxD0fBhTy3tH4eKznRFXFCYw\nYwYIKwYBBQUHAQEEVzBVMCEGCCsGAQUFBzABhhVodHRwOi8vb2NzcC5teXNzbC5j\nb20wMAYIKwYBBQUHMAKGJGh0dHA6Ly9jYS5teXNzbC5jb20vbXlzc2x0ZXN0ZWNj\nLmNydDAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIgDQUa\nGEdmKstLMHUmmPMGm/P9S4vvSZV2VHsb3+AEyIUCIQCdJpbyTCz+mEyskhwrGOw/\nblh3WBONv6MBtqPpmgE1AQ==\n-----END CERTIFICATE-----\n`\n\nvar eccKey = `\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIB8G2suYKuBLoodNIwRMp3JPN1fcZxCt3kcOYIx4nbcPoAoGCCqGSM49\nAwEHoUQDQgAEr2Mv6+10dWN/ZQtiaUSYefNs0dgkzcp27qJSvWtY7G12mb/oaQZR\n5IMzB7Fruy2PT52/w26eItZUQEjnXQ/6yw==\n-----END EC PRIVATE KEY-----\n`\n\nfunc TestDefaultTLSRSA2048(t *testing.T) {\n\tos.WriteFile(\"server-rsa2048.crt\", []byte(rsa2048Cert), 0o777)\n\tos.WriteFile(\"server-rsa2048.key\", []byte(rsa2048Key), 0o777)\n\tserverCfg := &Config{\n\t\tTLS: TLSConfig{\n\t\t\tVerifyHostName: true,\n\t\t\tCertCheckRate:  1,\n\t\t\tKeyPath:        \"server-rsa2048.key\",\n\t\t\tCertPath:       \"server-rsa2048.crt\",\n\t\t},\n\t}\n\tclientCfg := &Config{\n\t\tTLS: TLSConfig{\n\t\t\tVerify:      false,\n\t\t\tSNI:         \"localhost\",\n\t\t\tFingerprint: \"\",\n\t\t},\n\t}\n\tsctx := config.WithConfig(context.Background(), Name, serverCfg)\n\tcctx := config.WithConfig(context.Background(), Name, clientCfg)\n\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tctx := config.WithConfig(context.Background(), transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\tcommon.Must(err)\n\ts, err := NewServer(sctx, tcpServer)\n\tcommon.Must(err)\n\tc, err := NewClient(cctx, tcpClient)\n\tcommon.Must(err)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tvar conn1, conn2 net.Conn\n\tgo func() {\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\twg.Done()\n\t}()\n\tconn1, err = c.DialConn(nil, nil)\n\tcommon.Must(err)\n\n\tcommon.Must2(conn1.Write([]byte(\"12345678\\r\\n\")))\n\twg.Wait()\n\tbuf := [10]byte{}\n\tconn2.Read(buf[:])\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\tconn1.Close()\n\tconn2.Close()\n}\n\nfunc TestDefaultTLSECC(t *testing.T) {\n\tos.WriteFile(\"server-ecc.crt\", []byte(eccCert), 0o777)\n\tos.WriteFile(\"server-ecc.key\", []byte(eccKey), 0o777)\n\tserverCfg := &Config{\n\t\tTLS: TLSConfig{\n\t\t\tVerifyHostName: true,\n\t\t\tCertCheckRate:  1,\n\t\t\tKeyPath:        \"server-ecc.key\",\n\t\t\tCertPath:       \"server-ecc.crt\",\n\t\t},\n\t}\n\tclientCfg := &Config{\n\t\tTLS: TLSConfig{\n\t\t\tVerify:      false,\n\t\t\tSNI:         \"localhost\",\n\t\t\tFingerprint: \"\",\n\t\t},\n\t}\n\tsctx := config.WithConfig(context.Background(), Name, serverCfg)\n\tcctx := config.WithConfig(context.Background(), Name, clientCfg)\n\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tctx := config.WithConfig(context.Background(), transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\tcommon.Must(err)\n\ts, err := NewServer(sctx, tcpServer)\n\tcommon.Must(err)\n\tc, err := NewClient(cctx, tcpClient)\n\tcommon.Must(err)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tvar conn1, conn2 net.Conn\n\tgo func() {\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\twg.Done()\n\t}()\n\tconn1, err = c.DialConn(nil, nil)\n\tcommon.Must(err)\n\n\tcommon.Must2(conn1.Write([]byte(\"12345678\\r\\n\")))\n\twg.Wait()\n\tbuf := [10]byte{}\n\tconn2.Read(buf[:])\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\tconn1.Close()\n\tconn2.Close()\n}\n\nfunc TestUTLSRSA2048(t *testing.T) {\n\tos.WriteFile(\"server-rsa2048.crt\", []byte(rsa2048Cert), 0o777)\n\tos.WriteFile(\"server-rsa2048.key\", []byte(rsa2048Key), 0o777)\n\tfingerprints := []string{\n\t\t\"chrome\",\n\t\t\"firefox\",\n\t\t\"ios\",\n\t}\n\tfor _, s := range fingerprints {\n\t\tserverCfg := &Config{\n\t\t\tTLS: TLSConfig{\n\t\t\t\tCertCheckRate: 1,\n\t\t\t\tKeyPath:       \"server-rsa2048.key\",\n\t\t\t\tCertPath:      \"server-rsa2048.crt\",\n\t\t\t},\n\t\t}\n\t\tclientCfg := &Config{\n\t\t\tTLS: TLSConfig{\n\t\t\t\tVerify:      false,\n\t\t\t\tSNI:         \"localhost\",\n\t\t\t\tFingerprint: s,\n\t\t\t},\n\t\t}\n\t\tsctx := config.WithConfig(context.Background(), Name, serverCfg)\n\t\tcctx := config.WithConfig(context.Background(), Name, clientCfg)\n\n\t\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\t\ttransportConfig := &transport.Config{\n\t\t\tLocalHost:  \"127.0.0.1\",\n\t\t\tLocalPort:  port,\n\t\t\tRemoteHost: \"127.0.0.1\",\n\t\t\tRemotePort: port,\n\t\t}\n\t\tctx := config.WithConfig(context.Background(), transport.Name, transportConfig)\n\t\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\t\ttcpClient, err := transport.NewClient(ctx, nil)\n\t\tcommon.Must(err)\n\t\ttcpServer, err := transport.NewServer(ctx, nil)\n\t\tcommon.Must(err)\n\n\t\ts, err := NewServer(sctx, tcpServer)\n\t\tcommon.Must(err)\n\t\tc, err := NewClient(cctx, tcpClient)\n\t\tcommon.Must(err)\n\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(1)\n\t\tvar conn1, conn2 net.Conn\n\t\tgo func() {\n\t\t\tconn2, err = s.AcceptConn(nil)\n\t\t\tcommon.Must(err)\n\t\t\twg.Done()\n\t\t}()\n\t\tconn1, err = c.DialConn(nil, nil)\n\t\tcommon.Must(err)\n\n\t\tcommon.Must2(conn1.Write([]byte(\"12345678\\r\\n\")))\n\t\twg.Wait()\n\t\tbuf := [10]byte{}\n\t\tconn2.Read(buf[:])\n\t\tif !util.CheckConn(conn1, conn2) {\n\t\t\tt.Fail()\n\t\t}\n\t\tconn1.Close()\n\t\tconn2.Close()\n\t\ts.Close()\n\t\tc.Close()\n\t}\n}\n\nfunc TestUTLSECC(t *testing.T) {\n\tos.WriteFile(\"server-ecc.crt\", []byte(eccCert), 0o777)\n\tos.WriteFile(\"server-ecc.key\", []byte(eccKey), 0o777)\n\tfingerprints := []string{\n\t\t\"chrome\",\n\t\t\"firefox\",\n\t\t\"ios\",\n\t}\n\tfor _, s := range fingerprints {\n\t\tserverCfg := &Config{\n\t\t\tTLS: TLSConfig{\n\t\t\t\tCertCheckRate: 1,\n\t\t\t\tKeyPath:       \"server-ecc.key\",\n\t\t\t\tCertPath:      \"server-ecc.crt\",\n\t\t\t},\n\t\t}\n\t\tclientCfg := &Config{\n\t\t\tTLS: TLSConfig{\n\t\t\t\tVerify:      false,\n\t\t\t\tSNI:         \"localhost\",\n\t\t\t\tFingerprint: s,\n\t\t\t},\n\t\t}\n\t\tsctx := config.WithConfig(context.Background(), Name, serverCfg)\n\t\tcctx := config.WithConfig(context.Background(), Name, clientCfg)\n\n\t\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\t\ttransportConfig := &transport.Config{\n\t\t\tLocalHost:  \"127.0.0.1\",\n\t\t\tLocalPort:  port,\n\t\t\tRemoteHost: \"127.0.0.1\",\n\t\t\tRemotePort: port,\n\t\t}\n\t\tctx := config.WithConfig(context.Background(), transport.Name, transportConfig)\n\t\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\t\ttcpClient, err := transport.NewClient(ctx, nil)\n\t\tcommon.Must(err)\n\t\ttcpServer, err := transport.NewServer(ctx, nil)\n\t\tcommon.Must(err)\n\n\t\ts, err := NewServer(sctx, tcpServer)\n\t\tcommon.Must(err)\n\t\tc, err := NewClient(cctx, tcpClient)\n\t\tcommon.Must(err)\n\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(1)\n\t\tvar conn1, conn2 net.Conn\n\t\tgo func() {\n\t\t\tconn2, err = s.AcceptConn(nil)\n\t\t\tcommon.Must(err)\n\t\t\twg.Done()\n\t\t}()\n\t\tconn1, err = c.DialConn(nil, nil)\n\t\tcommon.Must(err)\n\n\t\tcommon.Must2(conn1.Write([]byte(\"12345678\\r\\n\")))\n\t\twg.Wait()\n\t\tbuf := [10]byte{}\n\t\tconn2.Read(buf[:])\n\t\tif !util.CheckConn(conn1, conn2) {\n\t\t\tt.Fail()\n\t\t}\n\t\tconn1.Close()\n\t\tconn2.Close()\n\t\ts.Close()\n\t\tc.Close()\n\t}\n}\n\nfunc TestMatch(t *testing.T) {\n\tif !isDomainNameMatched(\"*.google.com\", \"www.google.com\") {\n\t\tt.Fail()\n\t}\n\n\tif isDomainNameMatched(\"*.google.com\", \"google.com\") {\n\t\tt.Fail()\n\t}\n\n\tif !isDomainNameMatched(\"localhost\", \"localhost\") {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "tunnel/tls/tunnel.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"TLS\"\n\ntype Tunnel struct{}\n\nfunc (t *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/tproxy/config.go",
    "content": "//go:build linux\n// +build linux\n\npackage tproxy\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tLocalHost  string `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort  int    `json:\"local_port\" yaml:\"local-port\"`\n\tUDPTimeout int    `json:\"udp_timeout\" yaml:\"udp-timeout\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{\n\t\t\tUDPTimeout: 60,\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tunnel/tproxy/conn.go",
    "content": "//go:build linux\n// +build linux\n\npackage tproxy\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Conn struct {\n\tnet.Conn\n\tmetadata *tunnel.Metadata\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\ntype packetInfo struct {\n\tmetadata *tunnel.Metadata\n\tpayload  []byte\n}\n\ntype PacketConn struct {\n\tnet.PacketConn\n\tinput  chan *packetInfo\n\toutput chan *packetInfo\n\tsrc    net.Addr\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {\n\tpanic(\"implement me\")\n}\n\nfunc (c *PacketConn) Close() error {\n\tc.cancel()\n\treturn nil\n}\n\nfunc (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) {\n\tselect {\n\tcase c.output <- &packetInfo{\n\t\tmetadata: m,\n\t\tpayload:  p,\n\t}:\n\t\treturn len(p), nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, common.NewError(\"socks packet conn closed\")\n\t}\n}\n\nfunc (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) {\n\tselect {\n\tcase info := <-c.input:\n\t\tn := copy(p, info.payload)\n\t\treturn n, info.metadata, nil\n\tcase <-c.ctx.Done():\n\t\treturn 0, nil, common.NewError(\"socks packet conn closed\")\n\t}\n}\n"
  },
  {
    "path": "tunnel/tproxy/getsockopt.go",
    "content": "//go:build linux && !386\n// +build linux,!386\n\npackage tproxy\n\nimport (\n\t\"syscall\"\n\t\"unsafe\"\n)\n\nfunc getsockopt(fd int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) {\n\t_, _, e := syscall.Syscall6(\n\t\tsyscall.SYS_GETSOCKOPT, uintptr(fd), uintptr(level), uintptr(optname),\n\t\tuintptr(optval), uintptr(unsafe.Pointer(optlen)), 0)\n\tif e != 0 {\n\t\treturn e\n\t}\n\treturn\n}\n"
  },
  {
    "path": "tunnel/tproxy/getsockopt_i386.go",
    "content": "//go:build linux && 386\n// +build linux,386\n\npackage tproxy\n\nimport (\n\t\"syscall\"\n\t\"unsafe\"\n)\n\nconst GETSOCKOPT = 15\n\nfunc getsockopt(fd int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) {\n\t_, _, e := syscall.Syscall6(\n\t\tGETSOCKOPT, uintptr(fd), uintptr(level), uintptr(optname),\n\t\tuintptr(optval), uintptr(unsafe.Pointer(optlen)), 0)\n\tif e != 0 {\n\t\treturn e\n\t}\n\treturn\n}\n"
  },
  {
    "path": "tunnel/tproxy/server.go",
    "content": "//go:build linux\n// +build linux\n\npackage tproxy\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst MaxPacketSize = 1024 * 8\n\ntype Server struct {\n\ttcpListener net.Listener\n\tudpListener *net.UDPConn\n\tpacketChan  chan tunnel.PacketConn\n\ttimeout     time.Duration\n\tmappingLock sync.RWMutex\n\tmapping     map[string]*PacketConn\n\tctx         context.Context\n\tcancel      context.CancelFunc\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\ts.tcpListener.Close()\n\treturn s.udpListener.Close()\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := s.tcpListener.Accept()\n\tif err != nil {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\tdefault:\n\t\t\tlog.Fatal(common.NewError(\"tproxy failed to accept connection\").Base(err))\n\t\t}\n\t\treturn nil, common.NewError(\"tproxy failed to accept conn\")\n\t}\n\tdst, err := getOriginalTCPDest(conn.(*net.TCPConn))\n\tif err != nil {\n\t\treturn nil, common.NewError(\"tproxy failed to obtain original address of tcp socket\").Base(err)\n\t}\n\taddress, err := tunnel.NewAddressFromAddr(\"tcp\", dst.String())\n\tcommon.Must(err)\n\tlog.Info(\"tproxy connection from\", conn.RemoteAddr().String(), \"metadata\", dst.String())\n\treturn &Conn{\n\t\tmetadata: &tunnel.Metadata{\n\t\t\tAddress: address,\n\t\t},\n\t\tConn: conn,\n\t}, nil\n}\n\nfunc (s *Server) packetDispatchLoop() {\n\ttype tproxyPacketInfo struct {\n\t\tsrc     *net.UDPAddr\n\t\tdst     *net.UDPAddr\n\t\tpayload []byte\n\t}\n\tpacketQueue := make(chan *tproxyPacketInfo, 1024)\n\n\tgo func() {\n\t\tfor {\n\t\t\tbuf := make([]byte, MaxPacketSize)\n\t\t\tn, src, dst, err := ReadFromUDP(s.udpListener, buf)\n\t\t\tif err != nil {\n\t\t\t\tselect {\n\t\t\t\tcase <-s.ctx.Done():\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Fatal(common.NewError(\"tproxy failed to read from udp socket\").Base(err))\n\t\t\t\t}\n\t\t\t\ts.Close()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Debug(\"udp packet from\", src, \"metadata\", dst, \"size\", n)\n\t\t\tpacketQueue <- &tproxyPacketInfo{\n\t\t\t\tsrc:     src,\n\t\t\t\tdst:     dst,\n\t\t\t\tpayload: buf[:n],\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tvar info *tproxyPacketInfo\n\t\tselect {\n\t\tcase info = <-packetQueue:\n\t\tcase <-s.ctx.Done():\n\t\t\tlog.Debug(\"exiting\")\n\t\t\treturn\n\t\t}\n\n\t\ts.mappingLock.RLock()\n\t\tconn, found := s.mapping[info.src.String()]\n\t\ts.mappingLock.RUnlock()\n\n\t\tif !found {\n\t\t\tctx, cancel := context.WithCancel(s.ctx)\n\t\t\tconn = &PacketConn{\n\t\t\t\tinput:      make(chan *packetInfo, 128),\n\t\t\t\toutput:     make(chan *packetInfo, 128),\n\t\t\t\tPacketConn: s.udpListener,\n\t\t\t\tctx:        ctx,\n\t\t\t\tcancel:     cancel,\n\t\t\t\tsrc:        info.src,\n\t\t\t}\n\n\t\t\ts.mappingLock.Lock()\n\t\t\ts.mapping[info.src.String()] = conn\n\t\t\ts.mappingLock.Unlock()\n\n\t\t\tlog.Info(\"new tproxy udp session from\", info.src.String(), \"metadata\", info.dst.String())\n\t\t\ts.packetChan <- conn\n\n\t\t\tgo func(conn *PacketConn) {\n\t\t\t\tdefer conn.Close()\n\t\t\t\tlog.Debug(\"udp packet daemon for\", conn.src.String())\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase info := <-conn.output:\n\t\t\t\t\t\tif info.metadata.AddressType != tunnel.IPv4 &&\n\t\t\t\t\t\t\tinfo.metadata.AddressType != tunnel.IPv6 {\n\t\t\t\t\t\t\tlog.Error(\"tproxy invalid response metadata address\", info.metadata)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tback, err := DialUDP(\n\t\t\t\t\t\t\t\"udp\",\n\t\t\t\t\t\t\t&net.UDPAddr{\n\t\t\t\t\t\t\t\tIP:   info.metadata.IP,\n\t\t\t\t\t\t\t\tPort: info.metadata.Port,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tconn.src.(*net.UDPAddr),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Error(common.NewError(\"failed to dial tproxy udp\").Base(err))\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tn, err := back.Write(info.payload)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Error(common.NewError(\"tproxy udp write error\").Base(err))\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlog.Debug(\"recv packet, send back to\", conn.src, \"payload\", len(info.payload), \"sent\", n)\n\t\t\t\t\t\tback.Close()\n\t\t\t\t\tcase <-s.ctx.Done():\n\t\t\t\t\t\tlog.Debug(\"exiting\")\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase <-time.After(s.timeout):\n\t\t\t\t\t\ts.mappingLock.Lock()\n\t\t\t\t\t\tdelete(s.mapping, conn.src.String())\n\t\t\t\t\t\ts.mappingLock.Unlock()\n\t\t\t\t\t\tlog.Debug(\"packet session \", conn.src.String(), \"timeout\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\t\t}\n\n\t\tnewInfo := &packetInfo{\n\t\t\tmetadata: &tunnel.Metadata{\n\t\t\t\tAddress: tunnel.NewAddressFromHostPort(\"udp\", info.dst.IP.String(), info.dst.Port),\n\t\t\t},\n\t\t\tpayload: info.payload,\n\t\t}\n\n\t\tselect {\n\t\tcase conn.input <- newInfo:\n\t\t\tlog.Debug(\"tproxy packet sent with metadata\", newInfo.metadata, \"size\", len(info.payload))\n\t\tdefault:\n\t\t\t// if we got too many packets, simply drop it\n\t\t\tlog.Warn(\"tproxy udp relay queue full!\")\n\t\t}\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tselect {\n\tcase conn := <-s.packetChan:\n\t\tlog.Info(\"tproxy packet conn accepted\")\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, io.EOF\n\t}\n}\n\nfunc NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tctx, cancel := context.WithCancel(ctx)\n\tlistenAddr := tunnel.NewAddressFromHostPort(\"tcp\", cfg.LocalHost, cfg.LocalPort)\n\tip, err := listenAddr.ResolveIP()\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"invalid tproxy local address\").Base(err)\n\t}\n\ttcpListener, err := ListenTCP(\"tcp\", &net.TCPAddr{\n\t\tIP:   ip,\n\t\tPort: cfg.LocalPort,\n\t})\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"tproxy failed to listen tcp\").Base(err)\n\t}\n\n\tudpListener, err := ListenUDP(\"udp\", &net.UDPAddr{\n\t\tIP:   ip,\n\t\tPort: cfg.LocalPort,\n\t})\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"tproxy failed to listen udp\").Base(err)\n\t}\n\n\tserver := &Server{\n\t\ttcpListener: tcpListener,\n\t\tudpListener: udpListener,\n\t\tctx:         ctx,\n\t\tcancel:      cancel,\n\t\ttimeout:     time.Duration(cfg.UDPTimeout) * time.Second,\n\t\tmapping:     make(map[string]*PacketConn),\n\t\tpacketChan:  make(chan tunnel.PacketConn, 32),\n\t}\n\tgo server.packetDispatchLoop()\n\tlog.Info(\"tproxy server listening on\", tcpListener.Addr(), \"(tcp)\", udpListener.LocalAddr(), \"(udp)\")\n\tlog.Debug(\"tproxy server created\")\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/tproxy/tcp.go",
    "content": "//go:build linux\n// +build linux\n\npackage tproxy\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// Listener describes a TCP Listener\n// with the Linux IP_TRANSPARENT option defined\n// on the listening socket\ntype Listener struct {\n\tbase net.Listener\n}\n\n// Accept waits for and returns\n// the next connection to the listener.\n//\n// This command wraps the AcceptTProxy\n// method of the Listener\nfunc (listener *Listener) Accept() (net.Conn, error) {\n\ttcpConn, err := listener.base.(*net.TCPListener).AcceptTCP()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tcpConn, nil\n}\n\n// Addr returns the network address\n// the listener is accepting connections\n// from\nfunc (listener *Listener) Addr() net.Addr {\n\treturn listener.base.Addr()\n}\n\n// Close will close the listener from accepting\n// any more connections. Any blocked connections\n// will unblock and close\nfunc (listener *Listener) Close() error {\n\treturn listener.base.Close()\n}\n\n// ListenTCP will construct a new TCP listener\n// socket with the Linux IP_TRANSPARENT option\n// set on the underlying socket\nfunc ListenTCP(network string, laddr *net.TCPAddr) (net.Listener, error) {\n\tlistener, err := net.ListenTCP(network, laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileDescriptorSource, err := listener.File()\n\tif err != nil {\n\t\treturn nil, &net.OpError{Op: \"listen\", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf(\"get file descriptor: %s\", err)}\n\t}\n\tdefer fileDescriptorSource.Close()\n\n\tif err = syscall.SetsockoptInt(int(fileDescriptorSource.Fd()), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {\n\t\treturn nil, &net.OpError{Op: \"listen\", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf(\"set socket option: IP_TRANSPARENT: %s\", err)}\n\t}\n\n\treturn &Listener{listener}, nil\n}\n\nconst (\n\tIP6T_SO_ORIGINAL_DST = 80\n\tSO_ORIGINAL_DST      = 80\n)\n\n// getOriginalTCPDest retrieves the original destination address from\n// NATed connection.  Currently, only Linux iptables using DNAT/REDIRECT\n// is supported.  For other operating systems, this will just return\n// conn.LocalAddr().\n//\n// Note that this function only works when nf_conntrack_ipv4 and/or\n// nf_conntrack_ipv6 is loaded in the kernel.\nfunc getOriginalTCPDest(conn *net.TCPConn) (*net.TCPAddr, error) {\n\tf, err := conn.File()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\n\tfd := int(f.Fd())\n\t// revert to non-blocking mode.\n\t// see http://stackoverflow.com/a/28968431/1493661\n\tif err = syscall.SetNonblock(fd, true); err != nil {\n\t\treturn nil, os.NewSyscallError(\"setnonblock\", err)\n\t}\n\n\tv6 := conn.LocalAddr().(*net.TCPAddr).IP.To4() == nil\n\tif v6 {\n\t\tvar addr syscall.RawSockaddrInet6\n\t\tvar len uint32\n\t\tlen = uint32(unsafe.Sizeof(addr))\n\t\terr = getsockopt(fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST,\n\t\t\tunsafe.Pointer(&addr), &len)\n\t\tif err != nil {\n\t\t\treturn nil, os.NewSyscallError(\"getsockopt\", err)\n\t\t}\n\t\tip := make([]byte, 16)\n\t\tfor i, b := range addr.Addr {\n\t\t\tip[i] = b\n\t\t}\n\t\tpb := *(*[2]byte)(unsafe.Pointer(&addr.Port))\n\t\treturn &net.TCPAddr{\n\t\t\tIP:   ip,\n\t\t\tPort: int(pb[0])*256 + int(pb[1]),\n\t\t}, nil\n\t}\n\n\t// IPv4\n\tvar addr syscall.RawSockaddrInet4\n\tvar len uint32\n\tlen = uint32(unsafe.Sizeof(addr))\n\terr = getsockopt(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST,\n\t\tunsafe.Pointer(&addr), &len)\n\tif err != nil {\n\t\treturn nil, os.NewSyscallError(\"getsockopt\", err)\n\t}\n\tip := make([]byte, 4)\n\tfor i, b := range addr.Addr {\n\t\tip[i] = b\n\t}\n\tpb := *(*[2]byte)(unsafe.Pointer(&addr.Port))\n\treturn &net.TCPAddr{\n\t\tIP:   ip,\n\t\tPort: int(pb[0])*256 + int(pb[1]),\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/tproxy/tproxy_stub.go",
    "content": "package tproxy\n"
  },
  {
    "path": "tunnel/tproxy/tunnel.go",
    "content": "//go:build linux\n// +build linux\n\npackage tproxy\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"TPROXY\"\n\ntype Tunnel struct{}\n\nfunc (t *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\tpanic(\"not supported\")\n}\n\nfunc (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/tproxy/udp.go",
    "content": "//go:build linux\n// +build linux\n\npackage tproxy\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// ListenUDP will construct a new UDP listener\n// socket with the Linux IP_TRANSPARENT option\n// set on the underlying socket\nfunc ListenUDP(network string, laddr *net.UDPAddr) (*net.UDPConn, error) {\n\tlistener, err := net.ListenUDP(network, laddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileDescriptorSource, err := listener.File()\n\tif err != nil {\n\t\treturn nil, &net.OpError{Op: \"listen\", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf(\"get file descriptor: %s\", err)}\n\t}\n\tdefer fileDescriptorSource.Close()\n\n\tfileDescriptor := int(fileDescriptorSource.Fd())\n\tif err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {\n\t\treturn nil, &net.OpError{Op: \"listen\", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf(\"set socket option: IP_TRANSPARENT: %s\", err)}\n\t}\n\n\tif err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1); err != nil {\n\t\treturn nil, &net.OpError{Op: \"listen\", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf(\"set socket option: IP_RECVORIGDSTADDR: %s\", err)}\n\t}\n\n\treturn listener, nil\n}\n\n// ReadFromUDP reads a UDP packet from c, copying the payload into b.\n// It returns the number of bytes copied into b and the return address\n// that was on the packet.\n//\n// Out-of-band data is also read in so that the original destination\n// address can be identified and parsed.\nfunc ReadFromUDP(conn *net.UDPConn, b []byte) (int, *net.UDPAddr, *net.UDPAddr, error) {\n\toob := make([]byte, 1024)\n\tn, oobn, _, addr, err := conn.ReadMsgUDP(b, oob)\n\tif err != nil {\n\t\treturn 0, nil, nil, err\n\t}\n\n\tmsgs, err := syscall.ParseSocketControlMessage(oob[:oobn])\n\tif err != nil {\n\t\treturn 0, nil, nil, fmt.Errorf(\"parsing socket control message: %s\", err)\n\t}\n\n\tvar originalDst *net.UDPAddr\n\tfor _, msg := range msgs {\n\t\tif (msg.Header.Level == syscall.SOL_IP || msg.Header.Level == syscall.SOL_IPV6) && msg.Header.Type == syscall.IP_RECVORIGDSTADDR {\n\t\t\toriginalDstRaw := &syscall.RawSockaddrInet4{}\n\t\t\tif err = binary.Read(bytes.NewReader(msg.Data), binary.LittleEndian, originalDstRaw); err != nil {\n\t\t\t\treturn 0, nil, nil, fmt.Errorf(\"reading original destination address: %s\", err)\n\t\t\t}\n\n\t\t\tswitch originalDstRaw.Family {\n\t\t\tcase syscall.AF_INET:\n\t\t\t\tpp := (*syscall.RawSockaddrInet4)(unsafe.Pointer(originalDstRaw))\n\t\t\t\tp := (*[2]byte)(unsafe.Pointer(&pp.Port))\n\t\t\t\toriginalDst = &net.UDPAddr{\n\t\t\t\t\tIP:   net.IPv4(pp.Addr[0], pp.Addr[1], pp.Addr[2], pp.Addr[3]),\n\t\t\t\t\tPort: int(p[0])<<8 + int(p[1]),\n\t\t\t\t}\n\n\t\t\tcase syscall.AF_INET6:\n\t\t\t\tpp := (*syscall.RawSockaddrInet6)(unsafe.Pointer(originalDstRaw))\n\t\t\t\tp := (*[2]byte)(unsafe.Pointer(&pp.Port))\n\t\t\t\toriginalDst = &net.UDPAddr{\n\t\t\t\t\tIP:   net.IP(pp.Addr[:]),\n\t\t\t\t\tPort: int(p[0])<<8 + int(p[1]),\n\t\t\t\t\tZone: strconv.Itoa(int(pp.Scope_id)),\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\treturn 0, nil, nil, fmt.Errorf(\"original destination is an unsupported network family\")\n\t\t\t}\n\t\t}\n\t}\n\n\tif originalDst == nil {\n\t\treturn 0, nil, nil, fmt.Errorf(\"unable to obtain original destination: %s\", err)\n\t}\n\n\treturn n, addr, originalDst, nil\n}\n\n// DialUDP connects to the remote address raddr on the network net,\n// which must be \"udp\", \"udp4\", or \"udp6\".  If laddr is not nil, it is\n// used as the local address for the connection.\nfunc DialUDP(network string, laddr *net.UDPAddr, raddr *net.UDPAddr) (*net.UDPConn, error) {\n\tremoteSocketAddress, err := udpAddrToSocketAddr(raddr)\n\tif err != nil {\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"build destination socket address: %s\", err)}\n\t}\n\n\tlocalSocketAddress, err := udpAddrToSocketAddr(laddr)\n\tif err != nil {\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"build local socket address: %s\", err)}\n\t}\n\n\tfileDescriptor, err := syscall.Socket(udpAddrFamily(network, laddr, raddr), syscall.SOCK_DGRAM, 0)\n\tif err != nil {\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"socket open: %s\", err)}\n\t}\n\n\tif err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {\n\t\tsyscall.Close(fileDescriptor)\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"set socket option: SO_REUSEADDR: %s\", err)}\n\t}\n\n\tif err = syscall.SetsockoptInt(fileDescriptor, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {\n\t\tsyscall.Close(fileDescriptor)\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"set socket option: IP_TRANSPARENT: %s\", err)}\n\t}\n\n\tif err = syscall.Bind(fileDescriptor, localSocketAddress); err != nil {\n\t\tsyscall.Close(fileDescriptor)\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"socket bind: %s\", err)}\n\t}\n\n\tif err = syscall.Connect(fileDescriptor, remoteSocketAddress); err != nil {\n\t\tsyscall.Close(fileDescriptor)\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"socket connect: %s\", err)}\n\t}\n\n\tfdFile := os.NewFile(uintptr(fileDescriptor), fmt.Sprintf(\"net-udp-dial-%s\", raddr.String()))\n\tdefer fdFile.Close()\n\n\tremoteConn, err := net.FileConn(fdFile)\n\tif err != nil {\n\t\tsyscall.Close(fileDescriptor)\n\t\treturn nil, &net.OpError{Op: \"dial\", Err: fmt.Errorf(\"convert file descriptor to connection: %s\", err)}\n\t}\n\n\treturn remoteConn.(*net.UDPConn), nil\n}\n\n// udpAddToSockerAddr will convert a UDPAddr\n// into a Sockaddr that may be used when\n// connecting and binding sockets\nfunc udpAddrToSocketAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) {\n\tswitch {\n\tcase addr.IP.To4() != nil:\n\t\tip := [4]byte{}\n\t\tcopy(ip[:], addr.IP.To4())\n\n\t\treturn &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil\n\n\tdefault:\n\t\tip := [16]byte{}\n\t\tcopy(ip[:], addr.IP.To16())\n\n\t\tzoneID, err := strconv.ParseUint(addr.Zone, 10, 32)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil\n\t}\n}\n\n// udpAddrFamily will attempt to work\n// out the address family based on the\n// network and UDP addresses\nfunc udpAddrFamily(net string, laddr, raddr *net.UDPAddr) int {\n\tswitch net[len(net)-1] {\n\tcase '4':\n\t\treturn syscall.AF_INET\n\tcase '6':\n\t\treturn syscall.AF_INET6\n\t}\n\n\tif (laddr == nil || laddr.IP.To4() != nil) &&\n\t\t(raddr == nil || laddr.IP.To4() != nil) {\n\t\treturn syscall.AF_INET\n\t}\n\treturn syscall.AF_INET6\n}\n"
  },
  {
    "path": "tunnel/transport/client.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n)\n\n// Client implements tunnel.Client\ntype Client struct {\n\tserverAddress *tunnel.Address\n\tcmd           *exec.Cmd\n\tctx           context.Context\n\tcancel        context.CancelFunc\n\tdirect        *freedom.Client\n}\n\nfunc (c *Client) Close() error {\n\tc.cancel()\n\tif c.cmd != nil && c.cmd.Process != nil {\n\t\tc.cmd.Process.Kill()\n\t}\n\treturn nil\n}\n\nfunc (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\n// DialConn implements tunnel.Client. It will ignore the params and directly dial to the remote server\nfunc (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := c.direct.DialConn(c.serverAddress, nil)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"transport failed to connect to remote server\").Base(err)\n\t}\n\treturn &Conn{\n\t\tConn: conn,\n\t}, nil\n}\n\n// NewClient creates a transport layer client\nfunc NewClient(ctx context.Context, _ tunnel.Client) (*Client, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\n\tvar cmd *exec.Cmd\n\tserverAddress := tunnel.NewAddressFromHostPort(\"tcp\", cfg.RemoteHost, cfg.RemotePort)\n\n\tif cfg.TransportPlugin.Enabled {\n\t\tlog.Warn(\"trojan-go will use transport plugin and work in plain text mode\")\n\t\tswitch cfg.TransportPlugin.Type {\n\t\tcase \"shadowsocks\":\n\t\t\tpluginHost := \"127.0.0.1\"\n\t\t\tpluginPort := common.PickPort(\"tcp\", pluginHost)\n\t\t\tcfg.TransportPlugin.Env = append(\n\t\t\t\tcfg.TransportPlugin.Env,\n\t\t\t\t\"SS_LOCAL_HOST=\"+pluginHost,\n\t\t\t\t\"SS_LOCAL_PORT=\"+strconv.FormatInt(int64(pluginPort), 10),\n\t\t\t\t\"SS_REMOTE_HOST=\"+cfg.RemoteHost,\n\t\t\t\t\"SS_REMOTE_PORT=\"+strconv.FormatInt(int64(cfg.RemotePort), 10),\n\t\t\t\t\"SS_PLUGIN_OPTIONS=\"+cfg.TransportPlugin.Option,\n\t\t\t)\n\t\t\tcfg.RemoteHost = pluginHost\n\t\t\tcfg.RemotePort = pluginPort\n\t\t\tserverAddress = tunnel.NewAddressFromHostPort(\"tcp\", cfg.RemoteHost, cfg.RemotePort)\n\t\t\tlog.Debug(\"plugin address\", serverAddress.String())\n\t\t\tlog.Debug(\"plugin env\", cfg.TransportPlugin.Env)\n\n\t\t\tcmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)\n\t\t\tcmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stderr = os.Stdout\n\t\t\tcmd.Start()\n\t\tcase \"other\":\n\t\t\tcmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)\n\t\t\tcmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stderr = os.Stdout\n\t\t\tcmd.Start()\n\t\tcase \"plaintext\":\n\t\t\t// do nothing\n\t\tdefault:\n\t\t\treturn nil, common.NewError(\"invalid plugin type: \" + cfg.TransportPlugin.Type)\n\t\t}\n\t}\n\n\tdirect, err := freedom.NewClient(ctx, nil)\n\tcommon.Must(err)\n\tctx, cancel := context.WithCancel(ctx)\n\tclient := &Client{\n\t\tserverAddress: serverAddress,\n\t\tcmd:           cmd,\n\t\tctx:           ctx,\n\t\tcancel:        cancel,\n\t\tdirect:        direct,\n\t}\n\treturn client, nil\n}\n"
  },
  {
    "path": "tunnel/transport/config.go",
    "content": "package transport\n\nimport (\n\t\"github.com/p4gefau1t/trojan-go/config\"\n)\n\ntype Config struct {\n\tLocalHost       string                `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort       int                   `json:\"local_port\" yaml:\"local-port\"`\n\tRemoteHost      string                `json:\"remote_addr\" yaml:\"remote-addr\"`\n\tRemotePort      int                   `json:\"remote_port\" yaml:\"remote-port\"`\n\tTransportPlugin TransportPluginConfig `json:\"transport_plugin\" yaml:\"transport-plugin\"`\n}\n\ntype TransportPluginConfig struct {\n\tEnabled bool     `json:\"enabled\" yaml:\"enabled\"`\n\tType    string   `json:\"type\" yaml:\"type\"`\n\tCommand string   `json:\"command\" yaml:\"command\"`\n\tOption  string   `json:\"option\" yaml:\"option\"`\n\tArg     []string `json:\"arg\" yaml:\"arg\"`\n\tEnv     []string `json:\"env\" yaml:\"env\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(Config)\n\t})\n}\n"
  },
  {
    "path": "tunnel/transport/conn.go",
    "content": "package transport\n\nimport (\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Conn struct {\n\tnet.Conn\n}\n\nfunc (c *Conn) Metadata() *tunnel.Metadata {\n\treturn nil\n}\n"
  },
  {
    "path": "tunnel/transport/server.go",
    "content": "package transport\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\n// Server is a server of transport layer\ntype Server struct {\n\ttcpListener net.Listener\n\tcmd         *exec.Cmd\n\tconnChan    chan tunnel.Conn\n\twsChan      chan tunnel.Conn\n\thttpLock    sync.RWMutex\n\tnextHTTP    bool\n\tctx         context.Context\n\tcancel      context.CancelFunc\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\tif s.cmd != nil && s.cmd.Process != nil {\n\t\ts.cmd.Process.Kill()\n\t}\n\treturn s.tcpListener.Close()\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\ttcpConn, err := s.tcpListener.Accept()\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\tdefault:\n\t\t\t\tlog.Error(common.NewError(\"transport accept error\").Base(err))\n\t\t\t\ttime.Sleep(time.Millisecond * 100)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tgo func(tcpConn net.Conn) {\n\t\t\tlog.Info(\"tcp connection from\", tcpConn.RemoteAddr())\n\t\t\ts.httpLock.RLock()\n\t\t\tif s.nextHTTP { // plaintext mode enabled\n\t\t\t\ts.httpLock.RUnlock()\n\t\t\t\t// we use real http header parser to mimic a real http server\n\t\t\t\trewindConn := common.NewRewindConn(tcpConn)\n\t\t\t\trewindConn.SetBufferSize(512)\n\t\t\t\tdefer rewindConn.StopBuffering()\n\n\t\t\t\tr := bufio.NewReader(rewindConn)\n\t\t\t\thttpReq, err := http.ReadRequest(r)\n\t\t\t\trewindConn.Rewind()\n\t\t\t\trewindConn.StopBuffering()\n\t\t\t\tif err != nil {\n\t\t\t\t\t// this is not a http request, pass it to trojan protocol layer for further inspection\n\t\t\t\t\ts.connChan <- &Conn{\n\t\t\t\t\t\tConn: rewindConn,\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// this is a http request, pass it to websocket protocol layer\n\t\t\t\t\tlog.Debug(\"plaintext http request: \", httpReq)\n\t\t\t\t\ts.wsChan <- &Conn{\n\t\t\t\t\t\tConn: rewindConn,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.httpLock.RUnlock()\n\t\t\t\ts.connChan <- &Conn{\n\t\t\t\t\tConn: tcpConn,\n\t\t\t\t}\n\t\t\t}\n\t\t}(tcpConn)\n\t}\n}\n\nfunc (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\t// TODO fix import cycle\n\tif overlay != nil && (overlay.Name() == \"WEBSOCKET\" || overlay.Name() == \"HTTP\") {\n\t\ts.httpLock.Lock()\n\t\ts.nextHTTP = true\n\t\ts.httpLock.Unlock()\n\t\tselect {\n\t\tcase conn := <-s.wsChan:\n\t\t\treturn conn, nil\n\t\tcase <-s.ctx.Done():\n\t\t\treturn nil, common.NewError(\"transport server closed\")\n\t\t}\n\t}\n\tselect {\n\tcase conn := <-s.connChan:\n\t\treturn conn, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"transport server closed\")\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tpanic(\"not supported\")\n}\n\n// NewServer creates a transport layer server\nfunc NewServer(ctx context.Context, _ tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tlistenAddress := tunnel.NewAddressFromHostPort(\"tcp\", cfg.LocalHost, cfg.LocalPort)\n\n\tvar cmd *exec.Cmd\n\tif cfg.TransportPlugin.Enabled {\n\t\tlog.Warn(\"transport server will use plugin and work in plain text mode\")\n\t\tswitch cfg.TransportPlugin.Type {\n\t\tcase \"shadowsocks\":\n\t\t\ttrojanHost := \"127.0.0.1\"\n\t\t\ttrojanPort := common.PickPort(\"tcp\", trojanHost)\n\t\t\tcfg.TransportPlugin.Env = append(\n\t\t\t\tcfg.TransportPlugin.Env,\n\t\t\t\t\"SS_REMOTE_HOST=\"+cfg.LocalHost,\n\t\t\t\t\"SS_REMOTE_PORT=\"+strconv.FormatInt(int64(cfg.LocalPort), 10),\n\t\t\t\t\"SS_LOCAL_HOST=\"+trojanHost,\n\t\t\t\t\"SS_LOCAL_PORT=\"+strconv.FormatInt(int64(trojanPort), 10),\n\t\t\t\t\"SS_PLUGIN_OPTIONS=\"+cfg.TransportPlugin.Option,\n\t\t\t)\n\n\t\t\tcfg.LocalHost = trojanHost\n\t\t\tcfg.LocalPort = trojanPort\n\t\t\tlistenAddress = tunnel.NewAddressFromHostPort(\"tcp\", cfg.LocalHost, cfg.LocalPort)\n\t\t\tlog.Debug(\"new listen address\", listenAddress)\n\t\t\tlog.Debug(\"plugin env\", cfg.TransportPlugin.Env)\n\n\t\t\tcmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)\n\t\t\tcmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stderr = os.Stdout\n\t\t\tcmd.Start()\n\t\tcase \"other\":\n\t\t\tcmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...)\n\t\t\tcmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...)\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stderr = os.Stdout\n\t\t\tcmd.Start()\n\t\tcase \"plaintext\":\n\t\t\t// do nothing\n\t\tdefault:\n\t\t\treturn nil, common.NewError(\"invalid plugin type: \" + cfg.TransportPlugin.Type)\n\t\t}\n\t}\n\ttcpListener, err := net.Listen(\"tcp\", listenAddress.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\tserver := &Server{\n\t\ttcpListener: tcpListener,\n\t\tcmd:         cmd,\n\t\tctx:         ctx,\n\t\tcancel:      cancel,\n\t\tconnChan:    make(chan tunnel.Conn, 32),\n\t\twsChan:      make(chan tunnel.Conn, 32),\n\t}\n\tgo server.acceptLoop()\n\treturn server, nil\n}\n"
  },
  {
    "path": "tunnel/transport/transport_test.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n)\n\nfunc TestTransport(t *testing.T) {\n\tserverCfg := &Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t}\n\tclientCfg := &Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: serverCfg.LocalPort,\n\t}\n\tfreedomCfg := &freedom.Config{}\n\tsctx := config.WithConfig(context.Background(), Name, serverCfg)\n\tcctx := config.WithConfig(context.Background(), Name, clientCfg)\n\tcctx = config.WithConfig(cctx, freedom.Name, freedomCfg)\n\n\ts, err := NewServer(sctx, nil)\n\tcommon.Must(err)\n\tc, err := NewClient(cctx, nil)\n\tcommon.Must(err)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tvar conn1, conn2 net.Conn\n\tgo func() {\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\twg.Done()\n\t}()\n\tconn1, err = c.DialConn(nil, nil)\n\tcommon.Must(err)\n\n\tcommon.Must2(conn1.Write([]byte(\"12345678\\r\\n\")))\n\twg.Wait()\n\tbuf := [10]byte{}\n\tconn2.Read(buf[:])\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\ts.Close()\n\tc.Close()\n}\n\nfunc TestClientPlugin(t *testing.T) {\n\tclientCfg := &Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: 12345,\n\t\tTransportPlugin: TransportPluginConfig{\n\t\t\tEnabled: true,\n\t\t\tType:    \"shadowsocks\",\n\t\t\tCommand: \"echo $SS_REMOTE_PORT\",\n\t\t\tOption:  \"\",\n\t\t\tArg:     nil,\n\t\t\tEnv:     nil,\n\t\t},\n\t}\n\tctx := config.WithConfig(context.Background(), Name, clientCfg)\n\tfreedomCfg := &freedom.Config{}\n\tctx = config.WithConfig(ctx, freedom.Name, freedomCfg)\n\tc, err := NewClient(ctx, nil)\n\tcommon.Must(err)\n\tc.Close()\n}\n\nfunc TestServerPlugin(t *testing.T) {\n\tcfg := &Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  common.PickPort(\"tcp\", \"127.0.0.1\"),\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: 12345,\n\t\tTransportPlugin: TransportPluginConfig{\n\t\t\tEnabled: true,\n\t\t\tType:    \"shadowsocks\",\n\t\t\tCommand: \"echo $SS_REMOTE_PORT\",\n\t\t\tOption:  \"\",\n\t\t\tArg:     nil,\n\t\t\tEnv:     nil,\n\t\t},\n\t}\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\tfreedomCfg := &freedom.Config{}\n\tctx = config.WithConfig(ctx, freedom.Name, freedomCfg)\n\ts, err := NewServer(ctx, nil)\n\tcommon.Must(err)\n\ts.Close()\n}\n"
  },
  {
    "path": "tunnel/transport/tunnel.go",
    "content": "package transport\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"TRANSPORT\"\n\ntype Tunnel struct{}\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/trojan/client.go",
    "content": "package trojan\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/p4gefau1t/trojan-go/api\"\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/memory\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/mux\"\n)\n\nconst (\n\tMaxPacketSize = 1024 * 8\n)\n\nconst (\n\tConnect   tunnel.Command = 1\n\tAssociate tunnel.Command = 3\n\tMux       tunnel.Command = 0x7f\n)\n\ntype OutboundConn struct {\n\t// WARNING: do not change the order of these fields.\n\t// 64-bit fields that use `sync/atomic` package functions\n\t// must be 64-bit aligned on 32-bit systems.\n\t// Reference: https://github.com/golang/go/issues/599\n\t// Solution: https://github.com/golang/go/issues/11891#issuecomment-433623786\n\tsent uint64\n\trecv uint64\n\n\tmetadata          *tunnel.Metadata\n\tuser              statistic.User\n\theaderWrittenOnce sync.Once\n\tnet.Conn\n}\n\nfunc (c *OutboundConn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\nfunc (c *OutboundConn) WriteHeader(payload []byte) (bool, error) {\n\tvar err error\n\twritten := false\n\tc.headerWrittenOnce.Do(func() {\n\t\thash := c.user.Hash()\n\t\tbuf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize))\n\t\tcrlf := []byte{0x0d, 0x0a}\n\t\tbuf.Write([]byte(hash))\n\t\tbuf.Write(crlf)\n\t\tc.metadata.WriteTo(buf)\n\t\tbuf.Write(crlf)\n\t\tif payload != nil {\n\t\t\tbuf.Write(payload)\n\t\t}\n\t\t_, err = c.Conn.Write(buf.Bytes())\n\t\tif err == nil {\n\t\t\twritten = true\n\t\t}\n\t})\n\treturn written, err\n}\n\nfunc (c *OutboundConn) Write(p []byte) (int, error) {\n\twritten, err := c.WriteHeader(p)\n\tif err != nil {\n\t\treturn 0, common.NewError(\"trojan failed to flush header with payload\").Base(err)\n\t}\n\tif written {\n\t\treturn len(p), nil\n\t}\n\tn, err := c.Conn.Write(p)\n\tc.user.AddTraffic(n, 0)\n\tatomic.AddUint64(&c.sent, uint64(n))\n\treturn n, err\n}\n\nfunc (c *OutboundConn) Read(p []byte) (int, error) {\n\tn, err := c.Conn.Read(p)\n\tc.user.AddTraffic(0, n)\n\tatomic.AddUint64(&c.recv, uint64(n))\n\treturn n, err\n}\n\nfunc (c *OutboundConn) Close() error {\n\tlog.Info(\"connection to\", c.metadata, \"closed\", \"sent:\", common.HumanFriendlyTraffic(atomic.LoadUint64(&c.sent)), \"recv:\", common.HumanFriendlyTraffic(atomic.LoadUint64(&c.recv)))\n\treturn c.Conn.Close()\n}\n\ntype Client struct {\n\tunderlay tunnel.Client\n\tuser     statistic.User\n\tctx      context.Context\n\tcancel   context.CancelFunc\n}\n\nfunc (c *Client) Close() error {\n\tc.cancel()\n\treturn c.underlay.Close()\n}\n\nfunc (c *Client) DialConn(addr *tunnel.Address, overlay tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := c.underlay.DialConn(addr, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnewConn := &OutboundConn{\n\t\tConn: conn,\n\t\tuser: c.user,\n\t\tmetadata: &tunnel.Metadata{\n\t\t\tCommand: Connect,\n\t\t\tAddress: addr,\n\t\t},\n\t}\n\tif _, ok := overlay.(*mux.Tunnel); ok {\n\t\tnewConn.metadata.Command = Mux\n\t}\n\n\tgo func(newConn *OutboundConn) {\n\t\t// if the trojan header is still buffered after 100 ms, the client may expect data from the server\n\t\t// so we flush the trojan header\n\t\ttime.Sleep(time.Millisecond * 100)\n\t\tnewConn.WriteHeader(nil)\n\t}(newConn)\n\treturn newConn, nil\n}\n\nfunc (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tfakeAddr := &tunnel.Address{\n\t\tDomainName:  \"UDP_CONN\",\n\t\tAddressType: tunnel.DomainName,\n\t}\n\tconn, err := c.underlay.DialConn(fakeAddr, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &PacketConn{\n\t\tConn: &OutboundConn{\n\t\t\tConn: conn,\n\t\t\tuser: c.user,\n\t\t\tmetadata: &tunnel.Metadata{\n\t\t\t\tCommand: Associate,\n\t\t\t\tAddress: fakeAddr,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc NewClient(ctx context.Context, client tunnel.Client) (*Client, error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tauth, err := statistic.NewAuthenticator(ctx, memory.Name)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tif cfg.API.Enabled {\n\t\tgo api.RunService(ctx, Name+\"_CLIENT\", auth)\n\t}\n\n\tvar user statistic.User\n\tfor _, u := range auth.ListUsers() {\n\t\tuser = u\n\t\tbreak\n\t}\n\tif user == nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"no valid user found\")\n\t}\n\n\tlog.Debug(\"trojan client created\")\n\treturn &Client{\n\t\tunderlay: client,\n\t\tctx:      ctx,\n\t\tuser:     user,\n\t\tcancel:   cancel,\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/trojan/config.go",
    "content": "package trojan\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype Config struct {\n\tLocalHost        string      `json:\"local_addr\" yaml:\"local-addr\"`\n\tLocalPort        int         `json:\"local_port\" yaml:\"local-port\"`\n\tRemoteHost       string      `json:\"remote_addr\" yaml:\"remote-addr\"`\n\tRemotePort       int         `json:\"remote_port\" yaml:\"remote-port\"`\n\tDisableHTTPCheck bool        `json:\"disable_http_check\" yaml:\"disable-http-check\"`\n\tMySQL            MySQLConfig `json:\"mysql\" yaml:\"mysql\"`\n\tAPI              APIConfig   `json:\"api\" yaml:\"api\"`\n}\n\ntype MySQLConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\ntype APIConfig struct {\n\tEnabled bool `json:\"enabled\" yaml:\"enabled\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn &Config{}\n\t})\n}\n"
  },
  {
    "path": "tunnel/trojan/packet.go",
    "content": "package trojan\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype PacketConn struct {\n\ttunnel.Conn\n}\n\nfunc (c *PacketConn) ReadFrom(payload []byte) (int, net.Addr, error) {\n\treturn c.ReadWithMetadata(payload)\n}\n\nfunc (c *PacketConn) WriteTo(payload []byte, addr net.Addr) (int, error) {\n\taddress, err := tunnel.NewAddressFromAddr(\"udp\", addr.String())\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tm := &tunnel.Metadata{\n\t\tAddress: address,\n\t}\n\treturn c.WriteWithMetadata(payload, m)\n}\n\nfunc (c *PacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata) (int, error) {\n\tpacket := make([]byte, 0, MaxPacketSize)\n\tw := bytes.NewBuffer(packet)\n\tmetadata.Address.WriteTo(w)\n\n\tlength := len(payload)\n\tlengthBuf := [2]byte{}\n\tcrlf := [2]byte{0x0d, 0x0a}\n\n\tbinary.BigEndian.PutUint16(lengthBuf[:], uint16(length))\n\tw.Write(lengthBuf[:])\n\tw.Write(crlf[:])\n\tw.Write(payload)\n\n\t_, err := c.Conn.Write(w.Bytes())\n\n\tlog.Debug(\"udp packet remote\", c.RemoteAddr(), \"metadata\", metadata, \"size\", length)\n\treturn len(payload), err\n}\n\nfunc (c *PacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, error) {\n\taddr := &tunnel.Address{\n\t\tNetworkType: \"udp\",\n\t}\n\tif err := addr.ReadFrom(c.Conn); err != nil {\n\t\treturn 0, nil, common.NewError(\"failed to parse udp packet addr\").Base(err)\n\t}\n\tlengthBuf := [2]byte{}\n\tif _, err := io.ReadFull(c.Conn, lengthBuf[:]); err != nil {\n\t\treturn 0, nil, common.NewError(\"failed to read length\")\n\t}\n\tlength := int(binary.BigEndian.Uint16(lengthBuf[:]))\n\n\tcrlf := [2]byte{}\n\tif _, err := io.ReadFull(c.Conn, crlf[:]); err != nil {\n\t\treturn 0, nil, common.NewError(\"failed to read crlf\")\n\t}\n\n\tif len(payload) < length || length > MaxPacketSize {\n\t\tio.CopyN(ioutil.Discard, c.Conn, int64(length)) // drain the rest of the packet\n\t\treturn 0, nil, common.NewError(\"incoming packet size is too large\")\n\t}\n\n\tif _, err := io.ReadFull(c.Conn, payload[:length]); err != nil {\n\t\treturn 0, nil, common.NewError(\"failed to read payload\")\n\t}\n\n\tlog.Debug(\"udp packet from\", c.RemoteAddr(), \"metadata\", addr.String(), \"size\", length)\n\treturn length, &tunnel.Metadata{\n\t\tAddress: addr,\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/trojan/server.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"sync/atomic\"\n\n\t\"github.com/p4gefau1t/trojan-go/api\"\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/redirector\"\n\t\"github.com/p4gefau1t/trojan-go/statistic\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/memory\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/mysql\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/mux\"\n)\n\n// InboundConn is a trojan inbound connection\ntype InboundConn struct {\n\t// WARNING: do not change the order of these fields.\n\t// 64-bit fields that use `sync/atomic` package functions\n\t// must be 64-bit aligned on 32-bit systems.\n\t// Reference: https://github.com/golang/go/issues/599\n\t// Solution: https://github.com/golang/go/issues/11891#issuecomment-433623786\n\tsent uint64\n\trecv uint64\n\n\tnet.Conn\n\tauth     statistic.Authenticator\n\tuser     statistic.User\n\thash     string\n\tmetadata *tunnel.Metadata\n\tip       string\n}\n\nfunc (c *InboundConn) Metadata() *tunnel.Metadata {\n\treturn c.metadata\n}\n\nfunc (c *InboundConn) Write(p []byte) (int, error) {\n\tn, err := c.Conn.Write(p)\n\tatomic.AddUint64(&c.sent, uint64(n))\n\tc.user.AddTraffic(n, 0)\n\treturn n, err\n}\n\nfunc (c *InboundConn) Read(p []byte) (int, error) {\n\tn, err := c.Conn.Read(p)\n\tatomic.AddUint64(&c.recv, uint64(n))\n\tc.user.AddTraffic(0, n)\n\treturn n, err\n}\n\nfunc (c *InboundConn) Close() error {\n\tlog.Info(\"user\", c.hash, \"from\", c.Conn.RemoteAddr(), \"tunneling to\", c.metadata.Address, \"closed\",\n\t\t\"sent:\", common.HumanFriendlyTraffic(atomic.LoadUint64(&c.sent)), \"recv:\", common.HumanFriendlyTraffic(atomic.LoadUint64(&c.recv)))\n\tc.user.DelIP(c.ip)\n\treturn c.Conn.Close()\n}\n\nfunc (c *InboundConn) Auth() error {\n\tuserHash := [56]byte{}\n\tn, err := c.Conn.Read(userHash[:])\n\tif err != nil || n != 56 {\n\t\treturn common.NewError(\"failed to read hash\").Base(err)\n\t}\n\n\tvalid, user := c.auth.AuthUser(string(userHash[:]))\n\tif !valid {\n\t\treturn common.NewError(\"invalid hash:\" + string(userHash[:]))\n\t}\n\tc.hash = string(userHash[:])\n\tc.user = user\n\n\tip, _, err := net.SplitHostPort(c.Conn.RemoteAddr().String())\n\tif err != nil {\n\t\treturn common.NewError(\"failed to parse host:\" + c.Conn.RemoteAddr().String()).Base(err)\n\t}\n\n\tc.ip = ip\n\tok := user.AddIP(ip)\n\tif !ok {\n\t\treturn common.NewError(\"ip limit reached\")\n\t}\n\n\tcrlf := [2]byte{}\n\t_, err = io.ReadFull(c.Conn, crlf[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.metadata = &tunnel.Metadata{}\n\tif err := c.metadata.ReadFrom(c.Conn); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.ReadFull(c.Conn, crlf[:])\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Server is a trojan tunnel server\ntype Server struct {\n\tauth       statistic.Authenticator\n\tredir      *redirector.Redirector\n\tredirAddr  *tunnel.Address\n\tunderlay   tunnel.Server\n\tconnChan   chan tunnel.Conn\n\tmuxChan    chan tunnel.Conn\n\tpacketChan chan tunnel.PacketConn\n\tctx        context.Context\n\tcancel     context.CancelFunc\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\treturn s.underlay.Close()\n}\n\nfunc (s *Server) acceptLoop() {\n\tfor {\n\t\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\t\tif err != nil { // Closing\n\t\t\tlog.Error(common.NewError(\"trojan failed to accept conn\").Base(err))\n\t\t\tselect {\n\t\t\tcase <-s.ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tgo func(conn tunnel.Conn) {\n\t\t\trewindConn := common.NewRewindConn(conn)\n\t\t\trewindConn.SetBufferSize(128)\n\t\t\tdefer rewindConn.StopBuffering()\n\n\t\t\tinboundConn := &InboundConn{\n\t\t\t\tConn: rewindConn,\n\t\t\t\tauth: s.auth,\n\t\t\t}\n\n\t\t\tif err := inboundConn.Auth(); err != nil {\n\t\t\t\trewindConn.Rewind()\n\t\t\t\trewindConn.StopBuffering()\n\t\t\t\tlog.Warn(common.NewError(\"connection with invalid trojan header from \" + rewindConn.RemoteAddr().String()).Base(err))\n\t\t\t\ts.redir.Redirect(&redirector.Redirection{\n\t\t\t\t\tRedirectTo:  s.redirAddr,\n\t\t\t\t\tInboundConn: rewindConn,\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trewindConn.StopBuffering()\n\t\t\tswitch inboundConn.metadata.Command {\n\t\t\tcase Connect:\n\t\t\t\tif inboundConn.metadata.DomainName == \"MUX_CONN\" {\n\t\t\t\t\ts.muxChan <- inboundConn\n\t\t\t\t\tlog.Debug(\"mux(r) connection\")\n\t\t\t\t} else {\n\t\t\t\t\ts.connChan <- inboundConn\n\t\t\t\t\tlog.Debug(\"normal trojan connection\")\n\t\t\t\t}\n\n\t\t\tcase Associate:\n\t\t\t\ts.packetChan <- &PacketConn{\n\t\t\t\t\tConn: inboundConn,\n\t\t\t\t}\n\t\t\t\tlog.Debug(\"trojan udp connection\")\n\t\t\tcase Mux:\n\t\t\t\ts.muxChan <- inboundConn\n\t\t\t\tlog.Debug(\"mux connection\")\n\t\t\tdefault:\n\t\t\t\tlog.Error(common.NewError(fmt.Sprintf(\"unknown trojan command %d\", inboundConn.metadata.Command)))\n\t\t\t}\n\t\t}(conn)\n\t}\n}\n\nfunc (s *Server) AcceptConn(nextTunnel tunnel.Tunnel) (tunnel.Conn, error) {\n\tswitch nextTunnel.(type) {\n\tcase *mux.Tunnel:\n\t\tselect {\n\t\tcase t := <-s.muxChan:\n\t\t\treturn t, nil\n\t\tcase <-s.ctx.Done():\n\t\t\treturn nil, common.NewError(\"trojan client closed\")\n\t\t}\n\tdefault:\n\t\tselect {\n\t\tcase t := <-s.connChan:\n\t\t\treturn t, nil\n\t\tcase <-s.ctx.Done():\n\t\t\treturn nil, common.NewError(\"trojan client closed\")\n\t\t}\n\t}\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\tselect {\n\tcase t := <-s.packetChan:\n\t\treturn t, nil\n\tcase <-s.ctx.Done():\n\t\treturn nil, common.NewError(\"trojan client closed\")\n\t}\n}\n\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tctx, cancel := context.WithCancel(ctx)\n\n\t// TODO replace this dirty code\n\tvar auth statistic.Authenticator\n\tvar err error\n\tif cfg.MySQL.Enabled {\n\t\tlog.Debug(\"mysql enabled\")\n\t\tauth, err = statistic.NewAuthenticator(ctx, mysql.Name)\n\t} else {\n\t\tlog.Debug(\"auth by config file\")\n\t\tauth, err = statistic.NewAuthenticator(ctx, memory.Name)\n\t}\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"trojan failed to create authenticator\")\n\t}\n\n\tif cfg.API.Enabled {\n\t\tgo api.RunService(ctx, Name+\"_SERVER\", auth)\n\t}\n\n\tredirAddr := tunnel.NewAddressFromHostPort(\"tcp\", cfg.RemoteHost, cfg.RemotePort)\n\ts := &Server{\n\t\tunderlay:   underlay,\n\t\tauth:       auth,\n\t\tredirAddr:  redirAddr,\n\t\tconnChan:   make(chan tunnel.Conn, 32),\n\t\tmuxChan:    make(chan tunnel.Conn, 32),\n\t\tpacketChan: make(chan tunnel.PacketConn, 32),\n\t\tctx:        ctx,\n\t\tcancel:     cancel,\n\t\tredir:      redirector.NewRedirector(ctx),\n\t}\n\n\tif !cfg.DisableHTTPCheck {\n\t\tredirConn, err := net.Dial(\"tcp\", redirAddr.String())\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, common.NewError(\"invalid redirect address. check your http server: \" + redirAddr.String()).Base(err)\n\t\t}\n\t\tredirConn.Close()\n\t}\n\n\tgo s.acceptLoop()\n\tlog.Debug(\"trojan server created\")\n\treturn s, nil\n}\n"
  },
  {
    "path": "tunnel/trojan/trojan_test.go",
    "content": "package trojan\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/statistic/memory\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nfunc TestTrojan(t *testing.T) {\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tctx = config.WithConfig(ctx, transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{})\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\n\tserverPort := common.PickPort(\"tcp\", \"127.0.0.1\")\n\tauthConfig := &memory.Config{Passwords: []string{\"password\"}}\n\tclientConfig := &Config{\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: serverPort,\n\t}\n\tserverConfig := &Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  serverPort,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: util.EchoPort,\n\t}\n\n\tctx = config.WithConfig(ctx, memory.Name, authConfig)\n\tclientCtx := config.WithConfig(ctx, Name, clientConfig)\n\tserverCtx := config.WithConfig(ctx, Name, serverConfig)\n\tc, err := NewClient(clientCtx, tcpClient)\n\tcommon.Must(err)\n\ts, err := NewServer(serverCtx, tcpServer)\n\tcommon.Must(err)\n\tconn1, err := c.DialConn(&tunnel.Address{\n\t\tDomainName:  \"example.com\",\n\t\tAddressType: tunnel.DomainName,\n\t}, nil)\n\tcommon.Must(err)\n\tcommon.Must2(conn1.Write([]byte(\"87654321\")))\n\tconn2, err := s.AcceptConn(nil)\n\tcommon.Must(err)\n\tbuf := [8]byte{}\n\tconn2.Read(buf[:])\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\n\tpacket1, err := c.DialPacket(nil)\n\tcommon.Must(err)\n\tpacket1.WriteWithMetadata([]byte(\"12345678\"), &tunnel.Metadata{\n\t\tAddress: &tunnel.Address{\n\t\t\tDomainName:  \"example.com\",\n\t\t\tAddressType: tunnel.DomainName,\n\t\t\tPort:        80,\n\t\t},\n\t})\n\tpacket2, err := s.AcceptPacket(nil)\n\tcommon.Must(err)\n\n\t_, m, err := packet2.ReadWithMetadata(buf[:])\n\tcommon.Must(err)\n\n\tfmt.Println(m)\n\n\tif !util.CheckPacketOverConn(packet1, packet2) {\n\t\tt.Fail()\n\t}\n\n\t// redirecting\n\tconn, err := net.Dial(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", port))\n\tcommon.Must(err)\n\tsendBuf := util.GeneratePayload(1024)\n\trecvBuf := [1024]byte{}\n\tcommon.Must2(conn.Write(sendBuf))\n\tcommon.Must2(io.ReadFull(conn, recvBuf[:]))\n\tif !bytes.Equal(sendBuf, recvBuf[:]) {\n\t\tfmt.Println(sendBuf)\n\t\tfmt.Println(recvBuf[:])\n\t\tt.Fail()\n\t}\n\tconn1.Close()\n\tconn2.Close()\n\tpacket1.Close()\n\tpacket2.Close()\n\tconn.Close()\n\tc.Close()\n\ts.Close()\n\tcancel()\n}\n"
  },
  {
    "path": "tunnel/trojan/tunnel.go",
    "content": "package trojan\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"TROJAN\"\n\ntype Tunnel struct{}\n\nfunc (c *Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (c *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, client)\n}\n\nfunc (c *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, server)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/tunnel.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n)\n\n// Conn is the TCP connection in the tunnel\ntype Conn interface {\n\tnet.Conn\n\tMetadata() *Metadata\n}\n\n// PacketConn is the UDP packet stream in the tunnel\ntype PacketConn interface {\n\tnet.PacketConn\n\tWriteWithMetadata([]byte, *Metadata) (int, error)\n\tReadWithMetadata([]byte) (int, *Metadata, error)\n}\n\n// ConnDialer creates TCP connections from the tunnel\ntype ConnDialer interface {\n\tDialConn(*Address, Tunnel) (Conn, error)\n}\n\n// PacketDialer creates UDP packet stream from the tunnel\ntype PacketDialer interface {\n\tDialPacket(Tunnel) (PacketConn, error)\n}\n\n// ConnListener accept TCP connections\ntype ConnListener interface {\n\tAcceptConn(Tunnel) (Conn, error)\n}\n\n// PacketListener accept UDP packet stream\n// We don't have any tunnel based on packet streams, so AcceptPacket will always receive a real PacketConn\ntype PacketListener interface {\n\tAcceptPacket(Tunnel) (PacketConn, error)\n}\n\n// Dialer can dial to original server with a tunnel\ntype Dialer interface {\n\tConnDialer\n\tPacketDialer\n}\n\n// Listener can accept TCP and UDP streams from a tunnel\ntype Listener interface {\n\tConnListener\n\tPacketListener\n}\n\n// Client is the tunnel client based on stream connections\ntype Client interface {\n\tDialer\n\tio.Closer\n}\n\n// Server is the tunnel server based on stream connections\ntype Server interface {\n\tListener\n\tio.Closer\n}\n\n// Tunnel describes a tunnel, allowing creating a tunnel from another tunnel\n// We assume that the lower tunnels know exatly how upper tunnels work, and lower tunnels is transparent for the upper tunnels\ntype Tunnel interface {\n\tName() string\n\tNewClient(context.Context, Client) (Client, error)\n\tNewServer(context.Context, Server) (Server, error)\n}\n\nvar tunnels = make(map[string]Tunnel)\n\n// RegisterTunnel register a tunnel by tunnel name\nfunc RegisterTunnel(name string, tunnel Tunnel) {\n\ttunnels[name] = tunnel\n}\n\nfunc GetTunnel(name string) (Tunnel, error) {\n\tif t, ok := tunnels[name]; ok {\n\t\treturn t, nil\n\t}\n\treturn nil, common.NewError(\"unknown tunnel name \" + name)\n}\n"
  },
  {
    "path": "tunnel/websocket/client.go",
    "content": "package websocket\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"golang.org/x/net/websocket\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype Client struct {\n\tunderlay tunnel.Client\n\thostname string\n\tpath     string\n}\n\nfunc (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := c.underlay.DialConn(nil, &Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"websocket cannot dial with underlying client\").Base(err)\n\t}\n\turl := \"wss://\" + c.hostname + c.path\n\torigin := \"https://\" + c.hostname\n\twsConfig, err := websocket.NewConfig(url, origin)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"invalid websocket config\").Base(err)\n\t}\n\twsConn, err := websocket.NewClient(wsConfig, conn)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"websocket failed to handshake with server\").Base(err)\n\t}\n\treturn &OutboundConn{\n\t\tConn:    wsConn,\n\t\ttcpConn: conn,\n\t}, nil\n}\n\nfunc (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\treturn nil, common.NewError(\"not supported by websocket\")\n}\n\nfunc (c *Client) Close() error {\n\treturn c.underlay.Close()\n}\n\nfunc NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tif !strings.HasPrefix(cfg.Websocket.Path, \"/\") {\n\t\treturn nil, common.NewError(\"websocket path must start with \\\"/\\\"\")\n\t}\n\tif cfg.Websocket.Host == \"\" {\n\t\tcfg.Websocket.Host = cfg.RemoteHost\n\t\tlog.Warn(\"empty websocket hostname\")\n\t}\n\tlog.Debug(\"websocket client created\")\n\treturn &Client{\n\t\thostname: cfg.Websocket.Host,\n\t\tpath:     cfg.Websocket.Path,\n\t\tunderlay: underlay,\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/websocket/config.go",
    "content": "package websocket\n\nimport \"github.com/p4gefau1t/trojan-go/config\"\n\ntype WebsocketConfig struct {\n\tEnabled bool   `json:\"enabled\" yaml:\"enabled\"`\n\tHost    string `json:\"host\" yaml:\"host\"`\n\tPath    string `json:\"path\" yaml:\"path\"`\n}\n\ntype Config struct {\n\tRemoteHost string          `json:\"remote_addr\" yaml:\"remote-addr\"`\n\tRemotePort int             `json:\"remote_port\" yaml:\"remote-port\"`\n\tWebsocket  WebsocketConfig `json:\"websocket\" yaml:\"websocket\"`\n}\n\nfunc init() {\n\tconfig.RegisterConfigCreator(Name, func() interface{} {\n\t\treturn new(Config)\n\t})\n}\n"
  },
  {
    "path": "tunnel/websocket/conn.go",
    "content": "package websocket\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"golang.org/x/net/websocket\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\ntype OutboundConn struct {\n\t*websocket.Conn\n\ttcpConn net.Conn\n}\n\nfunc (c *OutboundConn) Metadata() *tunnel.Metadata {\n\treturn nil\n}\n\nfunc (c *OutboundConn) RemoteAddr() net.Addr {\n\t// override RemoteAddr of websocket.Conn, or it will return some url from \"Origin\"\n\treturn c.tcpConn.RemoteAddr()\n}\n\ntype InboundConn struct {\n\tOutboundConn\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc (c *InboundConn) Close() error {\n\tc.cancel()\n\treturn c.Conn.Close()\n}\n"
  },
  {
    "path": "tunnel/websocket/server.go",
    "content": "package websocket\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/net/websocket\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/redirector\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\n// Fake response writer\n// Websocket ServeHTTP method uses Hijack method to get the ReadWriter\ntype fakeHTTPResponseWriter struct {\n\thttp.Hijacker\n\thttp.ResponseWriter\n\n\tReadWriter *bufio.ReadWriter\n\tConn       net.Conn\n}\n\nfunc (w *fakeHTTPResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\treturn w.Conn, w.ReadWriter, nil\n}\n\ntype Server struct {\n\tunderlay  tunnel.Server\n\thostname  string\n\tpath      string\n\tenabled   bool\n\tredirAddr net.Addr\n\tredir     *redirector.Redirector\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttimeout   time.Duration\n}\n\nfunc (s *Server) Close() error {\n\ts.cancel()\n\treturn s.underlay.Close()\n}\n\nfunc (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) {\n\tconn, err := s.underlay.AcceptConn(&Tunnel{})\n\tif err != nil {\n\t\treturn nil, common.NewError(\"websocket failed to accept connection from underlying server\")\n\t}\n\tif !s.enabled {\n\t\ts.redir.Redirect(&redirector.Redirection{\n\t\t\tInboundConn: conn,\n\t\t\tRedirectTo:  s.redirAddr,\n\t\t})\n\t\treturn nil, common.NewError(\"websocket is disabled. redirecting http request from \" + conn.RemoteAddr().String())\n\t}\n\trewindConn := common.NewRewindConn(conn)\n\trewindConn.SetBufferSize(512)\n\tdefer rewindConn.StopBuffering()\n\trw := bufio.NewReadWriter(bufio.NewReader(rewindConn), bufio.NewWriter(rewindConn))\n\treq, err := http.ReadRequest(rw.Reader)\n\tif err != nil {\n\t\tlog.Debug(\"invalid http request\")\n\t\trewindConn.Rewind()\n\t\trewindConn.StopBuffering()\n\t\ts.redir.Redirect(&redirector.Redirection{\n\t\t\tInboundConn: rewindConn,\n\t\t\tRedirectTo:  s.redirAddr,\n\t\t})\n\t\treturn nil, common.NewError(\"not a valid http request: \" + conn.RemoteAddr().String()).Base(err)\n\t}\n\tif strings.ToLower(req.Header.Get(\"Upgrade\")) != \"websocket\" || req.URL.Path != s.path {\n\t\tlog.Debug(\"invalid http websocket handshake request\")\n\t\trewindConn.Rewind()\n\t\trewindConn.StopBuffering()\n\t\ts.redir.Redirect(&redirector.Redirection{\n\t\t\tInboundConn: rewindConn,\n\t\t\tRedirectTo:  s.redirAddr,\n\t\t})\n\t\treturn nil, common.NewError(\"not a valid websocket handshake request: \" + conn.RemoteAddr().String()).Base(err)\n\t}\n\n\thandshake := make(chan struct{})\n\n\turl := \"wss://\" + s.hostname + s.path\n\torigin := \"https://\" + s.hostname\n\twsConfig, err := websocket.NewConfig(url, origin)\n\tif err != nil {\n\t\treturn nil, common.NewError(\"failed to create websocket config\").Base(err)\n\t}\n\tvar wsConn *websocket.Conn\n\tctx, cancel := context.WithCancel(s.ctx)\n\n\twsServer := websocket.Server{\n\t\tConfig: *wsConfig,\n\t\tHandler: func(conn *websocket.Conn) {\n\t\t\twsConn = conn                              // store the websocket after handshaking\n\t\t\twsConn.PayloadType = websocket.BinaryFrame // treat it as a binary websocket\n\n\t\t\tlog.Debug(\"websocket obtained\")\n\t\t\thandshake <- struct{}{}\n\t\t\t// this function SHOULD NOT return unless the connection is ended\n\t\t\t// or the websocket will be closed by ServeHTTP method\n\t\t\t<-ctx.Done()\n\t\t\tlog.Debug(\"websocket closed\")\n\t\t},\n\t\tHandshake: func(wsConfig *websocket.Config, httpRequest *http.Request) error {\n\t\t\tlog.Debug(\"websocket url\", httpRequest.URL, \"origin\", httpRequest.Header.Get(\"Origin\"))\n\t\t\treturn nil\n\t\t},\n\t}\n\n\trespWriter := &fakeHTTPResponseWriter{\n\t\tConn:       conn,\n\t\tReadWriter: rw,\n\t}\n\tgo wsServer.ServeHTTP(respWriter, req)\n\n\tselect {\n\tcase <-handshake:\n\tcase <-time.After(s.timeout):\n\t}\n\n\tif wsConn == nil {\n\t\tcancel()\n\t\treturn nil, common.NewError(\"websocket failed to handshake\")\n\t}\n\n\treturn &InboundConn{\n\t\tOutboundConn: OutboundConn{\n\t\t\ttcpConn: conn,\n\t\t\tConn:    wsConn,\n\t\t},\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t}, nil\n}\n\nfunc (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) {\n\treturn nil, common.NewError(\"not supported\")\n}\n\nfunc NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) {\n\tcfg := config.FromContext(ctx, Name).(*Config)\n\tif cfg.Websocket.Enabled {\n\t\tif !strings.HasPrefix(cfg.Websocket.Path, \"/\") {\n\t\t\treturn nil, common.NewError(\"websocket path must start with \\\"/\\\"\")\n\t\t}\n\t}\n\tif cfg.RemoteHost == \"\" {\n\t\tlog.Warn(\"empty websocket redirection hostname\")\n\t\tcfg.RemoteHost = cfg.Websocket.Host\n\t}\n\tif cfg.RemotePort == 0 {\n\t\tlog.Warn(\"empty websocket redirection port\")\n\t\tcfg.RemotePort = 80\n\t}\n\tctx, cancel := context.WithCancel(ctx)\n\tlog.Debug(\"websocket server created\")\n\treturn &Server{\n\t\tenabled:   cfg.Websocket.Enabled,\n\t\thostname:  cfg.Websocket.Host,\n\t\tpath:      cfg.Websocket.Path,\n\t\tctx:       ctx,\n\t\tcancel:    cancel,\n\t\tunderlay:  underlay,\n\t\ttimeout:   time.Second * time.Duration(rand.Intn(10)+5),\n\t\tredir:     redirector.NewRedirector(ctx),\n\t\tredirAddr: tunnel.NewAddressFromHostPort(\"tcp\", cfg.RemoteHost, cfg.RemotePort),\n\t}, nil\n}\n"
  },
  {
    "path": "tunnel/websocket/tunnel.go",
    "content": "package websocket\n\nimport (\n\t\"context\"\n\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n)\n\nconst Name = \"WEBSOCKET\"\n\ntype Tunnel struct{}\n\nfunc (*Tunnel) Name() string {\n\treturn Name\n}\n\nfunc (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) {\n\treturn NewServer(ctx, underlay)\n}\n\nfunc (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) {\n\treturn NewClient(ctx, underlay)\n}\n\nfunc init() {\n\ttunnel.RegisterTunnel(Name, &Tunnel{})\n}\n"
  },
  {
    "path": "tunnel/websocket/websocket_test.go",
    "content": "package websocket\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/net/websocket\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/config\"\n\t\"github.com/p4gefau1t/trojan-go/test/util\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/freedom\"\n\t\"github.com/p4gefau1t/trojan-go/tunnel/transport\"\n)\n\nfunc TestWebsocket(t *testing.T) {\n\tcfg := &Config{\n\t\tWebsocket: WebsocketConfig{\n\t\t\tEnabled: true,\n\t\t\tHost:    \"localhost\",\n\t\t\tPath:    \"/ws\",\n\t\t},\n\t}\n\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost:  \"127.0.0.1\",\n\t\tLocalPort:  port,\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tRemotePort: port,\n\t}\n\tfreedomCfg := &freedom.Config{}\n\tctx = config.WithConfig(ctx, transport.Name, transportConfig)\n\tctx = config.WithConfig(ctx, freedom.Name, freedomCfg)\n\ttcpClient, err := transport.NewClient(ctx, nil)\n\tcommon.Must(err)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\n\tc, err := NewClient(ctx, tcpClient)\n\tcommon.Must(err)\n\ts, err := NewServer(ctx, tcpServer)\n\tvar conn2 tunnel.Conn\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tconn2, err = s.AcceptConn(nil)\n\t\tcommon.Must(err)\n\t\twg.Done()\n\t}()\n\ttime.Sleep(time.Second)\n\tconn1, err := c.DialConn(nil, nil)\n\tcommon.Must(err)\n\twg.Wait()\n\tif !util.CheckConn(conn1, conn2) {\n\t\tt.Fail()\n\t}\n\n\tif strings.HasPrefix(conn1.RemoteAddr().String(), \"ws\") {\n\t\tt.Fail()\n\t}\n\tif strings.HasPrefix(conn2.RemoteAddr().String(), \"ws\") {\n\t\tt.Fail()\n\t}\n\n\tconn1.Close()\n\tconn2.Close()\n\ts.Close()\n\tc.Close()\n}\n\nfunc TestRedirect(t *testing.T) {\n\tcfg := &Config{\n\t\tRemoteHost: \"127.0.0.1\",\n\t\tWebsocket: WebsocketConfig{\n\t\t\tEnabled: true,\n\t\t\tHost:    \"localhost\",\n\t\t\tPath:    \"/ws\",\n\t\t},\n\t}\n\tfmt.Sscanf(util.HTTPPort, \"%d\", &cfg.RemotePort)\n\tctx := config.WithConfig(context.Background(), Name, cfg)\n\n\tport := common.PickPort(\"tcp\", \"127.0.0.1\")\n\ttransportConfig := &transport.Config{\n\t\tLocalHost: \"127.0.0.1\",\n\t\tLocalPort: port,\n\t}\n\tctx = config.WithConfig(ctx, transport.Name, transportConfig)\n\ttcpServer, err := transport.NewServer(ctx, nil)\n\tcommon.Must(err)\n\n\ts, err := NewServer(ctx, tcpServer)\n\tcommon.Must(err)\n\n\tgo func() {\n\t\t_, err := s.AcceptConn(nil)\n\t\tif err == nil {\n\t\t\tt.Fail()\n\t\t}\n\t}()\n\ttime.Sleep(time.Second)\n\tconn, err := net.Dial(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", port))\n\tcommon.Must(err)\n\turl := \"wss://localhost/wrong-path\"\n\torigin := \"https://localhost\"\n\twsConfig, err := websocket.NewConfig(url, origin)\n\tcommon.Must(err)\n\t_, err = websocket.NewClient(wsConfig, conn)\n\tif err == nil {\n\t\tt.Fail()\n\t}\n\tconn.Close()\n\n\ts.Close()\n}\n"
  },
  {
    "path": "url/option.go",
    "content": "package url\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/log\"\n\t\"github.com/p4gefau1t/trojan-go/option\"\n\t\"github.com/p4gefau1t/trojan-go/proxy\"\n)\n\nconst Name = \"URL\"\n\ntype Websocket struct {\n\tEnabled bool   `json:\"enabled\"`\n\tHost    string `json:\"host\"`\n\tPath    string `json:\"path\"`\n}\n\ntype TLS struct {\n\tSNI string `json:\"sni\"`\n}\n\ntype Shadowsocks struct {\n\tEnabled  bool   `json:\"enabled\"`\n\tMethod   string `json:\"method\"`\n\tPassword string `json:\"password\"`\n}\n\ntype Mux struct {\n\tEnabled bool `json:\"enabled\"`\n}\n\ntype API struct {\n\tEnabled bool   `json:\"enabled\"`\n\tAPIHost string `json:\"api_addr\"`\n\tAPIPort int    `json:\"api_port\"`\n}\n\ntype UrlConfig struct {\n\tRunType     string   `json:\"run_type\"`\n\tLocalAddr   string   `json:\"local_addr\"`\n\tLocalPort   int      `json:\"local_port\"`\n\tRemoteAddr  string   `json:\"remote_addr\"`\n\tRemotePort  int      `json:\"remote_port\"`\n\tPassword    []string `json:\"password\"`\n\tWebsocket   `json:\"websocket\"`\n\tShadowsocks `json:\"shadowsocks\"`\n\tTLS         `json:\"ssl\"`\n\tMux         `json:\"mux\"`\n\tAPI         `json:\"api\"`\n}\n\ntype url struct {\n\turl    *string\n\toption *string\n}\n\nfunc (u *url) Name() string {\n\treturn Name\n}\n\nfunc (u *url) Handle() error {\n\tif u.url == nil || *u.url == \"\" {\n\t\treturn common.NewError(\"\")\n\t}\n\tinfo, err := NewShareInfoFromURL(*u.url)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\twsEnabled := false\n\tif info.Type == ShareInfoTypeWebSocket {\n\t\twsEnabled = true\n\t}\n\tssEnabled := false\n\tssPassword := \"\"\n\tssMethod := \"\"\n\tif strings.HasPrefix(info.Encryption, \"ss;\") {\n\t\tssEnabled = true\n\t\tssConfig := strings.Split(info.Encryption[3:], \":\")\n\t\tif len(ssConfig) != 2 {\n\t\t\tlog.Fatalf(\"invalid shadowsocks config: %s\", info.Encryption)\n\t\t}\n\t\tssMethod = ssConfig[0]\n\t\tssPassword = ssConfig[1]\n\t}\n\tmuxEnabled := false\n\tlistenHost := \"127.0.0.1\"\n\tlistenPort := 1080\n\n\tapiEnabled := false\n\tapiHost := \"127.0.0.1\"\n\tapiPort := 10000\n\n\toptions := strings.Split(*u.option, \";\")\n\tfor _, o := range options {\n\t\tkey := \"\"\n\t\tval := \"\"\n\t\tl := strings.Split(o, \"=\")\n\t\tif len(l) != 2 {\n\t\t\tlog.Fatal(\"option format error, no \\\"key=value\\\" pair found:\", o)\n\t\t}\n\t\tkey = l[0]\n\t\tval = l[1]\n\t\tswitch key {\n\t\tcase \"mux\":\n\t\t\tmuxEnabled, err = strconv.ParseBool(val)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\tcase \"listen\":\n\t\t\th, p, err := net.SplitHostPort(val)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tlistenHost = h\n\t\t\tlp, err := strconv.Atoi(p)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tlistenPort = lp\n\t\tcase \"api\":\n\t\t\tapiEnabled = true\n\t\t\th, p, err := net.SplitHostPort(val)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tapiHost = h\n\t\t\tlp, err := strconv.Atoi(p)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tapiPort = lp\n\t\tdefault:\n\t\t\tlog.Fatal(\"invalid option\", o)\n\t\t}\n\t}\n\tconfig := UrlConfig{\n\t\tRunType:    \"client\",\n\t\tLocalAddr:  listenHost,\n\t\tLocalPort:  listenPort,\n\t\tRemoteAddr: info.TrojanHost,\n\t\tRemotePort: int(info.Port),\n\t\tPassword:   []string{info.TrojanPassword},\n\t\tTLS: TLS{\n\t\t\tSNI: info.SNI,\n\t\t},\n\t\tWebsocket: Websocket{\n\t\t\tEnabled: wsEnabled,\n\t\t\tPath:    info.Path,\n\t\t\tHost:    info.Host,\n\t\t},\n\t\tMux: Mux{\n\t\t\tEnabled: muxEnabled,\n\t\t},\n\t\tShadowsocks: Shadowsocks{\n\t\t\tEnabled:  ssEnabled,\n\t\t\tPassword: ssPassword,\n\t\t\tMethod:   ssMethod,\n\t\t},\n\t\tAPI: API{\n\t\t\tEnabled: apiEnabled,\n\t\t\tAPIHost: apiHost,\n\t\t\tAPIPort: apiPort,\n\t\t},\n\t}\n\tdata, err := json.Marshal(&config)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tlog.Debug(string(data))\n\tclient, err := proxy.NewProxyFromConfigData(data, true)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn client.Run()\n}\n\nfunc (u *url) Priority() int {\n\treturn 10\n}\n\nfunc init() {\n\toption.RegisterHandler(&url{\n\t\turl:    flag.String(\"url\", \"\", \"Setup trojan-go client with a url link\"),\n\t\toption: flag.String(\"url-option\", \"mux=true;listen=127.0.0.1:1080\", \"URL mode options\"),\n\t})\n}\n"
  },
  {
    "path": "url/option_test.go",
    "content": "package url\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t_ \"github.com/p4gefau1t/trojan-go/proxy/client\"\n)\n\nfunc TestUrl_Handle(t *testing.T) {\n\turlCases := []string{\n\t\t\"trojan-go://password@server.com\",\n\t\t\"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath\",\n\t\t\"trojan-go://password@server.com/?encryption=ss%3baes-256-gcm%3afuckgfw\",\n\t\t\"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath&encryption=ss%3Baes-256-gcm%3Afuckgfw\",\n\t}\n\toptionCases := []string{\n\t\t\"mux=true;listen=127.0.0.1:0\",\n\t\t\"mux=false;listen=127.0.0.1:0\",\n\t\t\"mux=false;listen=127.0.0.1:0;api=127.0.0.1:0\",\n\t}\n\n\tfor _, s := range urlCases {\n\t\tfor _, option := range optionCases {\n\t\t\ts := s\n\t\t\toption := option\n\t\t\tu := &url{\n\t\t\t\turl:    &s,\n\t\t\t\toption: &option,\n\t\t\t}\n\t\t\tu.Name()\n\t\t\tu.Priority()\n\n\t\t\terrChan := make(chan error, 1)\n\t\t\tgo func() {\n\t\t\t\terrChan <- u.Handle()\n\t\t\t}()\n\n\t\t\tselect {\n\t\t\tcase err := <-errChan:\n\t\t\t\tt.Fatal(err)\n\t\t\tcase <-time.After(time.Second * 1):\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "url/share_link.go",
    "content": "package url\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tneturl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tShareInfoTypeOriginal  = \"original\"\n\tShareInfoTypeWebSocket = \"ws\"\n)\n\nvar validTypes = map[string]struct{}{\n\tShareInfoTypeOriginal:  {},\n\tShareInfoTypeWebSocket: {},\n}\n\nvar validEncryptionProviders = map[string]struct{}{\n\t\"ss\":   {},\n\t\"none\": {},\n}\n\nvar validSSEncryptionMap = map[string]struct{}{\n\t\"aes-128-gcm\":            {},\n\t\"aes-256-gcm\":            {},\n\t\"chacha20-ietf-poly1305\": {},\n}\n\ntype ShareInfo struct {\n\tTrojanHost     string // 节点 IP / 域名\n\tPort           uint16 // 节点端口\n\tTrojanPassword string // Trojan 密码\n\n\tSNI  string // SNI\n\tType string // 类型\n\tHost string // HTTP Host Header\n\n\tPath       string // WebSocket / H2 Path\n\tEncryption string // 额外加密\n\tPlugin     string // 插件设定\n\n\tDescription string // 节点说明\n}\n\nfunc NewShareInfoFromURL(shareLink string) (info ShareInfo, e error) {\n\t// share link must be valid url\n\tparse, e := neturl.Parse(shareLink)\n\tif e != nil {\n\t\te = fmt.Errorf(\"invalid url: %s\", e.Error())\n\t\treturn\n\t}\n\n\t// share link must have `trojan-go://` scheme\n\tif parse.Scheme != \"trojan-go\" {\n\t\te = errors.New(\"url does not have a trojan-go:// scheme\")\n\t\treturn\n\t}\n\n\t// password\n\tif info.TrojanPassword = parse.User.Username(); info.TrojanPassword == \"\" {\n\t\te = errors.New(\"no password specified\")\n\t\treturn\n\t} else if _, hasPassword := parse.User.Password(); hasPassword {\n\t\te = errors.New(\"password possibly missing percentage encoding for colon\")\n\t\treturn\n\t}\n\n\t// trojanHost: not empty & strip [] from IPv6 addresses\n\tif info.TrojanHost = parse.Hostname(); info.TrojanHost == \"\" {\n\t\te = errors.New(\"host is empty\")\n\t\treturn\n\t}\n\n\t// port\n\tif info.Port, e = handleTrojanPort(parse.Port()); e != nil {\n\t\treturn\n\t}\n\n\t// strictly parse the query\n\tquery, e := neturl.ParseQuery(parse.RawQuery)\n\tif e != nil {\n\t\treturn\n\t}\n\n\t// sni\n\tif SNIs, ok := query[\"sni\"]; !ok {\n\t\tinfo.SNI = info.TrojanHost\n\t} else if len(SNIs) > 1 {\n\t\te = errors.New(\"multiple SNIs\")\n\t\treturn\n\t} else if info.SNI = SNIs[0]; info.SNI == \"\" {\n\t\te = errors.New(\"empty SNI\")\n\t\treturn\n\t}\n\n\t// type\n\tif types, ok := query[\"type\"]; !ok {\n\t\tinfo.Type = ShareInfoTypeOriginal\n\t} else if len(types) > 1 {\n\t\te = errors.New(\"multiple transport types\")\n\t\treturn\n\t} else if info.Type = types[0]; info.Type == \"\" {\n\t\te = errors.New(\"empty transport type\")\n\t\treturn\n\t} else if _, ok := validTypes[info.Type]; !ok {\n\t\te = fmt.Errorf(\"unknown transport type: %s\", info.Type)\n\t\treturn\n\t}\n\n\t// host\n\tif hosts, ok := query[\"host\"]; !ok {\n\t\tinfo.Host = info.TrojanHost\n\t} else if len(hosts) > 1 {\n\t\te = errors.New(\"multiple hosts\")\n\t\treturn\n\t} else if info.Host = hosts[0]; info.Host == \"\" {\n\t\te = errors.New(\"empty host\")\n\t\treturn\n\t}\n\n\t// path\n\tif info.Type == ShareInfoTypeWebSocket {\n\t\tif paths, ok := query[\"path\"]; !ok {\n\t\t\te = errors.New(\"path is required in websocket\")\n\t\t\treturn\n\t\t} else if len(paths) > 1 {\n\t\t\te = errors.New(\"multiple paths\")\n\t\t\treturn\n\t\t} else if info.Path = paths[0]; info.Path == \"\" {\n\t\t\te = errors.New(\"empty path\")\n\t\t\treturn\n\t\t}\n\n\t\tif !strings.HasPrefix(info.Path, \"/\") {\n\t\t\te = errors.New(\"path must start with /\")\n\t\t\treturn\n\t\t}\n\t}\n\n\t// encryption\n\tif encryptionArr, ok := query[\"encryption\"]; !ok {\n\t\t// no encryption. that's okay.\n\t} else if len(encryptionArr) > 1 {\n\t\te = errors.New(\"multiple encryption fields\")\n\t\treturn\n\t} else if info.Encryption = encryptionArr[0]; info.Encryption == \"\" {\n\t\te = errors.New(\"empty encryption\")\n\t\treturn\n\t} else {\n\t\tencryptionParts := strings.SplitN(info.Encryption, \";\", 2)\n\t\tencryptionProviderName := encryptionParts[0]\n\n\t\tif _, ok := validEncryptionProviders[encryptionProviderName]; !ok {\n\t\t\te = fmt.Errorf(\"unsupported encryption provider name: %s\", encryptionProviderName)\n\t\t\treturn\n\t\t}\n\n\t\tvar encryptionParams string\n\t\tif len(encryptionParts) >= 2 {\n\t\t\tencryptionParams = encryptionParts[1]\n\t\t}\n\n\t\tif encryptionProviderName == \"ss\" {\n\t\t\tssParams := strings.SplitN(encryptionParams, \":\", 2)\n\t\t\tif len(ssParams) < 2 {\n\t\t\t\te = errors.New(\"missing ss password\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tssMethod, ssPassword := ssParams[0], ssParams[1]\n\t\t\tif _, ok := validSSEncryptionMap[ssMethod]; !ok {\n\t\t\t\te = fmt.Errorf(\"unsupported ss method: %s\", ssMethod)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif ssPassword == \"\" {\n\t\t\t\te = errors.New(\"ss password cannot be empty\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// plugin\n\tif plugins, ok := query[\"plugin\"]; !ok {\n\t\t// no plugin. that's okay.\n\t} else if len(plugins) > 1 {\n\t\te = errors.New(\"multiple plugins\")\n\t\treturn\n\t} else if info.Plugin = plugins[0]; info.Plugin == \"\" {\n\t\te = errors.New(\"empty plugin\")\n\t\treturn\n\t}\n\n\t// description\n\tinfo.Description = parse.Fragment\n\n\treturn\n}\n\nfunc handleTrojanPort(p string) (port uint16, e error) {\n\tif p == \"\" {\n\t\treturn 443, nil\n\t}\n\n\tportParsed, e := strconv.Atoi(p)\n\tif e != nil {\n\t\treturn\n\t}\n\n\tif portParsed < 1 || portParsed > 65535 {\n\t\te = fmt.Errorf(\"invalid port %d\", portParsed)\n\t\treturn\n\t}\n\n\tport = uint16(portParsed)\n\treturn\n}\n"
  },
  {
    "path": "url/share_link_test.go",
    "content": "package url\n\nimport (\n\tcrand \"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestHandleTrojanPort_Default(t *testing.T) {\n\tport, e := handleTrojanPort(\"\")\n\tassert.Nil(t, e, \"empty port should not error\")\n\tassert.EqualValues(t, 443, port, \"empty port should fallback to 443\")\n}\n\nfunc TestHandleTrojanPort_NotNumber(t *testing.T) {\n\t_, e := handleTrojanPort(\"fuck\")\n\tassert.Error(t, e, \"non-numerical port should error\")\n}\n\nfunc TestHandleTrojanPort_GoodNumber(t *testing.T) {\n\ttestCases := []string{\"443\", \"8080\", \"10086\", \"80\", \"65535\", \"1\"}\n\tfor _, testCase := range testCases {\n\t\t_, e := handleTrojanPort(testCase)\n\t\tassert.Nil(t, e, \"good port %s should not error\", testCase)\n\t}\n}\n\nfunc TestHandleTrojanPort_InvalidNumber(t *testing.T) {\n\ttestCases := []string{\"443.0\", \"443.000\", \"8e2\", \"3.5\", \"9.99\", \"-1\", \"-65535\", \"65536\", \"0\"}\n\n\tfor _, testCase := range testCases {\n\t\t_, e := handleTrojanPort(testCase)\n\t\tassert.Error(t, e, \"invalid number port %s should error\", testCase)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"\")\n\tassert.Error(t, e, \"empty link should lead to error\")\n}\n\nfunc TestNewShareInfoFromURL_RandomCrap(t *testing.T) {\n\tfor i := 0; i < 100; i++ {\n\t\trandomCrap, _ := ioutil.ReadAll(io.LimitReader(crand.Reader, 10))\n\t\t_, e := NewShareInfoFromURL(string(randomCrap))\n\t\tassert.Error(t, e, \"random crap %v should lead to error\", randomCrap)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_NotTrojanGo(t *testing.T) {\n\ttestCases := []string{\n\t\t\"trojan://what.ever@www.twitter.com:443?allowInsecure=1&allowInsecureHostname=1&allowInsecureCertificate=1&sessionTicket=0&tfo=1#some-trojan\",\n\t\t\"ssr://d3d3LnR3aXR0ZXIuY29tOjgwOmF1dGhfc2hhMV92NDpjaGFjaGEyMDpwbGFpbjpZbkpsWVd0M1lXeHMvP29iZnNwYXJhbT0mcmVtYXJrcz02TC1INXB5ZjVwZTI2WmUwNzd5YU1qQXlNQzB3TnkweE9DQXhNam8xTlRveU1RJmdyb3VwPVEzUkRiRzkxWkNCVFUxSQ\",\n\t\t\"vmess://eyJhZGQiOiJtb3RoZXIuZnVja2VyIiwiYWlkIjowLCJpZCI6IjFmYzI0NzVmLThmNDMtM2FlYi05MzUyLTU2MTFhZjg1NmQyOSIsIm5ldCI6InRjcCIsInBvcnQiOjEwMDg2LCJwcyI6Iui/h+acn+aXtumXtO+8mjIwMjAtMDYtMjMiLCJ0bHMiOiJub25lIiwidHlwZSI6Im5vbmUiLCJ2IjoyfQ==\",\n\t}\n\n\tfor _, testCase := range testCases {\n\t\t_, e := NewShareInfoFromURL(testCase)\n\t\tassert.Error(t, e, \"non trojan-go link %s should not decode\", testCase)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_EmptyTrojanHost(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://fuckyou@:443/\")\n\tassert.Error(t, e, \"empty host should not decode\")\n}\n\nfunc TestNewShareInfoFromURL_BadPassword(t *testing.T) {\n\ttestCases := []string{\n\t\t\"trojan-go://we:are:the:champion@114514.go\",\n\t\t\"trojan-go://evilpassword:@1919810.me\",\n\t\t\"trojan-go://evilpassword::@1919810.me\",\n\t\t\"trojan-go://@password.404\",\n\t\t\"trojan-go://mother.fuck#yeah\",\n\t}\n\n\tfor _, testCase := range testCases {\n\t\t_, e := NewShareInfoFromURL(testCase)\n\t\tassert.Error(t, e, \"bad password link %s should not decode\", testCase)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_GoodPassword(t *testing.T) {\n\ttestCases := []string{\n\t\t\"trojan-go://we%3Aare%3Athe%3Achampion@114514.go\",\n\t\t\"trojan-go://evilpassword%3A@1919810.me\",\n\t\t\"trojan-go://passw0rd-is-a-must@password.200\",\n\t}\n\n\tfor _, testCase := range testCases {\n\t\t_, e := NewShareInfoFromURL(testCase)\n\t\tassert.Nil(t, e, \"good password link %s should decode\", testCase)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_BadPort(t *testing.T) {\n\ttestCases := []string{\n\t\t\"trojan-go://pswd@example.com:114514\",\n\t\t\"trojan-go://pswd@example.com:443.0\",\n\t\t\"trojan-go://pswd@example.com:-1\",\n\t\t\"trojan-go://pswd@example.com:8e2\",\n\t\t\"trojan-go://pswd@example.com:65536\",\n\t}\n\n\tfor _, testCase := range testCases {\n\t\t_, e := NewShareInfoFromURL(testCase)\n\t\tassert.Error(t, e, \"decode url %s with invalid port should error\", testCase)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_BadQuery(t *testing.T) {\n\ttestCases := []string{\n\t\t\"trojan-go://cao@ni.ma?NMSL=%CG%GE%CAONIMA\",\n\t\t\"trojan-go://ni@ta.ma:13/?#%2e%fu\",\n\t}\n\n\tfor _, testCase := range testCases {\n\t\t_, e := NewShareInfoFromURL(testCase)\n\t\tassert.Error(t, e, \"parse bad query should error\")\n\t}\n}\n\nfunc TestNewShareInfoFromURL_SNI_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?sni=\")\n\tassert.Error(t, e, \"empty SNI should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_SNI_Default(t *testing.T) {\n\tinfo, e := NewShareInfoFromURL(\"trojan-go://a@b.c\")\n\tassert.Nil(t, e)\n\tassert.Equal(t, info.TrojanHost, info.SNI, \"default sni should be trojan hostname\")\n}\n\nfunc TestNewShareInfoFromURL_SNI_Multiple(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?sni=a&sni=b&sni=c\")\n\tassert.Error(t, e, \"multiple SNIs should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Type_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?type=\")\n\tassert.Error(t, e, \"empty type should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Type_Default(t *testing.T) {\n\tinfo, e := NewShareInfoFromURL(\"trojan-go://a@b.c\")\n\tassert.Nil(t, e)\n\tassert.Equal(t, ShareInfoTypeOriginal, info.Type, \"default type should be original\")\n}\n\nfunc TestNewShareInfoFromURL_Type_Invalid(t *testing.T) {\n\tinvalidTypes := []string{\"nmsl\", \"dio\"}\n\tfor _, invalidType := range invalidTypes {\n\t\t_, e := NewShareInfoFromURL(fmt.Sprintf(\"trojan-go://a@b.c?type=%s\", invalidType))\n\t\tassert.Error(t, e, \"%s should not be a valid type\", invalidType)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_Type_Multiple(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?type=a&type=b&type=c\")\n\tassert.Error(t, e, \"multiple types should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Host_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?host=\")\n\tassert.Error(t, e, \"empty host should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Host_Default(t *testing.T) {\n\tinfo, e := NewShareInfoFromURL(\"trojan-go://a@b.c\")\n\tassert.Nil(t, e)\n\tassert.Equal(t, info.TrojanHost, info.Host, \"default host should be trojan hostname\")\n}\n\nfunc TestNewShareInfoFromURL_Host_Multiple(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?host=a&host=b&host=c\")\n\tassert.Error(t, e, \"multiple hosts should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Type_WS_Multiple(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?type=ws&path=a&path=b&path=c\")\n\tassert.Error(t, e, \"multiple paths should not be allowed in wss\")\n}\n\nfunc TestNewShareInfoFromURL_Path_WS_None(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?type=ws\")\n\tassert.Error(t, e, \"ws should require path\")\n}\n\nfunc TestNewShareInfoFromURL_Path_WS_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?type=ws&path=\")\n\tassert.Error(t, e, \"empty path should not be allowed in ws\")\n}\n\nfunc TestNewShareInfoFromURL_Path_WS_Invalid(t *testing.T) {\n\tinvalidPaths := []string{\"../\", \".+!\", \" \"}\n\tfor _, invalidPath := range invalidPaths {\n\t\t_, e := NewShareInfoFromURL(fmt.Sprintf(\"trojan-go://a@b.c?type=ws&path=%s\", invalidPath))\n\t\tassert.Error(t, e, \"%s should not be a valid path in ws\", invalidPath)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_Path_Plain_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?type=original&path=\")\n\tassert.Nil(t, e, \"empty path should be ignored in original mode\")\n}\n\nfunc TestNewShareInfoFromURL_Encryption_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?encryption=\")\n\tassert.Error(t, e, \"encryption should not be empty\")\n}\n\nfunc TestNewShareInfoFromURL_Encryption_Unknown(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?encryption=motherfucker\")\n\tassert.Error(t, e, \"unknown encryption should not be supported\")\n}\n\nfunc TestNewShareInfoFromURL_Encryption_None(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://what@ever.me?encryption=none\")\n\tassert.Nil(t, e, \"should support none encryption\")\n}\n\nfunc TestNewShareInfoFromURL_Encryption_SS_NotSupportedMethods(t *testing.T) {\n\tinvalidMethods := []string{\"rc4-md5\", \"rc4\", \"des-cfb\", \"table\", \"salsa20-ctr\"}\n\tfor _, invalidMethod := range invalidMethods {\n\t\t_, e := NewShareInfoFromURL(fmt.Sprintf(\"trojan-go://a@b.c?encryption=ss%%3B%s%%3Ashabi\", invalidMethod))\n\t\tassert.Error(t, e, \"encryption %s should not be supported by ss\", invalidMethod)\n\t}\n}\n\nfunc TestNewShareInfoFromURL_Encryption_SS_NoPassword(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?encryption=ss%3Baes-256-gcm%3A\")\n\tassert.Error(t, e, \"empty ss password should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Encryption_SS_BadParams(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?encryption=ss%3Ba\")\n\tassert.Error(t, e, \"broken ss param should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Encryption_Multiple(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?encryption=a&encryption=b&encryption=c\")\n\tassert.Error(t, e, \"multiple encryption should not be allowed\")\n}\n\nfunc TestNewShareInfoFromURL_Plugin_Empty(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?plugin=\")\n\tassert.Error(t, e, \"plugin should not be empty\")\n}\n\nfunc TestNewShareInfoFromURL_Plugin_Multiple(t *testing.T) {\n\t_, e := NewShareInfoFromURL(\"trojan-go://a@b.c?plugin=a&plugin=b&plugin=c\")\n\tassert.Error(t, e, \"multiple plugin should not be allowed\")\n}\n"
  },
  {
    "path": "version/version.go",
    "content": "package version\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"runtime\"\n\n\t\"github.com/p4gefau1t/trojan-go/common\"\n\t\"github.com/p4gefau1t/trojan-go/constant\"\n\t\"github.com/p4gefau1t/trojan-go/option\"\n)\n\ntype versionOption struct {\n\tflag *bool\n}\n\nfunc (*versionOption) Name() string {\n\treturn \"version\"\n}\n\nfunc (*versionOption) Priority() int {\n\treturn 10\n}\n\nfunc (c *versionOption) Handle() error {\n\tif *c.flag {\n\t\tfmt.Println(\"Trojan-Go\", constant.Version)\n\t\tfmt.Println(\"Go Version:\", runtime.Version())\n\t\tfmt.Println(\"OS/Arch:\", runtime.GOOS+\"/\"+runtime.GOARCH)\n\t\tfmt.Println(\"Git Commit:\", constant.Commit)\n\t\tfmt.Println(\"\")\n\t\tfmt.Println(\"Developed by PageFault (p4gefau1t)\")\n\t\tfmt.Println(\"Licensed under GNU General Public License version 3\")\n\t\tfmt.Println(\"GitHub Repository:\\thttps://github.com/p4gefau1t/trojan-go\")\n\t\tfmt.Println(\"Trojan-Go Documents:\\thttps://p4gefau1t.github.io/trojan-go/\")\n\t\treturn nil\n\t}\n\treturn common.NewError(\"not set\")\n}\n\nfunc init() {\n\toption.RegisterHandler(&versionOption{\n\t\tflag: flag.Bool(\"version\", false, \"Display version and help info\"),\n\t})\n}\n"
  }
]