├── .github ├── ISSUE_TEMPLATE │ └── bug-report.md ├── linters │ └── .golangci.yml └── workflows │ ├── docker-build.yml │ ├── docker-nightly-build.yml │ ├── gh-pages.yml │ ├── linter.yml │ ├── nightly-build.yml │ ├── release-build.yml │ └── test.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── api ├── api.go ├── control │ └── control.go └── service │ ├── api.pb.go │ ├── api.proto │ ├── api_grpc.pb.go │ ├── client.go │ ├── client_test.go │ ├── config.go │ ├── gen.sh │ ├── server.go │ └── server_test.go ├── common ├── common.go ├── error.go ├── geodata │ ├── cache.go │ ├── decode.go │ ├── decode_test.go │ ├── interface.go │ └── loader.go ├── io.go ├── io_test.go ├── net.go └── sync.go ├── component ├── api.go ├── base.go ├── client.go ├── custom.go ├── forward.go ├── mysql.go ├── nat.go ├── other.go └── server.go ├── config ├── config.go └── config_test.go ├── constant └── constant.go ├── docs ├── .gitignore ├── Makefile ├── archetypes │ └── default.md ├── config.toml └── content │ ├── _index.md │ ├── advance │ ├── _index.md │ ├── aead.md │ ├── api.md │ ├── customize-protocol-stack.md │ ├── forward.md │ ├── mux.md │ ├── nat.md │ ├── nginx-relay.md │ ├── plugin.md │ ├── router.md │ └── websocket.md │ ├── basic │ ├── _index.md │ ├── config.md │ ├── full-config.md │ └── trojan.md │ └── developer │ ├── _index.md │ ├── api.md │ ├── build.md │ ├── mux.md │ ├── overview.md │ ├── plugin.md │ ├── simplesocks.md │ ├── trojan.md │ ├── url.md │ └── websocket.md ├── easy └── easy.go ├── example ├── client.json ├── client.yaml ├── server.json ├── server.yaml ├── trojan-go.service └── trojan-go@.service ├── go.mod ├── go.sum ├── log ├── golog │ ├── buffer │ │ ├── buffer.go │ │ └── buffer_test.go │ ├── colorful │ │ ├── colorful.go │ │ └── colorful_test.go │ └── golog.go ├── log.go └── simplelog │ └── simplelog.go ├── main.go ├── option └── option.go ├── proxy ├── client │ ├── client.go │ └── config.go ├── config.go ├── custom │ ├── config.go │ └── custom.go ├── forward │ └── forward.go ├── nat │ ├── nat.go │ └── nat_stub.go ├── option.go ├── proxy.go ├── server │ ├── config.go │ └── server.go └── stack.go ├── recorder └── recorder.go ├── redirector ├── redirector.go └── redirector_test.go ├── statistic ├── memory │ ├── config.go │ ├── memory.go │ └── memory_test.go ├── mysql │ ├── config.go │ └── mysql.go ├── sqlite │ ├── qulite_nonlinux.go │ ├── sqlite.go │ └── user.go └── statistics.go ├── test ├── scenario │ ├── custom_test.go │ └── proxy_test.go └── util │ ├── target.go │ └── util.go ├── tunnel ├── adapter │ ├── config.go │ ├── server.go │ └── tunnel.go ├── dokodemo │ ├── config.go │ ├── conn.go │ ├── dokodemo_test.go │ ├── server.go │ └── tunnel.go ├── freedom │ ├── client.go │ ├── config.go │ ├── conn.go │ ├── freedom_test.go │ └── tunnel.go ├── http │ ├── http_test.go │ ├── server.go │ └── tunnel.go ├── metadata.go ├── mux │ ├── client.go │ ├── config.go │ ├── conn.go │ ├── mux_test.go │ ├── server.go │ └── tunnel.go ├── router │ ├── client.go │ ├── config.go │ ├── conn.go │ ├── data.go │ ├── router_test.go │ └── tunnel.go ├── shadowsocks │ ├── client.go │ ├── config.go │ ├── conn.go │ ├── server.go │ ├── shadowsocks_test.go │ └── tunnel.go ├── simplesocks │ ├── client.go │ ├── conn.go │ ├── server.go │ ├── simplesocks_test.go │ └── tunnel.go ├── socks │ ├── config.go │ ├── conn.go │ ├── server.go │ ├── socks_test.go │ └── tunnel.go ├── tls │ ├── client.go │ ├── config.go │ ├── fingerprint │ │ └── tls.go │ ├── server.go │ ├── tls_test.go │ └── tunnel.go ├── tproxy │ ├── config.go │ ├── conn.go │ ├── getsockopt.go │ ├── getsockopt_i386.go │ ├── server.go │ ├── tcp.go │ ├── tproxy_stub.go │ ├── tunnel.go │ └── udp.go ├── transport │ ├── client.go │ ├── config.go │ ├── conn.go │ ├── server.go │ ├── transport_test.go │ └── tunnel.go ├── trojan │ ├── client.go │ ├── config.go │ ├── packet.go │ ├── server.go │ ├── trojan_test.go │ └── tunnel.go ├── tunnel.go └── websocket │ ├── client.go │ ├── config.go │ ├── conn.go │ ├── server.go │ ├── tunnel.go │ └── websocket_test.go ├── url ├── option.go ├── option_test.go ├── share_link.go └── share_link_test.go └── version └── version.go /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 报告 3 | about: 提交项目中存在的漏洞和问题 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | - [ ] **我确定我已经尝试多次复现此次问题,并且将会提供涉及此问题的系统和网络环境,软件及其版本。** 11 | 12 | 我们建议您按照下方模板填写 Bug Report,以便我们收集更多的有效信息 13 | 14 | ## 简单描述这个 Bug 15 | 16 | ## 如何复现这个 Bug 17 | 18 | 在此描述复现这个Bug所需要的操作步骤 19 | 20 | ## 服务器和客户端环境信息 21 | 22 | 在此描述你的服务器和客户端所处的网络环境,系统架构,以及其他信息 23 | 24 | ## 服务端和客户端日志 25 | 26 | 粘贴**故障发生时**,服务端和客户端日志 27 | 28 | ## 服务端和客户端配置文件 29 | 30 | 可以复现该问题的客户端和服务端的完整配置(请隐去域名和IP等隐私信息) 31 | 32 | ## 服务端和客户端版本信息 33 | 34 | 请执行./trojan-go -version并将输出完整粘贴在此处 35 | 36 | ## 其他信息 37 | 38 | 你认为对我们修复bug有帮助的任何信息都可以在这里写出来 39 | -------------------------------------------------------------------------------- /.github/linters/.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | skip-files: 4 | - \.pb\.go$ 5 | 6 | issues: 7 | new: true 8 | 9 | linters: 10 | enable: 11 | - asciicheck 12 | - bodyclose 13 | - depguard 14 | - gci 15 | - gocritic 16 | - gofmt 17 | - gofumpt 18 | - goimports 19 | - govet 20 | - ineffassign 21 | - misspell 22 | - typecheck 23 | - unconvert 24 | - whitespace 25 | disable: 26 | - deadcode 27 | - errcheck 28 | - goprintffuncname 29 | - gosimple 30 | - nilerr 31 | - rowserrcheck 32 | - staticcheck 33 | - structcheck 34 | - stylecheck 35 | - unparam 36 | - unused 37 | - varcheck 38 | 39 | linters-settings: 40 | gci: 41 | local-prefixes: github.com/p4gefau1t/trojan-go 42 | goimports: 43 | local-prefixes: github.com/p4gefau1t/trojan-go 44 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths-ignore: 6 | - "**.md" 7 | - "docs/**" 8 | tags: 9 | - "v*.*.*" 10 | name: docker-build 11 | jobs: 12 | build: 13 | if: github.repository == 'p4gefau1t/trojan-go' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout the code 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup QEMU 20 | uses: docker/setup-qemu-action@v1 21 | 22 | - name: Setup Docker Buildx 23 | uses: docker/setup-buildx-action@v1 24 | 25 | - name: Login to Docker Hub 26 | uses: docker/login-action@v1 27 | with: 28 | username: ${{ secrets.DOCKER_USERNAME }} 29 | password: ${{ secrets.DOCKER_PASSWORD }} 30 | 31 | - name: Prepare 32 | id: prepare 33 | run: | 34 | if [[ $GITHUB_REF == refs/tags/* ]]; then 35 | echo ::set-output name=version::${GITHUB_REF#refs/tags/} 36 | echo ::set-output name=ref::${GITHUB_REF#refs/tags/} 37 | else 38 | echo ::set-output name=version::snapshot 39 | echo ::set-output name=ref::${{ github.sha }} 40 | fi 41 | echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 42 | echo ::set-output name=docker_image::${{ secrets.DOCKER_USERNAME }}/trojan-go 43 | 44 | - name: Build and push docker image 45 | run: | 46 | docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \ 47 | --output "type=image,push=true" \ 48 | --tag "${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}" \ 49 | --tag "${{ steps.prepare.outputs.docker_image }}:latest" \ 50 | --build-arg REF=${{ steps.prepare.outputs.ref }} \ 51 | --file Dockerfile . 52 | 53 | test: 54 | needs: build 55 | runs-on: ubuntu-latest 56 | steps: 57 | - name: Test docker image 58 | run: | 59 | docker run --rm --entrypoint /usr/local/bin/trojan-go ${{ secrets.DOCKER_USERNAME }}/trojan-go -version 60 | -------------------------------------------------------------------------------- /.github/workflows/docker-nightly-build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths-ignore: 6 | - '**.md' 7 | - 'docs/**' 8 | name: docker-nightly-build 9 | jobs: 10 | build: 11 | if: github.repository == 'p4gefau1t/trojan-go' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout the code 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup QEMU 18 | uses: docker/setup-qemu-action@v1 19 | 20 | - name: Setup Docker Buildx 21 | uses: docker/setup-buildx-action@v1 22 | 23 | - name: Login to Docker Hub 24 | uses: docker/login-action@v1 25 | with: 26 | username: ${{ secrets.DOCKER_USERNAME }} 27 | password: ${{ secrets.DOCKER_PASSWORD }} 28 | 29 | - name: Prepare 30 | id: prepare 31 | run: | 32 | echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 33 | echo ::set-output name=docker_image::${{ secrets.DOCKER_USERNAME }}/trojan-go 34 | echo ::set-output name=ref::${{ github.sha }} 35 | 36 | - name: Build and push docker image 37 | run: | 38 | docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \ 39 | --output "type=image,push=true" \ 40 | --tag "${{ steps.prepare.outputs.docker_image }}:nightly" \ 41 | --build-arg REF=${{ steps.prepare.outputs.ref }} \ 42 | --file Dockerfile . 43 | 44 | test: 45 | needs: build 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Test docker image 49 | run: | 50 | docker run --rm --entrypoint /usr/local/bin/trojan-go ${{ secrets.DOCKER_USERNAME }}/trojan-go -version 51 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths: 6 | - "docs/**" 7 | - ".github/workflows/gh-pages.yml" 8 | pull_request: 9 | types: [opened, synchronize, reopened] 10 | branches: 11 | - master 12 | paths: 13 | - "docs/**" 14 | - ".github/workflows/gh-pages.yml" 15 | name: github-pages 16 | jobs: 17 | deploy: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | submodules: true # Fetch Hugo themes 23 | fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod 24 | 25 | - name: Setup Hugo 26 | uses: peaceiris/actions-hugo@v2 27 | with: 28 | hugo-version: "0.83.1" 29 | # extended: true 30 | 31 | - name: Build 32 | run: | 33 | cd docs 34 | make hugo-themes 35 | hugo 36 | 37 | - name: Deploy 38 | if: ${{ github.event_name == 'push' }} 39 | uses: peaceiris/actions-gh-pages@v3 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | publish_dir: ./docs/public 43 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - "**/*.go" 9 | - ".github/workflows/linter.yml" 10 | - ".github/linters/.golangci.yml" 11 | pull_request: 12 | types: [opened, synchronize, reopened] 13 | branches: 14 | - master 15 | paths: 16 | - "**/*.go" 17 | - ".github/workflows/linter.yml" 18 | - ".github/linters/.golangci.yml" 19 | 20 | jobs: 21 | lint: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout codebase 25 | uses: actions/checkout@v2 26 | 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v2 29 | with: 30 | version: latest 31 | args: --config=.github/linters/.golangci.yml 32 | only-new-issues: true 33 | -------------------------------------------------------------------------------- /.github/workflows/nightly-build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths: 6 | - "**/*.go" 7 | - "go.mod" 8 | - "go.sum" 9 | - "Makefile" 10 | - ".github/workflows/nightly-build.yml" 11 | pull_request: 12 | types: [opened, synchronize, reopened] 13 | branches: 14 | - master 15 | paths: 16 | - "**/*.go" 17 | - "go.mod" 18 | - "go.sum" 19 | - "Makefile" 20 | - ".github/workflows/nightly-build.yml" 21 | name: nightly-build 22 | jobs: 23 | build: 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | go-version: ["^1.18"] 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Install Go 31 | uses: actions/setup-go@v2 32 | with: 33 | go-version: ^1.18 34 | 35 | - name: Checkout code 36 | uses: actions/checkout@v2 37 | 38 | - name: Build 39 | run: | 40 | make release -j$(nproc) 41 | -------------------------------------------------------------------------------- /.github/workflows/release-build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v*.*.*" 5 | name: release-build 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Install Go 11 | uses: actions/setup-go@v2 12 | with: 13 | go-version: ^1.18 14 | 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Checkout tag 19 | run: | 20 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 21 | tag_name="${GITHUB_REF##*/}" 22 | echo Tag $tag_name 23 | git checkout $tag_name 24 | echo "TAG_NAME=${tag_name}" >> $GITHUB_ENV 25 | 26 | - name: Build 27 | run: | 28 | make release -j$(nproc) 29 | 30 | - name: Release 31 | uses: svenstaro/upload-release-action@v2 32 | with: 33 | repo_token: ${{ secrets.GITHUB_TOKEN }} 34 | tag: ${{ env.TAG_NAME }} 35 | file: ./trojan-go-*.zip 36 | file_glob: true 37 | prerelease: true 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - latest 7 | paths: 8 | - "**/*.go" 9 | - "go.mod" 10 | - "go.sum" 11 | - ".github/workflows/test.yml" 12 | pull_request: 13 | types: [opened, synchronize, reopened] 14 | branches: 15 | - master 16 | paths: 17 | - "**/*.go" 18 | - "go.mod" 19 | - "go.sum" 20 | - ".github/workflows/test.yml" 21 | jobs: 22 | test: 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | go-version: ["^1.18"] 27 | platform: [ubuntu-latest, windows-latest] 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - name: Install Go 31 | uses: actions/setup-go@v2 32 | with: 33 | go-version: ^1.18 34 | 35 | - name: Checkout code 36 | uses: actions/checkout@v2 37 | 38 | - name: Check Go modules 39 | run: | 40 | go mod tidy 41 | git diff --exit-code go.mod go.sum 42 | go mod verify 43 | 44 | - name: Test 45 | run: make test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | build/ 18 | *.DS_Store 19 | *.zip 20 | *.tar.gz 21 | *.crt 22 | *.key 23 | *.dat 24 | trojan-go 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine3.15 AS builder 2 | WORKDIR /trojan-go 3 | RUN apk add git make gcc g++ libtool 4 | COPY . . 5 | RUN make trojan-go &&\ 6 | wget https://github.com/v2fly/domain-list-community/raw/release/dlc.dat -O build/geosite.dat &&\ 7 | wget https://github.com/v2fly/geoip/raw/release/geoip.dat -O build/geoip.dat &&\ 8 | wget https://github.com/v2fly/geoip/raw/release/geoip-only-cn-private.dat -O build/geoip-only-cn-private.dat 9 | 10 | FROM alpine 11 | WORKDIR / 12 | RUN apk add --no-cache tzdata ca-certificates 13 | COPY --from=builder /trojan-go/build /usr/local/bin/ 14 | COPY --from=builder /trojan-go/example/server.json /etc/trojan-go/config.json 15 | 16 | ENTRYPOINT ["/usr/local/bin/trojan-go", "-config"] 17 | CMD ["/etc/trojan-go/config.json"] 18 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/log" 7 | "github.com/p4gefau1t/trojan-go/statistic" 8 | ) 9 | 10 | type Handler func(ctx context.Context, auth statistic.Authenticator) error 11 | 12 | var handlers = make(map[string]Handler) 13 | 14 | func RegisterHandler(name string, handler Handler) { 15 | handlers[name] = handler 16 | } 17 | 18 | func RunService(ctx context.Context, name string, auth statistic.Authenticator) error { 19 | if h, ok := handlers[name]; ok { 20 | log.Debug("api handler found", name) 21 | return h(ctx, auth) 22 | } 23 | log.Debug("api handler not found", name) 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /api/service/api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package trojan.api; 4 | option go_package = "github.com/p4gefau1t/trojan-go/api/service"; 5 | 6 | message Traffic { 7 | uint64 upload_traffic = 1; 8 | uint64 download_traffic = 2; 9 | } 10 | 11 | message Speed { 12 | uint64 upload_speed = 1; 13 | uint64 download_speed = 2; 14 | } 15 | 16 | message User { 17 | string password = 1; 18 | string hash = 2; 19 | } 20 | 21 | message UserStatus { 22 | User user = 1; 23 | Traffic traffic_total = 2; 24 | Speed speed_current = 3; 25 | Speed speed_limit = 4; 26 | int32 ip_current = 5; 27 | int32 ip_limit = 6; 28 | } 29 | 30 | message GetTrafficRequest { 31 | User user = 1; 32 | } 33 | 34 | message GetTrafficResponse { 35 | bool success = 1; 36 | string info = 2; 37 | Traffic traffic_total = 3; 38 | Speed speed_current = 4; 39 | } 40 | 41 | message ListUsersRequest { 42 | 43 | } 44 | 45 | message ListUsersResponse { 46 | UserStatus status = 1; 47 | } 48 | 49 | message GetUsersRequest { 50 | User user = 1; 51 | } 52 | 53 | message GetUsersResponse { 54 | bool success = 1; 55 | string info = 2; 56 | UserStatus status = 3; 57 | } 58 | 59 | message SetUsersRequest { 60 | enum Operation { 61 | Add = 0; 62 | Delete = 1; 63 | Modify = 2; 64 | } 65 | UserStatus status = 1; 66 | Operation operation = 2; 67 | } 68 | 69 | message SetUsersResponse { 70 | bool success = 1; 71 | string info = 2; 72 | } 73 | 74 | message GetRecordsRequest { 75 | string transport = 1; // UDP, TCP 76 | string target_port = 2; 77 | bool include_payload = 3; 78 | } 79 | 80 | message GetRecordsResponse { 81 | string timestamp = 1; 82 | string user_hash = 2; 83 | string client_ip = 3; 84 | string client_port = 4; 85 | string target_host = 5; 86 | string target_port = 6; 87 | string transport = 7; 88 | bytes payload = 8; 89 | } 90 | 91 | service TrojanClientService { 92 | rpc GetTraffic(GetTrafficRequest) returns(GetTrafficResponse){} 93 | } 94 | 95 | service TrojanServerService { 96 | // list all users 97 | rpc ListUsers(ListUsersRequest) returns(stream ListUsersResponse){} 98 | // obtain specified user's info 99 | rpc GetUsers(stream GetUsersRequest) returns(stream GetUsersResponse){} 100 | // setup existing users' config 101 | rpc SetUsers(stream SetUsersRequest) returns(stream SetUsersResponse){} 102 | // get traffic records 103 | rpc GetRecords(GetRecordsRequest) returns(stream GetRecordsResponse){} 104 | } 105 | -------------------------------------------------------------------------------- /api/service/client.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/p4gefau1t/trojan-go/api" 8 | "github.com/p4gefau1t/trojan-go/common" 9 | "github.com/p4gefau1t/trojan-go/config" 10 | "github.com/p4gefau1t/trojan-go/log" 11 | "github.com/p4gefau1t/trojan-go/statistic" 12 | "github.com/p4gefau1t/trojan-go/tunnel/trojan" 13 | ) 14 | 15 | type ClientAPI struct { 16 | TrojanClientServiceServer 17 | 18 | auth statistic.Authenticator 19 | ctx context.Context 20 | uploadSpeed uint64 21 | downloadSpeed uint64 22 | lastSent uint64 23 | lastRecv uint64 24 | } 25 | 26 | func (s *ClientAPI) GetTraffic(ctx context.Context, req *GetTrafficRequest) (*GetTrafficResponse, error) { 27 | log.Debug("API: GetTraffic") 28 | if req.User == nil { 29 | return nil, common.NewError("User is unspecified") 30 | } 31 | if req.User.Hash == "" { 32 | req.User.Hash = common.SHA224String(req.User.Password) 33 | } 34 | valid, user := s.auth.AuthUser(req.User.Hash) 35 | if !valid { 36 | return nil, common.NewError("User " + req.User.Hash + " not found") 37 | } 38 | sent, recv := user.GetTraffic() 39 | sentSpeed, recvSpeed := user.GetSpeed() 40 | resp := &GetTrafficResponse{ 41 | Success: true, 42 | TrafficTotal: &Traffic{ 43 | UploadTraffic: sent, 44 | DownloadTraffic: recv, 45 | }, 46 | SpeedCurrent: &Speed{ 47 | UploadSpeed: sentSpeed, 48 | DownloadSpeed: recvSpeed, 49 | }, 50 | } 51 | return resp, nil 52 | } 53 | 54 | func RunClientAPI(ctx context.Context, auth statistic.Authenticator) error { 55 | cfg := config.FromContext(ctx, Name).(*Config) 56 | if !cfg.API.Enabled { 57 | return nil 58 | } 59 | server, err := newAPIServer(cfg) 60 | if err != nil { 61 | return err 62 | } 63 | defer server.Stop() 64 | service := &ClientAPI{ 65 | ctx: ctx, 66 | auth: auth, 67 | } 68 | RegisterTrojanClientServiceServer(server, service) 69 | addr, err := net.ResolveIPAddr("ip", cfg.API.APIHost) 70 | if err != nil { 71 | return common.NewError("api found invalid addr").Base(err) 72 | } 73 | listener, err := net.Listen("tcp", (&net.TCPAddr{ 74 | IP: addr.IP, 75 | Port: cfg.API.APIPort, 76 | Zone: addr.Zone, 77 | }).String()) 78 | if err != nil { 79 | return common.NewError("client api failed to listen").Base(err) 80 | } 81 | defer listener.Close() 82 | log.Info("client-side api service is listening on", listener.Addr().String()) 83 | errChan := make(chan error, 1) 84 | go func() { 85 | errChan <- server.Serve(listener) 86 | }() 87 | select { 88 | case err := <-errChan: 89 | return err 90 | case <-ctx.Done(): 91 | log.Debug("closed") 92 | return nil 93 | } 94 | } 95 | 96 | func init() { 97 | api.RegisterHandler(trojan.Name+"_CLIENT", RunClientAPI) 98 | } 99 | -------------------------------------------------------------------------------- /api/service/client_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | 11 | "github.com/p4gefau1t/trojan-go/common" 12 | "github.com/p4gefau1t/trojan-go/config" 13 | "github.com/p4gefau1t/trojan-go/statistic/memory" 14 | ) 15 | 16 | func TestClientAPI(t *testing.T) { 17 | ctx, cancel := context.WithCancel(context.Background()) 18 | ctx = config.WithConfig(ctx, memory.Name, 19 | &memory.Config{ 20 | Passwords: []string{"useless"}, 21 | }) 22 | port := common.PickPort("tcp", "127.0.0.1") 23 | ctx = config.WithConfig(ctx, Name, &Config{ 24 | APIConfig{ 25 | Enabled: true, 26 | APIHost: "127.0.0.1", 27 | APIPort: port, 28 | }, 29 | }) 30 | auth, err := memory.NewAuthenticator(ctx) 31 | common.Must(err) 32 | go RunClientAPI(ctx, auth) 33 | 34 | time.Sleep(time.Second * 3) 35 | common.Must(auth.AddUser("hash1234")) 36 | valid, user := auth.AuthUser("hash1234") 37 | if !valid { 38 | t.Fail() 39 | } 40 | user.AddSentTraffic(1234) 41 | user.AddRecvTraffic(5678) 42 | time.Sleep(time.Second) 43 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1:%d", port), grpc.WithInsecure()) 44 | common.Must(err) 45 | client := NewTrojanClientServiceClient(conn) 46 | resp, err := client.GetTraffic(ctx, &GetTrafficRequest{User: &User{ 47 | Hash: "hash1234", 48 | }}) 49 | common.Must(err) 50 | if resp.TrafficTotal.DownloadTraffic != 5678 || resp.TrafficTotal.UploadTraffic != 1234 { 51 | t.Fail() 52 | } 53 | _, err = client.GetTraffic(ctx, &GetTrafficRequest{}) 54 | if err == nil { 55 | t.Fail() 56 | } 57 | cancel() 58 | } 59 | -------------------------------------------------------------------------------- /api/service/config.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | const Name = "API_SERVICE" 6 | 7 | type SSLConfig struct { 8 | Enabled bool `json:"enabled" yaml:"enabled"` 9 | CertPath string `json:"cert" yaml:"cert"` 10 | KeyPath string `json:"key" yaml:"key"` 11 | VerifyClient bool `json:"verify_client" yaml:"verify-client"` 12 | ClientCertPath []string `json:"client_cert" yaml:"client-cert"` 13 | } 14 | 15 | type APIConfig struct { 16 | Enabled bool `json:"enabled" yaml:"enabled"` 17 | APIHost string `json:"api_addr" yaml:"api-addr"` 18 | APIPort int `json:"api_port" yaml:"api-port"` 19 | SSL SSLConfig `json:"ssl" yaml:"ssl"` 20 | } 21 | 22 | type Config struct { 23 | API APIConfig `json:"api" yaml:"api"` 24 | } 25 | 26 | func init() { 27 | config.RegisterConfigCreator(Name, func() interface{} { 28 | return new(Config) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /api/service/gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Processing..." 4 | 5 | GOPATH=${GOPATH:-$(go env GOPATH)} 6 | GOBIN=${GOBIN:-$(go env GOBIN)} 7 | 8 | if [[ $GOBIN == "" ]]; then 9 | GOBIN=${GOPATH}/bin 10 | fi 11 | 12 | go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest 13 | go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 14 | 15 | echo "Use protoc-gen-go and protoc-gen-go-grpc in $GOBIN." 16 | 17 | protoc --go_out=. \ 18 | --go_opt=paths=source_relative \ 19 | --go-grpc_out=. \ 20 | --go-grpc_opt=paths=source_relative \ 21 | --plugin=protoc-gen-go=${GOBIN}/protoc-gen-go \ 22 | --plugin=protoc-gen-go-grpc=${GOBIN}/protoc-gen-go-grpc \ 23 | api.proto 24 | 25 | if [ $? -eq 0 ]; then 26 | echo "Generated successfully." 27 | fi 28 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/p4gefau1t/trojan-go/log" 10 | ) 11 | 12 | type Runnable interface { 13 | Run() error 14 | Close() error 15 | } 16 | 17 | func SHA224String(password string) string { 18 | hash := sha256.New224() 19 | hash.Write([]byte(password)) 20 | val := hash.Sum(nil) 21 | str := "" 22 | for _, v := range val { 23 | str += fmt.Sprintf("%02x", v) 24 | } 25 | return str 26 | } 27 | 28 | func GetProgramDir() string { 29 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | return dir 34 | } 35 | 36 | func GetAssetLocation(file string) string { 37 | if filepath.IsAbs(file) { 38 | return file 39 | } 40 | if loc := os.Getenv("TROJAN_GO_LOCATION_ASSET"); loc != "" { 41 | absPath, err := filepath.Abs(loc) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | log.Debugf("env set: TROJAN_GO_LOCATION_ASSET=%s", absPath) 46 | return filepath.Join(absPath, file) 47 | } 48 | return filepath.Join(GetProgramDir(), file) 49 | } 50 | -------------------------------------------------------------------------------- /common/error.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Error struct { 8 | info string 9 | } 10 | 11 | func (e *Error) Error() string { 12 | return e.info 13 | } 14 | 15 | func (e *Error) Base(err error) *Error { 16 | if err != nil { 17 | e.info += " | " + err.Error() 18 | } 19 | return e 20 | } 21 | 22 | func NewError(info string) *Error { 23 | return &Error{ 24 | info: info, 25 | } 26 | } 27 | 28 | func NewErrorf(format string, a ...interface{}) *Error { 29 | return NewError(fmt.Sprintf(format, a...)) 30 | } 31 | 32 | func Must(err error) { 33 | if err != nil { 34 | fmt.Println(err) 35 | panic(err) 36 | } 37 | } 38 | 39 | func Must2(_ interface{}, err error) { 40 | if err != nil { 41 | fmt.Println(err) 42 | panic(err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/geodata/decode_test.go: -------------------------------------------------------------------------------- 1 | package geodata_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/fs" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "testing" 11 | 12 | "github.com/p4gefau1t/trojan-go/common" 13 | "github.com/p4gefau1t/trojan-go/common/geodata" 14 | ) 15 | 16 | func init() { 17 | const ( 18 | geoipURL = "https://raw.githubusercontent.com/v2fly/geoip/release/geoip.dat" 19 | geositeURL = "https://raw.githubusercontent.com/v2fly/domain-list-community/release/dlc.dat" 20 | ) 21 | 22 | wd, err := os.Getwd() 23 | common.Must(err) 24 | 25 | tempPath := filepath.Join(wd, "..", "..", "test", "temp") 26 | os.Setenv("TROJAN_GO_LOCATION_ASSET", tempPath) 27 | 28 | geoipPath := common.GetAssetLocation("geoip.dat") 29 | geositePath := common.GetAssetLocation("geosite.dat") 30 | 31 | if _, err := os.Stat(geoipPath); err != nil && errors.Is(err, fs.ErrNotExist) { 32 | common.Must(os.MkdirAll(tempPath, 0o755)) 33 | geoipBytes, err := common.FetchHTTPContent(geoipURL) 34 | common.Must(err) 35 | common.Must(common.WriteFile(geoipPath, geoipBytes)) 36 | } 37 | if _, err := os.Stat(geositePath); err != nil && errors.Is(err, fs.ErrNotExist) { 38 | common.Must(os.MkdirAll(tempPath, 0o755)) 39 | geositeBytes, err := common.FetchHTTPContent(geositeURL) 40 | common.Must(err) 41 | common.Must(common.WriteFile(geositePath, geositeBytes)) 42 | } 43 | } 44 | 45 | func TestDecodeGeoIP(t *testing.T) { 46 | filename := common.GetAssetLocation("geoip.dat") 47 | result, err := geodata.Decode(filename, "test") 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | expected := []byte{10, 4, 84, 69, 83, 84, 18, 8, 10, 4, 127, 0, 0, 0, 16, 8} 53 | if !bytes.Equal(result, expected) { 54 | t.Errorf("failed to load geoip:test, expected: %v, got: %v", expected, result) 55 | } 56 | } 57 | 58 | func TestDecodeGeoSite(t *testing.T) { 59 | filename := common.GetAssetLocation("geosite.dat") 60 | result, err := geodata.Decode(filename, "test") 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | 65 | expected := []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} 66 | if !bytes.Equal(result, expected) { 67 | t.Errorf("failed to load geosite:test, expected: %v, got: %v", expected, result) 68 | } 69 | } 70 | 71 | func BenchmarkLoadGeoIP(b *testing.B) { 72 | m1 := runtime.MemStats{} 73 | m2 := runtime.MemStats{} 74 | 75 | loader := geodata.NewGeodataLoader() 76 | 77 | runtime.ReadMemStats(&m1) 78 | cn, _ := loader.LoadGeoIP("cn") 79 | private, _ := loader.LoadGeoIP("private") 80 | runtime.KeepAlive(cn) 81 | runtime.KeepAlive(private) 82 | runtime.ReadMemStats(&m2) 83 | 84 | b.ReportMetric(float64(m2.Alloc-m1.Alloc)/1024, "KiB(GeoIP-Alloc)") 85 | b.ReportMetric(float64(m2.TotalAlloc-m1.TotalAlloc)/1024/1024, "MiB(GeoIP-TotalAlloc)") 86 | } 87 | 88 | func BenchmarkLoadGeoSite(b *testing.B) { 89 | m3 := runtime.MemStats{} 90 | m4 := runtime.MemStats{} 91 | 92 | loader := geodata.NewGeodataLoader() 93 | 94 | runtime.ReadMemStats(&m3) 95 | cn, _ := loader.LoadGeoSite("cn") 96 | notcn, _ := loader.LoadGeoSite("geolocation-!cn") 97 | private, _ := loader.LoadGeoSite("private") 98 | runtime.KeepAlive(cn) 99 | runtime.KeepAlive(notcn) 100 | runtime.KeepAlive(private) 101 | runtime.ReadMemStats(&m4) 102 | 103 | b.ReportMetric(float64(m4.Alloc-m3.Alloc)/1024/1024, "MiB(GeoSite-Alloc)") 104 | b.ReportMetric(float64(m4.TotalAlloc-m3.TotalAlloc)/1024/1024, "MiB(GeoSite-TotalAlloc)") 105 | } 106 | -------------------------------------------------------------------------------- /common/geodata/interface.go: -------------------------------------------------------------------------------- 1 | package geodata 2 | 3 | import v2router "github.com/v2fly/v2ray-core/v4/app/router" 4 | 5 | type GeodataLoader interface { 6 | LoadIP(filename, country string) ([]*v2router.CIDR, error) 7 | LoadSite(filename, list string) ([]*v2router.Domain, error) 8 | LoadGeoIP(country string) ([]*v2router.CIDR, error) 9 | LoadGeoSite(list string) ([]*v2router.Domain, error) 10 | } 11 | -------------------------------------------------------------------------------- /common/geodata/loader.go: -------------------------------------------------------------------------------- 1 | package geodata 2 | 3 | import ( 4 | "runtime" 5 | 6 | v2router "github.com/v2fly/v2ray-core/v4/app/router" 7 | ) 8 | 9 | type geodataCache struct { 10 | geoipCache 11 | geositeCache 12 | } 13 | 14 | func NewGeodataLoader() GeodataLoader { 15 | return &geodataCache{ 16 | make(map[string]*v2router.GeoIP), 17 | make(map[string]*v2router.GeoSite), 18 | } 19 | } 20 | 21 | func (g *geodataCache) LoadIP(filename, country string) ([]*v2router.CIDR, error) { 22 | geoip, err := g.geoipCache.Unmarshal(filename, country) 23 | if err != nil { 24 | return nil, err 25 | } 26 | runtime.GC() 27 | return geoip.Cidr, nil 28 | } 29 | 30 | func (g *geodataCache) LoadSite(filename, list string) ([]*v2router.Domain, error) { 31 | geosite, err := g.geositeCache.Unmarshal(filename, list) 32 | if err != nil { 33 | return nil, err 34 | } 35 | runtime.GC() 36 | return geosite.Domain, nil 37 | } 38 | 39 | func (g *geodataCache) LoadGeoIP(country string) ([]*v2router.CIDR, error) { 40 | return g.LoadIP("geoip.dat", country) 41 | } 42 | 43 | func (g *geodataCache) LoadGeoSite(list string) ([]*v2router.Domain, error) { 44 | return g.LoadSite("geosite.dat", list) 45 | } 46 | -------------------------------------------------------------------------------- /common/io.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync" 7 | 8 | "github.com/p4gefau1t/trojan-go/log" 9 | ) 10 | 11 | type RewindReader struct { 12 | mu sync.Mutex 13 | rawReader io.Reader 14 | buf []byte 15 | bufReadIdx int 16 | rewound bool 17 | buffering bool 18 | bufferSize int 19 | } 20 | 21 | func (r *RewindReader) Read(p []byte) (int, error) { 22 | r.mu.Lock() 23 | defer r.mu.Unlock() 24 | 25 | if r.rewound { 26 | if len(r.buf) > r.bufReadIdx { 27 | n := copy(p, r.buf[r.bufReadIdx:]) 28 | r.bufReadIdx += n 29 | return n, nil 30 | } 31 | r.rewound = false // all buffering content has been read 32 | } 33 | n, err := r.rawReader.Read(p) 34 | if r.buffering { 35 | r.buf = append(r.buf, p[:n]...) 36 | if len(r.buf) > r.bufferSize*2 { 37 | log.Debug("read too many bytes!") 38 | } 39 | } 40 | return n, err 41 | } 42 | 43 | func (r *RewindReader) ReadByte() (byte, error) { 44 | buf := [1]byte{} 45 | _, err := r.Read(buf[:]) 46 | return buf[0], err 47 | } 48 | 49 | func (r *RewindReader) Discard(n int) (int, error) { 50 | buf := [128]byte{} 51 | if n < 128 { 52 | return r.Read(buf[:n]) 53 | } 54 | for discarded := 0; discarded+128 < n; discarded += 128 { 55 | _, err := r.Read(buf[:]) 56 | if err != nil { 57 | return discarded, err 58 | } 59 | } 60 | if rest := n % 128; rest != 0 { 61 | return r.Read(buf[:rest]) 62 | } 63 | return n, nil 64 | } 65 | 66 | func (r *RewindReader) Rewind() { 67 | r.mu.Lock() 68 | if r.bufferSize == 0 { 69 | panic("no buffer") 70 | } 71 | r.rewound = true 72 | r.bufReadIdx = 0 73 | r.mu.Unlock() 74 | } 75 | 76 | func (r *RewindReader) StopBuffering() { 77 | r.mu.Lock() 78 | r.buffering = false 79 | r.mu.Unlock() 80 | } 81 | 82 | func (r *RewindReader) SetBufferSize(size int) { 83 | r.mu.Lock() 84 | if size == 0 { // disable buffering 85 | if !r.buffering { 86 | panic("reader is disabled") 87 | } 88 | r.buffering = false 89 | r.buf = nil 90 | r.bufReadIdx = 0 91 | r.bufferSize = 0 92 | } else { 93 | if r.buffering { 94 | panic("reader is buffering") 95 | } 96 | r.buffering = true 97 | r.bufReadIdx = 0 98 | r.bufferSize = size 99 | r.buf = make([]byte, 0, size) 100 | } 101 | r.mu.Unlock() 102 | } 103 | 104 | type RewindConn struct { 105 | net.Conn 106 | *RewindReader 107 | } 108 | 109 | func (c *RewindConn) Read(p []byte) (int, error) { 110 | return c.RewindReader.Read(p) 111 | } 112 | 113 | func NewRewindConn(conn net.Conn) *RewindConn { 114 | return &RewindConn{ 115 | Conn: conn, 116 | RewindReader: &RewindReader{ 117 | rawReader: conn, 118 | }, 119 | } 120 | } 121 | 122 | type StickyWriter struct { 123 | rawWriter io.Writer 124 | writeBuffer []byte 125 | MaxBuffered int 126 | } 127 | 128 | func (w *StickyWriter) Write(p []byte) (int, error) { 129 | if w.MaxBuffered > 0 { 130 | w.MaxBuffered-- 131 | w.writeBuffer = append(w.writeBuffer, p...) 132 | if w.MaxBuffered != 0 { 133 | return len(p), nil 134 | } 135 | w.MaxBuffered = 0 136 | _, err := w.rawWriter.Write(w.writeBuffer) 137 | w.writeBuffer = nil 138 | return len(p), err 139 | } 140 | return w.rawWriter.Write(p) 141 | } 142 | -------------------------------------------------------------------------------- /common/io_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "testing" 7 | 8 | "github.com/v2fly/v2ray-core/v4/common" 9 | ) 10 | 11 | func TestBufferedReader(t *testing.T) { 12 | payload := [1024]byte{} 13 | rand.Reader.Read(payload[:]) 14 | rawReader := bytes.NewBuffer(payload[:]) 15 | r := RewindReader{ 16 | rawReader: rawReader, 17 | } 18 | r.SetBufferSize(2048) 19 | buf1 := make([]byte, 512) 20 | buf2 := make([]byte, 512) 21 | common.Must2(r.Read(buf1)) 22 | r.Rewind() 23 | common.Must2(r.Read(buf2)) 24 | if !bytes.Equal(buf1, buf2) { 25 | t.Fail() 26 | } 27 | buf3 := make([]byte, 512) 28 | common.Must2(r.Read(buf3)) 29 | if !bytes.Equal(buf3, payload[512:]) { 30 | t.Fail() 31 | } 32 | r.Rewind() 33 | buf4 := make([]byte, 1024) 34 | common.Must2(r.Read(buf4)) 35 | if !bytes.Equal(payload[:], buf4) { 36 | t.Fail() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/net.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | KiB = 1024 18 | MiB = KiB * 1024 19 | GiB = MiB * 1024 20 | ) 21 | 22 | func HumanFriendlyTraffic(bytes uint64) string { 23 | if bytes <= KiB { 24 | return fmt.Sprintf("%d B", bytes) 25 | } 26 | if bytes <= MiB { 27 | return fmt.Sprintf("%.2f KiB", float32(bytes)/KiB) 28 | } 29 | if bytes <= GiB { 30 | return fmt.Sprintf("%.2f MiB", float32(bytes)/MiB) 31 | } 32 | return fmt.Sprintf("%.2f GiB", float32(bytes)/GiB) 33 | } 34 | 35 | func PickPort(network string, host string) int { 36 | switch network { 37 | case "tcp": 38 | for retry := 0; retry < 16; retry++ { 39 | l, err := net.Listen("tcp", host+":0") 40 | if err != nil { 41 | continue 42 | } 43 | defer l.Close() 44 | _, port, err := net.SplitHostPort(l.Addr().String()) 45 | Must(err) 46 | p, err := strconv.ParseInt(port, 10, 32) 47 | Must(err) 48 | return int(p) 49 | } 50 | case "udp": 51 | for retry := 0; retry < 16; retry++ { 52 | conn, err := net.ListenPacket("udp", host+":0") 53 | if err != nil { 54 | continue 55 | } 56 | defer conn.Close() 57 | _, port, err := net.SplitHostPort(conn.LocalAddr().String()) 58 | Must(err) 59 | p, err := strconv.ParseInt(port, 10, 32) 60 | Must(err) 61 | return int(p) 62 | } 63 | default: 64 | return 0 65 | } 66 | return 0 67 | } 68 | 69 | func WriteAllBytes(writer io.Writer, payload []byte) error { 70 | for len(payload) > 0 { 71 | n, err := writer.Write(payload) 72 | if err != nil { 73 | return err 74 | } 75 | payload = payload[n:] 76 | } 77 | return nil 78 | } 79 | 80 | func WriteFile(path string, payload []byte) error { 81 | writer, err := os.Create(path) 82 | if err != nil { 83 | return err 84 | } 85 | defer writer.Close() 86 | 87 | return WriteAllBytes(writer, payload) 88 | } 89 | 90 | func FetchHTTPContent(target string) ([]byte, error) { 91 | parsedTarget, err := url.Parse(target) 92 | if err != nil { 93 | return nil, fmt.Errorf("invalid URL: %s", target) 94 | } 95 | 96 | if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" { 97 | return nil, fmt.Errorf("invalid scheme: %s", parsedTarget.Scheme) 98 | } 99 | 100 | client := &http.Client{ 101 | Timeout: 30 * time.Second, 102 | } 103 | resp, err := client.Do(&http.Request{ 104 | Method: "GET", 105 | URL: parsedTarget, 106 | Close: true, 107 | }) 108 | if err != nil { 109 | return nil, fmt.Errorf("failed to dial to %s", target) 110 | } 111 | defer resp.Body.Close() 112 | 113 | if resp.StatusCode != http.StatusOK { 114 | return nil, fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) 115 | } 116 | 117 | content, err := ioutil.ReadAll(resp.Body) 118 | if err != nil { 119 | return nil, fmt.Errorf("failed to read HTTP response") 120 | } 121 | 122 | return content, nil 123 | } 124 | -------------------------------------------------------------------------------- /common/sync.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously. 4 | type Notifier struct { 5 | c chan struct{} 6 | } 7 | 8 | // NewNotifier creates a new Notifier. 9 | func NewNotifier() *Notifier { 10 | return &Notifier{ 11 | c: make(chan struct{}, 1), 12 | } 13 | } 14 | 15 | // Signal signals a change, usually by producer. This method never blocks. 16 | func (n *Notifier) Signal() { 17 | select { 18 | case n.c <- struct{}{}: 19 | default: 20 | } 21 | } 22 | 23 | // Wait returns a channel for waiting for changes. The returned channel never gets closed. 24 | func (n *Notifier) Wait() <-chan struct{} { 25 | return n.c 26 | } 27 | -------------------------------------------------------------------------------- /component/api.go: -------------------------------------------------------------------------------- 1 | //go:build api || full 2 | // +build api full 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/api/control" 8 | _ "github.com/p4gefau1t/trojan-go/api/service" 9 | ) 10 | -------------------------------------------------------------------------------- /component/base.go: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import ( 4 | _ "github.com/p4gefau1t/trojan-go/log/golog" 5 | _ "github.com/p4gefau1t/trojan-go/statistic/memory" 6 | _ "github.com/p4gefau1t/trojan-go/version" 7 | ) 8 | -------------------------------------------------------------------------------- /component/client.go: -------------------------------------------------------------------------------- 1 | //go:build client || full || mini 2 | // +build client full mini 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/proxy/client" 8 | ) 9 | -------------------------------------------------------------------------------- /component/custom.go: -------------------------------------------------------------------------------- 1 | //go:build custom || full 2 | // +build custom full 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/proxy/custom" 8 | _ "github.com/p4gefau1t/trojan-go/tunnel/adapter" 9 | _ "github.com/p4gefau1t/trojan-go/tunnel/dokodemo" 10 | _ "github.com/p4gefau1t/trojan-go/tunnel/freedom" 11 | _ "github.com/p4gefau1t/trojan-go/tunnel/http" 12 | _ "github.com/p4gefau1t/trojan-go/tunnel/mux" 13 | _ "github.com/p4gefau1t/trojan-go/tunnel/router" 14 | _ "github.com/p4gefau1t/trojan-go/tunnel/shadowsocks" 15 | _ "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" 16 | _ "github.com/p4gefau1t/trojan-go/tunnel/socks" 17 | _ "github.com/p4gefau1t/trojan-go/tunnel/tls" 18 | _ "github.com/p4gefau1t/trojan-go/tunnel/tproxy" 19 | _ "github.com/p4gefau1t/trojan-go/tunnel/transport" 20 | _ "github.com/p4gefau1t/trojan-go/tunnel/trojan" 21 | _ "github.com/p4gefau1t/trojan-go/tunnel/websocket" 22 | ) 23 | -------------------------------------------------------------------------------- /component/forward.go: -------------------------------------------------------------------------------- 1 | //go:build forward || full || mini 2 | // +build forward full mini 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/proxy/forward" 8 | ) 9 | -------------------------------------------------------------------------------- /component/mysql.go: -------------------------------------------------------------------------------- 1 | //go:build mysql || full || mini 2 | // +build mysql full mini 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/statistic/mysql" 8 | ) 9 | -------------------------------------------------------------------------------- /component/nat.go: -------------------------------------------------------------------------------- 1 | //go:build nat || full || mini 2 | // +build nat full mini 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/proxy/nat" 8 | ) 9 | -------------------------------------------------------------------------------- /component/other.go: -------------------------------------------------------------------------------- 1 | //go:build other || full 2 | // +build other full 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/easy" 8 | _ "github.com/p4gefau1t/trojan-go/url" 9 | ) 10 | -------------------------------------------------------------------------------- /component/server.go: -------------------------------------------------------------------------------- 1 | //go:build server || full || mini 2 | // +build server full mini 3 | 4 | package build 5 | 6 | import ( 7 | _ "github.com/p4gefau1t/trojan-go/proxy/server" 8 | ) 9 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "gopkg.in/yaml.v3" 8 | ) 9 | 10 | var creators = make(map[string]Creator) 11 | 12 | // Creator creates default config struct for a module 13 | type Creator func() interface{} 14 | 15 | // RegisterConfigCreator registers a config struct for parsing 16 | func RegisterConfigCreator(name string, creator Creator) { 17 | name += "_CONFIG" 18 | creators[name] = creator 19 | } 20 | 21 | func parseJSON(data []byte) (map[string]interface{}, error) { 22 | result := make(map[string]interface{}) 23 | for name, creator := range creators { 24 | config := creator() 25 | if err := json.Unmarshal(data, config); err != nil { 26 | return nil, err 27 | } 28 | result[name] = config 29 | } 30 | return result, nil 31 | } 32 | 33 | func parseYAML(data []byte) (map[string]interface{}, error) { 34 | result := make(map[string]interface{}) 35 | for name, creator := range creators { 36 | config := creator() 37 | if err := yaml.Unmarshal(data, config); err != nil { 38 | return nil, err 39 | } 40 | result[name] = config 41 | } 42 | return result, nil 43 | } 44 | 45 | func WithJSONConfig(ctx context.Context, data []byte) (context.Context, error) { 46 | var configs map[string]interface{} 47 | var err error 48 | configs, err = parseJSON(data) 49 | if err != nil { 50 | return ctx, err 51 | } 52 | for name, config := range configs { 53 | ctx = context.WithValue(ctx, name, config) 54 | } 55 | return ctx, nil 56 | } 57 | 58 | func WithYAMLConfig(ctx context.Context, data []byte) (context.Context, error) { 59 | var configs map[string]interface{} 60 | var err error 61 | configs, err = parseYAML(data) 62 | if err != nil { 63 | return ctx, err 64 | } 65 | for name, config := range configs { 66 | ctx = context.WithValue(ctx, name, config) 67 | } 68 | return ctx, nil 69 | } 70 | 71 | func WithConfig(ctx context.Context, name string, cfg interface{}) context.Context { 72 | name += "_CONFIG" 73 | return context.WithValue(ctx, name, cfg) 74 | } 75 | 76 | // FromContext extracts config from a context 77 | func FromContext(ctx context.Context, name string) interface{} { 78 | return ctx.Value(name + "_CONFIG") 79 | } 80 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/p4gefau1t/trojan-go/common" 8 | ) 9 | 10 | type Foo struct { 11 | Field1 string `json,yaml:"field1"` 12 | Field2 bool `json:"field2" yaml:"field2"` 13 | } 14 | 15 | type TestStruct struct { 16 | Field1 string `json,yaml:"field1"` 17 | Field2 bool `json,yaml:"field2"` 18 | Field3 []Foo `json,yaml:"field3"` 19 | } 20 | 21 | func creator() interface{} { 22 | return &TestStruct{} 23 | } 24 | 25 | func TestJSONConfig(t *testing.T) { 26 | RegisterConfigCreator("test", creator) 27 | data := []byte(` 28 | { 29 | "field1": "test1", 30 | "field2": true, 31 | "field3": [ 32 | { 33 | "field1": "aaaa", 34 | "field2": true 35 | } 36 | ] 37 | } 38 | `) 39 | ctx, err := WithJSONConfig(context.Background(), data) 40 | common.Must(err) 41 | c := FromContext(ctx, "test").(*TestStruct) 42 | if c.Field1 != "test1" || c.Field2 != true { 43 | t.Fail() 44 | } 45 | } 46 | 47 | func TestYAMLConfig(t *testing.T) { 48 | RegisterConfigCreator("test", creator) 49 | data := []byte(` 50 | field1: 012345678 51 | field2: true 52 | field3: 53 | - field1: test 54 | field2: true 55 | `) 56 | ctx, err := WithYAMLConfig(context.Background(), data) 57 | common.Must(err) 58 | c := FromContext(ctx, "test").(*TestStruct) 59 | if c.Field1 != "012345678" || c.Field2 != true || c.Field3[0].Field1 != "test" { 60 | t.Fail() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var ( 4 | Version = "Custom Version" 5 | Commit = "Unknown Git Commit ID" 6 | ) 7 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | themes/ -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default clean hugo hugo-build 2 | 3 | default: hugo 4 | 5 | clean: 6 | rm -rf public/ 7 | 8 | hugo-build: clean hugo-themes 9 | hugo --enableGitInfo --source . 10 | 11 | hugo: 12 | hugo server --disableFastRender --enableGitInfo --watch --source . 13 | # hugo server -D 14 | 15 | hugo-themes: 16 | rm -rf themes 17 | mkdir themes 18 | git clone --depth=1 https://github.com/llc1123/hugo-theme-techdoc.git themes/hugo-theme-techdoc 19 | rm -rf themes/hugo-theme-techdoc/.git 20 | -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "简介" 3 | draft: false 4 | weight: 10 5 | --- 6 | 7 | # Trojan-Go 8 | 9 | 这里是Trojan-Go的文档,你可以在左侧的导航栏中找到一些使用技巧,以及完整的配置文件说明。 10 | 11 | Trojan-Go是使用Go语言实现的完整的Trojan代理,和Trojan协议以及原版的配置文件格式兼容。支持并且兼容Trojan-GFW版本的绝大多数功能,并扩展了更多的实用功能。 12 | 13 | Trojan-Go的的首要目标是保障传输安全性和隐蔽性。在此前提下,尽可能提升传输性能和易用性。 14 | 15 | 如果你遇到配置和使用方面的问题,发现了软件Bug,或是有更好的想法,欢迎加入Trojan-Go的[Telegram交流反馈群](https://t.me/trojan_go_chat)。 16 | 17 | ---- 18 | 19 | > Across the Great Wall, we can reach every corner in the world. 20 | > 21 | > (越过长城,走向世界。) 22 | -------------------------------------------------------------------------------- /docs/content/advance/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "高级配置" 3 | draft: false 4 | weight: 30 5 | --- 6 | 7 | 这一部分内容将介绍更复杂的Trojan-Go配置方法。对于与Trojan原版不兼容的特性,将在小节中明确标明。 8 | -------------------------------------------------------------------------------- /docs/content/advance/aead.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用Shadowsocks AEAD进行二次加密" 3 | draft: false 4 | weight: 8 5 | --- 6 | 7 | ### 注意,Trojan不支持这个特性 8 | 9 | Trojan协议本身无加密,其安全性依赖于下层的TLS。在一般情况下,TLS安全性很好,并不需要再次加密Trojan流量。但是,某些场景下,你可能无法保证TLS隧道的安全性: 10 | 11 | - 你使用了Websocket,经过不可信的CDN进行中转(如国内CDN) 12 | 13 | - 你与服务器的连接遭到了GFW针对TLS的中间人攻击 14 | 15 | - 你的证书失效,无法验证证书有效性 16 | 17 | - 你使用了无法保证密码学安全的可插拔传输层 18 | 19 | 等等。 20 | 21 | Trojan-Go支持使用Shadowsocks AEAD对Trojan-Go进行加密。其本质是在Trojan协议下方加上一层Shadowsocks AEAD加密。服务端和客户端必须同时开启,且密码和加密方式必须一致,否则无法进行通讯。 22 | 23 | 要开启AEAD加密,只需添加一个```shadowsocks```选项: 24 | 25 | ```json 26 | ... 27 | "shadowsocks": { 28 | "enabled": true, 29 | "method": "AES-128-GCM", 30 | "password": "1234567890" 31 | } 32 | ``` 33 | 34 | ```method```如果省略,则默认使用AES-128-GCM。更多信息,参见“完整的配置文件”一节。 35 | -------------------------------------------------------------------------------- /docs/content/advance/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用API动态管理用户" 3 | draft: false 4 | weight: 10 5 | --- 6 | 7 | ### 注意,Trojan不支持这个特性 8 | 9 | Trojan-Go使用gRPC提供了一组API,API支持以下功能: 10 | 11 | - 用户信息增删改查 12 | 13 | - 流量统计 14 | 15 | - 速度统计 16 | 17 | - IP连接数统计 18 | 19 | Trojan-Go本身集成了API控制功能,也即可以使用一个Trojan-Go实例控制另一个Trojan-Go服务器。 20 | 21 | 你需要在你需要被控制的服务端配置添加API设置,例如: 22 | 23 | ```json 24 | { 25 | ... 26 | "api": { 27 | "enabled": true, 28 | "api_addr": "127.0.0.1", 29 | "api_port": 10000, 30 | } 31 | } 32 | ``` 33 | 34 | 然后启动Trojan-Go服务器 35 | 36 | ```shell 37 | ./trojan-go -config ./server.json 38 | ``` 39 | 40 | 然后可以使用另一个Trojan-Go连接该服务器进行管理,基本命令格式为 41 | 42 | ```shell 43 | ./trojan-go -api-addr SERVER_API_ADDRESS -api COMMAND 44 | ``` 45 | 46 | 其中```SERVER_API_ADDRESS```为API地址和端口,如127.0.0.1:10000 47 | 48 | ```COMMAND```为API命令,合法的命令有 49 | 50 | - list 列出所有用户 51 | 52 | - get 获取某个用户信息 53 | 54 | - set 设置某个用户信息(添加/删除/修改) 55 | 56 | 下面是一些例子 57 | 58 | 1. 列出所有用户信息 59 | 60 | ```shell 61 | ./trojan-go -api-addr 127.0.0.1:10000 -api list 62 | ``` 63 | 64 | 所有的用户信息将以json的形式导出,信息包括在线IP数量,实时速度,总上传和下载流量等。下面是一个返回的结果的例子 65 | 66 | ```json 67 | [{"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}}] 68 | ``` 69 | 70 | 流量单位均为字节。 71 | 72 | 2. 获取一个用户信息 73 | 74 | 可以使用 -target-password 指定密码,也可以使用 -target-hash 指定目标用户密码的SHA224散列值。格式和list命令相同 75 | 76 | ```shell 77 | ./trojan-go -api-addr 127.0.0.1:10000 -api get -target-password password 78 | ``` 79 | 80 | 或者 81 | 82 | ```shell 83 | ./trojan-go -api-addr 127.0.0.1:10000 -api get -target-hash d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01 84 | ``` 85 | 86 | 以上两条命令等价,下面的例子统一使用明文密码的方式,散列值指定某个用户的方式以此类推。 87 | 88 | 该用户信息将以json的形式导出,格式与list命令类似。下面是一个返回的结果的例子 89 | 90 | ```json 91 | {"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}} 92 | ``` 93 | 94 | 3. 添加一个用户信息 95 | 96 | ```shell 97 | ./trojan-go -api-addr 127.0.0.1:10000 -api set -add-profile -target-password password 98 | ``` 99 | 100 | 4. 删除一个用户信息 101 | 102 | ```shell 103 | ./trojan-go -api-addr 127.0.0.1:10000 -api set -delete-profile -target-password password 104 | ``` 105 | 106 | 5. 修改一个用户信息 107 | 108 | ```shell 109 | ./trojan-go -api-addr 127.0.0.1:10000 -api set -modify-profile -target-password password \ 110 | -ip-limit 3 \ 111 | -upload-speed-limit 5242880 \ 112 | -download-speed-limit 5242880 113 | ``` 114 | 115 | 这个命令将密码为password的用户上传和下载速度限制为5MiB/s,同时连接的IP数量限制为3个,注意这里5242880的单位是字节。如果填写0或者负数,则表示不进行限制。 116 | -------------------------------------------------------------------------------- /docs/content/advance/forward.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "隧道和反向代理" 3 | draft: false 4 | weight: 5 5 | --- 6 | 7 | 你可以使用Trojan-Go建立隧道。一个典型的应用是,使用Trojan-Go在本地建立一个无污染的DNS服务器,下面是一个配置的例子 8 | 9 | ```json 10 | { 11 | "run_type": "forward", 12 | "local_addr": "127.0.0.1", 13 | "local_port": 53, 14 | "remote_addr": "your_awesome_server", 15 | "remote_port": 443, 16 | "target_addr": "8.8.8.8", 17 | "target_port": 53, 18 | "password": [ 19 | "your_awesome_password" 20 | ] 21 | } 22 | ``` 23 | 24 | forward本质上是一个客户端,不过你需要填入```target_addr```和```target_port```字段,指明反向代理的目标。 25 | 26 | 使用这份配置文件后,本地53的TCP和UDP端口将被监听,所有的向本地53端口发送的TCP或者UDP数据,都会通过TLS隧道转发给远端服务器your_awesome_server,远端服务器得到回应后,数据会通过隧道返回到本地53端口。 也就是说,你可以将127.0.0.1当作一个DNS服务器,本地查询的结果和远端服务器查询的结果是一致的。你可以使用这个配置避开DNS污染。 27 | 28 | 同样的原理,你可以在本地搭建一个Google的镜像 29 | 30 | ```json 31 | { 32 | "run_type": "forward", 33 | "local_addr": "127.0.0.1", 34 | "local_port": 443, 35 | "remote_addr": "your_awesome_server", 36 | "remote_port": 443, 37 | "target_addr": "www.google.com", 38 | "target_port": 443, 39 | "password": [ 40 | "your_awesome_password" 41 | ] 42 | } 43 | ``` 44 | 45 | 访问```https://127.0.0.1```即可访问谷歌主页,但是注意这里由于谷歌服务器提供的https证书是google.com的证书,而当前域名为127.0.0.1,因此浏览器会引发一个证书错误的警告。 46 | 47 | 类似的,可以使用forward传输其他代理协议。例如,使用Trojan-Go传输shadowsocks的流量,远端主机开启ss服务器,监听127.0.0.1:12345,并且远端服务器在443端口开启了正常的Trojan-Go服务器。你可以如此指定配置 48 | 49 | ```json 50 | { 51 | "run_type": "forward", 52 | "local_addr": "0.0.0.0", 53 | "local_port": 54321, 54 | "remote_addr": "your_awesome_server", 55 | "remote_port": 443, 56 | "target_addr": "www.google.com", 57 | "target_port": 12345, 58 | "password": [ 59 | "your_awesome_password" 60 | ] 61 | } 62 | ``` 63 | 64 | 此后,任何连接本机54321端口的TCP/UDP连接,等同于连接远端12345端口。你可以使用shadowsocks客户端连接本地的54321端口,ss流量将使用trojan的隧道连接传输至远端12345端口的ss服务器。 65 | -------------------------------------------------------------------------------- /docs/content/advance/mux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "启用多路复用提升网络并发性能" 3 | draft: false 4 | weight: 1 5 | --- 6 | 7 | ### 注意,Trojan不支持这个特性 8 | 9 | Trojan-Go支持使用多路复用提升网络并发性能。 10 | 11 | Trojan协议基于TLS。在一个TLS安全连接建立之前,连接双方需要进行密钥协商和交换等步骤确保后续通讯的安全性。这个过程即为TLS握手。 12 | 13 | 目前GFW对于TLS握手存在审查和干扰,同时由于出口网络拥塞的原因,普通的线路完成TLS握手通常需要将近一秒甚至更长的时间。这可能导致浏览网页和观看视频的延迟提高。 14 | 15 | Trojan-Go使用多路复用的方式解决这一问题。每个建立的TLS连接将承载多个TCP连接。当新的代理请求到来时,不需要和服务器握手发起一个新的TLS连接,而是尽可能重复使用已有的TLS连接。以此减少频繁TLS握手和TCP握手的带来的延迟。 16 | 17 | 启用多路复用不会增加你的链路速度(甚至会有所减少),而且可能会增加服务器和客户端的计算负担。可以粗略地理解为,多路复用牺牲网络吞吐和CPU功耗,换取更低的延迟。在高并发的情景下,如浏览含有大量图片的网页时,或者发送大量UDP请求时,可以提升使用体验。 18 | 19 | 激活```mux```模块,只需要将```mux```选项中```enabled```字段设为true即可,下面是一个客户端的例子 20 | 21 | ```json 22 | ... 23 | "mux" :{ 24 | "enabled": true 25 | } 26 | ``` 27 | 28 | 只需要配置客户端即可,服务端可以自动适配,无需配置```mux```选项。 29 | 30 | 完整的mux配置如下 31 | 32 | ```json 33 | "mux": { 34 | "enabled": false, 35 | "concurrency": 8, 36 | "idle_timeout": 60 37 | } 38 | ``` 39 | 40 | ```concurrency```是每个TLS连接最多可以承载的TCP连接数。这个数值越大,每个TLS连接被复用的程度就更高,握手导致的延迟越低。但服务器和客户端的计算负担也会越大,这有可能使你的网络吞吐量降低。如果你的线路的TLS握手极端缓慢,你可以将这个数值设置为-1,Trojan-Go将只进行一次TLS握手,只使用唯一的一条TLS连接进行传输。 41 | 42 | ```idle_timeout```指的是每个TLS连接空闲多长时间后关闭。设置超时时间,**可能**有助于减少不必要的长连接存活确认(Keep Alive)流量传输引发GFW的探测。你可以将这个数值设置为-1,TLS连接在空闲时将被立即关闭。 43 | -------------------------------------------------------------------------------- /docs/content/advance/nat.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "透明代理" 3 | draft: false 4 | weight: 11 5 | --- 6 | 7 | ### 注意,Trojan不完全支持这个特性(UDP) 8 | 9 | Trojan-Go支持基于tproxy的透明TCP/UDP代理。 10 | 11 | 要开启透明代理模式,将一份正确的客户端配置(配置方式参见基本配置部分)其中的```run_type```修改为```nat```,并按照需求修改本地监听端口即可。 12 | 13 | 之后需要添加iptables规则。这里假定你的网关具有两个网卡,下面这份配置将其中一个网卡(局域网)的入站包转交给Trojan-Go,由Trojan-Go通过隧道,通过另一个网卡(互联网)发送到远端Trojan-Go服务器。你需要将下面的```$SERVER_IP```,```$TROJAN_GO_PORT```,```$INTERFACE```替换为自己的配置。 14 | 15 | ```shell 16 | # 新建TROJAN_GO链 17 | iptables -t mangle -N TROJAN_GO 18 | 19 | # 绕过Trojan-Go服务器地址 20 | iptables -t mangle -A TROJAN_GO -d $SERVER_IP -j RETURN 21 | 22 | # 绕过私有地址 23 | iptables -t mangle -A TROJAN_GO -d 0.0.0.0/8 -j RETURN 24 | iptables -t mangle -A TROJAN_GO -d 10.0.0.0/8 -j RETURN 25 | iptables -t mangle -A TROJAN_GO -d 127.0.0.0/8 -j RETURN 26 | iptables -t mangle -A TROJAN_GO -d 169.254.0.0/16 -j RETURN 27 | iptables -t mangle -A TROJAN_GO -d 172.16.0.0/12 -j RETURN 28 | iptables -t mangle -A TROJAN_GO -d 192.168.0.0/16 -j RETURN 29 | iptables -t mangle -A TROJAN_GO -d 224.0.0.0/4 -j RETURN 30 | iptables -t mangle -A TROJAN_GO -d 240.0.0.0/4 -j RETURN 31 | 32 | # 未命中上文的规则的包,打上标记 33 | iptables -t mangle -A TROJAN_GO -j TPROXY -p tcp --on-port $TROJAN_GO_PORT --tproxy-mark 0x01/0x01 34 | iptables -t mangle -A TROJAN_GO -j TPROXY -p udp --on-port $TROJAN_GO_PORT --tproxy-mark 0x01/0x01 35 | 36 | # 从$INTERFACE网卡流入的所有TCP/UDP包,跳转TROJAN_GO链 37 | iptables -t mangle -A PREROUTING -p tcp -i $INTERFACE -j TROJAN_GO 38 | iptables -t mangle -A PREROUTING -p udp -i $INTERFACE -j TROJAN_GO 39 | 40 | # 添加路由,打上标记的包重新进入本地回环 41 | ip route add local default dev lo table 100 42 | ip rule add fwmark 1 lookup 100 43 | ``` 44 | 45 | 配置完成后**以root权限启动**Trojan-Go客户端: 46 | 47 | ```shell 48 | sudo trojan-go 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/content/advance/plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用Shadowsocks插件/可插拔传输层" 3 | draft: false 4 | weight: 7 5 | --- 6 | 7 | ### 注意,Trojan不支持这个特性 8 | 9 | Trojan-Go支持可插拔的传输层。原则上,Trojan-Go可以使用任何有TCP隧道功能的软件作为传输层,如v2ray、shadowsocks、kcp等。同时,Trojan-Go也兼容Shadowsocks的SIP003插件标准,如GoQuiet,v2ray-plugin等。也可以使用Tor的传输层插件,如obfs4,meek等。 10 | 11 | 你可以使用这些插件,替换Trojan-Go的TLS传输层。 12 | 13 | 开启可插拔传输层插件后,Trojan-Go客户端将会把**流量明文**直接传输给客户端本地的插件处理。由客户端插件负责进行加密和混淆,并将流量传输给服务端的插件。服务端的插件接收到流量,进行解密和解析,将**流量明文**传输给服务端本地的Trojan-Go服务端。 14 | 15 | 你可以使用任何插件对流量进行加密和混淆,只需添加"transport_plugin"选项,并指定插件的可执行文件的路径,并做好配置即可。 16 | 17 | 我们更建议**自行设计协议并开发相应插件**。因为目前现有的所有插件无法对接Trojan-Go的对抗主动探测的特性,而且部分插件并无加密能力。如果你对开发插件有兴趣,欢迎在"实现细节和开发指南"一节中查看插件设计的指南。 18 | 19 | 例如,可以使用符合SIP003标准的v2ray-plugin,下面是一个例子: 20 | 21 | **这个配置中使用了websocket明文传输未经加密的trojan协议,存在安全隐患。这个配置仅作为演示使用。** 22 | 23 | **不要在任何情况下使用这个配置穿透GFW。** 24 | 25 | 服务端配置: 26 | 27 | ```json 28 | ...(省略) 29 | "transport_plugin": { 30 | "enabled": true, 31 | "type": "shadowsocks", 32 | "command": "./v2ray-plugin", 33 | "arg": ["-server", "-host", "www.baidu.com"] 34 | } 35 | ``` 36 | 37 | 客户端配置: 38 | 39 | ```json 40 | ...(省略) 41 | "transport_plugin": { 42 | "enabled": true, 43 | "type": "shadowsocks", 44 | "command": "./v2ray-plugin", 45 | "arg": ["-host", "www.baidu.com"] 46 | } 47 | ``` 48 | 49 | 注意,v2ray-plugin插件需要指定```-server```参数来区分客户端和服务端。更多关于该插件详细的说明,参考v2ray-plugin的文档。 50 | 51 | 启动Trojan-Go后,你可以看到v2ray-plugin启动的输出。插件将把流量伪装为Websocket流量并传输。 52 | 53 | 非SIP003标准的插件可能需要不同的配置,你可以指定```type```为"other",并自行指定插件地址,插件启动参数、环境变量。 54 | -------------------------------------------------------------------------------- /docs/content/advance/router.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "国内直连和广告屏蔽" 3 | draft: false 4 | weight: 3 5 | --- 6 | 7 | ### 注意,Trojan不支持这个特性 8 | 9 | Trojan-Go内建的路由模块可以帮助你实现国内直连,即客户端对于国内网站不经过代理,直接连接。 10 | 11 | 路由模块在客户端可以配置三种策略(```bypass```, ```proxy```, ```block```),在服务端只可使用```block```策略。 12 | 13 | 下面是一个例子 14 | 15 | ```json 16 | { 17 | "run_type": "client", 18 | "local_addr": "127.0.0.1", 19 | "local_port": 1080, 20 | "remote_addr": "your_server", 21 | "remote_port": 443, 22 | "password": [ 23 | "your_password" 24 | ], 25 | "ssl": { 26 | "sni": "your-domain-name.com" 27 | }, 28 | "mux" :{ 29 | "enabled": true 30 | }, 31 | "router":{ 32 | "enabled": true, 33 | "bypass": [ 34 | "geoip:cn", 35 | "geoip:private", 36 | "geosite:cn", 37 | "geosite:geolocation-cn" 38 | ], 39 | "block": [ 40 | "geosite:category-ads" 41 | ], 42 | "proxy": [ 43 | "geosite:geolocation-!cn" 44 | ] 45 | } 46 | } 47 | ``` 48 | 49 | 这个配置文件激活了router模块,使用的是白名单的模式,当匹配到中国大陆或者局域网的IP/域名时,直接连接。如果是广告运营商的域名,则直接断开连接。 50 | 51 | 所需要的数据库```geoip.dat```和```geosite.dat```已经包含在release的压缩包中,直接使用即可。它们来自V2Ray的[domain-list-community](https://github.com/v2fly/domain-list-community)和[geoip](https://github.com/v2fly/geoip)。 52 | 53 | 你可以使用如```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#预定义域名列表)。 54 | 55 | 你可以使用如```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)。 56 | 57 | 你也可以配置自己的路由规则。例如,想要屏蔽所有example.com域名以及其子域名,以及192.168.1.0/24,添加下面的规则。 58 | 59 | ```json 60 | "block": [ 61 | "domain:example.com", 62 | "cidr:192.168.1.0/24" 63 | ] 64 | ``` 65 | 66 | 支持的格式有 67 | 68 | - "domain:",子域名匹配 69 | 70 | - "full:",完全域名匹配 71 | 72 | - "regexp:",正则表达式匹配 73 | 74 | - "cidr:",CIDR匹配 75 | 76 | 更详细的说明参考"完整的配置文件"一节。 77 | -------------------------------------------------------------------------------- /docs/content/advance/websocket.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "使用Websocket进行CDN转发和抵抗中间人攻击" 3 | draft: false 4 | weight: 2 5 | --- 6 | 7 | ### 注意,Trojan不支持这个特性 8 | 9 | Trojan-Go支持使用TLS+Websocket承载Trojan协议,使得利用CDN进行流量中转成为可能。 10 | 11 | Trojan协议本身不带加密,安全性依赖外层的TLS。但流量一旦经过CDN,TLS对CDN是透明的。其服务提供者可以对TLS的明文内容进行审查。**如果你使用的是不可信任的CDN(任何在中国大陆注册备案的CDN服务均应被视为不可信任),请务必开启Shadowsocks AEAD对Webosocket流量进行加密,以避免遭到识别和审查。** 12 | 13 | 服务器和客户端配置文件中同时添加websocket选项,并将其```enabled```字段设置为true,并填写```path```字段和```host```字段即可启用Websocket支持。下面是一个完整的Websocket选项: 14 | 15 | ```json 16 | "websocket": { 17 | "enabled": true, 18 | "path": "/your-websocket-path", 19 | "host": "example.com" 20 | } 21 | ``` 22 | 23 | ```host```是主机名,一般填写域名。客户端```host```是可选的,填写你的域名。如果留空,将会使用```remote_addr```填充。 24 | 25 | ```path```指的是websocket所在的URL路径,必须以斜杠("/")开始。路径并无特别要求,满足URL基本格式即可,但要保证客户端和服务端的```path```一致。```path```应当选择较长的字符串,以避免遭到GFW直接的主动探测。 26 | 27 | 客户端的```host```将包含在Websocket的握手HTTP请求中,发送给CDN服务器,必须有效;服务端和客户端```path```必须一致,否则Websocket握手无法进行。 28 | 29 | 下面是一个客户端配置文件的例子 30 | 31 | ```json 32 | { 33 | "run_type": "client", 34 | "local_addr": "127.0.0.1", 35 | "local_port": 1080, 36 | "remote_addr": "www.your_awesome_domain_name.com", 37 | "remote_port": 443, 38 | "password": [ 39 | "your_password" 40 | ], 41 | "websocket": { 42 | "enabled": true, 43 | "path": "/your-websocket-path", 44 | "host": "example.com" 45 | }, 46 | "shadowsocks": { 47 | "enabled": true, 48 | "password": "12345678" 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/content/basic/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "基本配置" 3 | draft: false 4 | weight: 20 5 | --- 6 | 7 | 这一部分内容将介绍如何配置基本的Trojan-Go代理服务器和客户端。 8 | -------------------------------------------------------------------------------- /docs/content/basic/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "正确配置Trojan-Go" 3 | draft: false 4 | weight: 22 5 | --- 6 | 7 | 下面将介绍如何正确配置Trojan-Go以完全隐藏你的代理节点特征。 8 | 9 | 在开始之前,你需要 10 | 11 | - 一个服务器,且未被GFW封锁 12 | 13 | - 一个域名,可以使用免费的域名服务,如.tk等 14 | 15 | - Trojan-Go,可以从release页面下载 16 | 17 | - 证书和密钥,可以从letsencrypt等机构免费申请签发 18 | 19 | ### 服务端配置 20 | 21 | 我们的目标是,使得你的服务器和正常的HTTPS网站表现相同。 22 | 23 | 首先你需要一个HTTP服务器,可以使用nginx,apache,caddy等配置一个本地HTTP服务器,也可以使用别人的HTTP服务器。HTTP服务器的作用是,当GFW主动探测时,向它展示一个完全正常的Web页面。 24 | 25 | **你需要在```remote_addr```和```remote_port```指定这个HTTP服务器的地址。```remote_addr```可以是IP或者域名。Trojan-Go将会测试这个HTTP服务器是否工作正常,如果不正常,Trojan-Go会拒绝启动。** 26 | 27 | 下面是一份比较安全的服务器配置server.json,需要你在本地80端口配置一个HTTP服务(必要,你也可以使用其他的网站HTTP服务器,如"remote_addr": "example.com"),在1234端口配置一个HTTPS服务,或是一个展示"400 Bad Request"的静态HTTP网页服务。(可选,可以删除```fallback_port```字段,跳过这个步骤) 28 | 29 | ```json 30 | { 31 | "run_type": "server", 32 | "local_addr": "0.0.0.0", 33 | "local_port": 443, 34 | "remote_addr": "127.0.0.1", 35 | "remote_port": 80, 36 | "password": [ 37 | "your_awesome_password" 38 | ], 39 | "ssl": { 40 | "cert": "server.crt", 41 | "key": "server.key", 42 | "fallback_port": 1234 43 | } 44 | } 45 | ``` 46 | 47 | 这个配置文件使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将拒绝启动。** 48 | 49 | 当一个客户端试图连接Trojan-Go的监听端口时,会发生下面的事情: 50 | 51 | - 如果TLS握手成功,检测到TLS的内容非Trojan协议(有可能是HTTP请求,或者来自GFW的主动探测)。Trojan-Go将TLS连接代理到本地127.0.0.1:80上的HTTP服务。这时在远端看来,Trojan-Go服务就是一个HTTPS网站。 52 | 53 | - 如果TLS握手成功,并且被确认是Trojan协议头部,并且其中的密码正确,那么服务器将解析来自客户端的请求并进行代理,否则和上一步的处理方法相同。 54 | 55 | - 如果TLS握手失败,说明对方使用的不是TLS协议进行连接。此时Trojan-Go将这个TCP连接代理到本地127.0.0.1:1234上运行的HTTPS服务(或者HTTP服务),返回一个展示400 Bad Reqeust的HTTP页面。```fallback_port```是一个可选选项,如果没有填写,Trojan-Go会直接终止连接。虽然是可选的,但是还是强烈建议填写。 56 | 57 | 你可以通过使用浏览器访问你的域名```https://your-domain-name.com```来验证。如果工作正常,你的浏览器会显示一个正常的HTTPS保护的Web页面,页面内容与服务器本机80端口上的页面一致。你还可以使用```http://your-domain-name.com:443```验证```fallback_port```工作是否正常。 58 | 59 | 事实上,你甚至可以将Trojan-Go当作你的HTTPS服务器,用来给你的网站提供HTTPS服务。访客可以正常地通过Trojan-Go浏览你的网站,而和代理流量互不影响。但是注意,不要在```remote_port```和```fallback_port```搭建有高实时性需求的服务,Trojan-Go识别到非Trojan协议流量时会有意增加少许延迟以抵抗GFW基于时间的检测。 60 | 61 | 配置完成后,可以使用 62 | 63 | ```shell 64 | ./trojan-go -config ./server.json 65 | ``` 66 | 67 | 启动服务端。 68 | 69 | ### 客户端配置 70 | 71 | 对应的客户端配置client.json 72 | 73 | ```json 74 | { 75 | "run_type": "client", 76 | "local_addr": "127.0.0.1", 77 | "local_port": 1080, 78 | "remote_addr": "your_awesome_server", 79 | "remote_port": 443, 80 | "password": [ 81 | "your_awesome_password" 82 | ], 83 | "ssl": { 84 | "sni": "your-domain-name.com" 85 | } 86 | } 87 | ``` 88 | 89 | 这个客户端配置使Trojan-Go开启一个监听在本地1080端口的socks5/http代理(自动识别),远端服务器为your_awesome_server:443,your_awesome_server可以是IP或者域名。 90 | 91 | 如果你在```remote_addr```中填写的是域名,```sni```可以省略。如果你在```remote_addr```填写的是IP地址,```sni```字段应当填写你申请证书的对应域名,或者你自己签发的证书的Common Name,而且必须一致。注意,```sni```字段目前的在TLS协议中是**明文传送**的(目的是使服务器提供相应证书)。GFW已经被证实具有SNI探测和阻断能力,所以不要填写类似```google.com```等已经被封锁的域名,否则很有可能导致你的服务器也被遭到封锁。 92 | 93 | 配置完成后,可以使用 94 | 95 | ```shell 96 | ./trojan-go -config ./client.json 97 | ``` 98 | 99 | 启动客户端。 100 | 101 | 更多关于配置文件的信息,可以在左侧导航栏中找到相应介绍。 102 | -------------------------------------------------------------------------------- /docs/content/basic/trojan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Trojan基本原理" 3 | draft: false 4 | weight: 21 5 | --- 6 | 7 | 这个页面将会简单讲述Trojan协议的基本工作原理。如果你对于GFW和Trojan的工作方式不感兴趣,可以跳过这一小节。但为了更好地保护你的通讯安全性和节点的隐蔽性,我还是建议你阅读。 8 | 9 | ## 为什么(使用流密码的)Shadowsocks容易遭到封锁 10 | 11 | 防火墙在早期仅仅只是对出境流量进行截获和审查,也即**被动检测**。Shadowsocks的加密协议设计使得传输的数据包本身几乎没有任何特征,看起来类似于完全随机的比特流,这在早期的确能有效绕过GFW。 12 | 13 | 目前的GFW已经开始采用**主动探测**的方式。具体来说,当GFW发现一个可疑的无法识别的连接时(大流量,随机字节流,高位端口等特征),将会**主动连接**这个服务器端口,重放之前捕获到的流量(或者经过一些精心修改后重放)。Shadowsocks服务器检测到不正常的连接,将连接断开。这种不正常的流量和断开连接的行为被视作可疑的Shadowsocks服务器的特征,于是该服务器被加入GFW的可疑名单中。这个名单不一定立即生效,而是在某些特殊的敏感时期,可疑名单中的服务器会遭到暂时或者永久的封锁。该可疑名单是否封锁,可能由人为因素决定。 14 | 15 | 如果你想了解更多,可以参考[这篇文章](https://gfw.report/blog/gfw_shadowsocks/)。 16 | 17 | ## Trojan如何绕过GFW 18 | 19 | 与Shadowsocks相反,Trojan不使用自定义的加密协议来隐藏自身。相反,使用特征明显的TLS协议(TLS/SSL),使得流量看起来与正常的HTTPS网站相同。TLS是一个成熟的加密体系,HTTPS即使用TLS承载HTTP流量。使用**正确配置**的加密TLS隧道,可以保证传输的 20 | 21 | - 保密性(GFW无法得知传输的内容) 22 | 23 | - 完整性(一旦GFW试图篡改传输的密文,通讯双方都会发现) 24 | 25 | - 不可抵赖(GFW无法伪造身份冒充服务端或者客户端) 26 | 27 | - 前向安全(即使密钥泄露,GFW也无法解密先前的加密流量) 28 | 29 | 对于被动检测,Trojan协议的流量与HTTPS流量的特征和行为完全一致。而HTTPS流量占据了目前互联网流量的一半以上,且TLS握手成功后流量均为密文,几乎不存在可行方法从其中分辨出Trojan协议流量。 30 | 31 | 对于主动检测,当防火墙主动连接Trojan服务器进行检测时,Trojan可以正确识别非Trojan协议的流量。与Shadowsocks等代理不同的是,此时Trojan不会断开连接,而是将这个连接代理到一个正常的Web服务器。在GFW看来,该服务器的行为和一个普通的HTTPS网站行为完全相同,无法判断是否是一个Trojan代理节点。这也是Trojan推荐使用合法的域名、使用权威CA签名的HTTPS证书的原因: 这让你的服务器完全无法被GFW使用主动检测判定是一个Trojan服务器。 32 | 33 | 因此,就目前的情况来看,若要识别并阻断Trojan的连接,只能使用无差别封锁(封锁某个IP段,某一类证书,某一类域名,甚至阻断全国所有出境HTTPS连接)或发动大规模的中间人攻击(劫持所有TLS流量并劫持证书,审查内容)。对于中间人攻击,可以使用Websocket的双重TLS应对,高级配置中有详细讲解。 34 | -------------------------------------------------------------------------------- /docs/content/developer/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "实现细节和开发指南" 3 | draft: false 4 | weight: 40 5 | --- 6 | 7 | 这一部分介绍Trojan-Go底层实现的细节,主要面向开发者。 8 | -------------------------------------------------------------------------------- /docs/content/developer/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "API开发" 3 | draft: false 4 | weight: 100 5 | --- 6 | 7 | Trojan-Go基于gRPC实现了API,使用protobuf交换数据。客户端可获取流量和速度信息;服务端可获取各用户流量,速度,在线情况,并动态增删用户和限制速度。可以通过在配置文件中添加```api```选项激活API模块。下面是一个例子,各字段含义参见“完整的配置文件”一节。 8 | 9 | ```json 10 | ... 11 | "api": { 12 | "enabled": true, 13 | "api_addr": "0.0.0.0", 14 | "api_port": 10000, 15 | "ssl": { 16 | "enabled": true, 17 | "cert": "api_cert.crt", 18 | "key": "api_key.key", 19 | "verify_client": true, 20 | "client_cert": [ 21 | "api_client_cert1.crt", 22 | "api_client_cert2.crt" 23 | ] 24 | }, 25 | } 26 | ``` 27 | 28 | 如果需要实现API客户端进行对接,请参考api/service/api.proto文件。 29 | -------------------------------------------------------------------------------- /docs/content/developer/build.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "编译和自定义Trojan-Go" 3 | draft: false 4 | weight: 10 5 | --- 6 | 7 | 编译需要Go版本号高于1.14.x,请在编译前确认你的编译器版本。推荐使用snap安装和更新go。 8 | 9 | 编译方式非常简单,可以使用Makefile预设步骤进行编译: 10 | 11 | ```shell 12 | make 13 | make install #安装systemd服务等,可选 14 | ``` 15 | 16 | 或者直接使用Go进行编译: 17 | 18 | ```shell 19 | go build -tags "full" #编译完整版本 20 | ``` 21 | 22 | 可以通过指定GOOS和GOARCH环境变量,指定交叉编译的目标操作系统和架构,例如 23 | 24 | ```shell 25 | GOOS=windows GOARCH=386 go build -tags "full" #windows x86 26 | GOOS=linux GOARCH=arm64 go build -tags "full" #linux arm64 27 | ``` 28 | 29 | 你可以使用release.sh进行批量的多个平台的交叉编译,release版本使用了这个脚本进行构建。 30 | 31 | Trojan-Go的大多数模块是可插拔的。在build文件夹下可以找到各个模块的导入声明。如果你不需要其中某些功能,或者需要缩小可执行文件的体积,可以使用构建标签(tags)进行模块的自定义,例如 32 | 33 | ```shell 34 | go build -tags "full" #编译所有模块 35 | go build -tags "client" -trimpath -ldflags="-s -w -buildid=" #只有客户端功能,且去除符号表缩小体积 36 | go build -tags "server mysql" #只有服务端和mysql支持 37 | ``` 38 | 39 | 使用full标签等价于 40 | 41 | ```shell 42 | go build -tags "api client server forward nat other" 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/content/developer/mux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "多路复用" 3 | draft: false 4 | weight: 30 5 | --- 6 | 7 | Trojan-Go使用[smux](https://github.com/xtaci/smux)实现多路复用。同时实现了simplesocks协议用于进行代理传输。 8 | 9 | 当启用多路复用时,客户端首先发起TLS连接,使用正常trojan协议格式,但协议Command部分填入0x7f(protocol.Mux),标识此连接为复用连接(类似于http的upgrade),之后连接交由smux客户端管理。服务器收到请求头部后,交由smux服务器解析该连接的所有流量。在每条分离出的smux连接上,使用simplesocks协议(去除认证部分的trojan协议)标明代理目的地。自顶向下的协议栈如下: 10 | 11 | | 协议 | 备注 | 12 | | ----------- | -------- | 13 | | 真实流量 | 14 | | SimpleSocks | 15 | | smux | 16 | | Trojan | 用于鉴权 | 17 | | 底层协议 | | 18 | -------------------------------------------------------------------------------- /docs/content/developer/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "基本介绍" 3 | draft: false 4 | weight: 1 5 | --- 6 | 7 | Trojan-Go的核心部分有 8 | 9 | - tunnel 各个协议具体实现 10 | 11 | - proxy 代理核心 12 | 13 | - config 配置注册和解析模块 14 | 15 | - redirector 主动检测欺骗模块 16 | 17 | - statistics 用户认证和统计模块 18 | 19 | 可以在对应文件夹中找到相关源代码。 20 | 21 | ## tunnel.Tunnel隧道 22 | 23 | Trojan-Go将所有协议(包括路由功能等)抽象为隧道(tunnel.Tunnel接口),每个隧道可开启服务端(tunnel.Server接口)和客户端(tunnel.Client)。每个服务端可以从其底层隧道中,剥离并接受流(tunnel.Conn)和包(tunnel.PacketConn)。客户端可以向底层隧道,创建流和包。 24 | 25 | 每个隧道并不关心其下方的隧道是什么,但是每个隧道清楚知道这个它上方的其他隧道的相关信息。 26 | 27 | 所有隧道需要下层提供流或包传输支持,或两者都要求提供。所有隧道必须向上层隧道提供流传输支持,但不一定提供包传输。 28 | 29 | 隧道可能只有服务端,也可能只有客户端,也可能两者皆有。两者皆有的隧道,可被用于作为Trojan-Go客户端和服务端间的传输隧道。 30 | 31 | 注意,请区分Trojan-Go的服务端/客户端,和隧道的服务端/客户端的区别。下面是一个方便理解的图例。 32 | 33 | ```text 34 | 35 | 入站 GFW 出站 36 | -------->隧道A服务端->隧道B客户端 ----------------> 隧道B服务端->隧道C客户端-----------> 37 | (Trojan-Go客户端) (Trojan-Go服务端) 38 | 39 | ``` 40 | 41 | 最底层的隧道为传输层,即不从其他隧道获取或者创建流和包的隧道,充当上图中隧道A或者C的角色。 42 | 43 | - transport,可插拔传输层 44 | 45 | - socks,socks5代理,仅隧道服务端 46 | 47 | - tproxy,透明代理,仅隧道服务端 48 | 49 | - dokodemo,反向代理,仅隧道服务端 50 | 51 | - freedom,自由出站,仅隧道客户端 52 | 53 | 这几个隧道直接从TCP/UDP Socket创建流和包,不接受为其底层添加的任何隧道。 54 | 55 | 其他隧道,只要下层能满足上层对包和流传输的需求,则原则上可以任何方式,任何数量进行组合和堆叠。这些隧道在上图中充当隧道B的角色,他们有 56 | 57 | - trojan 58 | 59 | - websocket 60 | 61 | - mux 62 | 63 | - simplesocks 64 | 65 | - tls 66 | 67 | - router,路由功能,仅隧道客户端 68 | 69 | 他们都不关心其下层隧道实现。但可以根据到来的流和包,将其分发给上层隧道。 70 | 71 | 例如,在这张图中,是一个典型的Trojan-Go客户端和服务端,各个隧道自下往上堆叠的顺序是: 72 | 73 | - 隧道A: transport->socks 74 | 75 | - 隧道B: transport->tls->trojan 76 | 77 | - 隧道C: freedom 78 | 79 | 实际上的隧道堆叠的情况会比这个更复杂一些。通常的入站的隧道是一棵多叉树的形式,而非一条链。具体解释参考下文。 80 | 81 | ## proxy.Proxy代理核心 82 | 83 | 代理核心的作用,是监听上述隧道进行组合堆叠并形成的协议栈,将所有的入站协议栈(多个隧道Server的终端节点,见下)中抽取的流和包,以及对应元信息,转送给出站协议栈(一个隧道Client)。 84 | 85 | 注意,这里的入站协议栈可以有多个,如客户端可以同时从Socks5和HTTP协议栈中抽取流和包,服务端可以同时从Websocket承载的Trojan协议,和TLS承载的Trojan协议中抽取流和包等。但是出站协议栈只能有一个,如只使用TLS承载的Trojan协议出站。 86 | 87 | 为了描述入站协议栈(隧道服务端)的组合和堆叠方式,使用一棵多叉树对所有协议栈进行描述。你可以在proxy文件夹中各组件,看到构建树的过程。 88 | 89 | 而出站协议栈则比较简单,使用一个简单列表即可描述。 90 | 91 | 所以实际上,对于一个典型的开启了Websocket和Mux的客户端/服务端,上图的隧道堆叠模型为: 92 | 93 | 客户端 94 | 95 | - 入站(树) 96 | - transport (根) 97 | - adapter 能够识别HTTP和Socks流量并分发给上层协议 98 | - http (终端节点) 99 | - socks(终端节点) 100 | 101 | - 出站(链) 102 | - transport (根) 103 | - tls 104 | - websocket 105 | - trojan 106 | - mux 107 | - simplesocks 108 | 109 | 服务端 110 | 111 | - 入站(树) 112 | - transport (根) 113 | - tls 能够识别HTTP和非HTTP流量并分发 114 | - websocket 115 | - trojan(终端节点) 116 | - mux 117 | - simplesocks (终端节点) 118 | - trojan 能够识别mux和普通trojan流量并分发(终端节点) 119 | - mux 120 | - simplesocks (终端节点) 121 | 122 | - 出站(链) 123 | - freedom 124 | 125 | 注意,代理核心只从隧道构成的树的终端节点抽取流和包,并转送到唯一的出站上。多个终端节点的设计的目的,是使Trojan-Go同时兼容Websocket和Trojan协议入站连接,开启/未开启Mux的入站连接,以及HTTP/Socks5自动识别的功能。每个拥有多个儿子的树上节点,具有精确识别和分发流和包给不同的儿子节点的能力。这符合我们假定每个协议了解其上层承载协议的假设。 126 | -------------------------------------------------------------------------------- /docs/content/developer/plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "可插拔传输层插件开发" 3 | draft: false 4 | weight: 150 5 | --- 6 | 7 | Trojan-Go鼓励开发传输层插件,以丰富协议类型,增加与GFW对抗的战略纵深。 8 | 9 | 传输层插件的作用,是替代tansport隧道的TLS进行传输加密和混淆。 10 | 11 | 插件与Trojan-Go基于TCP Socket通讯,与Trojan-Go本身不存在任何耦合关系,你可以使用任何你喜欢的语言和设计模式进行开发。我们建议的参照[SIP003](https://shadowsocks.org/en/spec/Plugin.html)标准进行开发。如此开发的插件可以同时用于Trojan-Go和Shadowsocks。 12 | 13 | Trojan-Go开启插件功能后,仅使用TCP进行传输(明文)。你的插件只需要处理入站的TCP请求即可。你可以将这些TCP流量转换成任何你喜欢的流量格式,如QUIC,HTTP,甚至是ICMP。 14 | 15 | Trojan-Go插件设计原则,与Shadowsocks略有不同: 16 | 17 | 1. 插件本身可以对传输内容进行加密,混淆和完整性校验,以及可以抵抗重放攻击。 18 | 19 | 2. 插件应该伪造一种已有的、常见的服务(记做X服务),及其流量,在此基础上嵌入自己的加密内容。 20 | 21 | 3. 服务端的插件,在检验到内容被篡改/遭到重放时,**必须将此连接交由Trojan-Go处理**。具体步骤是,将已读入和未读入的内容一并发送给Trojan-Go,并建立双向连接,而不是直接断开。Trojan-Go将与一个真实的X服务器建立连接,使攻击者直接与真实的X服务器进行交互。 22 | 23 | 解释如下: 24 | 25 | 第一点原则,是由于Trojan协议本身并不加密。将TLS替换为传输层插件后,将**完全信任插件的安全性**。 26 | 27 | 第二点原则,是继承Trojan的精神。最适合隐藏一棵树的地方,是森林。 28 | 29 | 第三点原则,是为了充分利用Trojan-Go的抗主动探测特性。即使GFW对你的服务器进行主动探测,你的服务器也可以表现得与X服务一致,而不存在其他特征。 30 | 31 | 为了方便理解,举一个例子。 32 | 33 | 1. 假设你的插件伪装的是MySQL流量。防火墙通过流量嗅探,发现你的MySQL流量大得异常,决定主动连接你的服务器进行主动探测。 34 | 35 | 2. 防火墙连接到你的服务器并发送探测载荷,你的Trojan-Go服务端插件,经过校验,发现这个异常连接不是代理流量,于是将这个连接交由Trojan-Go处理。 36 | 37 | 3. Trojan-Go发现这个连接异常,将这个连接重定向到一个真正的MySQL服务器上。于是,防火墙开始与一个真正的MySQL服务器进行交互,发现其行为与真实MySQL服务器无异,无法对服务器进行封禁。 38 | 39 | 另外,即使你的协议协议和插件不能满足原则2和原则3,甚至不能很好满足原则1,我们同样鼓励开发。因为GFW仅仅针对流行的协议进行审计和封锁,此类协议(土制密码学/土制协议)只要不公开发表,同样能保持非常强健的生命力。 40 | -------------------------------------------------------------------------------- /docs/content/developer/simplesocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "SimpleSocks协议" 3 | draft: false 4 | weight: 50 5 | --- 6 | 7 | SimpleSocks协议是无鉴权机制的简单代理协议,本质上是去除了sha224的Trojan协议。使用该协议的目的是减少多路复用时的overhead。 8 | 9 | 只有启用多路复用之后,被复用的连接才会使用这个协议。也即SimpleSocks总是被SMux承载。 10 | 11 | SimpleSocks甚至比Socks5更简单,下面是头部结构。 12 | 13 | ```text 14 | +-----+------+----------+----------+-----------+ 15 | | CMD | ATYP | DST.ADDR | DST.PORT | Payload | 16 | +-----+------+----------+----------+-----------+ 17 | | 1 | 1 | Variable | 2 | Variable | 18 | +-----+------+----------+----------+-----------+ 19 | ``` 20 | 21 | 各字段定义与Trojan协议相同,不再赘述。 22 | -------------------------------------------------------------------------------- /docs/content/developer/trojan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Trojan协议" 3 | draft: false 4 | weight: 20 5 | --- 6 | 7 | Trojan-Go遵循原始的trojan协议,具体格式可以参考[Trojan文档](https://trojan-gfw.github.io/trojan/protocol),这里不再赘述。 8 | 9 | 默认情况下,trojan协议使用TLS来承载,协议栈如下: 10 | 11 | | 协议 | 12 | | -------- | 13 | | 真实流量 | 14 | | Trojan | 15 | | TLS | 16 | | TCP | 17 | -------------------------------------------------------------------------------- /docs/content/developer/websocket.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Websocket" 3 | draft: false 4 | weight: 40 5 | --- 6 | 7 | 由于使用CDN中转时,HTTPS对CDN透明,CDN可以审查Websocket传输内容。而Trojan协议本身是明文传输,因此为保证安全性,可添加一层Shadowsocks AEAD加密层以混淆流量特征并保证安全性。 8 | 9 | **如果你使用的是中国境内运营商提供的CDN,请务必开启AEAD加密** 10 | 11 | 开启AEAD加密后,Websocket承载的流量将被Shadowsocks AEAD加密,头部具体格式参见Shadowsocks白皮书。 12 | 13 | 开启Websocket支持后,协议栈如下: 14 | 15 | | 协议 | 备注 | 16 | | ----------- | ---------------- | 17 | | 真实流量 | | 18 | | SimpleSocks | 如果开启多路复用 | 19 | | smux | 如果开启多路复用 | 20 | | Trojan | | 21 | | Shadowsocks | 如果开启加密 | 22 | | Websocket | | 23 | | 传输层协议 | | 24 | -------------------------------------------------------------------------------- /example/client.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "client", 3 | "local_addr": "127.0.0.1", 4 | "local_port": 1080, 5 | "remote_addr": "your_server", 6 | "remote_port": 443, 7 | "password": [ 8 | "your_password" 9 | ], 10 | "ssl": { 11 | "sni": "your-domain-name.com" 12 | }, 13 | "mux": { 14 | "enabled": true 15 | }, 16 | "router": { 17 | "enabled": true, 18 | "bypass": [ 19 | "geoip:cn", 20 | "geoip:private", 21 | "geosite:cn", 22 | "geosite:private" 23 | ], 24 | "block": [ 25 | "geosite:category-ads" 26 | ], 27 | "proxy": [ 28 | "geosite:geolocation-!cn" 29 | ], 30 | "default_policy": "proxy", 31 | "geoip": "/usr/share/trojan-go/geoip.dat", 32 | "geosite": "/usr/share/trojan-go/geosite.dat" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/client.yaml: -------------------------------------------------------------------------------- 1 | run-type: client 2 | local-addr: 127.0.0.1 3 | local-port: 1080 4 | remote-addr: your_server 5 | remote-port: 443 6 | password: 7 | - your_password 8 | ssl: 9 | sni: your-domain-name.com 10 | mux: 11 | enabled: true 12 | router: 13 | enabled: true 14 | bypass: ['geoip:cn', 'geoip:private', 'geosite:cn', 'geosite:private'] 15 | block: ['geosite:category-ads'] 16 | proxy: ['geosite:geolocation-!cn'] 17 | default_policy: proxy 18 | geoip: /usr/share/trojan-go/geoip.dat 19 | geosite: /usr/share/trojan-go/geosite.dat 20 | -------------------------------------------------------------------------------- /example/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "run_type": "server", 3 | "local_addr": "0.0.0.0", 4 | "local_port": 443, 5 | "remote_addr": "127.0.0.1", 6 | "remote_port": 80, 7 | "password": [ 8 | "your_password" 9 | ], 10 | "ssl": { 11 | "cert": "your_cert.crt", 12 | "key": "your_key.key", 13 | "sni": "your-domain-name.com" 14 | }, 15 | "router": { 16 | "enabled": true, 17 | "block": [ 18 | "geoip:private" 19 | ], 20 | "geoip": "/usr/share/trojan-go/geoip.dat", 21 | "geosite": "/usr/share/trojan-go/geosite.dat" 22 | } 23 | } -------------------------------------------------------------------------------- /example/server.yaml: -------------------------------------------------------------------------------- 1 | run-type: server 2 | local-addr: 0.0.0.0 3 | local-port: 443 4 | remote-addr: 127.0.0.1 5 | remote-port: 80 6 | password: 7 | - your_password 8 | ssl: 9 | cert: your_cert.crt 10 | key: your_key.key 11 | sni: your-domain-name.com 12 | router: 13 | enabled: true 14 | block: 15 | - 'geoip:private' 16 | geoip: /usr/share/trojan-go/geoip.dat 17 | geosite: /usr/share/trojan-go/geosite.dat 18 | -------------------------------------------------------------------------------- /example/trojan-go.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Trojan-Go - An unidentifiable mechanism that helps you bypass GFW 3 | Documentation=https://p4gefau1t.github.io/trojan-go/ 4 | After=network.target nss-lookup.target 5 | 6 | [Service] 7 | User=nobody 8 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE 9 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE 10 | NoNewPrivileges=true 11 | ExecStart=/usr/bin/trojan-go -config /etc/trojan-go/config.json 12 | Restart=on-failure 13 | RestartSec=10s 14 | LimitNOFILE=infinity 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /example/trojan-go@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Trojan-Go - An unidentifiable mechanism that helps you bypass GFW 3 | Documentation=https://p4gefau1t.github.io/trojan-go/ 4 | After=network.target nss-lookup.target 5 | 6 | [Service] 7 | User=nobody 8 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE 9 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE 10 | NoNewPrivileges=true 11 | ExecStart=/usr/bin/trojan-go -config /etc/trojan-go/%i.json 12 | Restart=on-failure 13 | RestartSec=10s 14 | LimitNOFILE=infinity 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/p4gefau1t/trojan-go 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.6.0 7 | github.com/google/uuid v1.1.2 8 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 9 | github.com/refraction-networking/utls v0.0.0-20210713165636-0b2885c8c0d4 10 | github.com/shadowsocks/go-shadowsocks2 v0.1.5 11 | github.com/smartystreets/goconvey v1.6.4 12 | github.com/stretchr/testify v1.7.0 13 | github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da 14 | github.com/v2fly/v2ray-core/v4 v4.42.1 15 | github.com/xtaci/smux v1.5.15 16 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e 17 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b 18 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac 19 | google.golang.org/grpc v1.40.0 20 | google.golang.org/protobuf v1.27.1 21 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 22 | gorm.io/gorm v1.21.10 23 | ) 24 | 25 | require ( 26 | github.com/davecgh/go-spew v1.1.1 // indirect 27 | github.com/golang/protobuf v1.5.2 // indirect 28 | github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 // indirect 29 | github.com/jinzhu/inflection v1.0.0 // indirect 30 | github.com/jinzhu/now v1.1.2 // indirect 31 | github.com/jtolds/gls v4.20.0+incompatible // indirect 32 | github.com/pires/go-proxyproto v0.6.0 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect 35 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect 36 | go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a // indirect 37 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 38 | golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 // indirect 39 | golang.org/x/text v0.3.6 // indirect 40 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 41 | ) 42 | 43 | require github.com/cloudquery/sqlite v1.0.1 44 | 45 | require ( 46 | github.com/mattn/go-isatty v0.0.12 // indirect 47 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 48 | github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect 49 | github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect 50 | modernc.org/libc v1.7.12 // indirect 51 | modernc.org/mathutil v1.2.2 // indirect 52 | modernc.org/memory v1.0.4 // indirect 53 | modernc.org/sqlite v1.8.8 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /log/golog/buffer/buffer.go: -------------------------------------------------------------------------------- 1 | // Buffer-like byte slice 2 | // Copyright (c) 2017 Fadhli Dzil Ikram 3 | 4 | package buffer 5 | 6 | // Buffer type wrap up byte slice built-in type 7 | type Buffer []byte 8 | 9 | // Reset buffer position to start 10 | func (b *Buffer) Reset() { 11 | *b = Buffer([]byte(*b)[:0]) 12 | } 13 | 14 | // Append byte slice to buffer 15 | func (b *Buffer) Append(data []byte) { 16 | *b = append(*b, data...) 17 | } 18 | 19 | // AppendByte to buffer 20 | func (b *Buffer) AppendByte(data byte) { 21 | *b = append(*b, data) 22 | } 23 | 24 | // AppendInt to buffer 25 | func (b *Buffer) AppendInt(val int, width int) { 26 | var repr [8]byte 27 | reprCount := len(repr) - 1 28 | for val >= 10 || width > 1 { 29 | reminder := val / 10 30 | repr[reprCount] = byte('0' + val - reminder*10) 31 | val = reminder 32 | reprCount-- 33 | width-- 34 | } 35 | repr[reprCount] = byte('0' + val) 36 | b.Append(repr[reprCount:]) 37 | } 38 | 39 | // Bytes return underlying slice data 40 | func (b Buffer) Bytes() []byte { 41 | return []byte(b) 42 | } 43 | -------------------------------------------------------------------------------- /log/golog/buffer/buffer_test.go: -------------------------------------------------------------------------------- 1 | // Buffer-like byte slice 2 | // Copyright (c) 2017 Fadhli Dzil Ikram 3 | // 4 | // Test file for buffer 5 | 6 | package buffer 7 | 8 | import ( 9 | "testing" 10 | 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | func TestBufferAllocation(t *testing.T) { 15 | Convey("Given new unallocated buffer", t, func() { 16 | var buf Buffer 17 | 18 | Convey("When appended with data", func() { 19 | data := []byte("Hello") 20 | buf.Append(data) 21 | 22 | Convey("It should have same content as the original data", func() { 23 | So(buf.Bytes(), ShouldResemble, data) 24 | }) 25 | }) 26 | 27 | Convey("When appended with single byte", func() { 28 | data := byte('H') 29 | buf.AppendByte(data) 30 | 31 | Convey("It should have 1 byte length", func() { 32 | So(len(buf), ShouldEqual, 1) 33 | }) 34 | 35 | Convey("It should have same content", func() { 36 | So(buf.Bytes()[0], ShouldEqual, data) 37 | }) 38 | }) 39 | 40 | Convey("When appended with integer", func() { 41 | data := 12345 42 | repr := []byte("012345") 43 | buf.AppendInt(data, len(repr)) 44 | 45 | Convey("Should have same content with the integer representation", func() { 46 | So(buf.Bytes(), ShouldResemble, repr) 47 | }) 48 | }) 49 | }) 50 | } 51 | 52 | func TestBufferReset(t *testing.T) { 53 | Convey("Given allocated buffer", t, func() { 54 | var buf Buffer 55 | data := []byte("Hello") 56 | replace := []byte("World") 57 | buf.Append(data) 58 | 59 | Convey("When buffer reset", func() { 60 | buf.Reset() 61 | 62 | Convey("It should have zero length", func() { 63 | So(len(buf), ShouldEqual, 0) 64 | }) 65 | }) 66 | 67 | Convey("When buffer reset and replaced with another append", func() { 68 | buf.Reset() 69 | buf.Append(replace) 70 | 71 | Convey("It should have same content with the replaced data", func() { 72 | So(buf.Bytes(), ShouldResemble, replace) 73 | }) 74 | }) 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /log/golog/colorful/colorful.go: -------------------------------------------------------------------------------- 1 | // The color engine for the go-log library 2 | // Copyright (c) 2017 Fadhli Dzil Ikram 3 | 4 | package colorful 5 | 6 | import ( 7 | "runtime" 8 | 9 | "github.com/p4gefau1t/trojan-go/log/golog/buffer" 10 | ) 11 | 12 | // ColorBuffer add color option to buffer append 13 | type ColorBuffer struct { 14 | buffer.Buffer 15 | } 16 | 17 | // color palette map 18 | var ( 19 | colorOff = []byte("\033[0m") 20 | colorRed = []byte("\033[0;31m") 21 | colorGreen = []byte("\033[0;32m") 22 | colorOrange = []byte("\033[0;33m") 23 | colorBlue = []byte("\033[0;34m") 24 | colorPurple = []byte("\033[0;35m") 25 | colorCyan = []byte("\033[0;36m") 26 | colorGray = []byte("\033[0;37m") 27 | ) 28 | 29 | func init() { 30 | if runtime.GOOS != "linux" { 31 | colorOff = []byte("") 32 | colorRed = []byte("") 33 | colorGreen = []byte("") 34 | colorOrange = []byte("") 35 | colorBlue = []byte("") 36 | colorPurple = []byte("") 37 | colorCyan = []byte("") 38 | colorGray = []byte("") 39 | } 40 | } 41 | 42 | // Off apply no color to the data 43 | func (cb *ColorBuffer) Off() { 44 | cb.Append(colorOff) 45 | } 46 | 47 | // Red apply red color to the data 48 | func (cb *ColorBuffer) Red() { 49 | cb.Append(colorRed) 50 | } 51 | 52 | // Green apply green color to the data 53 | func (cb *ColorBuffer) Green() { 54 | cb.Append(colorGreen) 55 | } 56 | 57 | // Orange apply orange color to the data 58 | func (cb *ColorBuffer) Orange() { 59 | cb.Append(colorOrange) 60 | } 61 | 62 | // Blue apply blue color to the data 63 | func (cb *ColorBuffer) Blue() { 64 | cb.Append(colorBlue) 65 | } 66 | 67 | // Purple apply purple color to the data 68 | func (cb *ColorBuffer) Purple() { 69 | cb.Append(colorPurple) 70 | } 71 | 72 | // Cyan apply cyan color to the data 73 | func (cb *ColorBuffer) Cyan() { 74 | cb.Append(colorCyan) 75 | } 76 | 77 | // Gray apply gray color to the data 78 | func (cb *ColorBuffer) Gray() { 79 | cb.Append(colorGray) 80 | } 81 | 82 | // mixer mix the color on and off byte with the actual data 83 | func mixer(data []byte, color []byte) []byte { 84 | var result []byte 85 | return append(append(append(result, color...), data...), colorOff...) 86 | } 87 | 88 | // Red apply red color to the data 89 | func Red(data []byte) []byte { 90 | return mixer(data, colorRed) 91 | } 92 | 93 | // Green apply green color to the data 94 | func Green(data []byte) []byte { 95 | return mixer(data, colorGreen) 96 | } 97 | 98 | // Orange apply orange color to the data 99 | func Orange(data []byte) []byte { 100 | return mixer(data, colorOrange) 101 | } 102 | 103 | // Blue apply blue color to the data 104 | func Blue(data []byte) []byte { 105 | return mixer(data, colorBlue) 106 | } 107 | 108 | // Purple apply purple color to the data 109 | func Purple(data []byte) []byte { 110 | return mixer(data, colorPurple) 111 | } 112 | 113 | // Cyan apply cyan color to the data 114 | func Cyan(data []byte) []byte { 115 | return mixer(data, colorCyan) 116 | } 117 | 118 | // Gray apply gray color to the data 119 | func Gray(data []byte) []byte { 120 | return mixer(data, colorGray) 121 | } 122 | -------------------------------------------------------------------------------- /log/golog/colorful/colorful_test.go: -------------------------------------------------------------------------------- 1 | // The color engine for the go-log library 2 | // Copyright (c) 2017 Fadhli Dzil Ikram 3 | // 4 | // Test file 5 | 6 | package colorful 7 | 8 | import ( 9 | "testing" 10 | 11 | . "github.com/smartystreets/goconvey/convey" 12 | 13 | "github.com/p4gefau1t/trojan-go/log/golog/buffer" 14 | ) 15 | 16 | func TestColorBuffer(t *testing.T) { 17 | Convey("Given empty color buffer and test data", t, func() { 18 | var cb ColorBuffer 19 | var result buffer.Buffer 20 | 21 | // Add color to the result buffer 22 | result.Append(colorRed) 23 | result.Append(colorGreen) 24 | result.Append(colorOrange) 25 | result.Append(colorBlue) 26 | result.Append(colorPurple) 27 | result.Append(colorCyan) 28 | result.Append(colorGray) 29 | result.Append(colorOff) 30 | 31 | Convey("When appended with color", func() { 32 | cb.Red() 33 | cb.Green() 34 | cb.Orange() 35 | cb.Blue() 36 | cb.Purple() 37 | cb.Cyan() 38 | cb.Gray() 39 | cb.Off() 40 | 41 | Convey("It should have same content with the test data", func() { 42 | So(result.Bytes(), ShouldResemble, cb.Bytes()) 43 | }) 44 | }) 45 | }) 46 | } 47 | 48 | func TestColorMixer(t *testing.T) { 49 | Convey("Given mixer test result data", t, func() { 50 | var ( 51 | data = []byte("Hello") 52 | resultRed buffer.Buffer 53 | resultGreen buffer.Buffer 54 | resultOrange buffer.Buffer 55 | resultBlue buffer.Buffer 56 | resultPurple buffer.Buffer 57 | resultCyan buffer.Buffer 58 | resultGray buffer.Buffer 59 | ) 60 | 61 | // Add result to buffer 62 | resultRed.Append(colorRed) 63 | resultRed.Append(data) 64 | resultRed.Append(colorOff) 65 | 66 | resultGreen.Append(colorGreen) 67 | resultGreen.Append(data) 68 | resultGreen.Append(colorOff) 69 | 70 | resultOrange.Append(colorOrange) 71 | resultOrange.Append(data) 72 | resultOrange.Append(colorOff) 73 | 74 | resultBlue.Append(colorBlue) 75 | resultBlue.Append(data) 76 | resultBlue.Append(colorOff) 77 | 78 | resultPurple.Append(colorPurple) 79 | resultPurple.Append(data) 80 | resultPurple.Append(colorOff) 81 | 82 | resultCyan.Append(colorCyan) 83 | resultCyan.Append(data) 84 | resultCyan.Append(colorOff) 85 | 86 | resultGray.Append(colorGray) 87 | resultGray.Append(data) 88 | resultGray.Append(colorOff) 89 | 90 | Convey("It should have same result when data appended with Red color", func() { 91 | So(Red(data), ShouldResemble, resultRed.Bytes()) 92 | }) 93 | 94 | Convey("It should have same result when data appended with Green color", func() { 95 | So(Green(data), ShouldResemble, resultGreen.Bytes()) 96 | }) 97 | 98 | Convey("It should have same result when data appended with Orange color", func() { 99 | So(Orange(data), ShouldResemble, resultOrange.Bytes()) 100 | }) 101 | 102 | Convey("It should have same result when data appended with Blue color", func() { 103 | So(Blue(data), ShouldResemble, resultBlue.Bytes()) 104 | }) 105 | 106 | Convey("It should have same result when data appended with Purple color", func() { 107 | So(Purple(data), ShouldResemble, resultPurple.Bytes()) 108 | }) 109 | 110 | Convey("It should have same result when data appended with Cyan color", func() { 111 | So(Cyan(data), ShouldResemble, resultCyan.Bytes()) 112 | }) 113 | 114 | Convey("It should have same result when data appended with Gray color", func() { 115 | So(Gray(data), ShouldResemble, resultGray.Bytes()) 116 | }) 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | // LogLevel how much log to dump 9 | // 0: ALL; 1: INFO; 2: WARN; 3: ERROR; 4: FATAL; 5: OFF 10 | type LogLevel int 11 | 12 | const ( 13 | AllLevel LogLevel = 0 14 | InfoLevel LogLevel = 1 15 | WarnLevel LogLevel = 2 16 | ErrorLevel LogLevel = 3 17 | FatalLevel LogLevel = 4 18 | OffLevel LogLevel = 5 19 | ) 20 | 21 | type Logger interface { 22 | Fatal(v ...interface{}) 23 | Fatalf(format string, v ...interface{}) 24 | Error(v ...interface{}) 25 | Errorf(format string, v ...interface{}) 26 | Warn(v ...interface{}) 27 | Warnf(format string, v ...interface{}) 28 | Info(v ...interface{}) 29 | Infof(format string, v ...interface{}) 30 | Debug(v ...interface{}) 31 | Debugf(format string, v ...interface{}) 32 | Trace(v ...interface{}) 33 | Tracef(format string, v ...interface{}) 34 | SetLogLevel(level LogLevel) 35 | SetOutput(io.Writer) 36 | } 37 | 38 | var logger Logger = &EmptyLogger{} 39 | 40 | type EmptyLogger struct{} 41 | 42 | func (l *EmptyLogger) SetLogLevel(LogLevel) {} 43 | 44 | func (l *EmptyLogger) Fatal(v ...interface{}) { os.Exit(1) } 45 | 46 | func (l *EmptyLogger) Fatalf(format string, v ...interface{}) { os.Exit(1) } 47 | 48 | func (l *EmptyLogger) Error(v ...interface{}) {} 49 | 50 | func (l *EmptyLogger) Errorf(format string, v ...interface{}) {} 51 | 52 | func (l *EmptyLogger) Warn(v ...interface{}) {} 53 | 54 | func (l *EmptyLogger) Warnf(format string, v ...interface{}) {} 55 | 56 | func (l *EmptyLogger) Info(v ...interface{}) {} 57 | 58 | func (l *EmptyLogger) Infof(format string, v ...interface{}) {} 59 | 60 | func (l *EmptyLogger) Debug(v ...interface{}) {} 61 | 62 | func (l *EmptyLogger) Debugf(format string, v ...interface{}) {} 63 | 64 | func (l *EmptyLogger) Trace(v ...interface{}) {} 65 | 66 | func (l *EmptyLogger) Tracef(format string, v ...interface{}) {} 67 | 68 | func (l *EmptyLogger) SetOutput(w io.Writer) {} 69 | 70 | func Error(v ...interface{}) { 71 | logger.Error(v...) 72 | } 73 | 74 | func Errorf(format string, v ...interface{}) { 75 | logger.Errorf(format, v...) 76 | } 77 | 78 | func Warn(v ...interface{}) { 79 | logger.Warn(v...) 80 | } 81 | 82 | func Warnf(format string, v ...interface{}) { 83 | logger.Warnf(format, v...) 84 | } 85 | 86 | func Info(v ...interface{}) { 87 | logger.Info(v...) 88 | } 89 | 90 | func Infof(format string, v ...interface{}) { 91 | logger.Infof(format, v...) 92 | } 93 | 94 | func Debug(v ...interface{}) { 95 | logger.Debug(v...) 96 | } 97 | 98 | func Debugf(format string, v ...interface{}) { 99 | logger.Debugf(format, v...) 100 | } 101 | 102 | func Trace(v ...interface{}) { 103 | logger.Trace(v...) 104 | } 105 | 106 | func Tracef(format string, v ...interface{}) { 107 | logger.Tracef(format, v...) 108 | } 109 | 110 | func Fatal(v ...interface{}) { 111 | logger.Fatal(v...) 112 | } 113 | 114 | func Fatalf(format string, v ...interface{}) { 115 | logger.Fatalf(format, v...) 116 | } 117 | 118 | func SetLogLevel(level LogLevel) { 119 | logger.SetLogLevel(level) 120 | } 121 | 122 | func SetOutput(w io.Writer) { 123 | logger.SetOutput(w) 124 | } 125 | 126 | func RegisterLogger(l Logger) { 127 | logger = l 128 | } 129 | -------------------------------------------------------------------------------- /log/simplelog/simplelog.go: -------------------------------------------------------------------------------- 1 | package simplelog 2 | 3 | import ( 4 | "io" 5 | golog "log" 6 | "os" 7 | 8 | "github.com/p4gefau1t/trojan-go/log" 9 | ) 10 | 11 | func init() { 12 | log.RegisterLogger(&SimpleLogger{}) 13 | } 14 | 15 | type SimpleLogger struct { 16 | logLevel log.LogLevel 17 | } 18 | 19 | func (l *SimpleLogger) SetLogLevel(level log.LogLevel) { 20 | l.logLevel = level 21 | } 22 | 23 | func (l *SimpleLogger) Fatal(v ...interface{}) { 24 | if l.logLevel <= log.FatalLevel { 25 | golog.Fatal(v...) 26 | } 27 | os.Exit(1) 28 | } 29 | 30 | func (l *SimpleLogger) Fatalf(format string, v ...interface{}) { 31 | if l.logLevel <= log.FatalLevel { 32 | golog.Fatalf(format, v...) 33 | } 34 | os.Exit(1) 35 | } 36 | 37 | func (l *SimpleLogger) Error(v ...interface{}) { 38 | if l.logLevel <= log.ErrorLevel { 39 | golog.Println(v...) 40 | } 41 | } 42 | 43 | func (l *SimpleLogger) Errorf(format string, v ...interface{}) { 44 | if l.logLevel <= log.ErrorLevel { 45 | golog.Printf(format, v...) 46 | } 47 | } 48 | 49 | func (l *SimpleLogger) Warn(v ...interface{}) { 50 | if l.logLevel <= log.WarnLevel { 51 | golog.Println(v...) 52 | } 53 | } 54 | 55 | func (l *SimpleLogger) Warnf(format string, v ...interface{}) { 56 | if l.logLevel <= log.WarnLevel { 57 | golog.Printf(format, v...) 58 | } 59 | } 60 | 61 | func (l *SimpleLogger) Info(v ...interface{}) { 62 | if l.logLevel <= log.InfoLevel { 63 | golog.Println(v...) 64 | } 65 | } 66 | 67 | func (l *SimpleLogger) Infof(format string, v ...interface{}) { 68 | if l.logLevel <= log.InfoLevel { 69 | golog.Printf(format, v...) 70 | } 71 | } 72 | 73 | func (l *SimpleLogger) Debug(v ...interface{}) { 74 | if l.logLevel <= log.AllLevel { 75 | golog.Println(v...) 76 | } 77 | } 78 | 79 | func (l *SimpleLogger) Debugf(format string, v ...interface{}) { 80 | if l.logLevel <= log.AllLevel { 81 | golog.Printf(format, v...) 82 | } 83 | } 84 | 85 | func (l *SimpleLogger) Trace(v ...interface{}) { 86 | if l.logLevel <= log.AllLevel { 87 | golog.Println(v...) 88 | } 89 | } 90 | 91 | func (l *SimpleLogger) Tracef(format string, v ...interface{}) { 92 | if l.logLevel <= log.AllLevel { 93 | golog.Printf(format, v...) 94 | } 95 | } 96 | 97 | func (l *SimpleLogger) SetOutput(io.Writer) { 98 | // do nothing 99 | } 100 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | _ "github.com/p4gefau1t/trojan-go/component" 7 | "github.com/p4gefau1t/trojan-go/log" 8 | "github.com/p4gefau1t/trojan-go/option" 9 | ) 10 | 11 | func main() { 12 | flag.Parse() 13 | for { 14 | h, err := option.PopOptionHandler() 15 | if err != nil { 16 | log.Fatal("invalid options") 17 | } 18 | err = h.Handle() 19 | if err == nil { 20 | break 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /option/option.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import "github.com/p4gefau1t/trojan-go/common" 4 | 5 | type Handler interface { 6 | Name() string 7 | Handle() error 8 | Priority() int 9 | } 10 | 11 | var handlers = make(map[string]Handler) 12 | 13 | func RegisterHandler(h Handler) { 14 | handlers[h.Name()] = h 15 | } 16 | 17 | func PopOptionHandler() (Handler, error) { 18 | var maxHandler Handler = nil 19 | for _, h := range handlers { 20 | if maxHandler == nil || maxHandler.Priority() < h.Priority() { 21 | maxHandler = h 22 | } 23 | } 24 | if maxHandler == nil { 25 | return nil, common.NewError("no option left") 26 | } 27 | delete(handlers, maxHandler.Name()) 28 | return maxHandler, nil 29 | } 30 | -------------------------------------------------------------------------------- /proxy/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/config" 7 | "github.com/p4gefau1t/trojan-go/proxy" 8 | "github.com/p4gefau1t/trojan-go/tunnel/adapter" 9 | "github.com/p4gefau1t/trojan-go/tunnel/http" 10 | "github.com/p4gefau1t/trojan-go/tunnel/mux" 11 | "github.com/p4gefau1t/trojan-go/tunnel/router" 12 | "github.com/p4gefau1t/trojan-go/tunnel/shadowsocks" 13 | "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" 14 | "github.com/p4gefau1t/trojan-go/tunnel/socks" 15 | "github.com/p4gefau1t/trojan-go/tunnel/tls" 16 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 17 | "github.com/p4gefau1t/trojan-go/tunnel/trojan" 18 | "github.com/p4gefau1t/trojan-go/tunnel/websocket" 19 | ) 20 | 21 | const Name = "CLIENT" 22 | 23 | // GenerateClientTree generate general outbound protocol stack 24 | func GenerateClientTree(transportPlugin bool, muxEnabled bool, wsEnabled bool, ssEnabled bool, routerEnabled bool) []string { 25 | clientStack := []string{transport.Name} 26 | if !transportPlugin { 27 | clientStack = append(clientStack, tls.Name) 28 | } 29 | if wsEnabled { 30 | clientStack = append(clientStack, websocket.Name) 31 | } 32 | if ssEnabled { 33 | clientStack = append(clientStack, shadowsocks.Name) 34 | } 35 | clientStack = append(clientStack, trojan.Name) 36 | if muxEnabled { 37 | clientStack = append(clientStack, []string{mux.Name, simplesocks.Name}...) 38 | } 39 | if routerEnabled { 40 | clientStack = append(clientStack, router.Name) 41 | } 42 | return clientStack 43 | } 44 | 45 | func init() { 46 | proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { 47 | cfg := config.FromContext(ctx, Name).(*Config) 48 | adapterServer, err := adapter.NewServer(ctx, nil) 49 | if err != nil { 50 | return nil, err 51 | } 52 | ctx, cancel := context.WithCancel(ctx) 53 | 54 | root := &proxy.Node{ 55 | Name: adapter.Name, 56 | Next: make(map[string]*proxy.Node), 57 | IsEndpoint: false, 58 | Context: ctx, 59 | Server: adapterServer, 60 | } 61 | 62 | root.BuildNext(http.Name).IsEndpoint = true 63 | root.BuildNext(socks.Name).IsEndpoint = true 64 | 65 | clientStack := GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, cfg.Router.Enabled) 66 | c, err := proxy.CreateClientStack(ctx, clientStack) 67 | if err != nil { 68 | cancel() 69 | return nil, err 70 | } 71 | s := proxy.FindAllEndpoints(root) 72 | return proxy.NewProxy(ctx, cancel, s, c), nil 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /proxy/client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type MuxConfig struct { 6 | Enabled bool `json:"enabled" yaml:"enabled"` 7 | } 8 | 9 | type WebsocketConfig struct { 10 | Enabled bool `json:"enabled" yaml:"enabled"` 11 | } 12 | 13 | type RouterConfig struct { 14 | Enabled bool `json:"enabled" yaml:"enabled"` 15 | } 16 | 17 | type ShadowsocksConfig struct { 18 | Enabled bool `json:"enabled" yaml:"enabled"` 19 | } 20 | 21 | type TransportPluginConfig struct { 22 | Enabled bool `json:"enabled" yaml:"enabled"` 23 | } 24 | 25 | type Config struct { 26 | Mux MuxConfig `json:"mux" yaml:"mux"` 27 | Websocket WebsocketConfig `json:"websocket" yaml:"websocket"` 28 | Router RouterConfig `json:"router" yaml:"router"` 29 | Shadowsocks ShadowsocksConfig `json:"shadowsocks" yaml:"shadowsocks"` 30 | TransportPlugin TransportPluginConfig `json:"transport_plugin" yaml:"transport-plugin"` 31 | } 32 | 33 | func init() { 34 | config.RegisterConfigCreator(Name, func() interface{} { 35 | return new(Config) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /proxy/config.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type Config struct { 6 | RunType string `json:"run_type" yaml:"run-type"` 7 | LogLevel int `json:"log_level" yaml:"log-level"` 8 | LogFile string `json:"log_file" yaml:"log-file"` 9 | RelayBufferSize int `json:"relay_buffer_size" yaml:"relay_buffer_size"` 10 | } 11 | 12 | func init() { 13 | config.RegisterConfigCreator(Name, func() interface{} { 14 | return &Config{ 15 | LogLevel: 1, 16 | RelayBufferSize: 4 * 1024, 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /proxy/custom/config.go: -------------------------------------------------------------------------------- 1 | package custom 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | const Name = "CUSTOM" 6 | 7 | type NodeConfig struct { 8 | Protocol string `json:"protocol" yaml:"protocol"` 9 | Tag string `json:"tag" yaml:"tag"` 10 | Config interface{} `json:"config" yaml:"config"` 11 | } 12 | 13 | type StackConfig struct { 14 | Path [][]string `json:"path" yaml:"path"` 15 | Node []NodeConfig `json:"node" yaml:"node"` 16 | } 17 | 18 | type Config struct { 19 | Inbound StackConfig `json:"inbound" yaml:"inbound"` 20 | Outbound StackConfig `json:"outbound" yaml:"outbound"` 21 | } 22 | 23 | func init() { 24 | config.RegisterConfigCreator(Name, func() interface{} { 25 | return new(Config) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /proxy/forward/forward.go: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/config" 7 | "github.com/p4gefau1t/trojan-go/proxy" 8 | "github.com/p4gefau1t/trojan-go/proxy/client" 9 | "github.com/p4gefau1t/trojan-go/tunnel" 10 | "github.com/p4gefau1t/trojan-go/tunnel/dokodemo" 11 | ) 12 | 13 | const Name = "FORWARD" 14 | 15 | func init() { 16 | proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { 17 | cfg := config.FromContext(ctx, Name).(*client.Config) 18 | ctx, cancel := context.WithCancel(ctx) 19 | serverStack := []string{dokodemo.Name} 20 | clientStack := client.GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, cfg.Router.Enabled) 21 | c, err := proxy.CreateClientStack(ctx, clientStack) 22 | if err != nil { 23 | cancel() 24 | return nil, err 25 | } 26 | s, err := proxy.CreateServerStack(ctx, serverStack) 27 | if err != nil { 28 | cancel() 29 | return nil, err 30 | } 31 | return proxy.NewProxy(ctx, cancel, []tunnel.Server{s}, c), nil 32 | }) 33 | } 34 | 35 | func init() { 36 | config.RegisterConfigCreator(Name, func() interface{} { 37 | return new(client.Config) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /proxy/nat/nat.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package nat 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/config" 11 | "github.com/p4gefau1t/trojan-go/proxy" 12 | "github.com/p4gefau1t/trojan-go/proxy/client" 13 | "github.com/p4gefau1t/trojan-go/tunnel" 14 | "github.com/p4gefau1t/trojan-go/tunnel/tproxy" 15 | ) 16 | 17 | const Name = "NAT" 18 | 19 | func init() { 20 | proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { 21 | cfg := config.FromContext(ctx, Name).(*client.Config) 22 | if cfg.Router.Enabled { 23 | return nil, common.NewError("router is not allowed in nat mode") 24 | } 25 | ctx, cancel := context.WithCancel(ctx) 26 | serverStack := []string{tproxy.Name} 27 | clientStack := client.GenerateClientTree(cfg.TransportPlugin.Enabled, cfg.Mux.Enabled, cfg.Websocket.Enabled, cfg.Shadowsocks.Enabled, false) 28 | c, err := proxy.CreateClientStack(ctx, clientStack) 29 | if err != nil { 30 | cancel() 31 | return nil, err 32 | } 33 | s, err := proxy.CreateServerStack(ctx, serverStack) 34 | if err != nil { 35 | cancel() 36 | return nil, err 37 | } 38 | return proxy.NewProxy(ctx, cancel, []tunnel.Server{s}, c), nil 39 | }) 40 | } 41 | 42 | func init() { 43 | config.RegisterConfigCreator(Name, func() interface{} { 44 | return new(client.Config) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /proxy/nat/nat_stub.go: -------------------------------------------------------------------------------- 1 | package nat 2 | -------------------------------------------------------------------------------- /proxy/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/p4gefau1t/trojan-go/config" 5 | "github.com/p4gefau1t/trojan-go/proxy/client" 6 | ) 7 | 8 | func init() { 9 | config.RegisterConfigCreator(Name, func() interface{} { 10 | return new(client.Config) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /proxy/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/config" 7 | "github.com/p4gefau1t/trojan-go/proxy" 8 | "github.com/p4gefau1t/trojan-go/proxy/client" 9 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 10 | "github.com/p4gefau1t/trojan-go/tunnel/mux" 11 | "github.com/p4gefau1t/trojan-go/tunnel/router" 12 | "github.com/p4gefau1t/trojan-go/tunnel/shadowsocks" 13 | "github.com/p4gefau1t/trojan-go/tunnel/simplesocks" 14 | "github.com/p4gefau1t/trojan-go/tunnel/tls" 15 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 16 | "github.com/p4gefau1t/trojan-go/tunnel/trojan" 17 | "github.com/p4gefau1t/trojan-go/tunnel/websocket" 18 | ) 19 | 20 | const Name = "SERVER" 21 | 22 | func init() { 23 | proxy.RegisterProxyCreator(Name, func(ctx context.Context) (*proxy.Proxy, error) { 24 | cfg := config.FromContext(ctx, Name).(*client.Config) 25 | ctx, cancel := context.WithCancel(ctx) 26 | transportServer, err := transport.NewServer(ctx, nil) 27 | if err != nil { 28 | cancel() 29 | return nil, err 30 | } 31 | clientStack := []string{freedom.Name} 32 | if cfg.Router.Enabled { 33 | clientStack = []string{freedom.Name, router.Name} 34 | } 35 | 36 | root := &proxy.Node{ 37 | Name: transport.Name, 38 | Next: make(map[string]*proxy.Node), 39 | IsEndpoint: false, 40 | Context: ctx, 41 | Server: transportServer, 42 | } 43 | 44 | if !cfg.TransportPlugin.Enabled { 45 | root = root.BuildNext(tls.Name) 46 | } 47 | 48 | trojanSubTree := root 49 | if cfg.Shadowsocks.Enabled { 50 | trojanSubTree = trojanSubTree.BuildNext(shadowsocks.Name) 51 | } 52 | trojanSubTree.BuildNext(trojan.Name).BuildNext(mux.Name).BuildNext(simplesocks.Name).IsEndpoint = true 53 | trojanSubTree.BuildNext(trojan.Name).IsEndpoint = true 54 | 55 | wsSubTree := root.BuildNext(websocket.Name) 56 | if cfg.Shadowsocks.Enabled { 57 | wsSubTree = wsSubTree.BuildNext(shadowsocks.Name) 58 | } 59 | wsSubTree.BuildNext(trojan.Name).BuildNext(mux.Name).BuildNext(simplesocks.Name).IsEndpoint = true 60 | wsSubTree.BuildNext(trojan.Name).IsEndpoint = true 61 | 62 | serverList := proxy.FindAllEndpoints(root) 63 | clientList, err := proxy.CreateClientStack(ctx, clientStack) 64 | if err != nil { 65 | cancel() 66 | return nil, err 67 | } 68 | return proxy.NewProxy(ctx, cancel, serverList, clientList), nil 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /proxy/stack.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/log" 7 | "github.com/p4gefau1t/trojan-go/tunnel" 8 | ) 9 | 10 | type Node struct { 11 | Name string 12 | Next map[string]*Node 13 | IsEndpoint bool 14 | context.Context 15 | tunnel.Server 16 | tunnel.Client 17 | } 18 | 19 | func (n *Node) BuildNext(name string) *Node { 20 | if next, found := n.Next[name]; found { 21 | return next 22 | } 23 | t, err := tunnel.GetTunnel(name) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | s, err := t.NewServer(n.Context, n.Server) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | newNode := &Node{ 32 | Name: name, 33 | Next: make(map[string]*Node), 34 | Context: n.Context, 35 | Server: s, 36 | } 37 | n.Next[name] = newNode 38 | return newNode 39 | } 40 | 41 | func (n *Node) LinkNextNode(next *Node) *Node { 42 | if next, found := n.Next[next.Name]; found { 43 | return next 44 | } 45 | n.Next[next.Name] = next 46 | t, err := tunnel.GetTunnel(next.Name) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | s, err := t.NewServer(next.Context, n.Server) // context of the child nodes have been initialized 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | next.Server = s 55 | return next 56 | } 57 | 58 | func FindAllEndpoints(root *Node) []tunnel.Server { 59 | list := make([]tunnel.Server, 0) 60 | if root.IsEndpoint || len(root.Next) == 0 { 61 | list = append(list, root.Server) 62 | } 63 | for _, next := range root.Next { 64 | list = append(list, FindAllEndpoints(next)...) 65 | } 66 | return list 67 | } 68 | 69 | // CreateClientStack create client tunnel stacks from lists 70 | func CreateClientStack(ctx context.Context, clientStack []string) (tunnel.Client, error) { 71 | var client tunnel.Client 72 | for _, name := range clientStack { 73 | t, err := tunnel.GetTunnel(name) 74 | if err != nil { 75 | return nil, err 76 | } 77 | client, err = t.NewClient(ctx, client) 78 | if err != nil { 79 | return nil, err 80 | } 81 | } 82 | return client, nil 83 | } 84 | 85 | // CreateServerStack create server tunnel stack from list 86 | func CreateServerStack(ctx context.Context, serverStack []string) (tunnel.Server, error) { 87 | var server tunnel.Server 88 | for _, name := range serverStack { 89 | t, err := tunnel.GetTunnel(name) 90 | if err != nil { 91 | return nil, err 92 | } 93 | server, err = t.NewServer(ctx, server) 94 | if err != nil { 95 | return nil, err 96 | } 97 | } 98 | return server, nil 99 | } 100 | -------------------------------------------------------------------------------- /recorder/recorder.go: -------------------------------------------------------------------------------- 1 | package recorder 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "sync" 7 | "time" 8 | 9 | "github.com/p4gefau1t/trojan-go/log" 10 | ) 11 | 12 | var Capacity int = 10 // capacity of each subscriber 13 | var subscribers sync.Map 14 | 15 | type option struct { 16 | recordChan chan Record 17 | transport string 18 | targetPort string 19 | includePayload bool 20 | } 21 | 22 | type Record struct { 23 | Timestamp string 24 | UserHash string 25 | ClientIp string 26 | ClientPort string 27 | TargetHost string 28 | TargetPort string 29 | Transport string 30 | Payload []byte 31 | } 32 | 33 | func Add(hash string, clientAddr, targetAddr net.Addr, transport string, payload []byte) { 34 | clientIP, clientPort, _ := net.SplitHostPort(clientAddr.String()) 35 | targetHost, targetPort, _ := net.SplitHostPort(targetAddr.String()) 36 | 37 | record := Record{ 38 | Timestamp: strconv.Itoa(int(time.Now().UnixMilli())), 39 | UserHash: hash, 40 | ClientIp: clientIP, 41 | ClientPort: clientPort, 42 | TargetHost: targetHost, 43 | TargetPort: targetPort, 44 | Transport: transport, 45 | Payload: payload, 46 | } 47 | broadcast(record) 48 | } 49 | 50 | func Subscribe(uid string, transport, targetPort string, includePayload bool) chan Record { 51 | log.Debug("New recorder subscriber", uid) 52 | opt := option{ 53 | recordChan: make(chan Record, Capacity), 54 | transport: transport, 55 | targetPort: targetPort, 56 | includePayload: includePayload, 57 | } 58 | subscribers.Store(uid, opt) 59 | return opt.recordChan 60 | } 61 | 62 | func Unsubscribe(uid string) { 63 | log.Debug("Delete recorder subscriber", uid) 64 | subscribers.Delete(uid) 65 | } 66 | 67 | func broadcast(record Record) { 68 | payload := record.Payload 69 | 70 | subscribers.Range(func(uid, o interface{}) bool { 71 | opt := o.(option) 72 | if opt.transport != "" && opt.transport != record.Transport { 73 | return true 74 | } 75 | if opt.targetPort != "" && opt.targetPort != record.TargetPort { 76 | return true 77 | } 78 | if opt.includePayload { 79 | buf := make([]byte, len(payload)) 80 | copy(buf, payload) 81 | record.Payload = buf 82 | } else { 83 | record.Payload = nil 84 | } 85 | 86 | select { 87 | case opt.recordChan <- record: 88 | default: 89 | } 90 | return true 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /redirector/redirector.go: -------------------------------------------------------------------------------- 1 | package redirector 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "reflect" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/log" 11 | ) 12 | 13 | type Dial func(net.Addr) (net.Conn, error) 14 | 15 | func defaultDial(addr net.Addr) (net.Conn, error) { 16 | return net.Dial("tcp", addr.String()) 17 | } 18 | 19 | type Redirection struct { 20 | Dial 21 | RedirectTo net.Addr 22 | InboundConn net.Conn 23 | } 24 | 25 | type Redirector struct { 26 | ctx context.Context 27 | redirectionChan chan *Redirection 28 | } 29 | 30 | func (r *Redirector) Redirect(redirection *Redirection) { 31 | select { 32 | case r.redirectionChan <- redirection: 33 | log.Debug("redirect request") 34 | case <-r.ctx.Done(): 35 | log.Debug("exiting") 36 | } 37 | } 38 | 39 | func (r *Redirector) worker() { 40 | for { 41 | select { 42 | case redirection := <-r.redirectionChan: 43 | handle := func(redirection *Redirection) { 44 | if redirection.InboundConn == nil || reflect.ValueOf(redirection.InboundConn).IsNil() { 45 | log.Error("nil inbound conn") 46 | return 47 | } 48 | defer redirection.InboundConn.Close() 49 | if redirection.RedirectTo == nil || reflect.ValueOf(redirection.RedirectTo).IsNil() { 50 | log.Error("nil redirection addr") 51 | return 52 | } 53 | if redirection.Dial == nil { 54 | redirection.Dial = defaultDial 55 | } 56 | log.Warn("redirecting connection from", redirection.InboundConn.RemoteAddr(), "to", redirection.RedirectTo.String()) 57 | outboundConn, err := redirection.Dial(redirection.RedirectTo) 58 | if err != nil { 59 | log.Error(common.NewError("failed to redirect to target address").Base(err)) 60 | return 61 | } 62 | defer outboundConn.Close() 63 | errChan := make(chan error, 2) 64 | copyConn := func(a, b net.Conn) { 65 | _, err := io.Copy(a, b) 66 | errChan <- err 67 | } 68 | go copyConn(outboundConn, redirection.InboundConn) 69 | go copyConn(redirection.InboundConn, outboundConn) 70 | select { 71 | case err := <-errChan: 72 | if err != nil { 73 | log.Error(common.NewError("failed to redirect").Base(err)) 74 | } 75 | log.Info("redirection done") 76 | case <-r.ctx.Done(): 77 | log.Debug("exiting") 78 | return 79 | } 80 | } 81 | go handle(redirection) 82 | case <-r.ctx.Done(): 83 | log.Debug("shutting down redirector") 84 | return 85 | } 86 | } 87 | } 88 | 89 | func NewRedirector(ctx context.Context) *Redirector { 90 | r := &Redirector{ 91 | ctx: ctx, 92 | redirectionChan: make(chan *Redirection, 64), 93 | } 94 | go r.worker() 95 | return r 96 | } 97 | -------------------------------------------------------------------------------- /redirector/redirector_test.go: -------------------------------------------------------------------------------- 1 | package redirector 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/p4gefau1t/trojan-go/common" 13 | "github.com/p4gefau1t/trojan-go/test/util" 14 | ) 15 | 16 | func TestRedirector(t *testing.T) { 17 | ctx, cancel := context.WithCancel(context.Background()) 18 | redir := NewRedirector(ctx) 19 | redir.Redirect(&Redirection{ 20 | Dial: nil, 21 | RedirectTo: nil, 22 | InboundConn: nil, 23 | }) 24 | var fakeAddr net.Addr 25 | var fakeConn net.Conn 26 | redir.Redirect(&Redirection{ 27 | Dial: nil, 28 | RedirectTo: fakeAddr, 29 | InboundConn: fakeConn, 30 | }) 31 | redir.Redirect(&Redirection{ 32 | Dial: nil, 33 | RedirectTo: nil, 34 | InboundConn: fakeConn, 35 | }) 36 | redir.Redirect(&Redirection{ 37 | Dial: nil, 38 | RedirectTo: fakeAddr, 39 | InboundConn: nil, 40 | }) 41 | l, err := net.Listen("tcp", "127.0.0.1:0") 42 | common.Must(err) 43 | conn1, err := net.Dial("tcp", l.Addr().String()) 44 | common.Must(err) 45 | conn2, err := l.Accept() 46 | common.Must(err) 47 | redirAddr, err := net.ResolveTCPAddr("tcp", util.HTTPAddr) 48 | common.Must(err) 49 | redir.Redirect(&Redirection{ 50 | Dial: nil, 51 | RedirectTo: redirAddr, 52 | InboundConn: conn2, 53 | }) 54 | time.Sleep(time.Second) 55 | req, err := http.NewRequest("GET", "http://localhost/", nil) 56 | common.Must(err) 57 | req.Write(conn1) 58 | buf := make([]byte, 1024) 59 | conn1.Read(buf) 60 | fmt.Println(string(buf)) 61 | if !strings.HasPrefix(string(buf), "HTTP/1.1 200 OK") { 62 | t.Fail() 63 | } 64 | cancel() 65 | conn1.Close() 66 | conn2.Close() 67 | } 68 | -------------------------------------------------------------------------------- /statistic/memory/config.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/p4gefau1t/trojan-go/config" 5 | ) 6 | 7 | type Config struct { 8 | Passwords []string `json:"password" yaml:"password"` 9 | Sqlite string `json:"sqlite" yaml:"sqlite"` 10 | } 11 | 12 | func init() { 13 | config.RegisterConfigCreator(Name, func() interface{} { 14 | return &Config{} 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /statistic/mysql/config.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/p4gefau1t/trojan-go/config" 5 | ) 6 | 7 | type MySQLConfig struct { 8 | Enabled bool `json:"enabled" yaml:"enabled"` 9 | ServerHost string `json:"server_addr" yaml:"server-addr"` 10 | ServerPort int `json:"server_port" yaml:"server-port"` 11 | Database string `json:"database" yaml:"database"` 12 | Username string `json:"username" yaml:"username"` 13 | Password string `json:"password" yaml:"password"` 14 | CheckRate int `json:"check_rate" yaml:"check-rate"` 15 | } 16 | 17 | type Config struct { 18 | MySQL MySQLConfig `json:"mysql" yaml:"mysql"` 19 | } 20 | 21 | func init() { 22 | config.RegisterConfigCreator(Name, func() interface{} { 23 | return &Config{ 24 | MySQL: MySQLConfig{ 25 | ServerPort: 3306, 26 | CheckRate: 30, 27 | }, 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /statistic/sqlite/qulite_nonlinux.go: -------------------------------------------------------------------------------- 1 | //go:build !linux || !(amd64 || 386 || arm || arm64) 2 | 3 | package sqlite 4 | 5 | import "github.com/p4gefau1t/trojan-go/statistic" 6 | 7 | const Name = "sqlite" 8 | 9 | type Persistencer struct{} 10 | 11 | func NewSqlitePersistencer(path string) (*Persistencer, error) { 12 | return &Persistencer{}, nil 13 | } 14 | 15 | func (p *Persistencer) SaveUser(u statistic.Metadata) error { 16 | return nil 17 | } 18 | 19 | func (p *Persistencer) LoadUser(hash string) (statistic.Metadata, error) { 20 | var u User 21 | return &u, nil 22 | } 23 | 24 | func (p *Persistencer) DeleteUser(hash string) error { 25 | return nil 26 | } 27 | 28 | func (p *Persistencer) ListUser(f func(hash string, u statistic.Metadata) bool) error { 29 | return nil 30 | } 31 | 32 | func (p *Persistencer) UpdateUserTraffic(hash string, sent, recv uint64) error { 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /statistic/sqlite/sqlite.go: -------------------------------------------------------------------------------- 1 | //go:build linux && (amd64 || 386 || arm || arm64) 2 | 3 | package sqlite 4 | 5 | import ( 6 | "errors" 7 | 8 | "github.com/cloudquery/sqlite" 9 | "github.com/p4gefau1t/trojan-go/statistic" 10 | "gorm.io/gorm" 11 | "gorm.io/gorm/clause" 12 | ) 13 | 14 | const Name = "sqlite" 15 | 16 | type Persistencer struct { 17 | db *gorm.DB 18 | } 19 | 20 | func NewSqlitePersistencer(path string) (*Persistencer, error) { 21 | db, err := gorm.Open(sqlite.Open(path), &gorm.Config{}) 22 | if err != nil { 23 | return nil, err 24 | } 25 | err = db.AutoMigrate(&User{}) 26 | if err != nil { 27 | return nil, err 28 | } 29 | sp := &Persistencer{ 30 | db: db, 31 | } 32 | 33 | return sp, nil 34 | } 35 | 36 | func (p *Persistencer) SaveUser(u statistic.Metadata) error { 37 | if u == nil { 38 | return errors.New("user is nil") 39 | } 40 | ls, lr := u.GetSpeedLimit() 41 | usr := &User{ 42 | Hash: u.GetHash(), 43 | MaxIPNum: u.GetIPLimit(), 44 | SendLimit: ls, 45 | RecvLimit: lr, 46 | Sent: make([]byte, 8), 47 | Recv: make([]byte, 8), 48 | } 49 | ts, tr := u.GetTraffic() 50 | usr.setSent(ts) 51 | usr.setRecv(tr) 52 | err := p.db.Clauses(clause.OnConflict{ 53 | Columns: []clause.Column{{Name: "hash"}}, 54 | UpdateAll: true, 55 | }).Create(usr).Error 56 | if err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | func (p *Persistencer) LoadUser(hash string) (statistic.Metadata, error) { 63 | var u User 64 | err := p.db.First(&u, "hash = ?", hash).Error 65 | if err != nil { 66 | return nil, err 67 | } 68 | return &u, nil 69 | } 70 | 71 | func (p *Persistencer) DeleteUser(hash string) error { 72 | err := p.db.Delete(&User{Hash: hash}).Error 73 | if err != nil { 74 | return err 75 | } 76 | return nil 77 | } 78 | 79 | func (p *Persistencer) ListUser(f func(hash string, u statistic.Metadata) bool) error { 80 | users := make([]User, 0) 81 | err := p.db.Find(&users).Error 82 | if err != nil { 83 | return err 84 | } 85 | for _, u := range users { 86 | if goOn := f(u.Hash, &u); !goOn { 87 | break 88 | } 89 | } 90 | return nil 91 | } 92 | 93 | func (p *Persistencer) UpdateUserTraffic(hash string, sent, recv uint64) error { 94 | u := &User{ 95 | Hash: hash, 96 | Sent: make([]byte, 8), 97 | Recv: make([]byte, 8), 98 | } 99 | u.setSent(sent) 100 | u.setRecv(recv) 101 | return p.db.Model(&User{Hash: hash}).Updates(u).Error 102 | } 103 | -------------------------------------------------------------------------------- /statistic/sqlite/user.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | type User struct { 8 | Hash string `gorm:"primary_key"` 9 | // uint64 = 8 byte binary 10 | Sent []byte `gorm:"type:TEXT"` 11 | Recv []byte `gorm:"type:TEXT"` 12 | MaxIPNum int 13 | SendLimit int 14 | RecvLimit int 15 | } 16 | 17 | func (u *User) setSent(sent uint64) { 18 | binary.BigEndian.PutUint64(u.Sent, sent) 19 | } 20 | func (u *User) getSent() uint64 { 21 | return binary.BigEndian.Uint64(u.Sent) 22 | } 23 | 24 | func (u *User) setRecv(recv uint64) { 25 | binary.BigEndian.PutUint64(u.Recv, recv) 26 | } 27 | func (u *User) getRecv() uint64 { 28 | return binary.BigEndian.Uint64(u.Recv) 29 | } 30 | 31 | func (u *User) GetHash() string { 32 | return u.Hash 33 | } 34 | 35 | func (u *User) GetTraffic() (sent, recv uint64) { 36 | return u.getSent(), u.getRecv() 37 | } 38 | 39 | func (u *User) GetSpeedLimit() (sent, recv int) { 40 | return u.SendLimit, u.RecvLimit 41 | } 42 | 43 | func (u *User) GetIPLimit() int { 44 | return u.MaxIPNum 45 | } 46 | -------------------------------------------------------------------------------- /statistic/statistics.go: -------------------------------------------------------------------------------- 1 | package statistic 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/log" 11 | ) 12 | 13 | const Name = "STATISTICS" 14 | 15 | type Metadata interface { 16 | GetHash() string 17 | GetTraffic() (sent, recv uint64) 18 | GetSpeedLimit() (sent, recv int) 19 | GetIPLimit() int 20 | } 21 | 22 | type TrafficMeter interface { 23 | io.Closer 24 | AddSentTraffic(sent int) 25 | AddRecvTraffic(recv int) 26 | ResetTraffic() (sent, recv uint64) 27 | GetSpeed() (sent, recv uint64) 28 | } 29 | 30 | type IPRecorder interface { 31 | AddIP(string) bool 32 | DelIP(string) bool 33 | GetIP() int 34 | } 35 | 36 | type User interface { 37 | Metadata 38 | TrafficMeter 39 | IPRecorder 40 | } 41 | 42 | type Persistencer interface { 43 | SaveUser(Metadata) error 44 | LoadUser(hash string) (Metadata, error) 45 | DeleteUser(hash string) error 46 | ListUser(func(hash string, u Metadata) bool) error 47 | UpdateUserTraffic(hash string, sent, recv uint64) error 48 | } 49 | 50 | type Authenticator interface { 51 | io.Closer 52 | AuthUser(hash string) (valid bool, user User) 53 | AddUser(hash string) error 54 | DelUser(hash string) error 55 | SetUserTraffic(hash string, sent, recv uint64) error 56 | SetUserSpeedLimit(hash string, send, recv int) error 57 | SetUserIPLimit(hash string, limit int) error 58 | ListUsers() []User 59 | } 60 | 61 | type Creator func(ctx context.Context) (Authenticator, error) 62 | 63 | var ( 64 | createdAuthLock sync.Mutex 65 | authCreators = make(map[string]Creator) 66 | createdAuth = make(map[context.Context]Authenticator) 67 | ) 68 | 69 | func RegisterAuthenticatorCreator(name string, creator Creator) { 70 | authCreators[name] = creator 71 | } 72 | 73 | func NewAuthenticator(ctx context.Context, name string) (Authenticator, error) { 74 | // allocate a unique authenticator for each context 75 | createdAuthLock.Lock() // avoid concurrent map read/write 76 | defer createdAuthLock.Unlock() 77 | if auth, found := createdAuth[ctx]; found { 78 | log.Debug("authenticator has been created:", name) 79 | return auth, nil 80 | } 81 | creator, found := authCreators[strings.ToUpper(name)] 82 | if !found { 83 | return nil, common.NewError("auth driver name " + name + " not found") 84 | } 85 | auth, err := creator(ctx) 86 | if err != nil { 87 | return nil, err 88 | } 89 | createdAuth[ctx] = auth 90 | return auth, err 91 | } 92 | -------------------------------------------------------------------------------- /test/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "fmt" 7 | "net" 8 | "sync" 9 | 10 | "github.com/p4gefau1t/trojan-go/common" 11 | ) 12 | 13 | // CheckConn checks if two netConn were connected and work properly 14 | func CheckConn(a net.Conn, b net.Conn) bool { 15 | payload1 := make([]byte, 1024) 16 | payload2 := make([]byte, 1024) 17 | 18 | result1 := make([]byte, 1024) 19 | result2 := make([]byte, 1024) 20 | 21 | rand.Reader.Read(payload1) 22 | rand.Reader.Read(payload2) 23 | 24 | wg := sync.WaitGroup{} 25 | wg.Add(2) 26 | 27 | go func() { 28 | a.Write(payload1) 29 | a.Read(result2) 30 | wg.Done() 31 | }() 32 | 33 | go func() { 34 | b.Read(result1) 35 | b.Write(payload2) 36 | wg.Done() 37 | }() 38 | 39 | wg.Wait() 40 | 41 | return bytes.Equal(payload1, result1) && bytes.Equal(payload2, result2) 42 | } 43 | 44 | // CheckPacketOverConn checks if two PacketConn streaming over a connection work properly 45 | func CheckPacketOverConn(a, b net.PacketConn) bool { 46 | port := common.PickPort("tcp", "127.0.0.1") 47 | addr := &net.UDPAddr{ 48 | IP: net.ParseIP("127.0.0.1"), 49 | Port: port, 50 | } 51 | 52 | payload1 := make([]byte, 1024) 53 | payload2 := make([]byte, 1024) 54 | 55 | result1 := make([]byte, 1024) 56 | result2 := make([]byte, 1024) 57 | 58 | rand.Reader.Read(payload1) 59 | rand.Reader.Read(payload2) 60 | 61 | common.Must2(a.WriteTo(payload1, addr)) 62 | _, addr1, err := b.ReadFrom(result1) 63 | common.Must(err) 64 | if addr1.String() != addr.String() { 65 | return false 66 | } 67 | 68 | common.Must2(a.WriteTo(payload2, addr)) 69 | _, addr2, err := b.ReadFrom(result2) 70 | common.Must(err) 71 | if addr2.String() != addr.String() { 72 | return false 73 | } 74 | 75 | return bytes.Equal(payload1, result1) && bytes.Equal(payload2, result2) 76 | } 77 | 78 | func CheckPacket(a, b net.PacketConn) bool { 79 | payload1 := make([]byte, 1024) 80 | payload2 := make([]byte, 1024) 81 | 82 | result1 := make([]byte, 1024) 83 | result2 := make([]byte, 1024) 84 | 85 | rand.Reader.Read(payload1) 86 | rand.Reader.Read(payload2) 87 | 88 | _, err := a.WriteTo(payload1, b.LocalAddr()) 89 | common.Must(err) 90 | _, _, err = b.ReadFrom(result1) 91 | common.Must(err) 92 | 93 | _, err = b.WriteTo(payload2, a.LocalAddr()) 94 | common.Must(err) 95 | _, _, err = a.ReadFrom(result2) 96 | common.Must(err) 97 | 98 | return bytes.Equal(payload1, result1) && bytes.Equal(payload2, result2) 99 | } 100 | 101 | func GetTestAddr() string { 102 | port := common.PickPort("tcp", "127.0.0.1") 103 | return fmt.Sprintf("127.0.0.1:%d", port) 104 | } 105 | -------------------------------------------------------------------------------- /tunnel/adapter/config.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type Config struct { 6 | LocalHost string `json:"local_addr" yaml:"local-addr"` 7 | LocalPort int `json:"local_port" yaml:"local-port"` 8 | } 9 | 10 | func init() { 11 | config.RegisterConfigCreator(Name, func() interface{} { 12 | return new(Config) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /tunnel/adapter/tunnel.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "ADAPTER" 10 | 11 | type Tunnel struct{} 12 | 13 | func (t *Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | panic("not supported") 19 | } 20 | 21 | func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/dokodemo/config.go: -------------------------------------------------------------------------------- 1 | package dokodemo 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type Config struct { 6 | LocalHost string `json:"local_addr" yaml:"local-addr"` 7 | LocalPort int `json:"local_port" yaml:"local-port"` 8 | TargetHost string `json:"target_addr" yaml:"target-addr"` 9 | TargetPort int `json:"target_port" yaml:"target-port"` 10 | UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"` 11 | } 12 | 13 | func init() { 14 | config.RegisterConfigCreator(Name, func() interface{} { 15 | return &Config{ 16 | UDPTimeout: 60, 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /tunnel/dokodemo/conn.go: -------------------------------------------------------------------------------- 1 | package dokodemo 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/p4gefau1t/trojan-go/common" 8 | "github.com/p4gefau1t/trojan-go/tunnel" 9 | ) 10 | 11 | const MaxPacketSize = 1024 * 8 12 | 13 | type Conn struct { 14 | net.Conn 15 | src *tunnel.Address 16 | targetMetadata *tunnel.Metadata 17 | } 18 | 19 | func (c *Conn) Metadata() *tunnel.Metadata { 20 | return c.targetMetadata 21 | } 22 | 23 | // PacketConn receive packet info from the packet dispatcher 24 | type PacketConn struct { 25 | net.PacketConn 26 | metadata *tunnel.Metadata 27 | input chan []byte 28 | output chan []byte 29 | src net.Addr 30 | ctx context.Context 31 | cancel context.CancelFunc 32 | } 33 | 34 | func (c *PacketConn) Close() error { 35 | c.cancel() 36 | // don't close the underlying udp socket 37 | return nil 38 | } 39 | 40 | func (c *PacketConn) ReadFrom(p []byte) (int, net.Addr, error) { 41 | return c.ReadWithMetadata(p) 42 | } 43 | 44 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { 45 | address, err := tunnel.NewAddressFromAddr("udp", addr.String()) 46 | if err != nil { 47 | return 0, err 48 | } 49 | return c.WriteWithMetadata(p, &tunnel.Metadata{ 50 | Address: address, 51 | }) 52 | } 53 | 54 | func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { 55 | select { 56 | case payload := <-c.input: 57 | n := copy(p, payload) 58 | return n, c.metadata, nil 59 | case <-c.ctx.Done(): 60 | return 0, nil, common.NewError("dokodemo packet conn closed") 61 | } 62 | } 63 | 64 | func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { 65 | newP := make([]byte, len(p)) 66 | copy(newP, p) 67 | select { 68 | case c.output <- newP: 69 | return len(newP), nil 70 | case <-c.ctx.Done(): 71 | return 0, common.NewError("dokodemo packet conn failed to write") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tunnel/dokodemo/dokodemo_test.go: -------------------------------------------------------------------------------- 1 | package dokodemo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "sync" 8 | "testing" 9 | 10 | "github.com/p4gefau1t/trojan-go/common" 11 | "github.com/p4gefau1t/trojan-go/config" 12 | "github.com/p4gefau1t/trojan-go/test/util" 13 | ) 14 | 15 | func TestDokodemo(t *testing.T) { 16 | cfg := &Config{ 17 | LocalHost: "127.0.0.1", 18 | LocalPort: common.PickPort("tcp", "127.0.0.1"), 19 | TargetHost: "127.0.0.1", 20 | TargetPort: common.PickPort("tcp", "127.0.0.1"), 21 | UDPTimeout: 30, 22 | } 23 | ctx := config.WithConfig(context.Background(), Name, cfg) 24 | s, err := NewServer(ctx, nil) 25 | common.Must(err) 26 | conn1, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", cfg.LocalPort)) 27 | common.Must(err) 28 | conn2, err := s.AcceptConn(nil) 29 | common.Must(err) 30 | if !util.CheckConn(conn1, conn2) { 31 | t.Fail() 32 | } 33 | conn1.Close() 34 | conn2.Close() 35 | 36 | wg := sync.WaitGroup{} 37 | wg.Add(1) 38 | 39 | packet1, err := net.ListenPacket("udp", "") 40 | common.Must(err) 41 | common.Must2(packet1.(*net.UDPConn).WriteToUDP([]byte("hello1"), &net.UDPAddr{ 42 | IP: net.ParseIP("127.0.0.1"), 43 | Port: cfg.LocalPort, 44 | })) 45 | packet2, err := s.AcceptPacket(nil) 46 | common.Must(err) 47 | buf := [100]byte{} 48 | n, m, err := packet2.ReadWithMetadata(buf[:]) 49 | common.Must(err) 50 | if m.Address.Port != cfg.TargetPort { 51 | t.Fail() 52 | } 53 | if string(buf[:n]) != "hello1" { 54 | t.Fail() 55 | } 56 | fmt.Println(n, m, string(buf[:n])) 57 | 58 | if !util.CheckPacket(packet1, packet2) { 59 | t.Fail() 60 | } 61 | 62 | packet3, err := net.ListenPacket("udp", "") 63 | common.Must(err) 64 | common.Must2(packet3.(*net.UDPConn).WriteToUDP([]byte("hello2"), &net.UDPAddr{ 65 | IP: net.ParseIP("127.0.0.1"), 66 | Port: cfg.LocalPort, 67 | })) 68 | packet4, err := s.AcceptPacket(nil) 69 | common.Must(err) 70 | n, m, err = packet4.ReadWithMetadata(buf[:]) 71 | common.Must(err) 72 | if m.Address.Port != cfg.TargetPort { 73 | t.Fail() 74 | } 75 | if string(buf[:n]) != "hello2" { 76 | t.Fail() 77 | } 78 | fmt.Println(n, m, string(buf[:n])) 79 | 80 | wg = sync.WaitGroup{} 81 | wg.Add(2) 82 | go func() { 83 | if !util.CheckPacket(packet3, packet4) { 84 | t.Fail() 85 | } 86 | wg.Done() 87 | }() 88 | go func() { 89 | if !util.CheckPacket(packet1, packet2) { 90 | t.Fail() 91 | } 92 | wg.Done() 93 | }() 94 | wg.Wait() 95 | s.Close() 96 | } 97 | -------------------------------------------------------------------------------- /tunnel/dokodemo/tunnel.go: -------------------------------------------------------------------------------- 1 | package dokodemo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/common" 7 | "github.com/p4gefau1t/trojan-go/tunnel" 8 | ) 9 | 10 | const Name = "DOKODEMO" 11 | 12 | type Tunnel struct{ tunnel.Tunnel } 13 | 14 | func (*Tunnel) Name() string { 15 | return Name 16 | } 17 | 18 | func (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { 19 | return NewServer(ctx, underlay) 20 | } 21 | 22 | func (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) { 23 | return nil, common.NewError("not supported") 24 | } 25 | 26 | func init() { 27 | tunnel.RegisterTunnel(Name, &Tunnel{}) 28 | } 29 | -------------------------------------------------------------------------------- /tunnel/freedom/config.go: -------------------------------------------------------------------------------- 1 | package freedom 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type Config struct { 6 | LocalHost string `json:"local_addr" yaml:"local-addr"` 7 | LocalPort int `json:"local_port" yaml:"local-port"` 8 | TCP TCPConfig `json:"tcp" yaml:"tcp"` 9 | ForwardProxy ForwardProxyConfig `json:"forward_proxy" yaml:"forward-proxy"` 10 | } 11 | 12 | type TCPConfig struct { 13 | PreferIPV4 bool `json:"prefer_ipv4" yaml:"prefer-ipv4"` 14 | KeepAlive bool `json:"keep_alive" yaml:"keep-alive"` 15 | NoDelay bool `json:"no_delay" yaml:"no-delay"` 16 | } 17 | 18 | type ForwardProxyConfig struct { 19 | Enabled bool `json:"enabled" yaml:"enabled"` 20 | ProxyHost string `json:"proxy_addr" yaml:"proxy-addr"` 21 | ProxyPort int `json:"proxy_port" yaml:"proxy-port"` 22 | Username string `json:"username" yaml:"username"` 23 | Password string `json:"password" yaml:"password"` 24 | } 25 | 26 | func init() { 27 | config.RegisterConfigCreator(Name, func() interface{} { 28 | return &Config{ 29 | TCP: TCPConfig{ 30 | PreferIPV4: false, 31 | NoDelay: true, 32 | KeepAlive: true, 33 | }, 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /tunnel/freedom/conn.go: -------------------------------------------------------------------------------- 1 | package freedom 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | "github.com/txthinking/socks5" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/log" 11 | "github.com/p4gefau1t/trojan-go/tunnel" 12 | ) 13 | 14 | const MaxPacketSize = 1024 * 8 15 | 16 | type Conn struct { 17 | net.Conn 18 | } 19 | 20 | func (c *Conn) Metadata() *tunnel.Metadata { 21 | return nil 22 | } 23 | 24 | type PacketConn struct { 25 | *net.UDPConn 26 | } 27 | 28 | func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { 29 | return c.WriteTo(p, m.Address) 30 | } 31 | 32 | func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { 33 | n, addr, err := c.ReadFrom(p) 34 | if err != nil { 35 | return 0, nil, err 36 | } 37 | address, err := tunnel.NewAddressFromAddr("udp", addr.String()) 38 | common.Must(err) 39 | metadata := &tunnel.Metadata{ 40 | Address: address, 41 | } 42 | return n, metadata, nil 43 | } 44 | 45 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { 46 | if udpAddr, ok := addr.(*net.UDPAddr); ok { 47 | return c.WriteToUDP(p, udpAddr) 48 | } 49 | ip, err := addr.(*tunnel.Address).ResolveIP() 50 | if err != nil { 51 | return 0, err 52 | } 53 | udpAddr := &net.UDPAddr{ 54 | IP: ip, 55 | Port: addr.(*tunnel.Address).Port, 56 | } 57 | return c.WriteToUDP(p, udpAddr) 58 | } 59 | 60 | type SocksPacketConn struct { 61 | net.PacketConn 62 | socksAddr *net.UDPAddr 63 | socksClient *socks5.Client 64 | } 65 | 66 | func (c *SocksPacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata) (int, error) { 67 | buf := bytes.NewBuffer(make([]byte, 0, MaxPacketSize)) 68 | buf.Write([]byte{0, 0, 0}) // RSV, FRAG 69 | common.Must(metadata.Address.WriteTo(buf)) 70 | buf.Write(payload) 71 | _, err := c.PacketConn.WriteTo(buf.Bytes(), c.socksAddr) 72 | if err != nil { 73 | return 0, err 74 | } 75 | log.Debug("sent udp packet to " + c.socksAddr.String() + " with metadata " + metadata.String()) 76 | return len(payload), nil 77 | } 78 | 79 | func (c *SocksPacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, error) { 80 | buf := make([]byte, MaxPacketSize) 81 | n, from, err := c.PacketConn.ReadFrom(buf) 82 | if err != nil { 83 | return 0, nil, err 84 | } 85 | log.Debug("recv udp packet from " + from.String()) 86 | addr := new(tunnel.Address) 87 | r := bytes.NewBuffer(buf[3:n]) 88 | if err := addr.ReadFrom(r); err != nil { 89 | return 0, nil, common.NewError("socks5 failed to parse addr in the packet").Base(err) 90 | } 91 | length, err := r.Read(payload) 92 | if err != nil { 93 | return 0, nil, err 94 | } 95 | return length, &tunnel.Metadata{ 96 | Address: addr, 97 | }, nil 98 | } 99 | 100 | func (c *SocksPacketConn) Close() error { 101 | c.socksClient.Close() 102 | return c.PacketConn.Close() 103 | } 104 | -------------------------------------------------------------------------------- /tunnel/freedom/freedom_test.go: -------------------------------------------------------------------------------- 1 | package freedom 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "testing" 8 | "time" 9 | 10 | "github.com/txthinking/socks5" 11 | 12 | "github.com/p4gefau1t/trojan-go/common" 13 | "github.com/p4gefau1t/trojan-go/test/util" 14 | "github.com/p4gefau1t/trojan-go/tunnel" 15 | ) 16 | 17 | func TestConn(t *testing.T) { 18 | ctx, cancel := context.WithCancel(context.Background()) 19 | client := &Client{ 20 | ctx: ctx, 21 | cancel: cancel, 22 | } 23 | addr, err := tunnel.NewAddressFromAddr("tcp", util.EchoAddr) 24 | common.Must(err) 25 | conn1, err := client.DialConn(addr, nil) 26 | common.Must(err) 27 | 28 | sendBuf := util.GeneratePayload(1024) 29 | recvBuf := [1024]byte{} 30 | 31 | common.Must2(conn1.Write(sendBuf)) 32 | common.Must2(conn1.Read(recvBuf[:])) 33 | 34 | if !bytes.Equal(sendBuf, recvBuf[:]) { 35 | t.Fail() 36 | } 37 | client.Close() 38 | } 39 | 40 | func TestPacket(t *testing.T) { 41 | ctx, cancel := context.WithCancel(context.Background()) 42 | client := &Client{ 43 | ctx: ctx, 44 | cancel: cancel, 45 | } 46 | addr, err := tunnel.NewAddressFromAddr("udp", util.EchoAddr) 47 | common.Must(err) 48 | conn1, err := client.DialPacket(nil) 49 | common.Must(err) 50 | 51 | sendBuf := util.GeneratePayload(1024) 52 | recvBuf := [1024]byte{} 53 | 54 | common.Must2(conn1.WriteTo(sendBuf, addr)) 55 | _, _, err = conn1.ReadFrom(recvBuf[:]) 56 | common.Must(err) 57 | 58 | if !bytes.Equal(sendBuf, recvBuf[:]) { 59 | t.Fail() 60 | } 61 | } 62 | 63 | func TestSocks(t *testing.T) { 64 | ctx, cancel := context.WithCancel(context.Background()) 65 | 66 | socksAddr := tunnel.NewAddressFromHostPort("udp", "127.0.0.1", common.PickPort("udp", "127.0.0.1")) 67 | client := &Client{ 68 | ctx: ctx, 69 | cancel: cancel, 70 | proxyAddr: socksAddr, 71 | forwardProxy: true, 72 | noDelay: true, 73 | } 74 | target, err := tunnel.NewAddressFromAddr("tcp", util.EchoAddr) 75 | common.Must(err) 76 | s, _ := socks5.NewClassicServer(socksAddr.String(), "127.0.0.1", "", "", 0, 0) 77 | s.Handle = &socks5.DefaultHandle{} 78 | go s.RunTCPServer() 79 | go s.RunUDPServer() 80 | 81 | time.Sleep(time.Second * 2) 82 | conn, err := client.DialConn(target, nil) 83 | common.Must(err) 84 | payload := util.GeneratePayload(1024) 85 | common.Must2(conn.Write(payload)) 86 | 87 | recvBuf := [1024]byte{} 88 | conn.Read(recvBuf[:]) 89 | if !bytes.Equal(recvBuf[:], payload) { 90 | t.Fail() 91 | } 92 | conn.Close() 93 | 94 | packet, err := client.DialPacket(nil) 95 | common.Must(err) 96 | common.Must2(packet.WriteWithMetadata(payload, &tunnel.Metadata{ 97 | Address: target, 98 | })) 99 | 100 | recvBuf = [1024]byte{} 101 | n, m, err := packet.ReadWithMetadata(recvBuf[:]) 102 | common.Must(err) 103 | 104 | if n != 1024 || !bytes.Equal(recvBuf[:], payload) { 105 | t.Fail() 106 | } 107 | 108 | fmt.Println(m) 109 | packet.Close() 110 | client.Close() 111 | } 112 | -------------------------------------------------------------------------------- /tunnel/freedom/tunnel.go: -------------------------------------------------------------------------------- 1 | package freedom 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "FREEDOM" 10 | 11 | type Tunnel struct{} 12 | 13 | func (*Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (*Tunnel) NewServer(ctx context.Context, client tunnel.Server) (tunnel.Server, error) { 22 | panic("not supported") 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/http/http_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "testing" 11 | "time" 12 | 13 | "github.com/p4gefau1t/trojan-go/common" 14 | "github.com/p4gefau1t/trojan-go/config" 15 | "github.com/p4gefau1t/trojan-go/test/util" 16 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 17 | ) 18 | 19 | func TestHTTP(t *testing.T) { 20 | port := common.PickPort("tcp", "127.0.0.1") 21 | ctx := config.WithConfig(context.Background(), transport.Name, &transport.Config{ 22 | LocalHost: "127.0.0.1", 23 | LocalPort: port, 24 | }) 25 | 26 | tcpServer, err := transport.NewServer(ctx, nil) 27 | common.Must(err) 28 | s, err := NewServer(ctx, tcpServer) 29 | common.Must(err) 30 | 31 | for i := 0; i < 10; i++ { 32 | go func() { 33 | resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d", port)) 34 | common.Must(err) 35 | defer resp.Body.Close() 36 | }() 37 | time.Sleep(time.Microsecond * 10) 38 | conn, err := s.AcceptConn(nil) 39 | common.Must(err) 40 | bufReader := bufio.NewReader(bufio.NewReader(conn)) 41 | req, err := http.ReadRequest(bufReader) 42 | common.Must(err) 43 | fmt.Println(req) 44 | ioutil.ReadAll(req.Body) 45 | req.Body.Close() 46 | resp, err := http.Get("http://127.0.0.1:" + util.HTTPPort) 47 | common.Must(err) 48 | defer resp.Body.Close() 49 | err = resp.Write(conn) 50 | common.Must(err) 51 | buf := [100]byte{} 52 | _, err = conn.Read(buf[:]) 53 | if err == nil { 54 | t.Fail() 55 | } 56 | conn.Close() 57 | } 58 | 59 | req, err := http.NewRequest(http.MethodConnect, "https://google.com:443", nil) 60 | common.Must(err) 61 | conn1, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 62 | common.Must(err) 63 | go func() { 64 | common.Must(req.Write(conn1)) 65 | }() 66 | 67 | conn2, err := s.AcceptConn(nil) 68 | common.Must(err) 69 | 70 | if conn2.Metadata().Port != 443 || conn2.Metadata().DomainName != "google.com" { 71 | t.Fail() 72 | } 73 | 74 | connResp := "HTTP/1.1 200 Connection established\r\n\r\n" 75 | buf := make([]byte, len(connResp)) 76 | _, err = conn1.Read(buf) 77 | common.Must(err) 78 | if string(buf) != connResp { 79 | t.Fail() 80 | } 81 | 82 | if !util.CheckConn(conn1, conn2) { 83 | t.Fail() 84 | } 85 | 86 | conn1.Close() 87 | conn2.Close() 88 | s.Close() 89 | } 90 | -------------------------------------------------------------------------------- /tunnel/http/tunnel.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "HTTP" 10 | 11 | type Tunnel struct{} 12 | 13 | func (t *Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | panic("not supported") 19 | } 20 | 21 | func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/mux/config.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type MuxConfig struct { 6 | Enabled bool `json:"enabled" yaml:"enabled"` 7 | IdleTimeout int `json:"idle_timeout" yaml:"idle-timeout"` 8 | Concurrency int `json:"concurrency" yaml:"concurrency"` 9 | } 10 | 11 | type Config struct { 12 | Mux MuxConfig `json:"mux" yaml:"mux"` 13 | } 14 | 15 | func init() { 16 | config.RegisterConfigCreator(Name, func() interface{} { 17 | return &Config{ 18 | Mux: MuxConfig{ 19 | Enabled: false, 20 | IdleTimeout: 30, 21 | Concurrency: 8, 22 | }, 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /tunnel/mux/conn.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "io" 5 | "math/rand" 6 | 7 | "github.com/p4gefau1t/trojan-go/log" 8 | "github.com/p4gefau1t/trojan-go/tunnel" 9 | ) 10 | 11 | type stickyConn struct { 12 | tunnel.Conn 13 | synQueue chan []byte 14 | finQueue chan []byte 15 | } 16 | 17 | func (c *stickyConn) stickToPayload(p []byte) []byte { 18 | buf := make([]byte, 0, len(p)+16) 19 | for { 20 | select { 21 | case header := <-c.synQueue: 22 | buf = append(buf, header...) 23 | default: 24 | goto stick1 25 | } 26 | } 27 | stick1: 28 | buf = append(buf, p...) 29 | for { 30 | select { 31 | case header := <-c.finQueue: 32 | buf = append(buf, header...) 33 | default: 34 | goto stick2 35 | } 36 | } 37 | stick2: 38 | return buf 39 | } 40 | 41 | func (c *stickyConn) Close() error { 42 | const maxPaddingLength = 512 43 | padding := [maxPaddingLength + 8]byte{'A', 'B', 'C', 'D', 'E', 'F'} // for debugging 44 | buf := c.stickToPayload(nil) 45 | c.Write(append(buf, padding[:rand.Intn(maxPaddingLength)]...)) 46 | return c.Conn.Close() 47 | } 48 | 49 | func (c *stickyConn) Write(p []byte) (int, error) { 50 | if len(p) == 8 { 51 | if p[0] == 1 || p[0] == 2 { // smux 8 bytes header 52 | switch p[1] { 53 | // THE CONTENT OF THE BUFFER MIGHT CHANGE 54 | // NEVER STORE THE POINTER TO HEADER, COPY THE HEADER INSTEAD 55 | case 0: 56 | // cmdSYN 57 | header := make([]byte, 8) 58 | copy(header, p) 59 | c.synQueue <- header 60 | return 8, nil 61 | case 1: 62 | // cmdFIN 63 | header := make([]byte, 8) 64 | copy(header, p) 65 | c.finQueue <- header 66 | return 8, nil 67 | } 68 | } else { 69 | log.Debug("other 8 bytes header") 70 | } 71 | } 72 | _, err := c.Conn.Write(c.stickToPayload(p)) 73 | return len(p), err 74 | } 75 | 76 | func newStickyConn(conn tunnel.Conn) *stickyConn { 77 | return &stickyConn{ 78 | Conn: conn, 79 | synQueue: make(chan []byte, 128), 80 | finQueue: make(chan []byte, 128), 81 | } 82 | } 83 | 84 | type Conn struct { 85 | rwc io.ReadWriteCloser 86 | tunnel.Conn 87 | } 88 | 89 | func (c *Conn) Read(p []byte) (int, error) { 90 | return c.rwc.Read(p) 91 | } 92 | 93 | func (c *Conn) Write(p []byte) (int, error) { 94 | return c.rwc.Write(p) 95 | } 96 | 97 | func (c *Conn) Close() error { 98 | return c.rwc.Close() 99 | } 100 | -------------------------------------------------------------------------------- /tunnel/mux/mux_test.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/p4gefau1t/trojan-go/common" 8 | "github.com/p4gefau1t/trojan-go/config" 9 | "github.com/p4gefau1t/trojan-go/test/util" 10 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 11 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 12 | ) 13 | 14 | func TestMux(t *testing.T) { 15 | muxCfg := &Config{ 16 | Mux: MuxConfig{ 17 | Enabled: true, 18 | Concurrency: 8, 19 | IdleTimeout: 60, 20 | }, 21 | } 22 | ctx := config.WithConfig(context.Background(), Name, muxCfg) 23 | 24 | port := common.PickPort("tcp", "127.0.0.1") 25 | transportConfig := &transport.Config{ 26 | LocalHost: "127.0.0.1", 27 | LocalPort: port, 28 | RemoteHost: "127.0.0.1", 29 | RemotePort: port, 30 | } 31 | ctx = config.WithConfig(ctx, transport.Name, transportConfig) 32 | ctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{}) 33 | 34 | tcpClient, err := transport.NewClient(ctx, nil) 35 | common.Must(err) 36 | tcpServer, err := transport.NewServer(ctx, nil) 37 | common.Must(err) 38 | 39 | common.Must(err) 40 | 41 | muxTunnel := Tunnel{} 42 | muxClient, _ := muxTunnel.NewClient(ctx, tcpClient) 43 | muxServer, _ := muxTunnel.NewServer(ctx, tcpServer) 44 | 45 | conn1, err := muxClient.DialConn(nil, nil) 46 | common.Must2(conn1.Write(util.GeneratePayload(1024))) 47 | common.Must(err) 48 | buf := [1024]byte{} 49 | conn2, err := muxServer.AcceptConn(nil) 50 | common.Must(err) 51 | common.Must2(conn2.Read(buf[:])) 52 | if !util.CheckConn(conn1, conn2) { 53 | t.Fail() 54 | } 55 | conn1.Close() 56 | conn2.Close() 57 | muxClient.Close() 58 | muxServer.Close() 59 | } 60 | -------------------------------------------------------------------------------- /tunnel/mux/server.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/xtaci/smux" 7 | 8 | "github.com/p4gefau1t/trojan-go/common" 9 | "github.com/p4gefau1t/trojan-go/log" 10 | "github.com/p4gefau1t/trojan-go/tunnel" 11 | ) 12 | 13 | // Server is a smux server 14 | type Server struct { 15 | underlay tunnel.Server 16 | connChan chan tunnel.Conn 17 | ctx context.Context 18 | cancel context.CancelFunc 19 | } 20 | 21 | func (s *Server) acceptConnWorker() { 22 | for { 23 | conn, err := s.underlay.AcceptConn(&Tunnel{}) 24 | if err != nil { 25 | log.Debug(err) 26 | select { 27 | case <-s.ctx.Done(): 28 | return 29 | default: 30 | } 31 | continue 32 | } 33 | go func(conn tunnel.Conn) { 34 | smuxConfig := smux.DefaultConfig() 35 | // smuxConfig.KeepAliveDisabled = true 36 | smuxSession, err := smux.Server(conn, smuxConfig) 37 | if err != nil { 38 | log.Error(err) 39 | return 40 | } 41 | go func(session *smux.Session, conn tunnel.Conn) { 42 | defer session.Close() 43 | defer conn.Close() 44 | for { 45 | stream, err := session.AcceptStream() 46 | if err != nil { 47 | log.Error(err) 48 | return 49 | } 50 | select { 51 | case s.connChan <- &Conn{ 52 | rwc: stream, 53 | Conn: conn, 54 | }: 55 | case <-s.ctx.Done(): 56 | log.Debug("exiting") 57 | return 58 | } 59 | } 60 | }(smuxSession, conn) 61 | }(conn) 62 | } 63 | } 64 | 65 | func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { 66 | select { 67 | case conn := <-s.connChan: 68 | return conn, nil 69 | case <-s.ctx.Done(): 70 | return nil, common.NewError("mux server closed") 71 | } 72 | } 73 | 74 | func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { 75 | panic("not supported") 76 | } 77 | 78 | func (s *Server) Close() error { 79 | s.cancel() 80 | return s.underlay.Close() 81 | } 82 | 83 | func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { 84 | ctx, cancel := context.WithCancel(ctx) 85 | server := &Server{ 86 | underlay: underlay, 87 | ctx: ctx, 88 | cancel: cancel, 89 | connChan: make(chan tunnel.Conn, 32), 90 | } 91 | go server.acceptConnWorker() 92 | log.Debug("mux server created") 93 | return server, nil 94 | } 95 | -------------------------------------------------------------------------------- /tunnel/mux/tunnel.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "MUX" 10 | 11 | type Tunnel struct{} 12 | 13 | func (*Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/router/config.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/p4gefau1t/trojan-go/common" 5 | "github.com/p4gefau1t/trojan-go/config" 6 | ) 7 | 8 | type Config struct { 9 | Router RouterConfig `json:"router" yaml:"router"` 10 | } 11 | 12 | type RouterConfig struct { 13 | Enabled bool `json:"enabled" yaml:"enabled"` 14 | Bypass []string `json:"bypass" yaml:"bypass"` 15 | Proxy []string `json:"proxy" yaml:"proxy"` 16 | Block []string `json:"block" yaml:"block"` 17 | DomainStrategy string `json:"domain_strategy" yaml:"domain-strategy"` 18 | DefaultPolicy string `json:"default_policy" yaml:"default-policy"` 19 | GeoIPFilename string `json:"geoip" yaml:"geoip"` 20 | GeoSiteFilename string `json:"geosite" yaml:"geosite"` 21 | } 22 | 23 | func init() { 24 | config.RegisterConfigCreator(Name, func() interface{} { 25 | cfg := &Config{ 26 | Router: RouterConfig{ 27 | DefaultPolicy: "proxy", 28 | DomainStrategy: "as_is", 29 | GeoIPFilename: common.GetAssetLocation("geoip.dat"), 30 | GeoSiteFilename: common.GetAssetLocation("geosite.dat"), 31 | }, 32 | } 33 | return cfg 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /tunnel/router/conn.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | 8 | "github.com/p4gefau1t/trojan-go/common" 9 | "github.com/p4gefau1t/trojan-go/log" 10 | "github.com/p4gefau1t/trojan-go/tunnel" 11 | ) 12 | 13 | type packetInfo struct { 14 | src *tunnel.Metadata 15 | payload []byte 16 | } 17 | 18 | type PacketConn struct { 19 | proxy tunnel.PacketConn 20 | net.PacketConn 21 | packetChan chan *packetInfo 22 | *Client 23 | ctx context.Context 24 | cancel context.CancelFunc 25 | } 26 | 27 | func (c *PacketConn) packetLoop() { 28 | go func() { 29 | for { 30 | buf := make([]byte, MaxPacketSize) 31 | n, addr, err := c.proxy.ReadWithMetadata(buf) 32 | if err != nil { 33 | select { 34 | case <-c.ctx.Done(): 35 | return 36 | default: 37 | log.Error("router packetConn error", err) 38 | continue 39 | } 40 | } 41 | c.packetChan <- &packetInfo{ 42 | src: addr, 43 | payload: buf[:n], 44 | } 45 | } 46 | }() 47 | for { 48 | buf := make([]byte, MaxPacketSize) 49 | n, addr, err := c.PacketConn.ReadFrom(buf) 50 | if err != nil { 51 | select { 52 | case <-c.ctx.Done(): 53 | return 54 | default: 55 | log.Error("router packetConn error", err) 56 | continue 57 | } 58 | } 59 | address, _ := tunnel.NewAddressFromAddr("udp", addr.String()) 60 | c.packetChan <- &packetInfo{ 61 | src: &tunnel.Metadata{ 62 | Address: address, 63 | }, 64 | payload: buf[:n], 65 | } 66 | } 67 | } 68 | 69 | func (c *PacketConn) Close() error { 70 | c.cancel() 71 | c.proxy.Close() 72 | return c.PacketConn.Close() 73 | } 74 | 75 | func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 76 | panic("implement me") 77 | } 78 | 79 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 80 | panic("implement me") 81 | } 82 | 83 | func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { 84 | policy := c.Route(m.Address) 85 | switch policy { 86 | case Proxy: 87 | return c.proxy.WriteWithMetadata(p, m) 88 | case Block: 89 | return 0, common.NewError("router blocked address (udp): " + m.Address.String()) 90 | case Bypass: 91 | ip, err := m.Address.ResolveIP() 92 | if err != nil { 93 | return 0, common.NewError("router failed to resolve udp address").Base(err) 94 | } 95 | return c.PacketConn.WriteTo(p, &net.UDPAddr{ 96 | IP: ip, 97 | Port: m.Address.Port, 98 | }) 99 | default: 100 | panic("unknown policy") 101 | } 102 | } 103 | 104 | func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { 105 | select { 106 | case info := <-c.packetChan: 107 | n := copy(p, info.payload) 108 | return n, info.src, nil 109 | case <-c.ctx.Done(): 110 | return 0, nil, io.EOF 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tunnel/router/data.go: -------------------------------------------------------------------------------- 1 | package router 2 | -------------------------------------------------------------------------------- /tunnel/router/tunnel.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "ROUTER" 10 | 11 | type Tunnel struct{} 12 | 13 | func (t *Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | panic("not supported") 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/shadowsocks/client.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/shadowsocks/go-shadowsocks2/core" 7 | 8 | "github.com/p4gefau1t/trojan-go/common" 9 | "github.com/p4gefau1t/trojan-go/config" 10 | "github.com/p4gefau1t/trojan-go/log" 11 | "github.com/p4gefau1t/trojan-go/tunnel" 12 | ) 13 | 14 | type Client struct { 15 | underlay tunnel.Client 16 | core.Cipher 17 | } 18 | 19 | func (c *Client) DialConn(address *tunnel.Address, tunnel tunnel.Tunnel) (tunnel.Conn, error) { 20 | conn, err := c.underlay.DialConn(address, &Tunnel{}) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &Conn{ 25 | aeadConn: c.Cipher.StreamConn(conn), 26 | Conn: conn, 27 | }, nil 28 | } 29 | 30 | func (c *Client) DialPacket(tunnel tunnel.Tunnel) (tunnel.PacketConn, error) { 31 | panic("not supported") 32 | } 33 | 34 | func (c *Client) Close() error { 35 | return c.underlay.Close() 36 | } 37 | 38 | func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { 39 | cfg := config.FromContext(ctx, Name).(*Config) 40 | cipher, err := core.PickCipher(cfg.Shadowsocks.Method, nil, cfg.Shadowsocks.Password) 41 | if err != nil { 42 | return nil, common.NewError("invalid shadowsocks cipher").Base(err) 43 | } 44 | log.Debug("shadowsocks client created") 45 | return &Client{ 46 | underlay: underlay, 47 | Cipher: cipher, 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /tunnel/shadowsocks/config.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type ShadowsocksConfig struct { 6 | Enabled bool `json:"enabled" yaml:"enabled"` 7 | Method string `json:"method" yaml:"method"` 8 | Password string `json:"password" yaml:"password"` 9 | } 10 | 11 | type Config struct { 12 | RemoteHost string `json:"remote_addr" yaml:"remote-addr"` 13 | RemotePort int `json:"remote_port" yaml:"remote-port"` 14 | Shadowsocks ShadowsocksConfig `json:"shadowsocks" yaml:"shadowsocks"` 15 | } 16 | 17 | func init() { 18 | config.RegisterConfigCreator(Name, func() interface{} { 19 | return &Config{ 20 | Shadowsocks: ShadowsocksConfig{ 21 | Method: "AES-128-GCM", 22 | }, 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /tunnel/shadowsocks/conn.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | type Conn struct { 10 | aeadConn net.Conn 11 | tunnel.Conn 12 | } 13 | 14 | func (c *Conn) Read(p []byte) (n int, err error) { 15 | return c.aeadConn.Read(p) 16 | } 17 | 18 | func (c *Conn) Write(p []byte) (n int, err error) { 19 | return c.aeadConn.Write(p) 20 | } 21 | 22 | func (c *Conn) Close() error { 23 | c.Conn.Close() 24 | return c.aeadConn.Close() 25 | } 26 | 27 | func (c *Conn) Metadata() *tunnel.Metadata { 28 | return c.Conn.Metadata() 29 | } 30 | -------------------------------------------------------------------------------- /tunnel/shadowsocks/server.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/shadowsocks/go-shadowsocks2/core" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/config" 11 | "github.com/p4gefau1t/trojan-go/log" 12 | "github.com/p4gefau1t/trojan-go/redirector" 13 | "github.com/p4gefau1t/trojan-go/tunnel" 14 | ) 15 | 16 | type Server struct { 17 | core.Cipher 18 | *redirector.Redirector 19 | underlay tunnel.Server 20 | redirAddr net.Addr 21 | } 22 | 23 | func (s *Server) AcceptConn(overlay tunnel.Tunnel) (tunnel.Conn, error) { 24 | conn, err := s.underlay.AcceptConn(&Tunnel{}) 25 | if err != nil { 26 | return nil, common.NewError("shadowsocks failed to accept connection from underlying tunnel").Base(err) 27 | } 28 | rewindConn := common.NewRewindConn(conn) 29 | rewindConn.SetBufferSize(1024) 30 | defer rewindConn.StopBuffering() 31 | 32 | // try to read something from this connection 33 | buf := [1024]byte{} 34 | testConn := s.Cipher.StreamConn(rewindConn) 35 | if _, err := testConn.Read(buf[:]); err != nil { 36 | // we are under attack 37 | log.Error(common.NewError("shadowsocks failed to decrypt").Base(err)) 38 | rewindConn.Rewind() 39 | rewindConn.StopBuffering() 40 | s.Redirect(&redirector.Redirection{ 41 | RedirectTo: s.redirAddr, 42 | InboundConn: rewindConn, 43 | }) 44 | return nil, common.NewError("invalid aead payload") 45 | } 46 | rewindConn.Rewind() 47 | rewindConn.StopBuffering() 48 | 49 | return &Conn{ 50 | aeadConn: s.Cipher.StreamConn(rewindConn), 51 | Conn: conn, 52 | }, nil 53 | } 54 | 55 | func (s *Server) AcceptPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) { 56 | panic("not supported") 57 | } 58 | 59 | func (s *Server) Close() error { 60 | return s.underlay.Close() 61 | } 62 | 63 | func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { 64 | cfg := config.FromContext(ctx, Name).(*Config) 65 | cipher, err := core.PickCipher(cfg.Shadowsocks.Method, nil, cfg.Shadowsocks.Password) 66 | if err != nil { 67 | return nil, common.NewError("invalid shadowsocks cipher").Base(err) 68 | } 69 | if cfg.RemoteHost == "" { 70 | return nil, common.NewError("invalid shadowsocks redirection address") 71 | } 72 | if cfg.RemotePort == 0 { 73 | return nil, common.NewError("invalid shadowsocks redirection port") 74 | } 75 | log.Debug("shadowsocks client created") 76 | return &Server{ 77 | underlay: underlay, 78 | Cipher: cipher, 79 | Redirector: redirector.NewRedirector(ctx), 80 | redirAddr: tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort), 81 | }, nil 82 | } 83 | -------------------------------------------------------------------------------- /tunnel/shadowsocks/shadowsocks_test.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "testing" 11 | 12 | "github.com/p4gefau1t/trojan-go/common" 13 | "github.com/p4gefau1t/trojan-go/config" 14 | "github.com/p4gefau1t/trojan-go/test/util" 15 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 16 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 17 | ) 18 | 19 | func TestShadowsocks(t *testing.T) { 20 | p, err := strconv.ParseInt(util.HTTPPort, 10, 32) 21 | common.Must(err) 22 | 23 | port := common.PickPort("tcp", "127.0.0.1") 24 | transportConfig := &transport.Config{ 25 | LocalHost: "127.0.0.1", 26 | LocalPort: port, 27 | RemoteHost: "127.0.0.1", 28 | RemotePort: port, 29 | } 30 | ctx := config.WithConfig(context.Background(), transport.Name, transportConfig) 31 | ctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{}) 32 | tcpClient, err := transport.NewClient(ctx, nil) 33 | common.Must(err) 34 | tcpServer, err := transport.NewServer(ctx, nil) 35 | common.Must(err) 36 | 37 | cfg := &Config{ 38 | RemoteHost: "127.0.0.1", 39 | RemotePort: int(p), 40 | Shadowsocks: ShadowsocksConfig{ 41 | Enabled: true, 42 | Method: "AES-128-GCM", 43 | Password: "password", 44 | }, 45 | } 46 | ctx = config.WithConfig(ctx, Name, cfg) 47 | 48 | c, err := NewClient(ctx, tcpClient) 49 | common.Must(err) 50 | s, err := NewServer(ctx, tcpServer) 51 | common.Must(err) 52 | 53 | wg := sync.WaitGroup{} 54 | wg.Add(2) 55 | var conn1, conn2 net.Conn 56 | go func() { 57 | var err error 58 | conn1, err = c.DialConn(nil, nil) 59 | common.Must(err) 60 | conn1.Write(util.GeneratePayload(1024)) 61 | wg.Done() 62 | }() 63 | go func() { 64 | var err error 65 | conn2, err = s.AcceptConn(nil) 66 | common.Must(err) 67 | buf := [1024]byte{} 68 | conn2.Read(buf[:]) 69 | wg.Done() 70 | }() 71 | wg.Wait() 72 | if !util.CheckConn(conn1, conn2) { 73 | t.Fail() 74 | } 75 | 76 | go func() { 77 | var err error 78 | conn2, err = s.AcceptConn(nil) 79 | if err == nil { 80 | t.Fail() 81 | } 82 | }() 83 | 84 | // test redirection 85 | conn3, err := tcpClient.DialConn(nil, nil) 86 | common.Must(err) 87 | n, err := conn3.Write(util.GeneratePayload(1024)) 88 | common.Must(err) 89 | fmt.Println("write:", n) 90 | buf := [1024]byte{} 91 | n, err = conn3.Read(buf[:]) 92 | common.Must(err) 93 | fmt.Println("read:", n) 94 | if !strings.Contains(string(buf[:n]), "Bad Request") { 95 | t.Fail() 96 | } 97 | conn1.Close() 98 | conn3.Close() 99 | c.Close() 100 | s.Close() 101 | } 102 | -------------------------------------------------------------------------------- /tunnel/shadowsocks/tunnel.go: -------------------------------------------------------------------------------- 1 | package shadowsocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "SHADOWSOCKS" 10 | 11 | type Tunnel struct{} 12 | 13 | func (t *Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/simplesocks/client.go: -------------------------------------------------------------------------------- 1 | package simplesocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/common" 7 | "github.com/p4gefau1t/trojan-go/log" 8 | "github.com/p4gefau1t/trojan-go/tunnel" 9 | "github.com/p4gefau1t/trojan-go/tunnel/trojan" 10 | ) 11 | 12 | const ( 13 | Connect tunnel.Command = 1 14 | Associate tunnel.Command = 3 15 | ) 16 | 17 | type Client struct { 18 | underlay tunnel.Client 19 | } 20 | 21 | func (c *Client) DialConn(addr *tunnel.Address, t tunnel.Tunnel) (tunnel.Conn, error) { 22 | conn, err := c.underlay.DialConn(nil, &Tunnel{}) 23 | if err != nil { 24 | return nil, common.NewError("simplesocks failed to dial using underlying tunnel").Base(err) 25 | } 26 | return &Conn{ 27 | Conn: conn, 28 | isOutbound: true, 29 | metadata: &tunnel.Metadata{ 30 | Command: Connect, 31 | Address: addr, 32 | }, 33 | }, nil 34 | } 35 | 36 | func (c *Client) DialPacket(t tunnel.Tunnel) (tunnel.PacketConn, error) { 37 | conn, err := c.underlay.DialConn(nil, &Tunnel{}) 38 | if err != nil { 39 | return nil, common.NewError("simplesocks failed to dial using underlying tunnel").Base(err) 40 | } 41 | metadata := &tunnel.Metadata{ 42 | Command: Associate, 43 | Address: &tunnel.Address{ 44 | DomainName: "UDP_CONN", 45 | AddressType: tunnel.DomainName, 46 | }, 47 | } 48 | if err := metadata.WriteTo(conn); err != nil { 49 | return nil, common.NewError("simplesocks failed to write udp associate").Base(err) 50 | } 51 | return &PacketConn{ 52 | PacketConn: trojan.PacketConn{ 53 | Conn: conn, 54 | }, 55 | }, nil 56 | } 57 | 58 | func (c *Client) Close() error { 59 | return c.underlay.Close() 60 | } 61 | 62 | func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { 63 | log.Debug("simplesocks client created") 64 | return &Client{ 65 | underlay: underlay, 66 | }, nil 67 | } 68 | -------------------------------------------------------------------------------- /tunnel/simplesocks/conn.go: -------------------------------------------------------------------------------- 1 | package simplesocks 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/p4gefau1t/trojan-go/common" 7 | "github.com/p4gefau1t/trojan-go/tunnel" 8 | "github.com/p4gefau1t/trojan-go/tunnel/trojan" 9 | ) 10 | 11 | // Conn is a simplesocks connection 12 | type Conn struct { 13 | tunnel.Conn 14 | metadata *tunnel.Metadata 15 | isOutbound bool 16 | headerWritten bool 17 | } 18 | 19 | func (c *Conn) Metadata() *tunnel.Metadata { 20 | return c.metadata 21 | } 22 | 23 | func (c *Conn) Write(payload []byte) (int, error) { 24 | if c.isOutbound && !c.headerWritten { 25 | buf := bytes.NewBuffer(make([]byte, 0, 4096)) 26 | c.metadata.WriteTo(buf) 27 | buf.Write(payload) 28 | _, err := c.Conn.Write(buf.Bytes()) 29 | if err != nil { 30 | return 0, common.NewError("failed to write simplesocks header").Base(err) 31 | } 32 | c.headerWritten = true 33 | return len(payload), nil 34 | } 35 | return c.Conn.Write(payload) 36 | } 37 | 38 | // PacketConn is a simplesocks packet connection 39 | // The header syntax is the same as trojan's 40 | type PacketConn struct { 41 | trojan.PacketConn 42 | } 43 | -------------------------------------------------------------------------------- /tunnel/simplesocks/server.go: -------------------------------------------------------------------------------- 1 | package simplesocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/p4gefau1t/trojan-go/common" 8 | "github.com/p4gefau1t/trojan-go/log" 9 | "github.com/p4gefau1t/trojan-go/recorder" 10 | "github.com/p4gefau1t/trojan-go/tunnel" 11 | "github.com/p4gefau1t/trojan-go/tunnel/mux" 12 | "github.com/p4gefau1t/trojan-go/tunnel/trojan" 13 | ) 14 | 15 | // Server is a simplesocks server 16 | type Server struct { 17 | underlay tunnel.Server 18 | connChan chan tunnel.Conn 19 | packetChan chan tunnel.PacketConn 20 | ctx context.Context 21 | cancel context.CancelFunc 22 | } 23 | 24 | func (s *Server) Close() error { 25 | s.cancel() 26 | return s.underlay.Close() 27 | } 28 | 29 | func (s *Server) acceptLoop() { 30 | for { 31 | conn, err := s.underlay.AcceptConn(&Tunnel{}) 32 | if err != nil { 33 | log.Error(common.NewError("simplesocks failed to accept connection from underlying tunnel").Base(err)) 34 | select { 35 | case <-s.ctx.Done(): 36 | return 37 | default: 38 | } 39 | continue 40 | } 41 | metadata := new(tunnel.Metadata) 42 | if err := metadata.ReadFrom(conn); err != nil { 43 | log.Error(common.NewError("simplesocks server faield to read header").Base(err)) 44 | conn.Close() 45 | continue 46 | } 47 | switch metadata.Command { 48 | case Connect: 49 | s.connChan <- &Conn{ 50 | metadata: metadata, 51 | Conn: conn, 52 | } 53 | Record(conn, metadata) 54 | case Associate: 55 | s.packetChan <- &PacketConn{ 56 | PacketConn: trojan.PacketConn{ 57 | Conn: conn, 58 | }, 59 | } 60 | default: 61 | log.Error(common.NewError(fmt.Sprintf("simplesocks unknown command %d", metadata.Command))) 62 | conn.Close() 63 | } 64 | } 65 | } 66 | 67 | func (s *Server) AcceptConn(tunnel.Tunnel) (tunnel.Conn, error) { 68 | select { 69 | case conn := <-s.connChan: 70 | return conn, nil 71 | case <-s.ctx.Done(): 72 | return nil, common.NewError("simplesocks server closed") 73 | } 74 | } 75 | 76 | func (s *Server) AcceptPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { 77 | select { 78 | case packetConn := <-s.packetChan: 79 | return packetConn, nil 80 | case <-s.ctx.Done(): 81 | return nil, common.NewError("simplesocks server closed") 82 | } 83 | } 84 | 85 | func NewServer(ctx context.Context, underlay tunnel.Server) (*Server, error) { 86 | ctx, cancel := context.WithCancel(ctx) 87 | server := &Server{ 88 | underlay: underlay, 89 | ctx: ctx, 90 | connChan: make(chan tunnel.Conn, 32), 91 | packetChan: make(chan tunnel.PacketConn, 32), 92 | cancel: cancel, 93 | } 94 | go server.acceptLoop() 95 | log.Debug("simplesocks server created") 96 | return server, nil 97 | } 98 | 99 | func Record(conn tunnel.Conn, metadata *tunnel.Metadata) { 100 | var userHash string 101 | if muxConn, ok := conn.(*mux.Conn); ok { 102 | c := muxConn.Conn 103 | if trojanConn, ok2 := c.(*trojan.InboundConn); ok2 { 104 | userHash = trojanConn.Hash() 105 | } 106 | } 107 | if userHash != "" { 108 | log.Debug("user", userHash, "from", conn.RemoteAddr(), "tunneling to", metadata.Address) 109 | recorder.Add(userHash, conn.RemoteAddr(), metadata.Address, "TCP", nil) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tunnel/simplesocks/simplesocks_test.go: -------------------------------------------------------------------------------- 1 | package simplesocks 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/p4gefau1t/trojan-go/common" 9 | "github.com/p4gefau1t/trojan-go/config" 10 | "github.com/p4gefau1t/trojan-go/test/util" 11 | "github.com/p4gefau1t/trojan-go/tunnel" 12 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 13 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 14 | ) 15 | 16 | func TestSimpleSocks(t *testing.T) { 17 | port := common.PickPort("tcp", "127.0.0.1") 18 | transportConfig := &transport.Config{ 19 | LocalHost: "127.0.0.1", 20 | LocalPort: port, 21 | RemoteHost: "127.0.0.1", 22 | RemotePort: port, 23 | } 24 | ctx := config.WithConfig(context.Background(), transport.Name, transportConfig) 25 | ctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{}) 26 | tcpClient, err := transport.NewClient(ctx, nil) 27 | common.Must(err) 28 | tcpServer, err := transport.NewServer(ctx, nil) 29 | common.Must(err) 30 | 31 | c, err := NewClient(ctx, tcpClient) 32 | common.Must(err) 33 | s, err := NewServer(ctx, tcpServer) 34 | common.Must(err) 35 | 36 | conn1, err := c.DialConn(&tunnel.Address{ 37 | DomainName: "www.baidu.com", 38 | AddressType: tunnel.DomainName, 39 | Port: 443, 40 | }, nil) 41 | common.Must(err) 42 | defer conn1.Close() 43 | conn1.Write(util.GeneratePayload(1024)) 44 | conn2, err := s.AcceptConn(nil) 45 | common.Must(err) 46 | defer conn2.Close() 47 | buf := [1024]byte{} 48 | common.Must2(conn2.Read(buf[:])) 49 | if !util.CheckConn(conn1, conn2) { 50 | t.Fail() 51 | } 52 | 53 | packet1, err := c.DialPacket(nil) 54 | common.Must(err) 55 | packet1.WriteWithMetadata([]byte("12345678"), &tunnel.Metadata{ 56 | Address: &tunnel.Address{ 57 | DomainName: "test.com", 58 | AddressType: tunnel.DomainName, 59 | Port: 443, 60 | }, 61 | }) 62 | defer packet1.Close() 63 | packet2, err := s.AcceptPacket(nil) 64 | common.Must(err) 65 | defer packet2.Close() 66 | _, m, err := packet2.ReadWithMetadata(buf[:]) 67 | common.Must(err) 68 | fmt.Println(m) 69 | 70 | if !util.CheckPacketOverConn(packet1, packet2) { 71 | t.Fail() 72 | } 73 | s.Close() 74 | c.Close() 75 | } 76 | -------------------------------------------------------------------------------- /tunnel/simplesocks/tunnel.go: -------------------------------------------------------------------------------- 1 | package simplesocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "SIMPLESOCKS" 10 | 11 | type Tunnel struct{} 12 | 13 | func (*Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { 18 | return NewServer(ctx, underlay) 19 | } 20 | 21 | func (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) { 22 | return NewClient(ctx, underlay) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/socks/config.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type Config struct { 6 | LocalHost string `json:"local_addr" yaml:"local-addr"` 7 | LocalPort int `json:"local_port" yaml:"local-port"` 8 | UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"` 9 | } 10 | 11 | func init() { 12 | config.RegisterConfigCreator(Name, func() interface{} { 13 | return &Config{ 14 | UDPTimeout: 60, 15 | } 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /tunnel/socks/conn.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/p4gefau1t/trojan-go/common" 8 | "github.com/p4gefau1t/trojan-go/tunnel" 9 | ) 10 | 11 | type Conn struct { 12 | net.Conn 13 | metadata *tunnel.Metadata 14 | } 15 | 16 | func (c *Conn) Metadata() *tunnel.Metadata { 17 | return c.metadata 18 | } 19 | 20 | type packetInfo struct { 21 | metadata *tunnel.Metadata 22 | payload []byte 23 | } 24 | 25 | type PacketConn struct { 26 | net.PacketConn 27 | input chan *packetInfo 28 | output chan *packetInfo 29 | src net.Addr 30 | ctx context.Context 31 | cancel context.CancelFunc 32 | } 33 | 34 | func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 35 | panic("implement me") 36 | } 37 | 38 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 39 | panic("implement me") 40 | } 41 | 42 | func (c *PacketConn) Close() error { 43 | c.cancel() 44 | return nil 45 | } 46 | 47 | func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { 48 | newP := make([]byte, len(p)) 49 | copy(newP, p) 50 | select { 51 | case c.output <- &packetInfo{ 52 | metadata: m, 53 | payload: newP, 54 | }: 55 | return len(p), nil 56 | case <-c.ctx.Done(): 57 | return 0, common.NewError("socks packet conn closed") 58 | } 59 | } 60 | 61 | func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { 62 | select { 63 | case info := <-c.input: 64 | n := copy(p, info.payload) 65 | return n, info.metadata, nil 66 | case <-c.ctx.Done(): 67 | return 0, nil, common.NewError("socks packet conn closed") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tunnel/socks/tunnel.go: -------------------------------------------------------------------------------- 1 | package socks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "SOCKS" 10 | 11 | type Tunnel struct{} 12 | 13 | func (*Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (*Tunnel) NewClient(context.Context, tunnel.Client) (tunnel.Client, error) { 18 | panic("not supported") 19 | } 20 | 21 | func (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/tls/config.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "github.com/p4gefau1t/trojan-go/config" 5 | ) 6 | 7 | type Config struct { 8 | RemoteHost string `json:"remote_addr" yaml:"remote-addr"` 9 | RemotePort int `json:"remote_port" yaml:"remote-port"` 10 | TLS TLSConfig `json:"ssl" yaml:"ssl"` 11 | Websocket WebsocketConfig `json:"websocket" yaml:"websocket"` 12 | } 13 | 14 | type WebsocketConfig struct { 15 | Enabled bool `json:"enabled" yaml:"enabled"` 16 | } 17 | 18 | type TLSConfig struct { 19 | Verify bool `json:"verify" yaml:"verify"` 20 | VerifyHostName bool `json:"verify_hostname" yaml:"verify-hostname"` 21 | CertPath string `json:"cert" yaml:"cert"` 22 | KeyPath string `json:"key" yaml:"key"` 23 | KeyPassword string `json:"key_password" yaml:"key-password"` 24 | Cipher string `json:"cipher" yaml:"cipher"` 25 | PreferServerCipher bool `json:"prefer_server_cipher" yaml:"prefer-server-cipher"` 26 | SNI string `json:"sni" yaml:"sni"` 27 | HTTPResponseFileName string `json:"plain_http_response" yaml:"plain-http-response"` 28 | FallbackHost string `json:"fallback_addr" yaml:"fallback-addr"` 29 | FallbackPort int `json:"fallback_port" yaml:"fallback-port"` 30 | ReuseSession bool `json:"reuse_session" yaml:"reuse-session"` 31 | ALPN []string `json:"alpn" yaml:"alpn"` 32 | Curves string `json:"curves" yaml:"curves"` 33 | Fingerprint string `json:"fingerprint" yaml:"fingerprint"` 34 | KeyLogPath string `json:"key_log" yaml:"key-log"` 35 | CertCheckRate int `json:"cert_check_rate" yaml:"cert-check-rate"` 36 | } 37 | 38 | func init() { 39 | config.RegisterConfigCreator(Name, func() interface{} { 40 | return &Config{ 41 | TLS: TLSConfig{ 42 | Verify: true, 43 | VerifyHostName: true, 44 | Fingerprint: "", 45 | ALPN: []string{"http/1.1"}, 46 | }, 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /tunnel/tls/fingerprint/tls.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | import ( 4 | "crypto/tls" 5 | 6 | "github.com/p4gefau1t/trojan-go/log" 7 | ) 8 | 9 | func ParseCipher(s []string) []uint16 { 10 | all := tls.CipherSuites() 11 | var result []uint16 12 | for _, p := range s { 13 | found := true 14 | for _, q := range all { 15 | if q.Name == p { 16 | result = append(result, q.ID) 17 | break 18 | } 19 | if !found { 20 | log.Warn("invalid cipher suite", p, "skipped") 21 | } 22 | } 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /tunnel/tls/tunnel.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "TLS" 10 | 11 | type Tunnel struct{} 12 | 13 | func (t *Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/tproxy/config.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package tproxy 5 | 6 | import "github.com/p4gefau1t/trojan-go/config" 7 | 8 | type Config struct { 9 | LocalHost string `json:"local_addr" yaml:"local-addr"` 10 | LocalPort int `json:"local_port" yaml:"local-port"` 11 | UDPTimeout int `json:"udp_timeout" yaml:"udp-timeout"` 12 | } 13 | 14 | func init() { 15 | config.RegisterConfigCreator(Name, func() interface{} { 16 | return &Config{ 17 | UDPTimeout: 60, 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /tunnel/tproxy/conn.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package tproxy 5 | 6 | import ( 7 | "context" 8 | "net" 9 | 10 | "github.com/p4gefau1t/trojan-go/common" 11 | "github.com/p4gefau1t/trojan-go/tunnel" 12 | ) 13 | 14 | type Conn struct { 15 | net.Conn 16 | metadata *tunnel.Metadata 17 | } 18 | 19 | func (c *Conn) Metadata() *tunnel.Metadata { 20 | return c.metadata 21 | } 22 | 23 | type packetInfo struct { 24 | metadata *tunnel.Metadata 25 | payload []byte 26 | } 27 | 28 | type PacketConn struct { 29 | net.PacketConn 30 | input chan *packetInfo 31 | output chan *packetInfo 32 | src net.Addr 33 | ctx context.Context 34 | cancel context.CancelFunc 35 | } 36 | 37 | func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { 38 | panic("implement me") 39 | } 40 | 41 | func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { 42 | panic("implement me") 43 | } 44 | 45 | func (c *PacketConn) Close() error { 46 | c.cancel() 47 | return nil 48 | } 49 | 50 | func (c *PacketConn) WriteWithMetadata(p []byte, m *tunnel.Metadata) (int, error) { 51 | newP := make([]byte, len(p)) 52 | select { 53 | case c.output <- &packetInfo{ 54 | metadata: m, 55 | payload: newP, 56 | }: 57 | return len(p), nil 58 | case <-c.ctx.Done(): 59 | return 0, common.NewError("socks packet conn closed") 60 | } 61 | } 62 | 63 | func (c *PacketConn) ReadWithMetadata(p []byte) (int, *tunnel.Metadata, error) { 64 | select { 65 | case info := <-c.input: 66 | n := copy(p, info.payload) 67 | return n, info.metadata, nil 68 | case <-c.ctx.Done(): 69 | return 0, nil, common.NewError("socks packet conn closed") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tunnel/tproxy/getsockopt.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !386 2 | // +build linux,!386 3 | 4 | package tproxy 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | func getsockopt(fd int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) { 12 | _, _, e := syscall.Syscall6( 13 | syscall.SYS_GETSOCKOPT, uintptr(fd), uintptr(level), uintptr(optname), 14 | uintptr(optval), uintptr(unsafe.Pointer(optlen)), 0) 15 | if e != 0 { 16 | return e 17 | } 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /tunnel/tproxy/getsockopt_i386.go: -------------------------------------------------------------------------------- 1 | //go:build linux && 386 2 | // +build linux,386 3 | 4 | package tproxy 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const GETSOCKOPT = 15 12 | 13 | func getsockopt(fd int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) { 14 | _, _, e := syscall.Syscall6( 15 | GETSOCKOPT, uintptr(fd), uintptr(level), uintptr(optname), 16 | uintptr(optval), uintptr(unsafe.Pointer(optlen)), 0) 17 | if e != 0 { 18 | return e 19 | } 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /tunnel/tproxy/tproxy_stub.go: -------------------------------------------------------------------------------- 1 | package tproxy 2 | -------------------------------------------------------------------------------- /tunnel/tproxy/tunnel.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package tproxy 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/p4gefau1t/trojan-go/tunnel" 10 | ) 11 | 12 | const Name = "TPROXY" 13 | 14 | type Tunnel struct{} 15 | 16 | func (t *Tunnel) Name() string { 17 | return Name 18 | } 19 | 20 | func (t *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 21 | panic("not supported") 22 | } 23 | 24 | func (t *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 25 | return NewServer(ctx, server) 26 | } 27 | 28 | func init() { 29 | tunnel.RegisterTunnel(Name, &Tunnel{}) 30 | } 31 | -------------------------------------------------------------------------------- /tunnel/transport/client.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/exec" 7 | "strconv" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/config" 11 | "github.com/p4gefau1t/trojan-go/log" 12 | "github.com/p4gefau1t/trojan-go/tunnel" 13 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 14 | ) 15 | 16 | // Client implements tunnel.Client 17 | type Client struct { 18 | serverAddress *tunnel.Address 19 | cmd *exec.Cmd 20 | ctx context.Context 21 | cancel context.CancelFunc 22 | direct *freedom.Client 23 | } 24 | 25 | func (c *Client) Close() error { 26 | c.cancel() 27 | if c.cmd != nil && c.cmd.Process != nil { 28 | c.cmd.Process.Kill() 29 | } 30 | return nil 31 | } 32 | 33 | func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { 34 | panic("not supported") 35 | } 36 | 37 | // DialConn implements tunnel.Client. It will ignore the params and directly dial to the remote server 38 | func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) { 39 | conn, err := c.direct.DialConn(c.serverAddress, nil) 40 | if err != nil { 41 | return nil, common.NewError("transport failed to connect to remote server").Base(err) 42 | } 43 | return &Conn{ 44 | Conn: conn, 45 | }, nil 46 | } 47 | 48 | // NewClient creates a transport layer client 49 | func NewClient(ctx context.Context, _ tunnel.Client) (*Client, error) { 50 | cfg := config.FromContext(ctx, Name).(*Config) 51 | 52 | var cmd *exec.Cmd 53 | serverAddress := tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort) 54 | 55 | if cfg.TransportPlugin.Enabled { 56 | log.Warn("trojan-go will use transport plugin and work in plain text mode") 57 | switch cfg.TransportPlugin.Type { 58 | case "shadowsocks": 59 | pluginHost := "127.0.0.1" 60 | pluginPort := common.PickPort("tcp", pluginHost) 61 | cfg.TransportPlugin.Env = append( 62 | cfg.TransportPlugin.Env, 63 | "SS_LOCAL_HOST="+pluginHost, 64 | "SS_LOCAL_PORT="+strconv.FormatInt(int64(pluginPort), 10), 65 | "SS_REMOTE_HOST="+cfg.RemoteHost, 66 | "SS_REMOTE_PORT="+strconv.FormatInt(int64(cfg.RemotePort), 10), 67 | "SS_PLUGIN_OPTIONS="+cfg.TransportPlugin.Option, 68 | ) 69 | cfg.RemoteHost = pluginHost 70 | cfg.RemotePort = pluginPort 71 | serverAddress = tunnel.NewAddressFromHostPort("tcp", cfg.RemoteHost, cfg.RemotePort) 72 | log.Debug("plugin address", serverAddress.String()) 73 | log.Debug("plugin env", cfg.TransportPlugin.Env) 74 | 75 | cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...) 76 | cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...) 77 | cmd.Stdout = os.Stdout 78 | cmd.Stderr = os.Stdout 79 | cmd.Start() 80 | case "other": 81 | cmd = exec.Command(cfg.TransportPlugin.Command, cfg.TransportPlugin.Arg...) 82 | cmd.Env = append(cmd.Env, cfg.TransportPlugin.Env...) 83 | cmd.Stdout = os.Stdout 84 | cmd.Stderr = os.Stdout 85 | cmd.Start() 86 | case "plaintext": 87 | // do nothing 88 | default: 89 | return nil, common.NewError("invalid plugin type: " + cfg.TransportPlugin.Type) 90 | } 91 | } 92 | 93 | direct, err := freedom.NewClient(ctx, nil) 94 | common.Must(err) 95 | ctx, cancel := context.WithCancel(ctx) 96 | client := &Client{ 97 | serverAddress: serverAddress, 98 | cmd: cmd, 99 | ctx: ctx, 100 | cancel: cancel, 101 | direct: direct, 102 | } 103 | return client, nil 104 | } 105 | -------------------------------------------------------------------------------- /tunnel/transport/config.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "github.com/p4gefau1t/trojan-go/config" 5 | ) 6 | 7 | type Config struct { 8 | LocalHost string `json:"local_addr" yaml:"local-addr"` 9 | LocalPort int `json:"local_port" yaml:"local-port"` 10 | RemoteHost string `json:"remote_addr" yaml:"remote-addr"` 11 | RemotePort int `json:"remote_port" yaml:"remote-port"` 12 | TransportPlugin TransportPluginConfig `json:"transport_plugin" yaml:"transport-plugin"` 13 | } 14 | 15 | type TransportPluginConfig struct { 16 | Enabled bool `json:"enabled" yaml:"enabled"` 17 | Type string `json:"type" yaml:"type"` 18 | Command string `json:"command" yaml:"command"` 19 | Option string `json:"option" yaml:"option"` 20 | Arg []string `json:"arg" yaml:"arg"` 21 | Env []string `json:"env" yaml:"env"` 22 | } 23 | 24 | func init() { 25 | config.RegisterConfigCreator(Name, func() interface{} { 26 | return new(Config) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /tunnel/transport/conn.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | type Conn struct { 10 | net.Conn 11 | } 12 | 13 | func (c *Conn) Metadata() *tunnel.Metadata { 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /tunnel/transport/transport_test.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/config" 11 | "github.com/p4gefau1t/trojan-go/test/util" 12 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 13 | ) 14 | 15 | func TestTransport(t *testing.T) { 16 | serverCfg := &Config{ 17 | LocalHost: "127.0.0.1", 18 | LocalPort: common.PickPort("tcp", "127.0.0.1"), 19 | RemoteHost: "127.0.0.1", 20 | RemotePort: common.PickPort("tcp", "127.0.0.1"), 21 | } 22 | clientCfg := &Config{ 23 | LocalHost: "127.0.0.1", 24 | LocalPort: common.PickPort("tcp", "127.0.0.1"), 25 | RemoteHost: "127.0.0.1", 26 | RemotePort: serverCfg.LocalPort, 27 | } 28 | freedomCfg := &freedom.Config{} 29 | sctx := config.WithConfig(context.Background(), Name, serverCfg) 30 | cctx := config.WithConfig(context.Background(), Name, clientCfg) 31 | cctx = config.WithConfig(cctx, freedom.Name, freedomCfg) 32 | 33 | s, err := NewServer(sctx, nil) 34 | common.Must(err) 35 | c, err := NewClient(cctx, nil) 36 | common.Must(err) 37 | 38 | wg := sync.WaitGroup{} 39 | wg.Add(1) 40 | var conn1, conn2 net.Conn 41 | go func() { 42 | conn2, err = s.AcceptConn(nil) 43 | common.Must(err) 44 | wg.Done() 45 | }() 46 | conn1, err = c.DialConn(nil, nil) 47 | common.Must(err) 48 | 49 | common.Must2(conn1.Write([]byte("12345678\r\n"))) 50 | wg.Wait() 51 | buf := [10]byte{} 52 | conn2.Read(buf[:]) 53 | if !util.CheckConn(conn1, conn2) { 54 | t.Fail() 55 | } 56 | s.Close() 57 | c.Close() 58 | } 59 | 60 | func TestClientPlugin(t *testing.T) { 61 | clientCfg := &Config{ 62 | LocalHost: "127.0.0.1", 63 | LocalPort: common.PickPort("tcp", "127.0.0.1"), 64 | RemoteHost: "127.0.0.1", 65 | RemotePort: 12345, 66 | TransportPlugin: TransportPluginConfig{ 67 | Enabled: true, 68 | Type: "shadowsocks", 69 | Command: "echo $SS_REMOTE_PORT", 70 | Option: "", 71 | Arg: nil, 72 | Env: nil, 73 | }, 74 | } 75 | ctx := config.WithConfig(context.Background(), Name, clientCfg) 76 | freedomCfg := &freedom.Config{} 77 | ctx = config.WithConfig(ctx, freedom.Name, freedomCfg) 78 | c, err := NewClient(ctx, nil) 79 | common.Must(err) 80 | c.Close() 81 | } 82 | 83 | func TestServerPlugin(t *testing.T) { 84 | cfg := &Config{ 85 | LocalHost: "127.0.0.1", 86 | LocalPort: common.PickPort("tcp", "127.0.0.1"), 87 | RemoteHost: "127.0.0.1", 88 | RemotePort: 12345, 89 | TransportPlugin: TransportPluginConfig{ 90 | Enabled: true, 91 | Type: "shadowsocks", 92 | Command: "echo $SS_REMOTE_PORT", 93 | Option: "", 94 | Arg: nil, 95 | Env: nil, 96 | }, 97 | } 98 | ctx := config.WithConfig(context.Background(), Name, cfg) 99 | freedomCfg := &freedom.Config{} 100 | ctx = config.WithConfig(ctx, freedom.Name, freedomCfg) 101 | s, err := NewServer(ctx, nil) 102 | common.Must(err) 103 | s.Close() 104 | } 105 | -------------------------------------------------------------------------------- /tunnel/transport/tunnel.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "TRANSPORT" 10 | 11 | type Tunnel struct{} 12 | 13 | func (*Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (*Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (*Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/trojan/config.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type Config struct { 6 | LocalHost string `json:"local_addr" yaml:"local-addr"` 7 | LocalPort int `json:"local_port" yaml:"local-port"` 8 | RemoteHost string `json:"remote_addr" yaml:"remote-addr"` 9 | RemotePort int `json:"remote_port" yaml:"remote-port"` 10 | DisableHTTPCheck bool `json:"disable_http_check" yaml:"disable-http-check"` 11 | RecordCapacity int `json:"record_capacity" yaml:"record-capacity"` 12 | MySQL MySQLConfig `json:"mysql" yaml:"mysql"` 13 | API APIConfig `json:"api" yaml:"api"` 14 | } 15 | 16 | type MySQLConfig struct { 17 | Enabled bool `json:"enabled" yaml:"enabled"` 18 | } 19 | 20 | type APIConfig struct { 21 | Enabled bool `json:"enabled" yaml:"enabled"` 22 | } 23 | 24 | func init() { 25 | config.RegisterConfigCreator(Name, func() interface{} { 26 | return &Config{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /tunnel/trojan/packet.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "io/ioutil" 8 | "net" 9 | 10 | "github.com/p4gefau1t/trojan-go/common" 11 | "github.com/p4gefau1t/trojan-go/log" 12 | "github.com/p4gefau1t/trojan-go/recorder" 13 | "github.com/p4gefau1t/trojan-go/tunnel" 14 | "github.com/p4gefau1t/trojan-go/tunnel/mux" 15 | ) 16 | 17 | type PacketConn struct { 18 | tunnel.Conn 19 | } 20 | 21 | func (c *PacketConn) ReadFrom(payload []byte) (int, net.Addr, error) { 22 | return c.ReadWithMetadata(payload) 23 | } 24 | 25 | func (c *PacketConn) WriteTo(payload []byte, addr net.Addr) (int, error) { 26 | address, err := tunnel.NewAddressFromAddr("udp", addr.String()) 27 | if err != nil { 28 | return 0, err 29 | } 30 | m := &tunnel.Metadata{ 31 | Address: address, 32 | } 33 | return c.WriteWithMetadata(payload, m) 34 | } 35 | 36 | func (c *PacketConn) WriteWithMetadata(payload []byte, metadata *tunnel.Metadata) (int, error) { 37 | packet := make([]byte, 0, MaxPacketSize) 38 | w := bytes.NewBuffer(packet) 39 | metadata.Address.WriteTo(w) 40 | 41 | length := len(payload) 42 | lengthBuf := [2]byte{} 43 | crlf := [2]byte{0x0d, 0x0a} 44 | 45 | binary.BigEndian.PutUint16(lengthBuf[:], uint16(length)) 46 | w.Write(lengthBuf[:]) 47 | w.Write(crlf[:]) 48 | w.Write(payload) 49 | 50 | _, err := c.Conn.Write(w.Bytes()) 51 | 52 | log.Debug("udp packet remote", c.RemoteAddr(), "metadata", metadata, "size", length) 53 | c.Record(metadata, payload) 54 | return len(payload), err 55 | } 56 | 57 | func (c *PacketConn) ReadWithMetadata(payload []byte) (int, *tunnel.Metadata, error) { 58 | addr := &tunnel.Address{ 59 | NetworkType: "udp", 60 | } 61 | if err := addr.ReadFrom(c.Conn); err != nil { 62 | return 0, nil, common.NewError("failed to parse udp packet addr").Base(err) 63 | } 64 | lengthBuf := [2]byte{} 65 | if _, err := io.ReadFull(c.Conn, lengthBuf[:]); err != nil { 66 | return 0, nil, common.NewError("failed to read length") 67 | } 68 | length := int(binary.BigEndian.Uint16(lengthBuf[:])) 69 | 70 | crlf := [2]byte{} 71 | if _, err := io.ReadFull(c.Conn, crlf[:]); err != nil { 72 | return 0, nil, common.NewError("failed to read crlf") 73 | } 74 | 75 | if len(payload) < length || length > MaxPacketSize { 76 | io.CopyN(ioutil.Discard, c.Conn, int64(length)) // drain the rest of the packet 77 | return 0, nil, common.NewError("incoming packet size is too large") 78 | } 79 | 80 | if _, err := io.ReadFull(c.Conn, payload[:length]); err != nil { 81 | return 0, nil, common.NewError("failed to read payload") 82 | } 83 | 84 | log.Debug("udp packet from", c.RemoteAddr(), "metadata", addr.String(), "size", length) 85 | c.Record(addr, payload[:length]) 86 | return length, &tunnel.Metadata{ 87 | Address: addr, 88 | }, nil 89 | } 90 | 91 | func (c *PacketConn) getUserHash() string { 92 | switch c.Conn.(type) { 93 | case *InboundConn: 94 | trojanConn := c.Conn.(*InboundConn) 95 | return trojanConn.Hash() 96 | case *mux.Conn: 97 | muxConn := c.Conn.(*mux.Conn) 98 | if trojanConn, ok := muxConn.Conn.(*InboundConn); ok { 99 | return trojanConn.Hash() 100 | } 101 | } 102 | return "" 103 | } 104 | 105 | func (c *PacketConn) Record(addr net.Addr, payload []byte) { 106 | userHash := c.getUserHash() 107 | if userHash == "" { 108 | return 109 | } 110 | log.Debug("user", userHash, "from", c.RemoteAddr(), "tunneling UDP to", addr) 111 | recorder.Add(userHash, c.RemoteAddr(), addr, "UDP", payload) 112 | } 113 | -------------------------------------------------------------------------------- /tunnel/trojan/trojan_test.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net" 9 | "testing" 10 | 11 | "github.com/p4gefau1t/trojan-go/common" 12 | "github.com/p4gefau1t/trojan-go/config" 13 | "github.com/p4gefau1t/trojan-go/statistic/memory" 14 | "github.com/p4gefau1t/trojan-go/test/util" 15 | "github.com/p4gefau1t/trojan-go/tunnel" 16 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 17 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 18 | ) 19 | 20 | func TestTrojan(t *testing.T) { 21 | port := common.PickPort("tcp", "127.0.0.1") 22 | transportConfig := &transport.Config{ 23 | LocalHost: "127.0.0.1", 24 | LocalPort: port, 25 | RemoteHost: "127.0.0.1", 26 | RemotePort: port, 27 | } 28 | ctx, cancel := context.WithCancel(context.Background()) 29 | ctx = config.WithConfig(ctx, transport.Name, transportConfig) 30 | ctx = config.WithConfig(ctx, freedom.Name, &freedom.Config{}) 31 | tcpClient, err := transport.NewClient(ctx, nil) 32 | common.Must(err) 33 | tcpServer, err := transport.NewServer(ctx, nil) 34 | common.Must(err) 35 | 36 | serverPort := common.PickPort("tcp", "127.0.0.1") 37 | authConfig := &memory.Config{Passwords: []string{"password"}} 38 | clientConfig := &Config{ 39 | RemoteHost: "127.0.0.1", 40 | RemotePort: serverPort, 41 | } 42 | serverConfig := &Config{ 43 | LocalHost: "127.0.0.1", 44 | LocalPort: serverPort, 45 | RemoteHost: "127.0.0.1", 46 | RemotePort: util.EchoPort, 47 | } 48 | 49 | ctx = config.WithConfig(ctx, memory.Name, authConfig) 50 | clientCtx := config.WithConfig(ctx, Name, clientConfig) 51 | serverCtx := config.WithConfig(ctx, Name, serverConfig) 52 | c, err := NewClient(clientCtx, tcpClient) 53 | common.Must(err) 54 | s, err := NewServer(serverCtx, tcpServer) 55 | common.Must(err) 56 | conn1, err := c.DialConn(&tunnel.Address{ 57 | DomainName: "example.com", 58 | AddressType: tunnel.DomainName, 59 | }, nil) 60 | common.Must(err) 61 | common.Must2(conn1.Write([]byte("87654321"))) 62 | conn2, err := s.AcceptConn(nil) 63 | common.Must(err) 64 | buf := [8]byte{} 65 | conn2.Read(buf[:]) 66 | if !util.CheckConn(conn1, conn2) { 67 | t.Fail() 68 | } 69 | 70 | packet1, err := c.DialPacket(nil) 71 | common.Must(err) 72 | packet1.WriteWithMetadata([]byte("12345678"), &tunnel.Metadata{ 73 | Address: &tunnel.Address{ 74 | DomainName: "example.com", 75 | AddressType: tunnel.DomainName, 76 | Port: 80, 77 | }, 78 | }) 79 | packet2, err := s.AcceptPacket(nil) 80 | common.Must(err) 81 | 82 | _, m, err := packet2.ReadWithMetadata(buf[:]) 83 | common.Must(err) 84 | 85 | fmt.Println(m) 86 | 87 | if !util.CheckPacketOverConn(packet1, packet2) { 88 | t.Fail() 89 | } 90 | 91 | // redirecting 92 | conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 93 | common.Must(err) 94 | sendBuf := util.GeneratePayload(1024) 95 | recvBuf := [1024]byte{} 96 | common.Must2(conn.Write(sendBuf)) 97 | common.Must2(io.ReadFull(conn, recvBuf[:])) 98 | if !bytes.Equal(sendBuf, recvBuf[:]) { 99 | fmt.Println(sendBuf) 100 | fmt.Println(recvBuf[:]) 101 | t.Fail() 102 | } 103 | conn1.Close() 104 | conn2.Close() 105 | packet1.Close() 106 | packet2.Close() 107 | conn.Close() 108 | c.Close() 109 | s.Close() 110 | cancel() 111 | } 112 | -------------------------------------------------------------------------------- /tunnel/trojan/tunnel.go: -------------------------------------------------------------------------------- 1 | package trojan 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "TROJAN" 10 | 11 | type Tunnel struct{} 12 | 13 | func (c *Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (c *Tunnel) NewClient(ctx context.Context, client tunnel.Client) (tunnel.Client, error) { 18 | return NewClient(ctx, client) 19 | } 20 | 21 | func (c *Tunnel) NewServer(ctx context.Context, server tunnel.Server) (tunnel.Server, error) { 22 | return NewServer(ctx, server) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/tunnel.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | 8 | "github.com/p4gefau1t/trojan-go/common" 9 | ) 10 | 11 | // Conn is the TCP connection in the tunnel 12 | type Conn interface { 13 | net.Conn 14 | Metadata() *Metadata 15 | } 16 | 17 | // PacketConn is the UDP packet stream in the tunnel 18 | type PacketConn interface { 19 | net.PacketConn 20 | WriteWithMetadata([]byte, *Metadata) (int, error) 21 | ReadWithMetadata([]byte) (int, *Metadata, error) 22 | } 23 | 24 | // ConnDialer creates TCP connections from the tunnel 25 | type ConnDialer interface { 26 | DialConn(*Address, Tunnel) (Conn, error) 27 | } 28 | 29 | // PacketDialer creates UDP packet stream from the tunnel 30 | type PacketDialer interface { 31 | DialPacket(Tunnel) (PacketConn, error) 32 | } 33 | 34 | // ConnListener accept TCP connections 35 | type ConnListener interface { 36 | AcceptConn(Tunnel) (Conn, error) 37 | } 38 | 39 | // PacketListener accept UDP packet stream 40 | // We don't have any tunnel based on packet streams, so AcceptPacket will always receive a real PacketConn 41 | type PacketListener interface { 42 | AcceptPacket(Tunnel) (PacketConn, error) 43 | } 44 | 45 | // Dialer can dial to original server with a tunnel 46 | type Dialer interface { 47 | ConnDialer 48 | PacketDialer 49 | } 50 | 51 | // Listener can accept TCP and UDP streams from a tunnel 52 | type Listener interface { 53 | ConnListener 54 | PacketListener 55 | } 56 | 57 | // Client is the tunnel client based on stream connections 58 | type Client interface { 59 | Dialer 60 | io.Closer 61 | } 62 | 63 | // Server is the tunnel server based on stream connections 64 | type Server interface { 65 | Listener 66 | io.Closer 67 | } 68 | 69 | // Tunnel describes a tunnel, allowing creating a tunnel from another tunnel 70 | // We assume that the lower tunnels know exatly how upper tunnels work, and lower tunnels is transparent for the upper tunnels 71 | type Tunnel interface { 72 | Name() string 73 | NewClient(context.Context, Client) (Client, error) 74 | NewServer(context.Context, Server) (Server, error) 75 | } 76 | 77 | var tunnels = make(map[string]Tunnel) 78 | 79 | // RegisterTunnel register a tunnel by tunnel name 80 | func RegisterTunnel(name string, tunnel Tunnel) { 81 | tunnels[name] = tunnel 82 | } 83 | 84 | func GetTunnel(name string) (Tunnel, error) { 85 | if t, ok := tunnels[name]; ok { 86 | return t, nil 87 | } 88 | return nil, common.NewError("unknown tunnel name " + name) 89 | } 90 | -------------------------------------------------------------------------------- /tunnel/websocket/client.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "golang.org/x/net/websocket" 8 | 9 | "github.com/p4gefau1t/trojan-go/common" 10 | "github.com/p4gefau1t/trojan-go/config" 11 | "github.com/p4gefau1t/trojan-go/log" 12 | "github.com/p4gefau1t/trojan-go/tunnel" 13 | ) 14 | 15 | type Client struct { 16 | underlay tunnel.Client 17 | hostname string 18 | path string 19 | } 20 | 21 | func (c *Client) DialConn(*tunnel.Address, tunnel.Tunnel) (tunnel.Conn, error) { 22 | conn, err := c.underlay.DialConn(nil, &Tunnel{}) 23 | if err != nil { 24 | return nil, common.NewError("websocket cannot dial with underlying client").Base(err) 25 | } 26 | url := "wss://" + c.hostname + c.path 27 | origin := "https://" + c.hostname 28 | wsConfig, err := websocket.NewConfig(url, origin) 29 | if err != nil { 30 | return nil, common.NewError("invalid websocket config").Base(err) 31 | } 32 | wsConn, err := websocket.NewClient(wsConfig, conn) 33 | if err != nil { 34 | return nil, common.NewError("websocket failed to handshake with server").Base(err) 35 | } 36 | return &OutboundConn{ 37 | Conn: wsConn, 38 | tcpConn: conn, 39 | }, nil 40 | } 41 | 42 | func (c *Client) DialPacket(tunnel.Tunnel) (tunnel.PacketConn, error) { 43 | return nil, common.NewError("not supported by websocket") 44 | } 45 | 46 | func (c *Client) Close() error { 47 | return c.underlay.Close() 48 | } 49 | 50 | func NewClient(ctx context.Context, underlay tunnel.Client) (*Client, error) { 51 | cfg := config.FromContext(ctx, Name).(*Config) 52 | if !strings.HasPrefix(cfg.Websocket.Path, "/") { 53 | return nil, common.NewError("websocket path must start with \"/\"") 54 | } 55 | if cfg.Websocket.Host == "" { 56 | cfg.Websocket.Host = cfg.RemoteHost 57 | log.Warn("empty websocket hostname") 58 | } 59 | log.Debug("websocket client created") 60 | return &Client{ 61 | hostname: cfg.Websocket.Host, 62 | path: cfg.Websocket.Path, 63 | underlay: underlay, 64 | }, nil 65 | } 66 | -------------------------------------------------------------------------------- /tunnel/websocket/config.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import "github.com/p4gefau1t/trojan-go/config" 4 | 5 | type WebsocketConfig struct { 6 | Enabled bool `json:"enabled" yaml:"enabled"` 7 | Host string `json:"host" yaml:"host"` 8 | Path string `json:"path" yaml:"path"` 9 | } 10 | 11 | type Config struct { 12 | RemoteHost string `json:"remote_addr" yaml:"remote-addr"` 13 | RemotePort int `json:"remote_port" yaml:"remote-port"` 14 | Websocket WebsocketConfig `json:"websocket" yaml:"websocket"` 15 | } 16 | 17 | func init() { 18 | config.RegisterConfigCreator(Name, func() interface{} { 19 | return new(Config) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /tunnel/websocket/conn.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "golang.org/x/net/websocket" 8 | 9 | "github.com/p4gefau1t/trojan-go/tunnel" 10 | ) 11 | 12 | type OutboundConn struct { 13 | *websocket.Conn 14 | tcpConn net.Conn 15 | } 16 | 17 | func (c *OutboundConn) Metadata() *tunnel.Metadata { 18 | return nil 19 | } 20 | 21 | func (c *OutboundConn) RemoteAddr() net.Addr { 22 | // override RemoteAddr of websocket.Conn, or it will return some url from "Origin" 23 | return c.tcpConn.RemoteAddr() 24 | } 25 | 26 | type InboundConn struct { 27 | OutboundConn 28 | ctx context.Context 29 | cancel context.CancelFunc 30 | } 31 | 32 | func (c *InboundConn) Close() error { 33 | c.cancel() 34 | return c.Conn.Close() 35 | } 36 | -------------------------------------------------------------------------------- /tunnel/websocket/tunnel.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/p4gefau1t/trojan-go/tunnel" 7 | ) 8 | 9 | const Name = "WEBSOCKET" 10 | 11 | type Tunnel struct{} 12 | 13 | func (*Tunnel) Name() string { 14 | return Name 15 | } 16 | 17 | func (*Tunnel) NewServer(ctx context.Context, underlay tunnel.Server) (tunnel.Server, error) { 18 | return NewServer(ctx, underlay) 19 | } 20 | 21 | func (*Tunnel) NewClient(ctx context.Context, underlay tunnel.Client) (tunnel.Client, error) { 22 | return NewClient(ctx, underlay) 23 | } 24 | 25 | func init() { 26 | tunnel.RegisterTunnel(Name, &Tunnel{}) 27 | } 28 | -------------------------------------------------------------------------------- /tunnel/websocket/websocket_test.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strings" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "golang.org/x/net/websocket" 13 | 14 | "github.com/p4gefau1t/trojan-go/common" 15 | "github.com/p4gefau1t/trojan-go/config" 16 | "github.com/p4gefau1t/trojan-go/test/util" 17 | "github.com/p4gefau1t/trojan-go/tunnel" 18 | "github.com/p4gefau1t/trojan-go/tunnel/freedom" 19 | "github.com/p4gefau1t/trojan-go/tunnel/transport" 20 | ) 21 | 22 | func TestWebsocket(t *testing.T) { 23 | cfg := &Config{ 24 | Websocket: WebsocketConfig{ 25 | Enabled: true, 26 | Host: "localhost", 27 | Path: "/ws", 28 | }, 29 | } 30 | 31 | ctx := config.WithConfig(context.Background(), Name, cfg) 32 | 33 | port := common.PickPort("tcp", "127.0.0.1") 34 | transportConfig := &transport.Config{ 35 | LocalHost: "127.0.0.1", 36 | LocalPort: port, 37 | RemoteHost: "127.0.0.1", 38 | RemotePort: port, 39 | } 40 | freedomCfg := &freedom.Config{} 41 | ctx = config.WithConfig(ctx, transport.Name, transportConfig) 42 | ctx = config.WithConfig(ctx, freedom.Name, freedomCfg) 43 | tcpClient, err := transport.NewClient(ctx, nil) 44 | common.Must(err) 45 | tcpServer, err := transport.NewServer(ctx, nil) 46 | common.Must(err) 47 | 48 | c, err := NewClient(ctx, tcpClient) 49 | common.Must(err) 50 | s, err := NewServer(ctx, tcpServer) 51 | var conn2 tunnel.Conn 52 | wg := sync.WaitGroup{} 53 | wg.Add(1) 54 | go func() { 55 | conn2, err = s.AcceptConn(nil) 56 | common.Must(err) 57 | wg.Done() 58 | }() 59 | time.Sleep(time.Second) 60 | conn1, err := c.DialConn(nil, nil) 61 | common.Must(err) 62 | wg.Wait() 63 | if !util.CheckConn(conn1, conn2) { 64 | t.Fail() 65 | } 66 | 67 | if strings.HasPrefix(conn1.RemoteAddr().String(), "ws") { 68 | t.Fail() 69 | } 70 | if strings.HasPrefix(conn2.RemoteAddr().String(), "ws") { 71 | t.Fail() 72 | } 73 | 74 | conn1.Close() 75 | conn2.Close() 76 | s.Close() 77 | c.Close() 78 | } 79 | 80 | func TestRedirect(t *testing.T) { 81 | cfg := &Config{ 82 | RemoteHost: "127.0.0.1", 83 | Websocket: WebsocketConfig{ 84 | Enabled: true, 85 | Host: "localhost", 86 | Path: "/ws", 87 | }, 88 | } 89 | fmt.Sscanf(util.HTTPPort, "%d", &cfg.RemotePort) 90 | ctx := config.WithConfig(context.Background(), Name, cfg) 91 | 92 | port := common.PickPort("tcp", "127.0.0.1") 93 | transportConfig := &transport.Config{ 94 | LocalHost: "127.0.0.1", 95 | LocalPort: port, 96 | } 97 | ctx = config.WithConfig(ctx, transport.Name, transportConfig) 98 | tcpServer, err := transport.NewServer(ctx, nil) 99 | common.Must(err) 100 | 101 | s, err := NewServer(ctx, tcpServer) 102 | common.Must(err) 103 | 104 | go func() { 105 | _, err := s.AcceptConn(nil) 106 | if err == nil { 107 | t.Fail() 108 | } 109 | }() 110 | time.Sleep(time.Second) 111 | conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) 112 | common.Must(err) 113 | url := "wss://localhost/wrong-path" 114 | origin := "https://localhost" 115 | wsConfig, err := websocket.NewConfig(url, origin) 116 | common.Must(err) 117 | _, err = websocket.NewClient(wsConfig, conn) 118 | if err == nil { 119 | t.Fail() 120 | } 121 | conn.Close() 122 | 123 | s.Close() 124 | } 125 | -------------------------------------------------------------------------------- /url/option_test.go: -------------------------------------------------------------------------------- 1 | package url 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | _ "github.com/p4gefau1t/trojan-go/proxy/client" 8 | ) 9 | 10 | func TestUrl_Handle(t *testing.T) { 11 | urlCases := []string{ 12 | "trojan-go://password@server.com", 13 | "trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath", 14 | "trojan-go://password@server.com/?encryption=ss%3baes-256-gcm%3afuckgfw", 15 | "trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath&encryption=ss%3Baes-256-gcm%3Afuckgfw", 16 | } 17 | optionCases := []string{ 18 | "mux=true;listen=127.0.0.1:0", 19 | "mux=false;listen=127.0.0.1:0", 20 | "mux=false;listen=127.0.0.1:0;api=127.0.0.1:0", 21 | } 22 | 23 | for _, s := range urlCases { 24 | for _, option := range optionCases { 25 | s := s 26 | option := option 27 | u := &url{ 28 | url: &s, 29 | option: &option, 30 | } 31 | u.Name() 32 | u.Priority() 33 | 34 | errChan := make(chan error, 1) 35 | go func() { 36 | errChan <- u.Handle() 37 | }() 38 | 39 | select { 40 | case err := <-errChan: 41 | t.Fatal(err) 42 | case <-time.After(time.Second * 1): 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "runtime" 7 | 8 | "github.com/p4gefau1t/trojan-go/common" 9 | "github.com/p4gefau1t/trojan-go/constant" 10 | "github.com/p4gefau1t/trojan-go/option" 11 | ) 12 | 13 | type versionOption struct { 14 | flag *bool 15 | } 16 | 17 | func (*versionOption) Name() string { 18 | return "version" 19 | } 20 | 21 | func (*versionOption) Priority() int { 22 | return 10 23 | } 24 | 25 | func (c *versionOption) Handle() error { 26 | if *c.flag { 27 | fmt.Println("Trojan-Go", constant.Version) 28 | fmt.Println("Go Version:", runtime.Version()) 29 | fmt.Println("OS/Arch:", runtime.GOOS+"/"+runtime.GOARCH) 30 | fmt.Println("Git Commit:", constant.Commit) 31 | fmt.Println("") 32 | fmt.Println("Developed by PageFault (p4gefau1t)") 33 | fmt.Println("Licensed under GNU General Public License version 3") 34 | fmt.Println("GitHub Repository:\thttps://github.com/p4gefau1t/trojan-go") 35 | fmt.Println("Trojan-Go Documents:\thttps://p4gefau1t.github.io/trojan-go/") 36 | return nil 37 | } 38 | return common.NewError("not set") 39 | } 40 | 41 | func init() { 42 | option.RegisterHandler(&versionOption{ 43 | flag: flag.Bool("version", false, "Display version and help info"), 44 | }) 45 | } 46 | --------------------------------------------------------------------------------