├── .config ├── bypass.txt ├── dns.txt ├── gost.json ├── hosts.txt ├── kcp.json ├── peer.txt ├── probe_resist.txt └── secrets.txt ├── .dockerignore ├── .github └── workflows │ ├── buildx.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yaml ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── README_en.md ├── auth.go ├── auth_test.go ├── bypass.go ├── bypass_test.go ├── chain.go ├── client.go ├── cmd └── gost │ ├── .ssl │ ├── README.md │ ├── localhost.crt │ ├── localhost.csr │ ├── localhost.key │ ├── rootCA.crt │ ├── rootCA.key │ └── rootCA.srl │ ├── cfg.go │ ├── main.go │ ├── peer.go │ └── route.go ├── common_test.go ├── dns.go ├── examples ├── bench │ ├── cli.go │ └── srv.go ├── forward │ ├── direct │ │ ├── client.go │ │ └── server.go │ ├── remote │ │ ├── client.go │ │ └── server.go │ └── udp │ │ ├── cli.go │ │ ├── direct.go │ │ ├── remote.go │ │ └── srv.go ├── http2 │ └── http2.go ├── quic │ ├── quicc.go │ └── quics.go ├── ssh │ ├── sshc.go │ └── sshd.go └── ssu │ └── ssu.go ├── forward.go ├── forward_test.go ├── ftcp.go ├── go.mod ├── go.sum ├── gost.go ├── gost.png ├── handler.go ├── handler_test.go ├── hosts.go ├── hosts_test.go ├── http.go ├── http2.go ├── http2_test.go ├── http_test.go ├── kcp.go ├── kcp_test.go ├── log.go ├── mux.go ├── node.go ├── node_test.go ├── obfs.go ├── obfs_test.go ├── permissions.go ├── permissions_test.go ├── quic.go ├── quic_test.go ├── redirect.go ├── redirect_other.go ├── relay.go ├── reload.go ├── resolver.go ├── resolver_test.go ├── selector.go ├── selector_test.go ├── server.go ├── signal.go ├── signal_unix.go ├── snap └── snapcraft.yaml ├── sni.go ├── sni_test.go ├── sockopts_linux.go ├── sockopts_other.go ├── socks.go ├── socks_test.go ├── ss.go ├── ss_test.go ├── ssh.go ├── ssh_test.go ├── tcp.go ├── tls.go ├── tls_test.go ├── tuntap.go ├── tuntap_darwin.go ├── tuntap_linux.go ├── tuntap_unix.go ├── tuntap_windows.go ├── udp.go ├── vsock.go ├── ws.go ├── ws_test.go └── wss_test.go /.config/bypass.txt: -------------------------------------------------------------------------------- 1 | # period for live reloading 2 | reload 10s 3 | 4 | # matcher reversed 5 | reverse true 6 | 7 | *.example.com 8 | 9 | # this will match example.org and *.example.org 10 | .example.org 11 | 12 | # From IANA IPv4 Special-Purpose Address Registry 13 | # http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml 14 | 15 | 0.0.0.0/8 # RFC1122: "This host on this network" 16 | 10.0.0.0/8 # RFC1918: Private-Use 17 | 100.64.0.0/10 # RFC6598: Shared Address Space 18 | 127.0.0.0/8 # RFC1122: Loopback 19 | 169.254.0.0/16 # RFC3927: Link Local 20 | 172.16.0.0/12 # RFC1918: Private-Use 21 | 192.0.0.0/24 # RFC6890: IETF Protocol Assignments 22 | 192.0.2.0/24 # RFC5737: Documentation (TEST-NET-1) 23 | 192.88.99.0/24 # RFC3068: 6to4 Relay Anycast 24 | 192.168.0.0/16 # RFC1918: Private-Use 25 | 198.18.0.0/15 # RFC2544: Benchmarking 26 | 198.51.100.0/24 # RFC5737: Documentation (TEST-NET-2) 27 | 203.0.113.0/24 # RFC5737: Documentation (TEST-NET-3) 28 | 240.0.0.0/4 # RFC1112: Reserved 29 | 255.255.255.255/32 # RFC0919: Limited Broadcast 30 | 31 | # From IANA Multicast Address Space Registry 32 | # http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml 33 | 34 | 224.0.0.0/4 # RFC5771: Multicast/Reserved 35 | -------------------------------------------------------------------------------- /.config/dns.txt: -------------------------------------------------------------------------------- 1 | # resolver timeout, default 30s. 2 | timeout 10s 3 | 4 | # resolver cache TTL, 5 | # minus value means that cache is disabled, 6 | # default to the TTL in DNS server response. 7 | # ttl 300s 8 | 9 | # period for live reloading 10 | reload 10s 11 | 12 | # ip[:port] [protocol] [hostname] 13 | 14 | https://1.0.0.1/dns-query 15 | 1.1.1.1:853 tls cloudflare-dns.com 16 | 8.8.8.8 17 | 8.8.8.8 tcp 18 | 1.1.1.1 udp 19 | 1.1.1.1:53 tcp -------------------------------------------------------------------------------- /.config/gost.json: -------------------------------------------------------------------------------- 1 | { 2 | "Retries": 1, 3 | "Debug": false, 4 | "ServeNodes": [ 5 | ":12345" 6 | ], 7 | "ChainNodes": [ 8 | "http://:8080" 9 | ], 10 | 11 | "Routes": [ 12 | { 13 | "Retries": 1, 14 | "ServeNodes": [ 15 | "ws://:1443" 16 | ], 17 | "ChainNodes": [ 18 | "socks://:192.168.1.1:1080" 19 | ] 20 | }, 21 | { 22 | "Retries": 3, 23 | "ServeNodes": [ 24 | "quic://:443" 25 | ] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.config/hosts.txt: -------------------------------------------------------------------------------- 1 | # period for live reloading 2 | reload 10s 3 | 4 | # The following lines are desirable for IPv4 capable hosts 5 | 127.0.0.1 localhost 6 | 7 | # 127.0.1.1 is often used for the FQDN of the machine 8 | 127.0.1.1 thishost.mydomain.org thishost 9 | 192.168.1.10 foo.mydomain.org foo 10 | 192.168.1.13 bar.mydomain.org bar 11 | 146.82.138.7 master.debian.org master 12 | 209.237.226.90 www.opensource.org 13 | 14 | # The following lines are desirable for IPv6 capable hosts 15 | ::1 localhost ip6-localhost ip6-loopback 16 | ff02::1 ip6-allnodes 17 | ff02::2 ip6-allrouters 18 | -------------------------------------------------------------------------------- /.config/kcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "it's a secrect", 3 | "crypt": "aes", 4 | "mode": "fast", 5 | "mtu" : 1350, 6 | "sndwnd": 1024, 7 | "rcvwnd": 1024, 8 | "datashard": 10, 9 | "parityshard": 3, 10 | "dscp": 0, 11 | "nocomp": false, 12 | "acknodelay": false, 13 | "nodelay": 0, 14 | "interval": 40, 15 | "resend": 0, 16 | "nc": 0, 17 | "sockbuf": 4194304, 18 | "keepalive": 10, 19 | "snmplog": "", 20 | "snmpperiod": 60 21 | } -------------------------------------------------------------------------------- /.config/peer.txt: -------------------------------------------------------------------------------- 1 | # strategy for node selecting 2 | strategy random 3 | 4 | max_fails 1 5 | 6 | fail_timeout 30s 7 | 8 | # period for live reloading 9 | reload 10s 10 | 11 | # peers 12 | peer http://:18080 13 | peer socks://:11080 14 | peer ss://chacha20:123456@:18338 -------------------------------------------------------------------------------- /.config/probe_resist.txt: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /.config/secrets.txt: -------------------------------------------------------------------------------- 1 | # period for live reloading 2 | reload 3s 3 | 4 | # username password 5 | 6 | $test.admin$ $123456$ 7 | @test.admin@ @123456@ 8 | test.admin# #123456# 9 | test.admin\admin 123456 10 | test001 123456 11 | test002 12345678 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | release 10 | debian 11 | docs 12 | 13 | *.exe 14 | *.test 15 | 16 | *.bak 17 | 18 | .git 19 | .gitignore 20 | LICENSE 21 | VERSION 22 | README.md 23 | Changelog.md 24 | Makefile 25 | docker-compose.yml -------------------------------------------------------------------------------- /.github/workflows/buildx.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.docker.com/ci-cd/github-actions/ 2 | # https://blog.oddbit.com/post/2020-09-25-building-multi-architecture-im/ 3 | 4 | name: docker 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | tags: 11 | - 'v*' 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Prepare 18 | id: prepare 19 | run: | 20 | DOCKER_IMAGE=${{ secrets.DOCKER_IMAGE }} 21 | VERSION=latest 22 | 23 | # If this is git tag, use the tag name as a docker tag 24 | if [[ $GITHUB_REF == refs/tags/* ]]; then 25 | VERSION=${GITHUB_REF#refs/tags/v} 26 | fi 27 | TAGS="${DOCKER_IMAGE}:${VERSION}" 28 | 29 | # If the VERSION looks like a version number, assume that 30 | # this is the most recent version of the image and also 31 | # tag it 'latest'. 32 | if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 33 | MAJOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[1]}'` 34 | MINOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[2]}'` 35 | TAGS="$TAGS,${DOCKER_IMAGE}:${MAJOR_VERSION},${DOCKER_IMAGE}:${MAJOR_VERSION}.${MINOR_VERSION},${DOCKER_IMAGE}:latest" 36 | fi 37 | 38 | # Set output parameters. 39 | echo "tags=${TAGS}" >> $GITHUB_OUTPUT 40 | echo "docker_image=${DOCKER_IMAGE}" >> $GITHUB_OUTPUT 41 | echo "docker_platforms=linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/s390x,linux/riscv64" >> $GITHUB_OUTPUT 42 | 43 | - name: Set up QEMU 44 | uses: docker/setup-qemu-action@v3 45 | 46 | - name: Set up Docker Buildx 47 | id: buildx 48 | uses: docker/setup-buildx-action@v3 49 | 50 | - name: Environment 51 | run: | 52 | echo home=$HOME 53 | echo git_ref=$GITHUB_REF 54 | echo git_sha=$GITHUB_SHA 55 | echo image=${{ steps.prepare.outputs.docker_image }} 56 | echo tags=${{ steps.prepare.outputs.tags }} 57 | echo platforms=${{ steps.prepare.outputs.docker_platforms }} 58 | echo avail_platforms=${{ steps.buildx.outputs.platforms }} 59 | 60 | - name: Login to DockerHub 61 | if: github.event_name != 'pull_request' 62 | uses: docker/login-action@v3 63 | with: 64 | username: ${{ secrets.DOCKER_USERNAME }} 65 | password: ${{ secrets.DOCKER_PASSWORD }} 66 | 67 | - name: Buildx and push 68 | uses: docker/build-push-action@v6 69 | with: 70 | platforms: ${{ steps.prepare.outputs.docker_platforms }} 71 | push: true 72 | tags: ${{ steps.prepare.outputs.tags }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | # run only against tags 6 | tags: 7 | - 'v*' 8 | 9 | permissions: 10 | contents: write 11 | # packages: write 12 | # issues: write 13 | 14 | jobs: 15 | goreleaser: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | - run: git fetch --force --tags 22 | - uses: actions/setup-go@v3 23 | with: 24 | go-version: '1.22' 25 | cache: true 26 | # More assembly might be required: Docker logins, GPG, etc. It all depends 27 | # on your needs. 28 | - uses: goreleaser/goreleaser-action@v4 29 | with: 30 | # either 'goreleaser' (default) or 'goreleaser-pro': 31 | distribution: goreleaser 32 | version: latest 33 | args: release --clean 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' 37 | # distribution: 38 | # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | release 10 | debian 11 | bin 12 | dist/ 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.swp 27 | *.swo 28 | 29 | *.exe 30 | *.test 31 | 32 | *.bak 33 | 34 | .vscode/ 35 | cmd/gost/gost 36 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | # - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | main: ./cmd/gost 13 | targets: 14 | - darwin_amd64 15 | - darwin_arm64 16 | - linux_386 17 | - linux_amd64 18 | - linux_amd64_v3 19 | - linux_arm_5 20 | - linux_arm_6 21 | - linux_arm_7 22 | - linux_arm64 23 | - linux_mips_softfloat 24 | - linux_mips_hardfloat 25 | - linux_mipsle_softfloat 26 | - linux_mipsle_hardfloat 27 | - linux_mips64 28 | - linux_mips64le 29 | - linux_s390x 30 | - linux_riscv64 31 | - freebsd_386 32 | - freebsd_amd64 33 | - windows_386 34 | - windows_amd64 35 | - windows_amd64_v3 36 | - windows_arm64 37 | 38 | archives: 39 | - format: tar.gz 40 | # use zip for windows archives 41 | format_overrides: 42 | - goos: windows 43 | format: zip 44 | checksum: 45 | name_template: 'checksums.txt' 46 | snapshot: 47 | name_template: "{{ incpatch .Version }}-next" 48 | changelog: 49 | sort: asc 50 | filters: 51 | exclude: 52 | - '^docs:' 53 | - '^test:' 54 | 55 | # The lines beneath this are called `modelines`. See `:help modeline` 56 | # Feel free to remove those if you don't want/use them. 57 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 58 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.x 5 | 6 | install: true 7 | script: 8 | - go test -race -v -coverprofile=coverage.txt -covermode=atomic 9 | - cd cmd/gost && go build 10 | 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx 2 | 3 | FROM --platform=$BUILDPLATFORM golang:1.23-alpine3.20 AS builder 4 | 5 | COPY --from=xx / / 6 | 7 | ARG TARGETPLATFORM 8 | 9 | RUN xx-info env 10 | 11 | ENV CGO_ENABLED=0 12 | 13 | ENV XX_VERIFY_STATIC=1 14 | 15 | WORKDIR /app 16 | 17 | COPY . . 18 | 19 | RUN cd cmd/gost && \ 20 | xx-go build && \ 21 | xx-verify gost 22 | 23 | FROM alpine:3.20 24 | 25 | # add iptables for tun/tap 26 | RUN apk add --no-cache iptables 27 | 28 | WORKDIR /bin/ 29 | 30 | COPY --from=builder /app/cmd/gost/gost . 31 | 32 | ENTRYPOINT ["/bin/gost"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 ginuerzh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=gost 2 | BINDIR=bin 3 | VERSION=$(shell cat gost.go | grep 'Version =' | sed 's/.*\"\(.*\)\".*/\1/g') 4 | GOBUILD=CGO_ENABLED=0 go build --ldflags="-s -w" -v -x -a 5 | GOFILES=cmd/gost/*.go 6 | 7 | PLATFORM_LIST = \ 8 | darwin-amd64 \ 9 | darwin-arm64 \ 10 | linux-386 \ 11 | linux-amd64 \ 12 | linux-armv5 \ 13 | linux-armv6 \ 14 | linux-armv7 \ 15 | linux-armv8 \ 16 | linux-mips-softfloat \ 17 | linux-mips-hardfloat \ 18 | linux-mipsle-softfloat \ 19 | linux-mipsle-hardfloat \ 20 | linux-mips64 \ 21 | linux-mips64le \ 22 | linux-s390x \ 23 | linux-riscv64 \ 24 | freebsd-386 \ 25 | freebsd-amd64 26 | 27 | WINDOWS_ARCH_LIST = \ 28 | windows-386 \ 29 | windows-amd64 \ 30 | windows-arm64 31 | 32 | all: linux-amd64 darwin-amd64 windows-amd64 # Most used 33 | 34 | darwin-amd64: 35 | GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 36 | 37 | darwin-arm64: 38 | GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 39 | 40 | linux-386: 41 | GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 42 | 43 | linux-amd64: 44 | GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 45 | 46 | linux-armv5: 47 | GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 48 | 49 | linux-armv6: 50 | GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 51 | 52 | linux-armv7: 53 | GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 54 | 55 | linux-armv8: 56 | GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 57 | 58 | linux-mips-softfloat: 59 | GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 60 | 61 | linux-mips-hardfloat: 62 | GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 63 | 64 | linux-mipsle-softfloat: 65 | GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 66 | 67 | linux-mipsle-hardfloat: 68 | GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 69 | 70 | linux-mips64: 71 | GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 72 | 73 | linux-mips64le: 74 | GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 75 | 76 | linux-s390x: 77 | GOARCH=s390x GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 78 | 79 | linux-riscv64: 80 | GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 81 | 82 | freebsd-386: 83 | GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 84 | 85 | freebsd-amd64: 86 | GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) 87 | 88 | windows-386: 89 | GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES) 90 | 91 | windows-amd64: 92 | GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES) 93 | 94 | windows-arm64: 95 | GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES) 96 | 97 | gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) 98 | zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) 99 | 100 | $(gz_releases): %.gz : % 101 | chmod +x $(BINDIR)/$(NAME)-$(basename $@) 102 | gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@) 103 | 104 | $(zip_releases): %.zip : % 105 | zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe 106 | 107 | all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) 108 | 109 | releases: $(gz_releases) $(zip_releases) 110 | clean: 111 | rm $(BINDIR)/* 112 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // Authenticator is an interface for user authentication. 12 | type Authenticator interface { 13 | Authenticate(user, password string) bool 14 | } 15 | 16 | // LocalAuthenticator is an Authenticator that authenticates client by local key-value pairs. 17 | type LocalAuthenticator struct { 18 | kvs map[string]string 19 | period time.Duration 20 | stopped chan struct{} 21 | mux sync.RWMutex 22 | } 23 | 24 | // NewLocalAuthenticator creates an Authenticator that authenticates client by local infos. 25 | func NewLocalAuthenticator(kvs map[string]string) *LocalAuthenticator { 26 | return &LocalAuthenticator{ 27 | kvs: kvs, 28 | stopped: make(chan struct{}), 29 | } 30 | } 31 | 32 | // Authenticate checks the validity of the provided user-password pair. 33 | func (au *LocalAuthenticator) Authenticate(user, password string) bool { 34 | if au == nil { 35 | return true 36 | } 37 | 38 | au.mux.RLock() 39 | defer au.mux.RUnlock() 40 | 41 | if len(au.kvs) == 0 { 42 | return true 43 | } 44 | 45 | v, ok := au.kvs[user] 46 | return ok && (v == "" || password == v) 47 | } 48 | 49 | // Add adds a key-value pair to the Authenticator. 50 | func (au *LocalAuthenticator) Add(k, v string) { 51 | au.mux.Lock() 52 | defer au.mux.Unlock() 53 | if au.kvs == nil { 54 | au.kvs = make(map[string]string) 55 | } 56 | au.kvs[k] = v 57 | } 58 | 59 | // Reload parses config from r, then live reloads the Authenticator. 60 | func (au *LocalAuthenticator) Reload(r io.Reader) error { 61 | var period time.Duration 62 | kvs := make(map[string]string) 63 | 64 | if r == nil || au.Stopped() { 65 | return nil 66 | } 67 | 68 | // splitLine splits a line text by white space. 69 | // A line started with '#' will be ignored, otherwise it is valid. 70 | split := func(line string) []string { 71 | if line == "" { 72 | return nil 73 | } 74 | line = strings.Replace(line, "\t", " ", -1) 75 | line = strings.TrimSpace(line) 76 | 77 | if strings.IndexByte(line, '#') == 0 { 78 | return nil 79 | } 80 | 81 | var ss []string 82 | for _, s := range strings.Split(line, " ") { 83 | if s = strings.TrimSpace(s); s != "" { 84 | ss = append(ss, s) 85 | } 86 | } 87 | return ss 88 | } 89 | 90 | scanner := bufio.NewScanner(r) 91 | for scanner.Scan() { 92 | line := scanner.Text() 93 | ss := split(line) 94 | if len(ss) == 0 { 95 | continue 96 | } 97 | 98 | switch ss[0] { 99 | case "reload": // reload option 100 | if len(ss) > 1 { 101 | period, _ = time.ParseDuration(ss[1]) 102 | } 103 | default: 104 | var k, v string 105 | k = ss[0] 106 | if len(ss) > 1 { 107 | v = ss[1] 108 | } 109 | kvs[k] = v 110 | } 111 | } 112 | 113 | if err := scanner.Err(); err != nil { 114 | return err 115 | } 116 | 117 | au.mux.Lock() 118 | defer au.mux.Unlock() 119 | 120 | au.period = period 121 | au.kvs = kvs 122 | 123 | return nil 124 | } 125 | 126 | // Period returns the reload period. 127 | func (au *LocalAuthenticator) Period() time.Duration { 128 | if au.Stopped() { 129 | return -1 130 | } 131 | 132 | au.mux.RLock() 133 | defer au.mux.RUnlock() 134 | 135 | return au.period 136 | } 137 | 138 | // Stop stops reloading. 139 | func (au *LocalAuthenticator) Stop() { 140 | select { 141 | case <-au.stopped: 142 | default: 143 | close(au.stopped) 144 | } 145 | } 146 | 147 | // Stopped checks whether the reloader is stopped. 148 | func (au *LocalAuthenticator) Stopped() bool { 149 | select { 150 | case <-au.stopped: 151 | return true 152 | default: 153 | return false 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/url" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | var localAuthenticatorTests = []struct { 13 | clientUser *url.Userinfo 14 | serverUsers []*url.Userinfo 15 | valid bool 16 | }{ 17 | {nil, nil, true}, 18 | {nil, []*url.Userinfo{url.User("admin")}, false}, 19 | {nil, []*url.Userinfo{url.UserPassword("", "123456")}, false}, 20 | {nil, []*url.Userinfo{url.UserPassword("admin", "123456")}, false}, 21 | 22 | {url.User("admin"), nil, true}, 23 | {url.User("admin"), []*url.Userinfo{url.User("admin")}, true}, 24 | {url.User("admin"), []*url.Userinfo{url.User("test")}, false}, 25 | {url.User("admin"), []*url.Userinfo{url.UserPassword("test", "123456")}, false}, 26 | {url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "123456")}, false}, 27 | {url.User("admin"), []*url.Userinfo{url.UserPassword("admin", "")}, true}, 28 | {url.User("admin"), []*url.Userinfo{url.UserPassword("", "123456")}, false}, 29 | 30 | {url.UserPassword("", ""), nil, true}, 31 | {url.UserPassword("", "123456"), nil, true}, 32 | {url.UserPassword("", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, true}, 33 | {url.UserPassword("", "123456"), []*url.Userinfo{url.UserPassword("admin", "")}, false}, 34 | {url.UserPassword("", "123456"), []*url.Userinfo{url.UserPassword("admin", "123456")}, false}, 35 | 36 | {url.UserPassword("admin", "123456"), nil, true}, 37 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.User("admin")}, true}, 38 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.User("test")}, false}, 39 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("admin", "")}, true}, 40 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("", "123456")}, false}, 41 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("admin", "123")}, false}, 42 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("test", "123456")}, false}, 43 | {url.UserPassword("admin", "123456"), []*url.Userinfo{url.UserPassword("admin", "123456")}, true}, 44 | 45 | {url.UserPassword("admin", "123456"), []*url.Userinfo{ 46 | url.UserPassword("test", "123"), 47 | url.UserPassword("admin", "123456"), 48 | }, true}, 49 | } 50 | 51 | func TestLocalAuthenticator(t *testing.T) { 52 | for i, tc := range localAuthenticatorTests { 53 | tc := tc 54 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 55 | au := NewLocalAuthenticator(nil) 56 | for _, u := range tc.serverUsers { 57 | if u != nil { 58 | p, _ := u.Password() 59 | au.Add(u.Username(), p) 60 | } 61 | } 62 | 63 | var u, p string 64 | if tc.clientUser != nil { 65 | u = tc.clientUser.Username() 66 | p, _ = tc.clientUser.Password() 67 | } 68 | if au.Authenticate(u, p) != tc.valid { 69 | t.Error("authenticate result should be", tc.valid) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | var localAuthenticatorReloadTests = []struct { 76 | r io.Reader 77 | period time.Duration 78 | kvs map[string]string 79 | stopped bool 80 | }{ 81 | { 82 | r: nil, 83 | period: 0, 84 | kvs: nil, 85 | }, 86 | { 87 | r: bytes.NewBufferString(""), 88 | period: 0, 89 | }, 90 | { 91 | r: bytes.NewBufferString("reload 10s"), 92 | period: 10 * time.Second, 93 | }, 94 | { 95 | r: bytes.NewBufferString("# reload 10s\n"), 96 | }, 97 | { 98 | r: bytes.NewBufferString("reload 10s\n#admin"), 99 | period: 10 * time.Second, 100 | }, 101 | { 102 | r: bytes.NewBufferString("reload 10s\nadmin"), 103 | period: 10 * time.Second, 104 | kvs: map[string]string{ 105 | "admin": "", 106 | }, 107 | }, 108 | { 109 | r: bytes.NewBufferString("# reload 10s\nadmin"), 110 | kvs: map[string]string{ 111 | "admin": "", 112 | }, 113 | }, 114 | { 115 | r: bytes.NewBufferString("# reload 10s\nadmin #123456"), 116 | kvs: map[string]string{ 117 | "admin": "#123456", 118 | }, 119 | stopped: true, 120 | }, 121 | { 122 | r: bytes.NewBufferString("admin \t #123456\n\n\ntest \t 123456"), 123 | kvs: map[string]string{ 124 | "admin": "#123456", 125 | "test": "123456", 126 | }, 127 | stopped: true, 128 | }, 129 | { 130 | r: bytes.NewBufferString(` 131 | $test.admin$ $123456$ 132 | @test.admin@ @123456@ 133 | test.admin# #123456# 134 | test.admin\admin 123456 135 | `), 136 | kvs: map[string]string{ 137 | "$test.admin$": "$123456$", 138 | "@test.admin@": "@123456@", 139 | "test.admin#": "#123456#", 140 | "test.admin\\admin": "123456", 141 | }, 142 | stopped: true, 143 | }, 144 | } 145 | 146 | func TestLocalAuthenticatorReload(t *testing.T) { 147 | isEquals := func(a, b map[string]string) bool { 148 | if len(a) == 0 && len(b) == 0 { 149 | return true 150 | } 151 | if len(a) != len(b) { 152 | return false 153 | } 154 | 155 | for k, v := range a { 156 | if b[k] != v { 157 | return false 158 | } 159 | } 160 | return true 161 | } 162 | for i, tc := range localAuthenticatorReloadTests { 163 | tc := tc 164 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 165 | au := NewLocalAuthenticator(nil) 166 | 167 | if err := au.Reload(tc.r); err != nil { 168 | t.Error(err) 169 | } 170 | if au.Period() != tc.period { 171 | t.Errorf("#%d test failed: period value should be %v, got %v", 172 | i, tc.period, au.Period()) 173 | } 174 | if !isEquals(au.kvs, tc.kvs) { 175 | t.Errorf("#%d test failed: %v, %s", i, au.kvs, tc.kvs) 176 | } 177 | 178 | if tc.stopped { 179 | au.Stop() 180 | if au.Period() >= 0 { 181 | t.Errorf("period of the stopped reloader should be minus value") 182 | } 183 | au.Stop() 184 | } 185 | if au.Stopped() != tc.stopped { 186 | t.Errorf("#%d test failed: stopped value should be %v, got %v", 187 | i, tc.stopped, au.Stopped()) 188 | } 189 | }) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /bypass.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "net" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | glob "github.com/gobwas/glob" 15 | ) 16 | 17 | // Matcher is a generic pattern matcher, 18 | // it gives the match result of the given pattern for specific v. 19 | type Matcher interface { 20 | Match(v string) bool 21 | String() string 22 | } 23 | 24 | // NewMatcher creates a Matcher for the given pattern. 25 | // The acutal Matcher depends on the pattern: 26 | // IP Matcher if pattern is a valid IP address. 27 | // CIDR Matcher if pattern is a valid CIDR address. 28 | // Domain Matcher if both of the above are not. 29 | func NewMatcher(pattern string) Matcher { 30 | if pattern == "" { 31 | return nil 32 | } 33 | if ip := net.ParseIP(pattern); ip != nil { 34 | return IPMatcher(ip) 35 | } 36 | if _, inet, err := net.ParseCIDR(pattern); err == nil { 37 | return CIDRMatcher(inet) 38 | } 39 | return DomainMatcher(pattern) 40 | } 41 | 42 | type ipMatcher struct { 43 | ip net.IP 44 | } 45 | 46 | // IPMatcher creates a Matcher for a specific IP address. 47 | func IPMatcher(ip net.IP) Matcher { 48 | return &ipMatcher{ 49 | ip: ip, 50 | } 51 | } 52 | 53 | func (m *ipMatcher) Match(ip string) bool { 54 | if m == nil { 55 | return false 56 | } 57 | return m.ip.Equal(net.ParseIP(ip)) 58 | } 59 | 60 | func (m *ipMatcher) String() string { 61 | return "ip " + m.ip.String() 62 | } 63 | 64 | type cidrMatcher struct { 65 | ipNet *net.IPNet 66 | } 67 | 68 | // CIDRMatcher creates a Matcher for a specific CIDR notation IP address. 69 | func CIDRMatcher(inet *net.IPNet) Matcher { 70 | return &cidrMatcher{ 71 | ipNet: inet, 72 | } 73 | } 74 | 75 | func (m *cidrMatcher) Match(ip string) bool { 76 | if m == nil || m.ipNet == nil { 77 | return false 78 | } 79 | return m.ipNet.Contains(net.ParseIP(ip)) 80 | } 81 | 82 | func (m *cidrMatcher) String() string { 83 | return "cidr " + m.ipNet.String() 84 | } 85 | 86 | type domainMatcher struct { 87 | pattern string 88 | glob glob.Glob 89 | } 90 | 91 | // DomainMatcher creates a Matcher for a specific domain pattern, 92 | // the pattern can be a plain domain such as 'example.com', 93 | // a wildcard such as '*.exmaple.com' or a special wildcard '.example.com'. 94 | func DomainMatcher(pattern string) Matcher { 95 | p := pattern 96 | if strings.HasPrefix(pattern, ".") { 97 | p = pattern[1:] // trim the prefix '.' 98 | pattern = "*" + p 99 | } 100 | return &domainMatcher{ 101 | pattern: p, 102 | glob: glob.MustCompile(pattern), 103 | } 104 | } 105 | 106 | func (m *domainMatcher) Match(domain string) bool { 107 | if m == nil || m.glob == nil { 108 | return false 109 | } 110 | 111 | if domain == m.pattern { 112 | return true 113 | } 114 | return m.glob.Match(domain) 115 | } 116 | 117 | func (m *domainMatcher) String() string { 118 | return "domain " + m.pattern 119 | } 120 | 121 | // Bypass is a filter for address (IP or domain). 122 | // It contains a list of matchers. 123 | type Bypass struct { 124 | matchers []Matcher 125 | period time.Duration // the period for live reloading 126 | reversed bool 127 | stopped chan struct{} 128 | mux sync.RWMutex 129 | } 130 | 131 | // NewBypass creates and initializes a new Bypass using matchers as its match rules. 132 | // The rules will be reversed if the reversed is true. 133 | func NewBypass(reversed bool, matchers ...Matcher) *Bypass { 134 | return &Bypass{ 135 | matchers: matchers, 136 | reversed: reversed, 137 | stopped: make(chan struct{}), 138 | } 139 | } 140 | 141 | // NewBypassPatterns creates and initializes a new Bypass using matcher patterns as its match rules. 142 | // The rules will be reversed if the reverse is true. 143 | func NewBypassPatterns(reversed bool, patterns ...string) *Bypass { 144 | var matchers []Matcher 145 | for _, pattern := range patterns { 146 | if m := NewMatcher(pattern); m != nil { 147 | matchers = append(matchers, m) 148 | } 149 | } 150 | bp := NewBypass(reversed) 151 | bp.AddMatchers(matchers...) 152 | return bp 153 | } 154 | 155 | // Contains reports whether the bypass includes addr. 156 | func (bp *Bypass) Contains(addr string) bool { 157 | if bp == nil || addr == "" { 158 | return false 159 | } 160 | 161 | // try to strip the port 162 | if host, port, _ := net.SplitHostPort(addr); host != "" && port != "" { 163 | if p, _ := strconv.Atoi(port); p > 0 { // port is valid 164 | addr = host 165 | } 166 | } 167 | 168 | bp.mux.RLock() 169 | defer bp.mux.RUnlock() 170 | 171 | if len(bp.matchers) == 0 { 172 | return false 173 | } 174 | 175 | var matched bool 176 | for _, matcher := range bp.matchers { 177 | if matcher == nil { 178 | continue 179 | } 180 | if matcher.Match(addr) { 181 | matched = true 182 | break 183 | } 184 | } 185 | return !bp.reversed && matched || 186 | bp.reversed && !matched 187 | } 188 | 189 | // AddMatchers appends matchers to the bypass matcher list. 190 | func (bp *Bypass) AddMatchers(matchers ...Matcher) { 191 | bp.mux.Lock() 192 | defer bp.mux.Unlock() 193 | 194 | bp.matchers = append(bp.matchers, matchers...) 195 | } 196 | 197 | // Matchers return the bypass matcher list. 198 | func (bp *Bypass) Matchers() []Matcher { 199 | bp.mux.RLock() 200 | defer bp.mux.RUnlock() 201 | 202 | return bp.matchers 203 | } 204 | 205 | // Reversed reports whether the rules of the bypass are reversed. 206 | func (bp *Bypass) Reversed() bool { 207 | bp.mux.RLock() 208 | defer bp.mux.RUnlock() 209 | 210 | return bp.reversed 211 | } 212 | 213 | // Reload parses config from r, then live reloads the bypass. 214 | func (bp *Bypass) Reload(r io.Reader) error { 215 | var matchers []Matcher 216 | var period time.Duration 217 | var reversed bool 218 | 219 | if r == nil || bp.Stopped() { 220 | return nil 221 | } 222 | 223 | scanner := bufio.NewScanner(r) 224 | for scanner.Scan() { 225 | line := scanner.Text() 226 | ss := splitLine(line) 227 | if len(ss) == 0 { 228 | continue 229 | } 230 | switch ss[0] { 231 | case "reload": // reload option 232 | if len(ss) > 1 { 233 | period, _ = time.ParseDuration(ss[1]) 234 | } 235 | case "reverse": // reverse option 236 | if len(ss) > 1 { 237 | reversed, _ = strconv.ParseBool(ss[1]) 238 | } 239 | default: 240 | matchers = append(matchers, NewMatcher(ss[0])) 241 | } 242 | } 243 | 244 | if err := scanner.Err(); err != nil { 245 | return err 246 | } 247 | 248 | bp.mux.Lock() 249 | defer bp.mux.Unlock() 250 | 251 | bp.matchers = matchers 252 | bp.period = period 253 | bp.reversed = reversed 254 | 255 | return nil 256 | } 257 | 258 | // Period returns the reload period. 259 | func (bp *Bypass) Period() time.Duration { 260 | if bp.Stopped() { 261 | return -1 262 | } 263 | 264 | bp.mux.RLock() 265 | defer bp.mux.RUnlock() 266 | 267 | return bp.period 268 | } 269 | 270 | // Stop stops reloading. 271 | func (bp *Bypass) Stop() { 272 | select { 273 | case <-bp.stopped: 274 | default: 275 | close(bp.stopped) 276 | } 277 | } 278 | 279 | // Stopped checks whether the reloader is stopped. 280 | func (bp *Bypass) Stopped() bool { 281 | select { 282 | case <-bp.stopped: 283 | return true 284 | default: 285 | return false 286 | } 287 | } 288 | 289 | func (bp *Bypass) String() string { 290 | b := &bytes.Buffer{} 291 | fmt.Fprintf(b, "reversed: %v\n", bp.Reversed()) 292 | fmt.Fprintf(b, "reload: %v\n", bp.Period()) 293 | for _, m := range bp.Matchers() { 294 | b.WriteString(m.String()) 295 | b.WriteByte('\n') 296 | } 297 | return b.String() 298 | } 299 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/README.md: -------------------------------------------------------------------------------- 1 | [//]: <> (https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309) 2 | 3 | # Create Root CA (Done once) 4 | 5 | ## Create Root Key 6 | 7 | **Attention:** this is the key used to sign the certificate requests, anyone holding this can sign certificates on your behalf. So keep it in a safe place! 8 | 9 | ```bash 10 | openssl genrsa -des3 -out rootCA.key 4096 11 | ``` 12 | 13 | If you want a non password protected key just remove the `-des3` option 14 | 15 | 16 | ## Create and self sign the Root Certificate 17 | 18 | ```bash 19 | openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt 20 | ``` 21 | 22 | Here we used our root key to create the root certificate that needs to be distributed in all the computers that have to trust us. 23 | 24 | 25 | # Create a certificate (Done for each server) 26 | 27 | This procedure needs to be followed for each server/appliance that needs a trusted certificate from our CA 28 | 29 | ## Create the certificate key 30 | 31 | ``` 32 | openssl genrsa -out mydomain.com.key 2048 33 | ``` 34 | 35 | ## Create the signing request 36 | 37 | **Important:** Please mind that while creating the signign request is important to specify the `Common Name` providing the IP address or URL for the service, otherwise the certificate 38 | cannot be verified 39 | 40 | ``` 41 | openssl req -new -key mydomain.com.key -out mydomain.com.csr 42 | ``` 43 | 44 | ## Generate the certificate using the `mydomain` csr and key along with the CA Root key 45 | 46 | ``` 47 | openssl x509 -req -in mydomain.com.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out mydomain.com.crt -days 500 -sha256 48 | ``` 49 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDvjCCAaYCCQC0XjV3wljvnjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls 3 | b2NhbGhvc3QwHhcNMTgwNzA4MDQ1MzIyWhcNMTkxMTIwMDQ1MzIyWjAuMQswCQYD 4 | VQQGEwJDTjELMAkGA1UEBwwCU0gxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJ 5 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOKgzWil/KjyRy2Axb3XlLB1nMwLFJC 6 | pC6r8yb+1Kq/ldZghJZvymuFVjn+bihvJqvZiOv4KRtnM8gD55AhaQp6Ese5M9b+ 7 | 47HLB//SkfJsQREsmnrHfHxjmUQjhMy7jrpcf9OnDOXQ5zk3v6AWEIqMtAiZ99ku 8 | AQvyJJ07+VpwZrMuzbSGfFBCKEbbqP7yKHjSUm3QDTpTiK4AnBmzlVeThUIA68oa 9 | XZKQVXX/8U2i6H4eq5eNpyUsKSnnuK+cryHpAIK4vNMzw96vATTfEmuWASEzkHhW 10 | 3KtfXE0CIH0GsK5zueGDo9ygnO7hjtx60SWynlGf6c6edxPwNvEmTZcCAwEAATAN 11 | BgkqhkiG9w0BAQsFAAOCAgEApLkdhnDzErgBljY6qRaR0JlouTpqJXwi5BRi7F1P 12 | bx5ukrZAVSOsZ7ncEkZuxkIX+ktBVFBL8twkvMEl+sMQ24R+F+TrlHWN2xPR/pez 13 | 9V19hq26yMIlYLqSq3KZ0W9ZlT2ge+3sTvY+gAJhZ6nOz9WGRJ1mi+pN/ok678QX 14 | KdOJXcePzYr5iKqMq/5cJ2sA1xYwVl+0xrvfRVTFkp4yR6wzGODtjquB+scZ9S64 15 | GWnFTjHAJvUKYxpeoLAt9lZHsESDqGq7hA4z1uVjhNEDJHKnXW4OhXxMB8Gk2hY8 16 | 3k4zbnKsouNNW0a7jijCMpXOem/vgQF4GK5ecp0S+Ml/AunsPoi6rGgOCX8XXmti 17 | 6DfQhsxxgn1co/JKNxhgsnQftXFwKivh73JFctSh+bMLsewfXsvq+b0K3EuuV9bV 18 | EttVCgbUaCDYdA6IDkqD2PRx9tsotne76r+cX+ah+NjnA6XN+XY2bJgV1UaiKTrP 19 | moNHglw+xoUqOJ7FlGJcVC7uIFPhMviNkpSZh6WxX+OSS4fPO25kxxNpldql6I+3 20 | xb5XEHLpPCEI4PyK0rYnsjk764Loqff8YBMFRQSXIUz9ot5SgGs/FY1vsQap5OeD 21 | Hw2usWhCvkSzr7kiXI+30BvJKK2r9GOAM7mtO9dfkM9MMKKnMzd+O2XE4r6PNLrg 22 | Rds= 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/localhost.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICczCCAVsCAQAwLjELMAkGA1UEBhMCQ04xCzAJBgNVBAcMAlNIMRIwEAYDVQQD 3 | DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDioM1 4 | opfyo8kctgMW915SwdZzMCxSQqQuq/Mm/tSqv5XWYISWb8prhVY5/m4obyar2Yjr 5 | +CkbZzPIA+eQIWkKehLHuTPW/uOxywf/0pHybEERLJp6x3x8Y5lEI4TMu466XH/T 6 | pwzl0Oc5N7+gFhCKjLQImffZLgEL8iSdO/lacGazLs20hnxQQihG26j+8ih40lJt 7 | 0A06U4iuAJwZs5VXk4VCAOvKGl2SkFV1//FNouh+HquXjaclLCkp57ivnK8h6QCC 8 | uLzTM8PerwE03xJrlgEhM5B4VtyrX1xNAiB9BrCuc7nhg6PcoJzu4Y7cetElsp5R 9 | n+nOnncT8DbxJk2XAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAr+AkYRAPulBU 10 | B5HR3pAreYrf3Y2fvGLSNo4hvsJkmXJxDgMZnGsjVzW1IZLF8szn4v050y6Qm/Ne 11 | qupabYP5zpj0vKkACYGJ2zadnowmwlTzwlxEOv27uQykC/IuRcjdloAD7ZwhNwmO 12 | dLNjdiXn63GUeSL/JK0UHyXTqvpmiHq+6TAOdl3vmsRFCQDChRtViK2fwSeX2y87 13 | hLicSVQyNOe0gUx7IvE9B2QPNhdzaMVPYeN8I/cayNeUKhiWxEGKhwPAaievuSXJ 14 | fUsz11XYBYW+kjFsTqkV1OjkG0mxvwaiq5W3CRx8365w71IMdKV5t5xhc0n0TXp7 15 | cT27XN7cdw== 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAw4qDNaKX8qPJHLYDFvdeUsHWczAsUkKkLqvzJv7Uqr+V1mCE 3 | lm/Ka4VWOf5uKG8mq9mI6/gpG2czyAPnkCFpCnoSx7kz1v7jscsH/9KR8mxBESya 4 | esd8fGOZRCOEzLuOulx/06cM5dDnOTe/oBYQioy0CJn32S4BC/IknTv5WnBmsy7N 5 | tIZ8UEIoRtuo/vIoeNJSbdANOlOIrgCcGbOVV5OFQgDryhpdkpBVdf/xTaLofh6r 6 | l42nJSwpKee4r5yvIekAgri80zPD3q8BNN8Sa5YBITOQeFbcq19cTQIgfQawrnO5 7 | 4YOj3KCc7uGO3HrRJbKeUZ/pzp53E/A28SZNlwIDAQABAoIBAGx1pMeYMw3L2R5K 8 | urX/aVsf1xI3My5Bdo3IpGsJx+4ZrEOnb4N96FnxMF2kiXd2B44kb/TqxepEOQ2F 9 | VOi2D2xXP5l2WZGz+ZnBUuOL6ZX8g67B/cGCasMX/4gy51Mj6UvnSKOeMeI7GDW9 10 | fVWPR4eB+c4XkMju4ne8zKBGBs4pN4KoxTWSnZSM4p+q/Jb/DMa+kVhFfRjkqfWc 11 | vCpDgHs1uMcHvPBNYO9flDaC2Jgk4cvV9mX0TolXAvaNo8aN0joM7WH3fvw7NCD9 12 | LCkqCmpjOxJIqJQT1twIkSy42q7VaFi7ApyIaMfXlmnj4UTlVTe5bBO+2AgwLYtC 13 | cKgDMjECgYEA8JPm3Pc80EsYB6d4qp/Qmy2VrnlaxZwvaRwh63Pssqthg4SZkIp5 14 | yjXOT4MDlJdrEzMtATRZUXTCRxGFSs0tolNY2KQ2WvYRhISlN8UBkGuMEkRGLuct 15 | p++qpPcSZJcox25kT82CKin1nQYb48k33JAOMUOWIBJO56G35sfPj28CgYEA0BOE 16 | pa+FYj/WxZS79YT1ZbsajeuUKlNUtAIxKJ2cKSQyfFuPM+xZG3H+iroRfR8HCVai 17 | 2+Oz9/TlxZOPR7+P+2fpS8W2tT+Qkmiyz8QJAULd+Irw5XIdatkpVm343XxMx3Pa 18 | 2qtBmgj6RINvsWTWRotMqhcuDRirxqm1IIQhkFkCgYBLNmIhyOXpVODRW8k8xrQI 19 | H6tBHc2EJD0qRlJQczCX9z6ISIdeCfzjfAjhENuos+IU4ZX7X2thLPikEVUzuou+ 20 | yQHo0QXxUCbP4Exq8Bt6FDV5bIDonvvGGgamhlvouN1V5CxWSrCcD/wquEM15q2h 21 | NiRJwJCJvE+Q2R1OeD9q3wKBgFWDkAJf7luAjQ3KoKy4pfnXOYSWCuCSOr94Hyfo 22 | DmPCIpWFM4dNXRmwccIl0kYv2D54QppILJB9L2lRyZLdIZlbDUA802gN5aamLMbC 23 | dEj2aC9bOsGxcnGVKi4BKEQub4eRD6LKuz1I70H1GpQ3MvDvEuTcfeqX9xDAclYY 24 | t4qRAoGBAI6YSTs97DUe7Zwk7q+S3PBU5uct4Dwtmy2XWZdgHwl5aP8apSLciL5Y 25 | PMkpcTMzkuC+QFaPZ8wFcI7GLg0bOs91hkrqscDKEg4nGB9fJkU82iOQZNL4Hv1u 26 | wO7uIGa2kcpNtQOLNO88y45WFyrn5a+T6VhDmIuc+F+TU1ZzdYdH 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/rootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEpDCCAowCCQDwV08QFUCcSzANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls 3 | b2NhbGhvc3QwHhcNMTgwNzA4MDQ1MzE3WhcNMjEwNDI3MDQ1MzE3WjAUMRIwEAYD 4 | VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDE 5 | +71yX9vQY3l52C31e04ACvm2oNMLSCrLOXGewOTFpv9yXjinMC0Ab6Xa4yB4MPtd 6 | ujWDSEq9gkKCkILoalVX7R4gtLDN+EdoVadBw/WHbrGB4sHnFOWwpUbjiiwwPSU5 7 | qXjcqIqR2sA1BgoAv6c1qHq7V4bgbEjtGL71KkDoQZSIgNyJlXe1mcUKqmqeAnro 8 | 0WfwrNXyYt66L3PmCy9MIpWRxf9sa8PDsgT3SQEN4BHb70Z8hNj6RXfVGDZcpfcI 9 | iwbrL//YnK7waRKaKD4LoLOodE0cx3fowSWYvlwUAoYx8wFKOcdtM1zaUp+QDAug 10 | xT5g5ghU150XuqhDU6Wq1An8dLgcDU1D4cxhLk/W8OXtIk4k7yny6eUJi2zHziMm 11 | 8jfHd3M6SwohUrE3LsQI5gpvu4sAVFLMkRxaWZ95XhsVMmIsE/L5FHwDfqid0dvx 12 | bafOKT+fI3N3BaUPJlVHCNqSzSZIW59+ufnDwBV7SmJj4KMlvixEU+EFfPFdGiCA 13 | Lr0dSG5+Scx1aClaMUeVccCljp2f99IEa9wI+xwMPDStkOmnhVuqG1aEogggQZkD 14 | /5yh04wrn8EwYCAiasNNUXTV7AoqIt2bgeFbGo2Qr7LdsYuUmaWEzTm0KsHogkkg 15 | Ibd3RPBLDr/WfWI13oHMdsz8jjbXG/D1AhrcdozYDQIDAQABMA0GCSqGSIb3DQEB 16 | CwUAA4ICAQC1EeQqn2AwrS+UVc5fKRpHzV9ZMiDpxFMRLsDWBP5kNr1nSA72yYcR 17 | WgxvdqG/rGRds6lvRbvIaWD0zeujPkR3iCpb1V5oRXQ6lWOlY44pZEwCdnDd2M8I 18 | yQ7BLZCHHmlCN7a51n2o0D78HeILIeeTCQlKFDc5r51qrZbZR5DZmrp9jaZ+3eCg 19 | LQ3Onfj0WEmQFuMFGQrbJ2oaCC1GvuZWEbRh+lrxjRKOyCaRQFTY4Efe8tIwMm6J 20 | 1iyMtqK7BxminQCfizQrstB67wMljydYeUf+wwbgkiKGYc9VGopckrO3lntzKycu 21 | 9l0BmlZYkmCFt3cv23BcqAbcLdyLXh3yASwVMXaLZ4iVSaslRm4uX+gbKFCBABLa 22 | vqu7JQHfAPOeYj7zCrN12EHejPxdCjSImBeAbe56vax4uFGAodXxDGcepRItSzax 23 | qPKJd8U/8e3JDn+wmZNKwD9UGLZPbiuYOg7X+EWhjki0J6ZjgLc8dMleeD2rO+j2 24 | P/Wgv1gMr6J1svUlqkNf1Ng9eSbl/nMhuOBVOGcPnK7+wCLxM7ByaR0QgeH6/9VO 25 | 4urq53/vspBC679BHsZx3gIhcg4VefmOM2cZnTRM4izPstq1JBQkbuvz+5XuT7Yj 26 | 5Fk1/xkapCUifntKYSoslkkbNHRYxAInqkc0txn3qNBI8GAQFksz5g== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/rootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEAxPu9cl/b0GN5edgt9XtOAAr5tqDTC0gqyzlxnsDkxab/cl44 3 | pzAtAG+l2uMgeDD7Xbo1g0hKvYJCgpCC6GpVV+0eILSwzfhHaFWnQcP1h26xgeLB 4 | 5xTlsKVG44osMD0lOal43KiKkdrANQYKAL+nNah6u1eG4GxI7Ri+9SpA6EGUiIDc 5 | iZV3tZnFCqpqngJ66NFn8KzV8mLeui9z5gsvTCKVkcX/bGvDw7IE90kBDeAR2+9G 6 | fITY+kV31Rg2XKX3CIsG6y//2Jyu8GkSmig+C6CzqHRNHMd36MElmL5cFAKGMfMB 7 | SjnHbTNc2lKfkAwLoMU+YOYIVNedF7qoQ1OlqtQJ/HS4HA1NQ+HMYS5P1vDl7SJO 8 | JO8p8unlCYtsx84jJvI3x3dzOksKIVKxNy7ECOYKb7uLAFRSzJEcWlmfeV4bFTJi 9 | LBPy+RR8A36ondHb8W2nzik/nyNzdwWlDyZVRwjaks0mSFuffrn5w8AVe0piY+Cj 10 | Jb4sRFPhBXzxXRoggC69HUhufknMdWgpWjFHlXHApY6dn/fSBGvcCPscDDw0rZDp 11 | p4VbqhtWhKIIIEGZA/+codOMK5/BMGAgImrDTVF01ewKKiLdm4HhWxqNkK+y3bGL 12 | lJmlhM05tCrB6IJJICG3d0TwSw6/1n1iNd6BzHbM/I421xvw9QIa3HaM2A0CAwEA 13 | AQKCAgBXbeSH/0PxGjWwfuLnMfNM0ZJEHN2PBFj6GmTzsWnY0GZQvMEoc5mFuAhF 14 | PsoKjrMCxsM5obyKoGYkzT9NKOT4QaY9nfVbdfc7t8ikx/USR29B1wN5LS1FWhY8 15 | p/c08e6zySR7y9K1KgJlhmiqLGZqynyu6gpTUbyMf49CAZ8Ndw4WCBvadRzM3ZM3 16 | SKxJtZAYBdm8WPocuwVgXe9zC0PS5wa7zMWxuaMKGNlbaGuvXOSQWYNPgSdM7chi 17 | LHz0YjVi9VH80TEdU23SBtDa20Gup4UWH4iaXW47QH8PbG4x82zcfp7z8vEw5rsv 18 | q7xmkvIWSXWGTJMmFQ0EmzRTray5fj38Oo2ZtwHvkJQ1WkiFNFiWFZXnoS3z6h5q 19 | 1lX6ZUhCoobUJRRlDYCwNDV6dMYKXK2NNeD1MPvzUoUIpoQnoxnNF+VYMMENax3e 20 | YuEiT6xbBXzB/WE0bFVAtSPzf1vPVw+8MP5BhaH3lQb6XA89FiEZg+u95rNpf2SA 21 | gFWvz0VZsGab+LwYhbYdicmKPRH+2Pzpt5MdWt8jyo066Lv0NP5xpH9IUv/u2RX7 22 | Ycw0Bu1HWKLoEzovoH6OEa0n1A7H+PNOhABzrLvbU8GMp4kEQpXACxR43KruxE7S 23 | QgotUAb7teCP54yEHTVAe06YIaq4JPk5xqnmMVvaeuy5rvssAQKCAQEA5d4NUONV 24 | /An+bAf31HicZfRH6Pf1N3JUdjYz2l1y60Pf7dzlDI2fjOWlccp24+efXLM1sMeK 25 | GXQZsAnYJevZktQxodM67CsgEFgdGhH2s5Ey3Dp5bt3uS/SaFv4sT1x9awYdYtKp 26 | 6fGovjB1Qp/eMuZNJVl9RwegFVzzrrSMxucNzUuL4v4L911ypR4wz4s1ptqI31/U 27 | 56B1VRKjwZntqJaNO2Plt/yY0s+ganhzdKBynoOKzTpYHCqZhHdqvqfc1qC0W1xI 28 | E/b3Nf0J+GqjjT7JDbWgqNty5ipDfCdIeems96U1Gu9oeKGDzvCgtImZNFMyHzLM 29 | MhO0v6GA6zkuVQKCAQEA22Cls2AAUuugi2tdZR/krHokrUMPaKvi9RIQpqHpoKqL 30 | E3rKX9aWyIMhktch7VsnDF52R8CMUhgc7PfL4wsWA5cCq26x4E57aJUH5mxc5va5 31 | n5Sxb3C99Ytr6GpCkG4Y3pzO93ihgfuW+mQREYLpFYd/c/1SH/e4Wx5nGKx6YocY 32 | 6b/AbxWcRMlihC2gK7JFgSZMqaL+wn68oKJ7j8RUN+ykZEzBXBWL2l4oKWm+qBDx 33 | pOFSQODeQ0CQhPWovD4dVNmyrh5TcDUlJQ3+iU2hRkXe13mLTkGpSM53kwkrfVn+ 34 | 4SmVLEm5YhNcHG14A1yDqs6SY//8l5xfUeZyrPBK2QKCAQEAqIsRLm8SG9RkFWge 35 | Qk8RNfxQQbSVu0r8PRTvHjyIx5Ij/e+KjpLFGvVDQtUWKXMquTi5tF4KlzE2qIn/ 36 | T4bIKE2n+qS7vnC8eN9yryvevLlJFotVgIH/ePfnh9ZkPOhvGWsJXu1iIqPLe3Bi 37 | ejBoJuAQTsN4BP3FVgSqtD20Px8pUo8DCbQGqCB/sCwb1AGZnDb+RvKoVBGmFnOt 38 | WIX56TRCZ/qOdEIk9+W/FHIvDaObhziiLGqMMlLV73fz78l7Nm/s7lQSkXjyuEZJ 39 | 6jiepTEVEBVNsKH/dF4mz0CqdqFs7sPW1WIXMuQSlkh/PQDrMZ+Sz6daa5lhXWUY 40 | 9uAdZQKCAQBrDzRuYIhn7yPPRlsy0ai4X3dsstBfRZsh/Gnx2Ax64x+yJveCY+f7 41 | /LqyvZiKDDT3PVY92ALiwW/EWX2/1JYutFCSNxhJniNtu2U6l2GTOY8HCPq6puud 42 | XCgSKWFIuOIcKax7avxuwchBc/o8cIWtgw25HkQo46ytkx2/FdU4JjQLRw/zZjl3 43 | /Eu+s8F58asnxvgcxTXM1yrYvdLNK4PqMutbI3YtqToyHEc/RqLLxFEZJPkOPm9Z 44 | pLWinXx2OV35HbCsdpJDrTvuZHD2stLkx45j26YXT8X8iP4j3JLDvtq7KZ7qGSSG 45 | b2pBWU77XPfIsL0SXkf3+VEvV+ZY7X+pAoIBABV6Mu5Yr4UxI+ZgsaKgcK8aKoyD 46 | 5GDshxkxs8R3K1i7eCF0mEjxkV25r11KX10qFvG+hPJqizKQDBOCQC2w3noqz42p 47 | QVUeBNXpDVGoImD1/4DqUvQMivTwHWS+wSAi/wYAODJ6/bWP5Kil/7iDOwCPp0WD 48 | mLd0ujjwkOw3Xksn2Gd01pXeiT4FZpkYnyh5ddWGf1TihRFATW5+vpi6t+6KX3LR 49 | hwd9zi6soSwju/n986NUfGfeewBb6/fnh6hM/vfS2a0Blvk/7yM1k2P0uN+TzLYf 50 | skhRay10UoMwtXak+q/DBzrrAbW3EwuIdV66H4dx1AV5NMq6kAAtfDXc728= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /cmd/gost/.ssl/rootCA.srl: -------------------------------------------------------------------------------- 1 | B45E3577C258EF9E 2 | -------------------------------------------------------------------------------- /cmd/gost/cfg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "encoding/json" 8 | "fmt" 9 | "net" 10 | "net/url" 11 | "os" 12 | "strings" 13 | 14 | "github.com/ginuerzh/gost" 15 | ) 16 | 17 | var ( 18 | routers []router 19 | ) 20 | 21 | type baseConfig struct { 22 | route 23 | Routes []route 24 | Debug bool 25 | } 26 | 27 | func parseBaseConfig(s string) (*baseConfig, error) { 28 | file, err := os.Open(s) 29 | if err != nil { 30 | return nil, err 31 | } 32 | defer file.Close() 33 | 34 | if err := json.NewDecoder(file).Decode(baseCfg); err != nil { 35 | return nil, err 36 | } 37 | 38 | return baseCfg, nil 39 | } 40 | 41 | var ( 42 | defaultCertFile = "cert.pem" 43 | defaultKeyFile = "key.pem" 44 | ) 45 | 46 | // Load the certificate from cert & key files and optional client CA file, 47 | // will use the default certificate if the provided info are invalid. 48 | func tlsConfig(certFile, keyFile, caFile string) (*tls.Config, error) { 49 | if certFile == "" || keyFile == "" { 50 | certFile, keyFile = defaultCertFile, defaultKeyFile 51 | } 52 | 53 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | cfg := &tls.Config{Certificates: []tls.Certificate{cert}} 59 | 60 | pool, err := loadCA(caFile) 61 | if err != nil { 62 | return nil, err 63 | } 64 | if pool != nil { 65 | cfg.ClientCAs = pool 66 | cfg.ClientAuth = tls.RequireAndVerifyClientCert 67 | } 68 | 69 | return cfg, nil 70 | } 71 | 72 | func loadCA(caFile string) (cp *x509.CertPool, err error) { 73 | if caFile == "" { 74 | return 75 | } 76 | cp = x509.NewCertPool() 77 | data, err := os.ReadFile(caFile) 78 | if err != nil { 79 | return nil, err 80 | } 81 | if !cp.AppendCertsFromPEM(data) { 82 | return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile) 83 | } 84 | return 85 | } 86 | 87 | func parseKCPConfig(configFile string) (*gost.KCPConfig, error) { 88 | if configFile == "" { 89 | return nil, nil 90 | } 91 | file, err := os.Open(configFile) 92 | if err != nil { 93 | return nil, err 94 | } 95 | defer file.Close() 96 | 97 | config := &gost.KCPConfig{} 98 | if err = json.NewDecoder(file).Decode(config); err != nil { 99 | return nil, err 100 | } 101 | return config, nil 102 | } 103 | 104 | func parseUsers(authFile string) (users []*url.Userinfo, err error) { 105 | if authFile == "" { 106 | return 107 | } 108 | 109 | file, err := os.Open(authFile) 110 | if err != nil { 111 | return 112 | } 113 | defer file.Close() 114 | scanner := bufio.NewScanner(file) 115 | for scanner.Scan() { 116 | line := strings.TrimSpace(scanner.Text()) 117 | if line == "" || strings.HasPrefix(line, "#") { 118 | continue 119 | } 120 | 121 | s := strings.SplitN(line, " ", 2) 122 | if len(s) == 1 { 123 | users = append(users, url.User(strings.TrimSpace(s[0]))) 124 | } else if len(s) == 2 { 125 | users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1]))) 126 | } 127 | } 128 | 129 | err = scanner.Err() 130 | return 131 | } 132 | 133 | func parseAuthenticator(s string) (gost.Authenticator, error) { 134 | if s == "" { 135 | return nil, nil 136 | } 137 | f, err := os.Open(s) 138 | if err != nil { 139 | return nil, err 140 | } 141 | defer f.Close() 142 | 143 | au := gost.NewLocalAuthenticator(nil) 144 | au.Reload(f) 145 | 146 | go gost.PeriodReload(au, s) 147 | 148 | return au, nil 149 | } 150 | 151 | func parseIP(s string, port string) (ips []string) { 152 | if s == "" { 153 | return 154 | } 155 | if port == "" { 156 | port = "8080" // default port 157 | } 158 | 159 | addrFn := func(s, port string) string { 160 | c := strings.Count(s, ":") 161 | if c == 0 || //ipv4 or domain 162 | s[len(s)-1] == ']' { //[ipv6] 163 | return s + ":" + port 164 | } 165 | if c > 1 && s[0] != '[' { // ipv6 166 | return "[" + s + "]:" + port 167 | } 168 | return s //ipv4:port or [ipv6]:port 169 | } 170 | 171 | file, err := os.Open(s) 172 | if err != nil { 173 | ss := strings.Split(s, ",") 174 | for _, s := range ss { 175 | s = strings.TrimSpace(s) 176 | if s != "" { 177 | ips = append(ips, addrFn(s, port)) 178 | } 179 | 180 | } 181 | return 182 | } 183 | defer file.Close() 184 | scanner := bufio.NewScanner(file) 185 | for scanner.Scan() { 186 | line := strings.TrimSpace(scanner.Text()) 187 | if line == "" || strings.HasPrefix(line, "#") { 188 | continue 189 | } 190 | ips = append(ips, addrFn(line, port)) 191 | } 192 | return 193 | } 194 | 195 | func parseBypass(s string) *gost.Bypass { 196 | if s == "" { 197 | return nil 198 | } 199 | var matchers []gost.Matcher 200 | var reversed bool 201 | if strings.HasPrefix(s, "~") { 202 | reversed = true 203 | s = strings.TrimLeft(s, "~") 204 | } 205 | 206 | f, err := os.Open(s) 207 | if err != nil { 208 | for _, s := range strings.Split(s, ",") { 209 | s = strings.TrimSpace(s) 210 | if s == "" { 211 | continue 212 | } 213 | matchers = append(matchers, gost.NewMatcher(s)) 214 | } 215 | return gost.NewBypass(reversed, matchers...) 216 | } 217 | defer f.Close() 218 | 219 | bp := gost.NewBypass(reversed) 220 | bp.Reload(f) 221 | go gost.PeriodReload(bp, s) 222 | 223 | return bp 224 | } 225 | 226 | func parseResolver(cfg string) gost.Resolver { 227 | if cfg == "" { 228 | return nil 229 | } 230 | var nss []gost.NameServer 231 | 232 | f, err := os.Open(cfg) 233 | if err != nil { 234 | for _, s := range strings.Split(cfg, ",") { 235 | s = strings.TrimSpace(s) 236 | if s == "" { 237 | continue 238 | } 239 | if strings.HasPrefix(s, "https") { 240 | p := "https" 241 | u, _ := url.Parse(s) 242 | if u == nil || u.Scheme == "" { 243 | continue 244 | } 245 | if u.Scheme == "https-chain" { 246 | p = u.Scheme 247 | } 248 | ns := gost.NameServer{ 249 | Addr: s, 250 | Protocol: p, 251 | } 252 | nss = append(nss, ns) 253 | continue 254 | } 255 | 256 | ss := strings.Split(s, "/") 257 | if len(ss) == 1 { 258 | ns := gost.NameServer{ 259 | Addr: ss[0], 260 | } 261 | nss = append(nss, ns) 262 | } 263 | if len(ss) == 2 { 264 | ns := gost.NameServer{ 265 | Addr: ss[0], 266 | Protocol: ss[1], 267 | } 268 | nss = append(nss, ns) 269 | } 270 | } 271 | return gost.NewResolver(0, nss...) 272 | } 273 | defer f.Close() 274 | 275 | resolver := gost.NewResolver(0) 276 | resolver.Reload(f) 277 | 278 | go gost.PeriodReload(resolver, cfg) 279 | 280 | return resolver 281 | } 282 | 283 | func parseHosts(s string) *gost.Hosts { 284 | f, err := os.Open(s) 285 | if err != nil { 286 | return nil 287 | } 288 | defer f.Close() 289 | 290 | hosts := gost.NewHosts() 291 | hosts.Reload(f) 292 | 293 | go gost.PeriodReload(hosts, s) 294 | 295 | return hosts 296 | } 297 | 298 | func parseIPRoutes(s string) (routes []gost.IPRoute) { 299 | if s == "" { 300 | return 301 | } 302 | 303 | file, err := os.Open(s) 304 | if err != nil { 305 | ss := strings.Split(s, ",") 306 | for _, s := range ss { 307 | if _, inet, _ := net.ParseCIDR(strings.TrimSpace(s)); inet != nil { 308 | routes = append(routes, gost.IPRoute{Dest: inet}) 309 | } 310 | } 311 | return 312 | } 313 | 314 | defer file.Close() 315 | scanner := bufio.NewScanner(file) 316 | for scanner.Scan() { 317 | line := strings.Replace(scanner.Text(), "\t", " ", -1) 318 | line = strings.TrimSpace(line) 319 | if line == "" || strings.HasPrefix(line, "#") { 320 | continue 321 | } 322 | 323 | var route gost.IPRoute 324 | var ss []string 325 | for _, s := range strings.Split(line, " ") { 326 | if s = strings.TrimSpace(s); s != "" { 327 | ss = append(ss, s) 328 | } 329 | } 330 | if len(ss) > 0 && ss[0] != "" { 331 | _, route.Dest, _ = net.ParseCIDR(strings.TrimSpace(ss[0])) 332 | if route.Dest == nil { 333 | continue 334 | } 335 | } 336 | if len(ss) > 1 && ss[1] != "" { 337 | route.Gateway = net.ParseIP(ss[1]) 338 | } 339 | routes = append(routes, route) 340 | } 341 | return routes 342 | } 343 | -------------------------------------------------------------------------------- /cmd/gost/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "runtime" 11 | 12 | _ "net/http/pprof" 13 | 14 | "github.com/ginuerzh/gost" 15 | "github.com/go-log/log" 16 | ) 17 | 18 | var ( 19 | configureFile string 20 | baseCfg = &baseConfig{} 21 | pprofAddr string 22 | pprofEnabled = os.Getenv("PROFILING") != "" 23 | ) 24 | 25 | func init() { 26 | gost.SetLogger(&gost.LogLogger{}) 27 | 28 | var ( 29 | printVersion bool 30 | ) 31 | 32 | flag.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain") 33 | flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)") 34 | flag.IntVar(&baseCfg.route.Mark, "M", 0, "Specify out connection mark") 35 | flag.StringVar(&configureFile, "C", "", "configure file") 36 | flag.StringVar(&baseCfg.route.Interface, "I", "", "Interface to bind") 37 | flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log") 38 | flag.BoolVar(&printVersion, "V", false, "print version") 39 | if pprofEnabled { 40 | flag.StringVar(&pprofAddr, "P", ":6060", "profiling HTTP server address") 41 | } 42 | flag.Parse() 43 | 44 | if printVersion { 45 | fmt.Fprintf(os.Stdout, "gost %s (%s %s/%s)\n", 46 | gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) 47 | os.Exit(0) 48 | } 49 | 50 | if configureFile != "" { 51 | _, err := parseBaseConfig(configureFile) 52 | if err != nil { 53 | log.Log(err) 54 | os.Exit(1) 55 | } 56 | } 57 | if flag.NFlag() == 0 { 58 | flag.PrintDefaults() 59 | os.Exit(0) 60 | } 61 | } 62 | 63 | func main() { 64 | if pprofEnabled { 65 | go func() { 66 | log.Log("profiling server on", pprofAddr) 67 | log.Log(http.ListenAndServe(pprofAddr, nil)) 68 | }() 69 | } 70 | 71 | // NOTE: as of 2.6, you can use custom cert/key files to initialize the default certificate. 72 | tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile, "") 73 | if err != nil { 74 | // generate random self-signed certificate. 75 | cert, err := gost.GenCertificate() 76 | if err != nil { 77 | log.Log(err) 78 | os.Exit(1) 79 | } 80 | tlsConfig = &tls.Config{ 81 | Certificates: []tls.Certificate{cert}, 82 | } 83 | } else { 84 | log.Log("load TLS certificate files OK") 85 | } 86 | 87 | gost.DefaultTLSConfig = tlsConfig 88 | 89 | if err := start(); err != nil { 90 | log.Log(err) 91 | os.Exit(1) 92 | } 93 | 94 | select {} 95 | } 96 | 97 | func start() error { 98 | gost.Debug = baseCfg.Debug 99 | 100 | var routers []router 101 | rts, err := baseCfg.route.GenRouters() 102 | if err != nil { 103 | return err 104 | } 105 | routers = append(routers, rts...) 106 | 107 | for _, route := range baseCfg.Routes { 108 | rts, err := route.GenRouters() 109 | if err != nil { 110 | return err 111 | } 112 | routers = append(routers, rts...) 113 | } 114 | 115 | if len(routers) == 0 { 116 | return errors.New("invalid config") 117 | } 118 | for i := range routers { 119 | go routers[i].Serve() 120 | } 121 | 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /cmd/gost/peer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "io" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/ginuerzh/gost" 13 | ) 14 | 15 | type peerConfig struct { 16 | Strategy string `json:"strategy"` 17 | MaxFails int `json:"max_fails"` 18 | FastestCount int `json:"fastest_count"` // topN fastest node count 19 | FailTimeout time.Duration 20 | period time.Duration // the period for live reloading 21 | 22 | Nodes []string `json:"nodes"` 23 | group *gost.NodeGroup 24 | baseNodes []gost.Node 25 | stopped chan struct{} 26 | } 27 | 28 | func newPeerConfig() *peerConfig { 29 | return &peerConfig{ 30 | stopped: make(chan struct{}), 31 | } 32 | } 33 | 34 | func (cfg *peerConfig) Validate() { 35 | } 36 | 37 | func (cfg *peerConfig) Reload(r io.Reader) error { 38 | if cfg.Stopped() { 39 | return nil 40 | } 41 | 42 | if err := cfg.parse(r); err != nil { 43 | return err 44 | } 45 | cfg.Validate() 46 | 47 | group := cfg.group 48 | group.SetSelector( 49 | nil, 50 | gost.WithFilter( 51 | &gost.FailFilter{ 52 | MaxFails: cfg.MaxFails, 53 | FailTimeout: cfg.FailTimeout, 54 | }, 55 | &gost.InvalidFilter{}, 56 | gost.NewFastestFilter(0, cfg.FastestCount), 57 | ), 58 | gost.WithStrategy(gost.NewStrategy(cfg.Strategy)), 59 | ) 60 | 61 | gNodes := cfg.baseNodes 62 | nid := len(gNodes) + 1 63 | for _, s := range cfg.Nodes { 64 | nodes, err := parseChainNode(s) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | for i := range nodes { 70 | nodes[i].ID = nid 71 | nid++ 72 | } 73 | 74 | gNodes = append(gNodes, nodes...) 75 | } 76 | 77 | nodes := group.SetNodes(gNodes...) 78 | for _, node := range nodes[len(cfg.baseNodes):] { 79 | if node.Bypass != nil { 80 | node.Bypass.Stop() // clear the old nodes 81 | } 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func (cfg *peerConfig) parse(r io.Reader) error { 88 | data, err := io.ReadAll(r) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | // compatible with JSON format 94 | if err := json.NewDecoder(bytes.NewReader(data)).Decode(cfg); err == nil { 95 | return nil 96 | } 97 | 98 | split := func(line string) []string { 99 | if line == "" { 100 | return nil 101 | } 102 | if n := strings.IndexByte(line, '#'); n >= 0 { 103 | line = line[:n] 104 | } 105 | line = strings.Replace(line, "\t", " ", -1) 106 | line = strings.TrimSpace(line) 107 | 108 | var ss []string 109 | for _, s := range strings.Split(line, " ") { 110 | if s = strings.TrimSpace(s); s != "" { 111 | ss = append(ss, s) 112 | } 113 | } 114 | return ss 115 | } 116 | 117 | cfg.Nodes = nil 118 | scanner := bufio.NewScanner(bytes.NewReader(data)) 119 | for scanner.Scan() { 120 | line := scanner.Text() 121 | ss := split(line) 122 | if len(ss) < 2 { 123 | continue 124 | } 125 | 126 | switch ss[0] { 127 | case "strategy": 128 | cfg.Strategy = ss[1] 129 | case "max_fails": 130 | cfg.MaxFails, _ = strconv.Atoi(ss[1]) 131 | case "fastest_count": 132 | cfg.FastestCount, _ = strconv.Atoi(ss[1]) 133 | case "fail_timeout": 134 | cfg.FailTimeout, _ = time.ParseDuration(ss[1]) 135 | case "reload": 136 | cfg.period, _ = time.ParseDuration(ss[1]) 137 | case "peer": 138 | cfg.Nodes = append(cfg.Nodes, ss[1]) 139 | } 140 | } 141 | 142 | return scanner.Err() 143 | } 144 | 145 | func (cfg *peerConfig) Period() time.Duration { 146 | if cfg.Stopped() { 147 | return -1 148 | } 149 | return cfg.period 150 | } 151 | 152 | // Stop stops reloading. 153 | func (cfg *peerConfig) Stop() { 154 | select { 155 | case <-cfg.stopped: 156 | default: 157 | close(cfg.stopped) 158 | } 159 | } 160 | 161 | // Stopped checks whether the reloader is stopped. 162 | func (cfg *peerConfig) Stopped() bool { 163 | select { 164 | case <-cfg.stopped: 165 | return true 166 | default: 167 | return false 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/tls" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "sync" 14 | "time" 15 | 16 | "github.com/go-log/log" 17 | ) 18 | 19 | func init() { 20 | SetLogger(&NopLogger{}) 21 | // SetLogger(&LogLogger{}) 22 | Debug = true 23 | DialTimeout = 1000 * time.Millisecond 24 | HandshakeTimeout = 1000 * time.Millisecond 25 | ConnectTimeout = 1000 * time.Millisecond 26 | 27 | cert, err := GenCertificate() 28 | if err != nil { 29 | panic(err) 30 | } 31 | DefaultTLSConfig = &tls.Config{ 32 | Certificates: []tls.Certificate{cert}, 33 | } 34 | } 35 | 36 | var ( 37 | httpTestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 38 | data, _ := io.ReadAll(r.Body) 39 | if len(data) == 0 { 40 | data = []byte("Hello World!") 41 | } 42 | io.Copy(w, bytes.NewReader(data)) 43 | }) 44 | 45 | udpTestHandler = udpHandlerFunc(func(w io.Writer, r *udpRequest) { 46 | io.Copy(w, r.Body) 47 | }) 48 | ) 49 | 50 | // proxyConn obtains a connection to the proxy server. 51 | func proxyConn(client *Client, server *Server) (net.Conn, error) { 52 | conn, err := client.Dial(server.Addr().String()) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | cc, err := client.Handshake(conn, AddrHandshakeOption(server.Addr().String())) 58 | if err != nil { 59 | conn.Close() 60 | return nil, err 61 | } 62 | 63 | return cc, nil 64 | } 65 | 66 | // httpRoundtrip does a HTTP request-response roundtrip, and checks the data received. 67 | func httpRoundtrip(conn net.Conn, targetURL string, data []byte) (err error) { 68 | req, err := http.NewRequest( 69 | http.MethodGet, 70 | targetURL, 71 | bytes.NewReader(data), 72 | ) 73 | if err != nil { 74 | return 75 | } 76 | if err = req.Write(conn); err != nil { 77 | return 78 | } 79 | resp, err := http.ReadResponse(bufio.NewReader(conn), req) 80 | if err != nil { 81 | return 82 | } 83 | defer resp.Body.Close() 84 | 85 | if resp.StatusCode != http.StatusOK { 86 | return errors.New(resp.Status) 87 | } 88 | 89 | recv, err := io.ReadAll(resp.Body) 90 | if err != nil { 91 | return 92 | } 93 | 94 | if !bytes.Equal(data, recv) { 95 | return fmt.Errorf("data not equal") 96 | } 97 | return 98 | } 99 | 100 | func udpRoundtrip(logger log.Logger, client *Client, server *Server, host string, data []byte) (err error) { 101 | conn, err := proxyConn(client, server) 102 | if err != nil { 103 | return 104 | } 105 | defer conn.Close() 106 | 107 | conn, err = client.Connect(conn, host) 108 | if err != nil { 109 | return 110 | } 111 | 112 | conn.SetDeadline(time.Now().Add(1 * time.Second)) 113 | defer conn.SetDeadline(time.Time{}) 114 | 115 | if _, err = conn.Write(data); err != nil { 116 | logger.Logf("write to %s via %s: %s", host, server.Addr(), err) 117 | return 118 | } 119 | 120 | recv := make([]byte, len(data)) 121 | if _, err = conn.Read(recv); err != nil { 122 | logger.Logf("read from %s via %s: %s", host, server.Addr(), err) 123 | return 124 | } 125 | 126 | if !bytes.Equal(data, recv) { 127 | return fmt.Errorf("data not equal") 128 | } 129 | 130 | return 131 | } 132 | 133 | func proxyRoundtrip(client *Client, server *Server, targetURL string, data []byte) (err error) { 134 | conn, err := proxyConn(client, server) 135 | if err != nil { 136 | return err 137 | } 138 | defer conn.Close() 139 | 140 | u, err := url.Parse(targetURL) 141 | if err != nil { 142 | return 143 | } 144 | 145 | conn, err = client.Connect(conn, u.Host) 146 | if err != nil { 147 | return 148 | } 149 | 150 | conn.SetDeadline(time.Now().Add(1000 * time.Millisecond)) 151 | defer conn.SetDeadline(time.Time{}) 152 | 153 | return httpRoundtrip(conn, targetURL, data) 154 | } 155 | 156 | type udpRequest struct { 157 | Body io.Reader 158 | RemoteAddr string 159 | } 160 | 161 | type udpResponseWriter struct { 162 | conn net.PacketConn 163 | addr net.Addr 164 | } 165 | 166 | func (w *udpResponseWriter) Write(p []byte) (int, error) { 167 | return w.conn.WriteTo(p, w.addr) 168 | } 169 | 170 | type udpHandlerFunc func(w io.Writer, r *udpRequest) 171 | 172 | // udpTestServer is a UDP server for test. 173 | type udpTestServer struct { 174 | ln net.PacketConn 175 | handler udpHandlerFunc 176 | wg sync.WaitGroup 177 | mu sync.Mutex // guards closed and conns 178 | closed bool 179 | startChan chan struct{} 180 | exitChan chan struct{} 181 | } 182 | 183 | func newUDPTestServer(handler udpHandlerFunc) *udpTestServer { 184 | laddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:0") 185 | ln, err := net.ListenUDP("udp", laddr) 186 | if err != nil { 187 | panic(fmt.Sprintf("udptest: failed to listen on a port: %v", err)) 188 | } 189 | 190 | return &udpTestServer{ 191 | ln: ln, 192 | handler: handler, 193 | startChan: make(chan struct{}), 194 | exitChan: make(chan struct{}), 195 | } 196 | } 197 | 198 | func (s *udpTestServer) Start() { 199 | go s.serve() 200 | <-s.startChan 201 | } 202 | 203 | func (s *udpTestServer) serve() { 204 | select { 205 | case <-s.startChan: 206 | return 207 | default: 208 | close(s.startChan) 209 | } 210 | 211 | for { 212 | data := make([]byte, 32*1024) 213 | n, raddr, err := s.ln.ReadFrom(data) 214 | if err != nil { 215 | break 216 | } 217 | if s.handler != nil { 218 | s.wg.Add(1) 219 | go func() { 220 | defer s.wg.Done() 221 | w := &udpResponseWriter{ 222 | conn: s.ln, 223 | addr: raddr, 224 | } 225 | r := &udpRequest{ 226 | Body: bytes.NewReader(data[:n]), 227 | RemoteAddr: raddr.String(), 228 | } 229 | s.handler(w, r) 230 | }() 231 | } 232 | } 233 | 234 | // signal the listener has been exited. 235 | close(s.exitChan) 236 | } 237 | 238 | func (s *udpTestServer) Addr() string { 239 | return s.ln.LocalAddr().String() 240 | } 241 | 242 | func (s *udpTestServer) Close() error { 243 | s.mu.Lock() 244 | 245 | if s.closed { 246 | s.mu.Unlock() 247 | return nil 248 | } 249 | 250 | err := s.ln.Close() 251 | s.closed = true 252 | s.mu.Unlock() 253 | 254 | <-s.exitChan 255 | 256 | s.wg.Wait() 257 | 258 | return err 259 | } 260 | -------------------------------------------------------------------------------- /examples/bench/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "log" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "sync" 11 | "time" 12 | 13 | "github.com/ginuerzh/gost" 14 | "golang.org/x/net/http2" 15 | ) 16 | 17 | var ( 18 | requests, concurrency int 19 | quiet bool 20 | swg, ewg sync.WaitGroup 21 | ) 22 | 23 | func init() { 24 | log.SetFlags(log.LstdFlags | log.Lshortfile) 25 | 26 | flag.IntVar(&requests, "n", 1, "Number of requests to perform") 27 | flag.IntVar(&concurrency, "c", 1, "Number of multiple requests to make at a time") 28 | flag.BoolVar(&quiet, "q", false, "quiet mode") 29 | flag.BoolVar(&http2.VerboseLogs, "v", false, "HTTP2 verbose logs") 30 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 31 | flag.Parse() 32 | 33 | if quiet { 34 | gost.SetLogger(&gost.NopLogger{}) 35 | } 36 | } 37 | 38 | func main() { 39 | chain := gost.NewChain( 40 | 41 | /* 42 | // http+tcp 43 | gost.Node{ 44 | Addr: "127.0.0.1:18080", 45 | Client: gost.NewClient( 46 | gost.HTTPConnector(url.UserPassword("admin", "123456")), 47 | gost.TCPTransporter(), 48 | ), 49 | }, 50 | */ 51 | 52 | /* 53 | // socks5+tcp 54 | gost.Node{ 55 | Addr: "127.0.0.1:11080", 56 | Client: gost.NewClient( 57 | gost.SOCKS5Connector(url.UserPassword("admin", "123456")), 58 | gost.TCPTransporter(), 59 | ), 60 | }, 61 | */ 62 | 63 | /* 64 | // ss+tcp 65 | gost.Node{ 66 | Addr: "127.0.0.1:18338", 67 | Client: gost.NewClient( 68 | gost.ShadowConnector(url.UserPassword("chacha20", "123456")), 69 | gost.TCPTransporter(), 70 | ), 71 | }, 72 | */ 73 | 74 | /* 75 | // http+ws 76 | gost.Node{ 77 | Addr: "127.0.0.1:18000", 78 | Client: gost.NewClient( 79 | gost.HTTPConnector(url.UserPassword("admin", "123456")), 80 | gost.WSTransporter(nil), 81 | ), 82 | }, 83 | */ 84 | 85 | /* 86 | // http+wss 87 | gost.Node{ 88 | Addr: "127.0.0.1:18443", 89 | Client: gost.NewClient( 90 | gost.HTTPConnector(url.UserPassword("admin", "123456")), 91 | gost.WSSTransporter(nil), 92 | ), 93 | }, 94 | */ 95 | 96 | /* 97 | // http+tls 98 | gost.Node{ 99 | Addr: "127.0.0.1:11443", 100 | Client: gost.NewClient( 101 | gost.HTTPConnector(url.UserPassword("admin", "123456")), 102 | gost.TLSTransporter(), 103 | ), 104 | }, 105 | */ 106 | 107 | /* 108 | // http2 109 | gost.Node{ 110 | Addr: "127.0.0.1:1443", 111 | Client: &gost.Client{ 112 | Connector: gost.HTTP2Connector(url.UserPassword("admin", "123456")), 113 | Transporter: gost.HTTP2Transporter(nil), 114 | }, 115 | }, 116 | */ 117 | 118 | /* 119 | // http+kcp 120 | gost.Node{ 121 | Addr: "127.0.0.1:18388", 122 | Client: gost.NewClient( 123 | gost.HTTPConnector(nil), 124 | gost.KCPTransporter(nil), 125 | ), 126 | }, 127 | */ 128 | 129 | /* 130 | // http+ssh 131 | gost.Node{ 132 | Addr: "127.0.0.1:12222", 133 | Client: gost.NewClient( 134 | gost.HTTPConnector(url.UserPassword("admin", "123456")), 135 | gost.SSHTunnelTransporter(), 136 | ), 137 | }, 138 | */ 139 | 140 | /* 141 | // http+quic 142 | gost.Node{ 143 | Addr: "localhost:6121", 144 | Client: &gost.Client{ 145 | Connector: gost.HTTPConnector(url.UserPassword("admin", "123456")), 146 | Transporter: gost.QUICTransporter(nil), 147 | }, 148 | }, 149 | */ 150 | // socks5+h2 151 | gost.Node{ 152 | Addr: "localhost:8443", 153 | Client: &gost.Client{ 154 | // Connector: gost.HTTPConnector(url.UserPassword("admin", "123456")), 155 | Connector: gost.SOCKS5Connector(url.UserPassword("admin", "123456")), 156 | // Transporter: gost.H2CTransporter(), // HTTP2 h2c mode 157 | Transporter: gost.H2Transporter(nil), // HTTP2 h2 158 | }, 159 | }, 160 | ) 161 | 162 | total := 0 163 | for total < requests { 164 | if total+concurrency > requests { 165 | concurrency = requests - total 166 | } 167 | startChan := make(chan struct{}) 168 | for i := 0; i < concurrency; i++ { 169 | swg.Add(1) 170 | ewg.Add(1) 171 | go request(chain, startChan) 172 | } 173 | 174 | start := time.Now() 175 | swg.Wait() // wait for workers ready 176 | close(startChan) // start signal 177 | ewg.Wait() // wait for workers done 178 | 179 | duration := time.Since(start) 180 | total += concurrency 181 | log.Printf("%d/%d/%d requests done (%v/%v)", total, requests, concurrency, duration, duration/time.Duration(concurrency)) 182 | } 183 | } 184 | 185 | func request(chain *gost.Chain, start <-chan struct{}) { 186 | defer ewg.Done() 187 | 188 | swg.Done() 189 | <-start 190 | 191 | conn, err := chain.Dial("localhost:18888") 192 | if err != nil { 193 | log.Println(err) 194 | return 195 | } 196 | defer conn.Close() 197 | //conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) 198 | req, err := http.NewRequest(http.MethodGet, "http://localhost:18888", nil) 199 | if err != nil { 200 | log.Println(err) 201 | return 202 | } 203 | if err := req.Write(conn); err != nil { 204 | log.Println(err) 205 | return 206 | } 207 | resp, err := http.ReadResponse(bufio.NewReader(conn), req) 208 | if err != nil { 209 | log.Println(err) 210 | return 211 | } 212 | defer resp.Body.Close() 213 | 214 | if gost.Debug { 215 | rb, _ := httputil.DumpRequest(req, true) 216 | log.Println(string(rb)) 217 | rb, _ = httputil.DumpResponse(resp, true) 218 | log.Println(string(rb)) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /examples/forward/direct/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/ginuerzh/gost" 7 | ) 8 | 9 | func main() { 10 | tcpForward() 11 | } 12 | 13 | func tcpForward() { 14 | chain := gost.NewChain( 15 | gost.Node{ 16 | Addr: "localhost:11222", 17 | Client: &gost.Client{ 18 | Connector: gost.SSHDirectForwardConnector(), 19 | Transporter: gost.SSHForwardTransporter(), 20 | }, 21 | }, 22 | ) 23 | 24 | ln, err := gost.TCPListener(":11800") 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | h := gost.TCPDirectForwardHandler( 29 | "localhost:22", 30 | gost.ChainHandlerOption(chain), 31 | ) 32 | s := &gost.Server{ln} 33 | log.Fatal(s.Serve(h)) 34 | } 35 | -------------------------------------------------------------------------------- /examples/forward/direct/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | 7 | "github.com/ginuerzh/gost" 8 | ) 9 | 10 | func main() { 11 | sshForwardServer() 12 | } 13 | 14 | func sshForwardServer() { 15 | ln, err := gost.TCPListener(":11222") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | h := gost.SSHForwardHandler( 20 | gost.AddrHandlerOption(":11222"), 21 | // gost.UsersHandlerOption(url.UserPassword("admin", "123456")), 22 | gost.TLSConfigHandlerOption(tlsConfig()), 23 | ) 24 | s := &gost.Server{ln} 25 | log.Fatal(s.Serve(h)) 26 | } 27 | 28 | var ( 29 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 30 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 31 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 32 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 33 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 34 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 35 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 36 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 37 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 38 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 39 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 40 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 41 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 42 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 43 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 44 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 45 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 46 | -----END CERTIFICATE-----`) 47 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 48 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 49 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 50 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 51 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 52 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 53 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 54 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 55 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 56 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 57 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 58 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 59 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 60 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 61 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 62 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 63 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 64 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 65 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 66 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 67 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 68 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 69 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 70 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 71 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 72 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 73 | -----END RSA PRIVATE KEY-----`) 74 | ) 75 | 76 | func tlsConfig() *tls.Config { 77 | cert, err := tls.X509KeyPair(rawCert, rawKey) 78 | if err != nil { 79 | panic(err) 80 | } 81 | return &tls.Config{Certificates: []tls.Certificate{cert}} 82 | } 83 | -------------------------------------------------------------------------------- /examples/forward/remote/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/ginuerzh/gost" 7 | ) 8 | 9 | func main() { 10 | sshRemoteForward() 11 | } 12 | 13 | func sshRemoteForward() { 14 | chain := gost.NewChain( 15 | gost.Node{ 16 | Protocol: "forward", 17 | Transport: "ssh", 18 | Addr: "localhost:11222", 19 | Client: &gost.Client{ 20 | Connector: gost.SSHRemoteForwardConnector(), 21 | Transporter: gost.SSHForwardTransporter(), 22 | }, 23 | }, 24 | ) 25 | 26 | ln, err := gost.TCPRemoteForwardListener(":11800", chain) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | h := gost.TCPRemoteForwardHandler( 31 | "localhost:10000", 32 | ) 33 | s := &gost.Server{ln} 34 | log.Fatal(s.Serve(h)) 35 | } 36 | -------------------------------------------------------------------------------- /examples/forward/remote/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | 7 | "github.com/ginuerzh/gost" 8 | ) 9 | 10 | func main() { 11 | sshRemoteForwardServer() 12 | } 13 | 14 | func sshRemoteForwardServer() { 15 | ln, err := gost.TCPListener(":11222") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | h := gost.SSHForwardHandler( 20 | gost.AddrHandlerOption(":11222"), 21 | // gost.UsersHandlerOption(url.UserPassword("admin", "123456")), 22 | gost.TLSConfigHandlerOption(tlsConfig()), 23 | ) 24 | s := &gost.Server{ln} 25 | log.Fatal(s.Serve(h)) 26 | } 27 | 28 | var ( 29 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 30 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 31 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 32 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 33 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 34 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 35 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 36 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 37 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 38 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 39 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 40 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 41 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 42 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 43 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 44 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 45 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 46 | -----END CERTIFICATE-----`) 47 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 48 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 49 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 50 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 51 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 52 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 53 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 54 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 55 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 56 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 57 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 58 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 59 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 60 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 61 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 62 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 63 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 64 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 65 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 66 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 67 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 68 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 69 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 70 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 71 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 72 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 73 | -----END RSA PRIVATE KEY-----`) 74 | ) 75 | 76 | func tlsConfig() *tls.Config { 77 | cert, err := tls.X509KeyPair(rawCert, rawKey) 78 | if err != nil { 79 | panic(err) 80 | } 81 | return &tls.Config{Certificates: []tls.Certificate{cert}} 82 | } 83 | -------------------------------------------------------------------------------- /examples/forward/udp/cli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "time" 8 | ) 9 | 10 | var ( 11 | concurrency int 12 | saddr string 13 | ) 14 | 15 | func init() { 16 | log.SetFlags(log.LstdFlags | log.Lshortfile) 17 | 18 | flag.StringVar(&saddr, "S", ":18080", "server address") 19 | flag.IntVar(&concurrency, "c", 1, "Number of multiple echo to make at a time") 20 | flag.Parse() 21 | } 22 | 23 | func main() { 24 | for i := 0; i < concurrency; i++ { 25 | go udpEchoLoop() 26 | } 27 | select {} 28 | } 29 | 30 | func udpEchoLoop() { 31 | addr, err := net.ResolveUDPAddr("udp", saddr) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | conn, err := net.DialUDP("udp", nil, addr) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | msg := []byte(`abcdefghijklmnopqrstuvwxyz`) 41 | for { 42 | if _, err := conn.Write(msg); err != nil { 43 | log.Fatal(err) 44 | } 45 | b := make([]byte, 1024) 46 | _, err := conn.Read(b) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | // log.Println(string(b[:n])) 51 | time.Sleep(100 * time.Millisecond) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/forward/udp/direct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "time" 7 | 8 | "github.com/ginuerzh/gost" 9 | ) 10 | 11 | var ( 12 | laddr, faddr string 13 | quiet bool 14 | ) 15 | 16 | func init() { 17 | log.SetFlags(log.LstdFlags | log.Lshortfile) 18 | 19 | flag.StringVar(&laddr, "L", ":18080", "listen address") 20 | flag.StringVar(&faddr, "F", ":8080", "forward address") 21 | flag.BoolVar(&quiet, "q", false, "quiet mode") 22 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 23 | flag.Parse() 24 | 25 | if quiet { 26 | gost.SetLogger(&gost.NopLogger{}) 27 | } 28 | } 29 | func main() { 30 | udpDirectForwardServer() 31 | } 32 | 33 | func udpDirectForwardServer() { 34 | ln, err := gost.UDPDirectForwardListener(laddr, time.Second*30) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | h := gost.UDPDirectForwardHandler( 39 | faddr, 40 | /* 41 | gost.ChainHandlerOption(gost.NewChain(gost.Node{ 42 | Protocol: "socks5", 43 | Transport: "tcp", 44 | Addr: ":11080", 45 | User: url.UserPassword("admin", "123456"), 46 | Client: &gost.Client{ 47 | Connector: gost.SOCKS5Connector( 48 | url.UserPassword("admin", "123456"), 49 | ), 50 | Transporter: gost.TCPTransporter(), 51 | }, 52 | })), 53 | */ 54 | ) 55 | s := &gost.Server{ln} 56 | log.Fatal(s.Serve(h)) 57 | } 58 | -------------------------------------------------------------------------------- /examples/forward/udp/remote.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "time" 7 | 8 | "github.com/ginuerzh/gost" 9 | ) 10 | 11 | var ( 12 | laddr, faddr string 13 | quiet bool 14 | ) 15 | 16 | func init() { 17 | log.SetFlags(log.LstdFlags | log.Lshortfile) 18 | 19 | flag.StringVar(&laddr, "L", ":18080", "listen address") 20 | flag.StringVar(&faddr, "F", ":8080", "forward address") 21 | flag.BoolVar(&quiet, "q", false, "quiet mode") 22 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 23 | flag.Parse() 24 | 25 | if quiet { 26 | gost.SetLogger(&gost.NopLogger{}) 27 | } 28 | } 29 | func main() { 30 | udpRemoteForwardServer() 31 | } 32 | 33 | func udpRemoteForwardServer() { 34 | ln, err := gost.UDPRemoteForwardListener( 35 | laddr, 36 | /* 37 | gost.NewChain(gost.Node{ 38 | Protocol: "socks5", 39 | Transport: "tcp", 40 | Addr: ":11080", 41 | User: url.UserPassword("admin", "123456"), 42 | Client: &gost.Client{ 43 | Connector: gost.SOCKS5Connector( 44 | url.UserPassword("admin", "123456"), 45 | ), 46 | Transporter: gost.TCPTransporter(), 47 | }, 48 | }), 49 | */ 50 | nil, 51 | time.Second*30) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | h := gost.UDPRemoteForwardHandler( 56 | faddr, 57 | ) 58 | s := &gost.Server{ln} 59 | log.Fatal(s.Serve(h)) 60 | } 61 | -------------------------------------------------------------------------------- /examples/forward/udp/srv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | ) 8 | 9 | var ( 10 | laddr string 11 | ) 12 | 13 | func init() { 14 | log.SetFlags(log.LstdFlags | log.Lshortfile) 15 | 16 | flag.StringVar(&laddr, "L", ":8080", "listen address") 17 | flag.Parse() 18 | } 19 | func main() { 20 | udpEchoServer() 21 | } 22 | 23 | func udpEchoServer() { 24 | addr, err := net.ResolveUDPAddr("udp", laddr) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | conn, err := net.ListenUDP("udp", addr) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | for { 34 | b := make([]byte, 1024) 35 | n, raddr, err := conn.ReadFromUDP(b) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | if _, err = conn.WriteToUDP(b[:n], raddr); err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/http2/http2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "log" 7 | "net/url" 8 | 9 | "golang.org/x/net/http2" 10 | 11 | "github.com/ginuerzh/gost" 12 | ) 13 | 14 | var ( 15 | quiet bool 16 | keyFile, certFile string 17 | laddr string 18 | user, passwd string 19 | ) 20 | 21 | func init() { 22 | log.SetFlags(log.LstdFlags | log.Lshortfile) 23 | 24 | flag.StringVar(&laddr, "L", ":1443", "listen address") 25 | flag.StringVar(&user, "u", "", "username") 26 | flag.StringVar(&passwd, "p", "", "password") 27 | flag.BoolVar(&quiet, "q", false, "quiet mode") 28 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 29 | flag.BoolVar(&http2.VerboseLogs, "v", false, "HTTP2 verbose log") 30 | flag.StringVar(&keyFile, "key", "key.pem", "TLS key file") 31 | flag.StringVar(&certFile, "cert", "cert.pem", "TLS cert file") 32 | flag.Parse() 33 | 34 | if quiet { 35 | gost.SetLogger(&gost.NopLogger{}) 36 | } 37 | } 38 | 39 | func main() { 40 | http2Server() 41 | } 42 | 43 | func http2Server() { 44 | cert, er := tls.LoadX509KeyPair(certFile, keyFile) 45 | if er != nil { 46 | log.Println(er) 47 | cert, er = tls.X509KeyPair(rawCert, rawKey) 48 | if er != nil { 49 | panic(er) 50 | } 51 | } 52 | 53 | ln, err := gost.HTTP2Listener(laddr, &tls.Config{Certificates: []tls.Certificate{cert}}) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | var users []*url.Userinfo 59 | if user != "" || passwd != "" { 60 | users = append(users, url.UserPassword(user, passwd)) 61 | } 62 | 63 | h := gost.HTTP2Handler( 64 | gost.UsersHandlerOption(users...), 65 | gost.AddrHandlerOption(laddr), 66 | ) 67 | s := &gost.Server{ln} 68 | log.Fatal(s.Serve(h)) 69 | } 70 | 71 | var ( 72 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 73 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 74 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 75 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 76 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 77 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 78 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 79 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 80 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 81 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 82 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 83 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 84 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 85 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 86 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 87 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 88 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 89 | -----END CERTIFICATE-----`) 90 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 91 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 92 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 93 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 94 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 95 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 96 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 97 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 98 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 99 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 100 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 101 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 102 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 103 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 104 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 105 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 106 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 107 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 108 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 109 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 110 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 111 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 112 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 113 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 114 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 115 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 116 | -----END RSA PRIVATE KEY-----`) 117 | ) 118 | 119 | func tlsConfig() *tls.Config { 120 | cert, err := tls.X509KeyPair(rawCert, rawKey) 121 | if err != nil { 122 | panic(err) 123 | } 124 | return &tls.Config{Certificates: []tls.Certificate{cert}} 125 | } 126 | -------------------------------------------------------------------------------- /examples/quic/quicc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | "github.com/ginuerzh/gost" 10 | ) 11 | 12 | var ( 13 | laddr, faddr string 14 | quiet bool 15 | ) 16 | 17 | func init() { 18 | log.SetFlags(log.LstdFlags | log.Lshortfile) 19 | 20 | flag.StringVar(&laddr, "L", ":18080", "listen address") 21 | flag.StringVar(&faddr, "F", "localhost:6121", "forward address") 22 | flag.BoolVar(&quiet, "q", false, "quiet mode") 23 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 24 | flag.Parse() 25 | 26 | if quiet { 27 | gost.SetLogger(&gost.NopLogger{}) 28 | } 29 | } 30 | 31 | func main() { 32 | chain := gost.NewChain( 33 | gost.Node{ 34 | Protocol: "socks5", 35 | Transport: "quic", 36 | Addr: faddr, 37 | Client: &gost.Client{ 38 | Connector: gost.SOCKS5Connector(nil), 39 | Transporter: gost.QUICTransporter(&gost.QUICConfig{Timeout: 30 * time.Second, KeepAlive: true}), 40 | }, 41 | }, 42 | ) 43 | 44 | ln, err := gost.TCPListener(laddr) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | h := gost.SOCKS5Handler( 49 | gost.ChainHandlerOption(chain), 50 | gost.TLSConfigHandlerOption(tlsConfig()), 51 | ) 52 | s := &gost.Server{ln} 53 | log.Fatal(s.Serve(h)) 54 | } 55 | 56 | var ( 57 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 58 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 59 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 60 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 61 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 62 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 63 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 64 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 65 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 66 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 67 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 68 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 69 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 70 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 71 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 72 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 73 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 74 | -----END CERTIFICATE-----`) 75 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 76 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 77 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 78 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 79 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 80 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 81 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 82 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 83 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 84 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 85 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 86 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 87 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 88 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 89 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 90 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 91 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 92 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 93 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 94 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 95 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 96 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 97 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 98 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 99 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 100 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 101 | -----END RSA PRIVATE KEY-----`) 102 | ) 103 | 104 | func tlsConfig() *tls.Config { 105 | cert, err := tls.X509KeyPair(rawCert, rawKey) 106 | if err != nil { 107 | panic(err) 108 | } 109 | return &tls.Config{Certificates: []tls.Certificate{cert}} 110 | } 111 | -------------------------------------------------------------------------------- /examples/quic/quics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "log" 7 | 8 | "github.com/ginuerzh/gost" 9 | ) 10 | 11 | var ( 12 | laddr string 13 | quiet bool 14 | ) 15 | 16 | func init() { 17 | log.SetFlags(log.LstdFlags | log.Lshortfile) 18 | 19 | flag.StringVar(&laddr, "L", ":6121", "listen address") 20 | flag.BoolVar(&quiet, "q", false, "quiet mode") 21 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 22 | 23 | flag.Parse() 24 | 25 | if quiet { 26 | gost.SetLogger(&gost.NopLogger{}) 27 | } 28 | } 29 | 30 | func main() { 31 | quicServer() 32 | } 33 | 34 | func quicServer() { 35 | ln, err := gost.QUICListener(laddr, &gost.QUICConfig{TLSConfig: tlsConfig()}) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | h := gost.SOCKS5Handler(gost.TLSConfigHandlerOption(tlsConfig())) 40 | log.Println("server listen on", laddr) 41 | 42 | s := &gost.Server{ln} 43 | log.Fatal(s.Serve(h)) 44 | } 45 | 46 | var ( 47 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 48 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 49 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 50 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 51 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 52 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 53 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 54 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 55 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 56 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 57 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 58 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 59 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 60 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 61 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 62 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 63 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 64 | -----END CERTIFICATE-----`) 65 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 66 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 67 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 68 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 69 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 70 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 71 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 72 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 73 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 74 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 75 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 76 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 77 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 78 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 79 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 80 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 81 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 82 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 83 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 84 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 85 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 86 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 87 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 88 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 89 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 90 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 91 | -----END RSA PRIVATE KEY-----`) 92 | ) 93 | 94 | func tlsConfig() *tls.Config { 95 | cert, err := tls.X509KeyPair(rawCert, rawKey) 96 | if err != nil { 97 | panic(err) 98 | } 99 | return &tls.Config{Certificates: []tls.Certificate{cert}} 100 | } 101 | -------------------------------------------------------------------------------- /examples/ssh/sshc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | "github.com/ginuerzh/gost" 10 | ) 11 | 12 | var ( 13 | laddr, faddr string 14 | quiet bool 15 | ) 16 | 17 | func init() { 18 | log.SetFlags(log.LstdFlags | log.Lshortfile) 19 | 20 | flag.StringVar(&laddr, "L", ":18080", "listen address") 21 | flag.StringVar(&faddr, "F", ":12222", "forward address") 22 | flag.BoolVar(&quiet, "q", false, "quiet mode") 23 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 24 | flag.Parse() 25 | 26 | if quiet { 27 | gost.SetLogger(&gost.NopLogger{}) 28 | } 29 | } 30 | 31 | func main() { 32 | chain := gost.NewChain( 33 | gost.Node{ 34 | Protocol: "socks5", 35 | Transport: "ssh", 36 | Addr: faddr, 37 | HandshakeOptions: []gost.HandshakeOption{ 38 | gost.IntervalHandshakeOption(30 * time.Second), 39 | }, 40 | Client: &gost.Client{ 41 | Connector: gost.SOCKS5Connector(nil), 42 | Transporter: gost.SSHTunnelTransporter(), 43 | }, 44 | }, 45 | ) 46 | 47 | ln, err := gost.TCPListener(laddr) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | h := gost.SOCKS5Handler( 52 | gost.ChainHandlerOption(chain), 53 | gost.TLSConfigHandlerOption(tlsConfig()), 54 | ) 55 | s := &gost.Server{ln} 56 | log.Fatal(s.Serve(h)) 57 | } 58 | 59 | var ( 60 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 61 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 62 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 63 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 64 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 65 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 66 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 67 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 68 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 69 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 70 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 71 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 72 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 73 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 74 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 75 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 76 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 77 | -----END CERTIFICATE-----`) 78 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 79 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 80 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 81 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 82 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 83 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 84 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 85 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 86 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 87 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 88 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 89 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 90 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 91 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 92 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 93 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 94 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 95 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 96 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 97 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 98 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 99 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 100 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 101 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 102 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 103 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 104 | -----END RSA PRIVATE KEY-----`) 105 | ) 106 | 107 | func tlsConfig() *tls.Config { 108 | cert, err := tls.X509KeyPair(rawCert, rawKey) 109 | if err != nil { 110 | panic(err) 111 | } 112 | return &tls.Config{Certificates: []tls.Certificate{cert}} 113 | } 114 | -------------------------------------------------------------------------------- /examples/ssh/sshd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "log" 7 | 8 | "github.com/ginuerzh/gost" 9 | ) 10 | 11 | var ( 12 | laddr string 13 | quiet bool 14 | ) 15 | 16 | func init() { 17 | log.SetFlags(log.LstdFlags | log.Lshortfile) 18 | 19 | flag.StringVar(&laddr, "L", ":12222", "listen address") 20 | flag.BoolVar(&quiet, "q", false, "quiet mode") 21 | flag.BoolVar(&gost.Debug, "d", false, "debug mode") 22 | 23 | flag.Parse() 24 | 25 | if quiet { 26 | gost.SetLogger(&gost.NopLogger{}) 27 | } 28 | } 29 | 30 | func main() { 31 | sshTunnelServer() 32 | } 33 | 34 | func sshTunnelServer() { 35 | ln, err := gost.SSHTunnelListener(laddr, &gost.SSHConfig{TLSConfig: tlsConfig()}) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | h := gost.SOCKS5Handler(gost.TLSConfigHandlerOption(tlsConfig())) 40 | log.Println("server listen on", laddr) 41 | s := &gost.Server{ln} 42 | log.Fatal(s.Serve(h)) 43 | } 44 | 45 | var ( 46 | rawCert = []byte(`-----BEGIN CERTIFICATE----- 47 | MIIC+jCCAeKgAwIBAgIRAMlREhz8Miu1FQozsxbeqyMwDQYJKoZIhvcNAQELBQAw 48 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNzA1MTkwNTM5MDJaFw0xODA1MTkwNTM5 49 | MDJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 50 | ggEKAoIBAQCyfqvv0kDriciEAVIW6JaWYFCL9a19jj1wmAGmVGxV3kNsr01kpa6N 51 | 0EBqnrcy7WknhCt1d43CqhKtTcXgJ/J9phZVxlizb8sUB85hm+MvP0N3HCg3f0Jw 52 | hLuMrPijS6xjyw0fKCK/p6OUYMIfo5cdqeZid2WV4Ozts5uRd6Dmy2kyBe8Zg1F4 53 | 8YJGuTWZmL2L7uZUiPY4T3q9+1iucq3vUpxymVRi1BTXnTpx+C0GS8NNgeEmevHv 54 | 482vHM5DNflAQ+mvGZvBVduq/AfirCDnt2DIZm1DcZXLrY9F3EPrlRZexmAhCDGR 55 | LIKnMmoGicBM11Aw1fDIfJAHynk43tjPAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 56 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 57 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAx8Lna8DcQv0bRB3L9i2+KRN 58 | l/UhPCoFagxk1cZore4p0w+1m7OgigOoTpg5jh78DzVDhScZlgJ0bBVYp5rojeJS 59 | cBDC9lCDcaXQfFmT5LykCAwIgw/gs+rw5Aq0y3D0m8CcqKosyZa9wnZ2cVy/+45w 60 | emcSdboc65ueZScv38/W7aTUoVRcjyRUv0jv0zW0EPnnDlluVkeZo9spBhiTTwoj 61 | b3zGODs6alTNIJwZIHNxxyOmfJPpVVp8BzGbMk7YBixSlZ/vbrrYV34TcSiy7J57 62 | lNNoVWM+OwiVk1+AEZfQDwaQfef5tsIkAZBUyITkkDKRhygtwM2110dejbEsgg== 63 | -----END CERTIFICATE-----`) 64 | rawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 65 | MIIEpQIBAAKCAQEAsn6r79JA64nIhAFSFuiWlmBQi/WtfY49cJgBplRsVd5DbK9N 66 | ZKWujdBAap63Mu1pJ4QrdXeNwqoSrU3F4CfyfaYWVcZYs2/LFAfOYZvjLz9Ddxwo 67 | N39CcIS7jKz4o0usY8sNHygiv6ejlGDCH6OXHanmYndlleDs7bObkXeg5stpMgXv 68 | GYNRePGCRrk1mZi9i+7mVIj2OE96vftYrnKt71KccplUYtQU1506cfgtBkvDTYHh 69 | Jnrx7+PNrxzOQzX5QEPprxmbwVXbqvwH4qwg57dgyGZtQ3GVy62PRdxD65UWXsZg 70 | IQgxkSyCpzJqBonATNdQMNXwyHyQB8p5ON7YzwIDAQABAoIBAQCG4doj3Apa8z+n 71 | IShbT1+cOyQi34A+xOIA151Hh7xmFxN0afRd/iWt3JUQ/OcLgQRZbDM7DSD+3W5H 72 | r+G7xfQkpwFxx/T3g58+f7ehYx+GcJQWyhxJ88zNIkBnyb4KCAE5WBOOW9IGajPe 73 | yE9pgUGMlPsXpYoKfHIOHg+NGY1pWUGBfBNR2kGrbkpZMmyy5bGa8dyrwAFBFRru 74 | kcmmKvate8UlbRspFtd4nR/GQLTBrcDJ1k1i1Su/4BpDuDeK6LPI8ZRePGqbdcxk 75 | TS30lsdYozuGfjZ5Zu8lSIJ//+7RjfDg8r684dpWjpalq8Quen60ZrIs01CSbfyU 76 | k8gOzTHhAoGBAOKhp41wXveegq+WylSXFyngm4bzF4dVdTRsSbJVk7NaOx1vCU6o 77 | /xIHoGEQyLI6wF+EaHmY89/Qu6tSV97XyBbiKeskopv5iXS/BsWTHJ1VbCA1ZLmK 78 | HgGllEkS0xfc9AdB7b6/K7LxAAQVKP3DtN6+6pSDZh9Sv2M1j0DbhkNbAoGBAMmg 79 | HcMfExaaeskjHqyLudtKX+znwaIoumleOGuavohR4R+Fpk8Yv8Xhb5U7Yr4gk0vY 80 | CFmhp1WAi6QMZ/8jePlKKXl3Ney827luoKiMczp2DoYE0t0u2Kw3LfkNKfjADZ7d 81 | JI6xPJV9/X1erwjq+4UdKqrpOf05SY4nkBMcvr6dAoGAXzisvbDJNiFTp5Mj0Abr 82 | pJzKvBjHegVeCXi2PkfWlzUCQYu1zWcURO8PY7k5mik1SuzHONAbJ578Oy+N3AOt 83 | /m9oTXRHHmHqbzMUFU+KZlDN7XqBp7NwiCCZ/Vn7d7tOjP4Wdl68baL07sI1RupD 84 | xJNS3LOY5PBPmc+XMRkLgKECgYEAgBNDlJSCrZMHeAjlDTncn53I/VXiPD2e3BvL 85 | vx6W9UT9ueZN1GSmPO6M0MDeYmOS7VSXSUhUYQ28pkJzNTC1QbWITu4YxP7anBnX 86 | 1/kPoQ0pAJzDzVharlqGy3M/PBHTFRzogfO3xkY35ZFlokaR6uayGcr42Q+w16nt 87 | 7RYPXEkCgYEA3GQYirHnGZuQ952jMvduqnpgkJiSnr0fa+94Rwa1pAhxHLFMo5s4 88 | fqZOtqKPj2s5X1JR0VCey1ilCcaAhWeb3tXCpbYLZSbMtjtqwA6LUeGY+Xdupsjw 89 | cfWIcOfHsIm2kP+RCxEnZf1XwiN9wyJeiUKlE0dqmx9j7F0Bm+7YDhI= 90 | -----END RSA PRIVATE KEY-----`) 91 | ) 92 | 93 | func tlsConfig() *tls.Config { 94 | cert, err := tls.X509KeyPair(rawCert, rawKey) 95 | if err != nil { 96 | panic(err) 97 | } 98 | return &tls.Config{Certificates: []tls.Certificate{cert}} 99 | } 100 | -------------------------------------------------------------------------------- /examples/ssu/ssu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net" 7 | "strconv" 8 | 9 | "github.com/go-gost/gosocks5" 10 | ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" 11 | ) 12 | 13 | func main() { 14 | ssuClient() 15 | } 16 | 17 | func ssuClient() { 18 | addr, err := net.ResolveUDPAddr("udp", ":18338") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | laddr, _ := net.ResolveUDPAddr("udp", ":10800") 23 | conn, err := net.ListenUDP("udp", laddr) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | cp, err := ss.NewCipher("chacha20", "123456") 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | cc := ss.NewSecurePacketConn(conn, cp, false) 32 | 33 | raddr, _ := net.ResolveUDPAddr("udp", ":8080") 34 | msg := []byte(`abcdefghijklmnopqrstuvwxyz`) 35 | dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(raddr)), msg) 36 | buf := bytes.Buffer{} 37 | dgram.Write(&buf) 38 | for { 39 | log.Printf("%# x", buf.Bytes()[3:]) 40 | if _, err := cc.WriteTo(buf.Bytes()[3:], addr); err != nil { 41 | log.Fatal(err) 42 | } 43 | b := make([]byte, 1024) 44 | n, adr, err := cc.ReadFrom(b) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | log.Printf("%s: %# x", adr, b[:n]) 49 | } 50 | } 51 | 52 | func toSocksAddr(addr net.Addr) *gosocks5.Addr { 53 | host := "0.0.0.0" 54 | port := 0 55 | if addr != nil { 56 | h, p, _ := net.SplitHostPort(addr.String()) 57 | host = h 58 | port, _ = strconv.Atoi(p) 59 | } 60 | return &gosocks5.Addr{ 61 | Type: gosocks5.AddrIPv4, 62 | Host: host, 63 | Port: uint16(port), 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /forward_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "crypto/rand" 5 | "net/http/httptest" 6 | "net/url" 7 | "testing" 8 | ) 9 | 10 | func tcpDirectForwardRoundtrip(targetURL string, data []byte) error { 11 | ln, err := TCPListener("") 12 | if err != nil { 13 | return err 14 | } 15 | 16 | u, err := url.Parse(targetURL) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | client := &Client{ 22 | Connector: ForwardConnector(), 23 | Transporter: TCPTransporter(), 24 | } 25 | 26 | h := TCPDirectForwardHandler(u.Host) 27 | h.Init() 28 | server := &Server{ 29 | Listener: ln, 30 | Handler: h, 31 | } 32 | 33 | go server.Run() 34 | defer server.Close() 35 | 36 | return proxyRoundtrip(client, server, targetURL, data) 37 | } 38 | 39 | func TestTCPDirectForward(t *testing.T) { 40 | httpSrv := httptest.NewServer(httpTestHandler) 41 | defer httpSrv.Close() 42 | 43 | sendData := make([]byte, 128) 44 | rand.Read(sendData) 45 | 46 | err := tcpDirectForwardRoundtrip(httpSrv.URL, sendData) 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | } 51 | 52 | func BenchmarkTCPDirectForward(b *testing.B) { 53 | httpSrv := httptest.NewServer(httpTestHandler) 54 | defer httpSrv.Close() 55 | 56 | sendData := make([]byte, 128) 57 | rand.Read(sendData) 58 | 59 | ln, err := TCPListener("") 60 | if err != nil { 61 | b.Error(err) 62 | } 63 | 64 | client := &Client{ 65 | Connector: ForwardConnector(), 66 | Transporter: TCPTransporter(), 67 | } 68 | 69 | u, err := url.Parse(httpSrv.URL) 70 | if err != nil { 71 | b.Error(err) 72 | } 73 | 74 | h := TCPDirectForwardHandler(u.Host) 75 | h.Init() 76 | server := &Server{ 77 | Listener: ln, 78 | Handler: h, 79 | } 80 | go server.Run() 81 | defer server.Close() 82 | 83 | for i := 0; i < b.N; i++ { 84 | if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { 85 | b.Error(err) 86 | } 87 | } 88 | } 89 | 90 | func BenchmarkTCPDirectForwardParallel(b *testing.B) { 91 | httpSrv := httptest.NewServer(httpTestHandler) 92 | defer httpSrv.Close() 93 | 94 | sendData := make([]byte, 128) 95 | rand.Read(sendData) 96 | 97 | ln, err := TCPListener("") 98 | if err != nil { 99 | b.Error(err) 100 | } 101 | 102 | client := &Client{ 103 | Connector: ForwardConnector(), 104 | Transporter: TCPTransporter(), 105 | } 106 | 107 | u, err := url.Parse(httpSrv.URL) 108 | if err != nil { 109 | b.Error(err) 110 | } 111 | 112 | h := TCPDirectForwardHandler(u.Host) 113 | h.Init() 114 | server := &Server{ 115 | Listener: ln, 116 | Handler: h, 117 | } 118 | go server.Run() 119 | defer server.Close() 120 | 121 | b.RunParallel(func(pb *testing.PB) { 122 | for pb.Next() { 123 | if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil { 124 | b.Error(err) 125 | } 126 | } 127 | }) 128 | } 129 | 130 | func udpDirectForwardRoundtrip(t *testing.T, host string, data []byte) error { 131 | ln, err := UDPListener("localhost:0", nil) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | client := &Client{ 137 | Connector: ForwardConnector(), 138 | Transporter: UDPTransporter(), 139 | } 140 | 141 | h := UDPDirectForwardHandler(host) 142 | h.Init() 143 | server := &Server{ 144 | Listener: ln, 145 | Handler: h, 146 | } 147 | 148 | go server.Run() 149 | defer server.Close() 150 | 151 | return udpRoundtrip(t, client, server, host, data) 152 | } 153 | 154 | func TestUDPDirectForward(t *testing.T) { 155 | udpSrv := newUDPTestServer(udpTestHandler) 156 | udpSrv.Start() 157 | defer udpSrv.Close() 158 | 159 | sendData := make([]byte, 128) 160 | rand.Read(sendData) 161 | err := udpDirectForwardRoundtrip(t, udpSrv.Addr(), sendData) 162 | if err != nil { 163 | t.Error(err) 164 | } 165 | } 166 | 167 | func BenchmarkUDPDirectForward(b *testing.B) { 168 | udpSrv := newUDPTestServer(udpTestHandler) 169 | udpSrv.Start() 170 | defer udpSrv.Close() 171 | 172 | sendData := make([]byte, 128) 173 | rand.Read(sendData) 174 | 175 | ln, err := UDPListener("localhost:0", nil) 176 | if err != nil { 177 | b.Error(err) 178 | } 179 | 180 | client := &Client{ 181 | Connector: ForwardConnector(), 182 | Transporter: UDPTransporter(), 183 | } 184 | 185 | h := UDPDirectForwardHandler(udpSrv.Addr()) 186 | h.Init() 187 | server := &Server{ 188 | Listener: ln, 189 | Handler: h, 190 | } 191 | 192 | go server.Run() 193 | defer server.Close() 194 | 195 | for i := 0; i < b.N; i++ { 196 | if err := udpRoundtrip(b, client, server, udpSrv.Addr(), sendData); err != nil { 197 | b.Error(err) 198 | } 199 | } 200 | } 201 | 202 | func BenchmarkUDPDirectForwardParallel(b *testing.B) { 203 | udpSrv := newUDPTestServer(udpTestHandler) 204 | udpSrv.Start() 205 | defer udpSrv.Close() 206 | 207 | sendData := make([]byte, 128) 208 | rand.Read(sendData) 209 | 210 | ln, err := UDPListener("localhost:0", nil) 211 | if err != nil { 212 | b.Error(err) 213 | } 214 | 215 | client := &Client{ 216 | Connector: ForwardConnector(), 217 | Transporter: UDPTransporter(), 218 | } 219 | 220 | h := UDPDirectForwardHandler(udpSrv.Addr()) 221 | h.Init() 222 | server := &Server{ 223 | Listener: ln, 224 | Handler: h, 225 | } 226 | 227 | go server.Run() 228 | defer server.Close() 229 | 230 | b.RunParallel(func(pb *testing.PB) { 231 | for pb.Next() { 232 | if err := udpRoundtrip(b, client, server, udpSrv.Addr(), sendData); err != nil { 233 | b.Error(err) 234 | } 235 | } 236 | }) 237 | } 238 | 239 | func tcpRemoteForwardRoundtrip(t *testing.T, targetURL string, data []byte) error { 240 | ln, err := TCPRemoteForwardListener("localhost:0", nil) // listening on localhost 241 | if err != nil { 242 | return err 243 | } 244 | 245 | u, err := url.Parse(targetURL) 246 | if err != nil { 247 | return err 248 | } 249 | 250 | client := &Client{ 251 | Connector: ForwardConnector(), 252 | Transporter: TCPTransporter(), 253 | } 254 | 255 | h := TCPRemoteForwardHandler(u.Host) // forward to u.Host 256 | h.Init() 257 | server := &Server{ 258 | Listener: ln, 259 | Handler: h, 260 | } 261 | 262 | go server.Run() 263 | defer server.Close() 264 | 265 | return proxyRoundtrip(client, server, targetURL, data) 266 | } 267 | 268 | func TestTCPRemoteForward(t *testing.T) { 269 | httpSrv := httptest.NewServer(httpTestHandler) 270 | defer httpSrv.Close() 271 | 272 | sendData := make([]byte, 128) 273 | rand.Read(sendData) 274 | 275 | err := tcpRemoteForwardRoundtrip(t, httpSrv.URL, sendData) 276 | if err != nil { 277 | t.Error(err) 278 | } 279 | } 280 | 281 | func udpRemoteForwardRoundtrip(t *testing.T, host string, data []byte) error { 282 | ln, err := UDPRemoteForwardListener("localhost:0", nil, nil) 283 | if err != nil { 284 | return err 285 | } 286 | 287 | client := &Client{ 288 | Connector: ForwardConnector(), 289 | Transporter: UDPTransporter(), 290 | } 291 | 292 | h := UDPRemoteForwardHandler(host) 293 | h.Init() 294 | server := &Server{ 295 | Listener: ln, 296 | Handler: h, 297 | } 298 | 299 | go server.Run() 300 | defer server.Close() 301 | 302 | return udpRoundtrip(t, client, server, host, data) 303 | } 304 | 305 | func TestUDPRemoteForward(t *testing.T) { 306 | udpSrv := newUDPTestServer(udpTestHandler) 307 | udpSrv.Start() 308 | defer udpSrv.Close() 309 | 310 | sendData := make([]byte, 128) 311 | rand.Read(sendData) 312 | 313 | err := udpRemoteForwardRoundtrip(t, udpSrv.Addr(), sendData) 314 | if err != nil { 315 | t.Error(err) 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /ftcp.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "time" 7 | 8 | "github.com/go-log/log" 9 | "github.com/xtaci/tcpraw" 10 | ) 11 | 12 | type fakeTCPTransporter struct{} 13 | 14 | // FakeTCPTransporter creates a Transporter that is used by fake tcp client. 15 | func FakeTCPTransporter() Transporter { 16 | return &fakeTCPTransporter{} 17 | } 18 | 19 | func (tr *fakeTCPTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { 20 | opts := &DialOptions{} 21 | for _, option := range options { 22 | option(opts) 23 | } 24 | 25 | raddr, er := net.ResolveTCPAddr("tcp", addr) 26 | if er != nil { 27 | return nil, er 28 | } 29 | c, err := tcpraw.Dial("tcp", addr) 30 | if err != nil { 31 | return 32 | } 33 | conn = &fakeTCPConn{ 34 | raddr: raddr, 35 | PacketConn: c, 36 | } 37 | return conn, nil 38 | } 39 | 40 | func (tr *fakeTCPTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { 41 | return conn, nil 42 | } 43 | 44 | func (tr *fakeTCPTransporter) Multiplex() bool { 45 | return false 46 | } 47 | 48 | // FakeTCPListenConfig is config for fake TCP Listener. 49 | type FakeTCPListenConfig struct { 50 | TTL time.Duration 51 | Backlog int 52 | QueueSize int 53 | } 54 | 55 | type fakeTCPListener struct { 56 | ln net.PacketConn 57 | connChan chan net.Conn 58 | errChan chan error 59 | connMap udpConnMap 60 | config *FakeTCPListenConfig 61 | } 62 | 63 | // FakeTCPListener creates a Listener for fake TCP server. 64 | func FakeTCPListener(addr string, cfg *FakeTCPListenConfig) (Listener, error) { 65 | ln, err := tcpraw.Listen("tcp", addr) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if cfg == nil { 71 | cfg = &FakeTCPListenConfig{} 72 | } 73 | 74 | backlog := cfg.Backlog 75 | if backlog <= 0 { 76 | backlog = defaultBacklog 77 | } 78 | 79 | l := &fakeTCPListener{ 80 | ln: ln, 81 | connChan: make(chan net.Conn, backlog), 82 | errChan: make(chan error, 1), 83 | config: cfg, 84 | } 85 | go l.listenLoop() 86 | return l, nil 87 | } 88 | 89 | func (l *fakeTCPListener) listenLoop() { 90 | for { 91 | b := make([]byte, mediumBufferSize) 92 | n, raddr, err := l.ln.ReadFrom(b) 93 | if err != nil { 94 | log.Logf("[ftcp] peer -> %s : %s", l.Addr(), err) 95 | l.Close() 96 | l.errChan <- err 97 | close(l.errChan) 98 | return 99 | } 100 | 101 | conn, ok := l.connMap.Get(raddr.String()) 102 | if !ok { 103 | conn = newUDPServerConn(l.ln, raddr, &udpServerConnConfig{ 104 | ttl: l.config.TTL, 105 | qsize: l.config.QueueSize, 106 | onClose: func() { 107 | l.connMap.Delete(raddr.String()) 108 | log.Logf("[ftcp] %s closed (%d)", raddr, l.connMap.Size()) 109 | }, 110 | }) 111 | 112 | select { 113 | case l.connChan <- conn: 114 | l.connMap.Set(raddr.String(), conn) 115 | log.Logf("[ftcp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size()) 116 | default: 117 | conn.Close() 118 | log.Logf("[ftcp] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan)) 119 | } 120 | } 121 | 122 | select { 123 | case conn.rChan <- b[:n]: 124 | if Debug { 125 | log.Logf("[ftcp] %s >>> %s : length %d", raddr, l.Addr(), n) 126 | } 127 | default: 128 | log.Logf("[ftcp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan)) 129 | } 130 | } 131 | } 132 | 133 | func (l *fakeTCPListener) Accept() (conn net.Conn, err error) { 134 | var ok bool 135 | select { 136 | case conn = <-l.connChan: 137 | case err, ok = <-l.errChan: 138 | if !ok { 139 | err = errors.New("accpet on closed listener") 140 | } 141 | } 142 | return 143 | } 144 | 145 | func (l *fakeTCPListener) Addr() net.Addr { 146 | return l.ln.LocalAddr() 147 | } 148 | 149 | func (l *fakeTCPListener) Close() error { 150 | err := l.ln.Close() 151 | l.connMap.Range(func(k interface{}, v *udpServerConn) bool { 152 | v.Close() 153 | return true 154 | }) 155 | 156 | return err 157 | } 158 | 159 | type fakeTCPConn struct { 160 | raddr net.Addr 161 | net.PacketConn 162 | } 163 | 164 | func (c *fakeTCPConn) Read(b []byte) (n int, err error) { 165 | n, _, err = c.ReadFrom(b) 166 | return 167 | } 168 | 169 | func (c *fakeTCPConn) Write(b []byte) (n int, err error) { 170 | return c.WriteTo(b, c.raddr) 171 | } 172 | 173 | func (c *fakeTCPConn) RemoteAddr() net.Addr { 174 | return c.raddr 175 | } 176 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ginuerzh/gost 2 | 3 | go 1.22 4 | 5 | replace github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a 6 | 7 | require ( 8 | git.torproject.org/pluggable-transports/goptlib.git v1.3.0 9 | github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed 10 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 11 | github.com/go-gost/gosocks4 v0.0.1 12 | github.com/go-gost/gosocks5 v0.3.0 13 | github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 14 | github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 15 | github.com/go-log/log v0.2.0 16 | github.com/gobwas/glob v0.2.3 17 | github.com/gorilla/websocket v1.5.1 18 | github.com/klauspost/compress v1.17.6 19 | github.com/mdlayher/vsock v1.2.1 20 | github.com/miekg/dns v1.1.58 21 | github.com/quic-go/quic-go v0.45.0 22 | github.com/ryanuber/go-glob v1.0.0 23 | github.com/shadowsocks/go-shadowsocks2 v0.1.5 24 | github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 25 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 26 | github.com/xtaci/kcp-go/v5 v5.6.7 27 | github.com/xtaci/smux v1.5.24 28 | github.com/xtaci/tcpraw v1.2.25 29 | gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d 30 | golang.org/x/crypto v0.24.0 31 | golang.org/x/net v0.26.0 32 | ) 33 | 34 | require ( 35 | filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 // indirect 36 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect 37 | github.com/coreos/go-iptables v0.6.0 // indirect 38 | github.com/dchest/siphash v1.2.2 // indirect 39 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 40 | github.com/google/gopacket v1.1.19 // indirect 41 | github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect 42 | github.com/klauspost/cpuid/v2 v2.2.6 // indirect 43 | github.com/klauspost/reedsolomon v1.12.0 // indirect 44 | github.com/mdlayher/socket v0.4.1 // indirect 45 | github.com/onsi/ginkgo/v2 v2.19.0 // indirect 46 | github.com/pkg/errors v0.9.1 // indirect 47 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect 48 | github.com/templexxx/cpu v0.1.0 // indirect 49 | github.com/templexxx/xorsimd v0.4.2 // indirect 50 | github.com/tjfoc/gmsm v1.4.1 // indirect 51 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect 52 | gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb // indirect 53 | go.uber.org/mock v0.4.0 // indirect 54 | golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect 55 | golang.org/x/mod v0.18.0 // indirect 56 | golang.org/x/sync v0.7.0 // indirect 57 | golang.org/x/sys v0.21.0 // indirect 58 | golang.org/x/text v0.16.0 // indirect 59 | golang.org/x/tools v0.22.0 // indirect 60 | ) 61 | -------------------------------------------------------------------------------- /gost.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "errors" 11 | "io" 12 | "math/big" 13 | "net" 14 | "net/http" 15 | "strings" 16 | "sync" 17 | "time" 18 | 19 | "github.com/go-log/log" 20 | ) 21 | 22 | // Version is the gost version. 23 | const Version = "2.12.0" 24 | 25 | // Debug is a flag that enables the debug log. 26 | var Debug bool 27 | 28 | var ( 29 | tinyBufferSize = 512 30 | smallBufferSize = 2 * 1024 // 2KB small buffer 31 | mediumBufferSize = 8 * 1024 // 8KB medium buffer 32 | largeBufferSize = 32 * 1024 // 32KB large buffer 33 | ) 34 | 35 | var ( 36 | sPool = sync.Pool{ 37 | New: func() interface{} { 38 | return make([]byte, smallBufferSize) 39 | }, 40 | } 41 | mPool = sync.Pool{ 42 | New: func() interface{} { 43 | return make([]byte, mediumBufferSize) 44 | }, 45 | } 46 | lPool = sync.Pool{ 47 | New: func() interface{} { 48 | return make([]byte, largeBufferSize) 49 | }, 50 | } 51 | ) 52 | 53 | var ( 54 | // KeepAliveTime is the keep alive time period for TCP connection. 55 | KeepAliveTime = 180 * time.Second 56 | // DialTimeout is the timeout of dial. 57 | DialTimeout = 5 * time.Second 58 | // HandshakeTimeout is the timeout of handshake. 59 | HandshakeTimeout = 5 * time.Second 60 | // ConnectTimeout is the timeout for connect. 61 | ConnectTimeout = 5 * time.Second 62 | // ReadTimeout is the timeout for reading. 63 | ReadTimeout = 10 * time.Second 64 | // WriteTimeout is the timeout for writing. 65 | WriteTimeout = 10 * time.Second 66 | // PingTimeout is the timeout for pinging. 67 | PingTimeout = 30 * time.Second 68 | // PingRetries is the reties of ping. 69 | PingRetries = 1 70 | // default udp node TTL in second for udp port forwarding. 71 | defaultTTL = 60 * time.Second 72 | defaultBacklog = 128 73 | defaultQueueSize = 128 74 | ) 75 | 76 | var ( 77 | // DefaultTLSConfig is a default TLS config for internal use. 78 | DefaultTLSConfig *tls.Config 79 | 80 | // DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket. 81 | DefaultUserAgent = "Chrome/78.0.3904.106" 82 | 83 | DefaultProxyAgent = "gost/" + Version 84 | 85 | // DefaultMTU is the default mtu for tun/tap device 86 | DefaultMTU = 1350 87 | ) 88 | 89 | // SetLogger sets a new logger for internal log system. 90 | func SetLogger(logger log.Logger) { 91 | log.DefaultLogger = logger 92 | } 93 | 94 | // GenCertificate generates a random TLS certificate. 95 | func GenCertificate() (cert tls.Certificate, err error) { 96 | rawCert, rawKey, err := generateKeyPair() 97 | if err != nil { 98 | return 99 | } 100 | return tls.X509KeyPair(rawCert, rawKey) 101 | } 102 | 103 | func generateKeyPair() (rawCert, rawKey []byte, err error) { 104 | // Create private key and self-signed certificate 105 | // Adapted from https://golang.org/src/crypto/tls/generate_cert.go 106 | 107 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 108 | if err != nil { 109 | return 110 | } 111 | validFor := time.Hour * 24 * 365 * 10 // ten years 112 | notBefore := time.Now() 113 | notAfter := notBefore.Add(validFor) 114 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 115 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 116 | template := x509.Certificate{ 117 | SerialNumber: serialNumber, 118 | Subject: pkix.Name{ 119 | Organization: []string{"gost"}, 120 | }, 121 | NotBefore: notBefore, 122 | NotAfter: notAfter, 123 | 124 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 125 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 126 | BasicConstraintsValid: true, 127 | } 128 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 129 | if err != nil { 130 | return 131 | } 132 | 133 | rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 134 | rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 135 | 136 | return 137 | } 138 | 139 | type readWriter struct { 140 | r io.Reader 141 | w io.Writer 142 | } 143 | 144 | func (rw *readWriter) Read(p []byte) (n int, err error) { 145 | return rw.r.Read(p) 146 | } 147 | 148 | func (rw *readWriter) Write(p []byte) (n int, err error) { 149 | return rw.w.Write(p) 150 | } 151 | 152 | var nopClientConn = &nopConn{} 153 | 154 | // a nop connection implements net.Conn, 155 | // it does nothing. 156 | type nopConn struct{} 157 | 158 | func (c *nopConn) Read(b []byte) (n int, err error) { 159 | return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")} 160 | } 161 | 162 | func (c *nopConn) Write(b []byte) (n int, err error) { 163 | return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")} 164 | } 165 | 166 | func (c *nopConn) Close() error { 167 | return nil 168 | } 169 | 170 | func (c *nopConn) LocalAddr() net.Addr { 171 | return nil 172 | } 173 | 174 | func (c *nopConn) RemoteAddr() net.Addr { 175 | return nil 176 | } 177 | 178 | func (c *nopConn) SetDeadline(t time.Time) error { 179 | return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} 180 | } 181 | 182 | func (c *nopConn) SetReadDeadline(t time.Time) error { 183 | return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} 184 | } 185 | 186 | func (c *nopConn) SetWriteDeadline(t time.Time) error { 187 | return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} 188 | } 189 | 190 | // splitLine splits a line text by white space, mainly used by config parser. 191 | func splitLine(line string) []string { 192 | if line == "" { 193 | return nil 194 | } 195 | if n := strings.IndexByte(line, '#'); n >= 0 { 196 | line = line[:n] 197 | } 198 | line = strings.Replace(line, "\t", " ", -1) 199 | line = strings.TrimSpace(line) 200 | 201 | var ss []string 202 | for _, s := range strings.Split(line, " ") { 203 | if s = strings.TrimSpace(s); s != "" { 204 | ss = append(ss, s) 205 | } 206 | } 207 | return ss 208 | } 209 | 210 | func connStateCallback(conn net.Conn, cs http.ConnState) { 211 | switch cs { 212 | case http.StateNew: 213 | conn.SetReadDeadline(time.Now().Add(30 * time.Second)) 214 | default: 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /gost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginuerzh/gost/1d516e35e654c7864a0ce907b021a4799715c832/gost.png -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "net" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/go-gost/gosocks4" 11 | "github.com/go-gost/gosocks5" 12 | "github.com/go-log/log" 13 | ) 14 | 15 | // Handler is a proxy server handler 16 | type Handler interface { 17 | Init(options ...HandlerOption) 18 | Handle(net.Conn) 19 | } 20 | 21 | // HandlerOptions describes the options for Handler. 22 | type HandlerOptions struct { 23 | Addr string 24 | Chain *Chain 25 | Users []*url.Userinfo 26 | Authenticator Authenticator 27 | TLSConfig *tls.Config 28 | Whitelist *Permissions 29 | Blacklist *Permissions 30 | Strategy Strategy 31 | MaxFails int 32 | FailTimeout time.Duration 33 | Bypass *Bypass 34 | Retries int 35 | Timeout time.Duration 36 | Resolver Resolver 37 | Hosts *Hosts 38 | ProbeResist string 39 | KnockingHost string 40 | Node Node 41 | Host string 42 | IPs []string 43 | TCPMode bool 44 | IPRoutes []IPRoute 45 | ProxyAgent string 46 | HTTPTunnel bool 47 | } 48 | 49 | // HandlerOption allows a common way to set handler options. 50 | type HandlerOption func(opts *HandlerOptions) 51 | 52 | // AddrHandlerOption sets the Addr option of HandlerOptions. 53 | func AddrHandlerOption(addr string) HandlerOption { 54 | return func(opts *HandlerOptions) { 55 | opts.Addr = addr 56 | } 57 | } 58 | 59 | // ChainHandlerOption sets the Chain option of HandlerOptions. 60 | func ChainHandlerOption(chain *Chain) HandlerOption { 61 | return func(opts *HandlerOptions) { 62 | opts.Chain = chain 63 | } 64 | } 65 | 66 | // UsersHandlerOption sets the Users option of HandlerOptions. 67 | func UsersHandlerOption(users ...*url.Userinfo) HandlerOption { 68 | return func(opts *HandlerOptions) { 69 | opts.Users = users 70 | 71 | kvs := make(map[string]string) 72 | for _, u := range users { 73 | if u != nil { 74 | kvs[u.Username()], _ = u.Password() 75 | } 76 | } 77 | if len(kvs) > 0 { 78 | opts.Authenticator = NewLocalAuthenticator(kvs) 79 | } 80 | } 81 | } 82 | 83 | // AuthenticatorHandlerOption sets the Authenticator option of HandlerOptions. 84 | func AuthenticatorHandlerOption(au Authenticator) HandlerOption { 85 | return func(opts *HandlerOptions) { 86 | opts.Authenticator = au 87 | } 88 | } 89 | 90 | // TLSConfigHandlerOption sets the TLSConfig option of HandlerOptions. 91 | func TLSConfigHandlerOption(config *tls.Config) HandlerOption { 92 | return func(opts *HandlerOptions) { 93 | opts.TLSConfig = config 94 | } 95 | } 96 | 97 | // WhitelistHandlerOption sets the Whitelist option of HandlerOptions. 98 | func WhitelistHandlerOption(whitelist *Permissions) HandlerOption { 99 | return func(opts *HandlerOptions) { 100 | opts.Whitelist = whitelist 101 | } 102 | } 103 | 104 | // BlacklistHandlerOption sets the Blacklist option of HandlerOptions. 105 | func BlacklistHandlerOption(blacklist *Permissions) HandlerOption { 106 | return func(opts *HandlerOptions) { 107 | opts.Blacklist = blacklist 108 | } 109 | } 110 | 111 | // BypassHandlerOption sets the bypass option of HandlerOptions. 112 | func BypassHandlerOption(bypass *Bypass) HandlerOption { 113 | return func(opts *HandlerOptions) { 114 | opts.Bypass = bypass 115 | } 116 | } 117 | 118 | // StrategyHandlerOption sets the strategy option of HandlerOptions. 119 | func StrategyHandlerOption(strategy Strategy) HandlerOption { 120 | return func(opts *HandlerOptions) { 121 | opts.Strategy = strategy 122 | } 123 | } 124 | 125 | // MaxFailsHandlerOption sets the max_fails option of HandlerOptions. 126 | func MaxFailsHandlerOption(n int) HandlerOption { 127 | return func(opts *HandlerOptions) { 128 | opts.MaxFails = n 129 | } 130 | } 131 | 132 | // FailTimeoutHandlerOption sets the fail_timeout option of HandlerOptions. 133 | func FailTimeoutHandlerOption(d time.Duration) HandlerOption { 134 | return func(opts *HandlerOptions) { 135 | opts.FailTimeout = d 136 | } 137 | } 138 | 139 | // RetryHandlerOption sets the retry option of HandlerOptions. 140 | func RetryHandlerOption(retries int) HandlerOption { 141 | return func(opts *HandlerOptions) { 142 | opts.Retries = retries 143 | } 144 | } 145 | 146 | // TimeoutHandlerOption sets the timeout option of HandlerOptions. 147 | func TimeoutHandlerOption(timeout time.Duration) HandlerOption { 148 | return func(opts *HandlerOptions) { 149 | opts.Timeout = timeout 150 | } 151 | } 152 | 153 | // ResolverHandlerOption sets the resolver option of HandlerOptions. 154 | func ResolverHandlerOption(resolver Resolver) HandlerOption { 155 | return func(opts *HandlerOptions) { 156 | opts.Resolver = resolver 157 | } 158 | } 159 | 160 | // HostsHandlerOption sets the Hosts option of HandlerOptions. 161 | func HostsHandlerOption(hosts *Hosts) HandlerOption { 162 | return func(opts *HandlerOptions) { 163 | opts.Hosts = hosts 164 | } 165 | } 166 | 167 | // ProbeResistHandlerOption adds the probe resistance for HTTP proxy. 168 | func ProbeResistHandlerOption(pr string) HandlerOption { 169 | return func(opts *HandlerOptions) { 170 | opts.ProbeResist = pr 171 | } 172 | } 173 | 174 | // KnockingHandlerOption adds the knocking host for probe resistance. 175 | func KnockingHandlerOption(host string) HandlerOption { 176 | return func(opts *HandlerOptions) { 177 | opts.KnockingHost = host 178 | } 179 | } 180 | 181 | // NodeHandlerOption set the server node for server handler. 182 | func NodeHandlerOption(node Node) HandlerOption { 183 | return func(opts *HandlerOptions) { 184 | opts.Node = node 185 | } 186 | } 187 | 188 | // HostHandlerOption sets the target host for SNI proxy. 189 | func HostHandlerOption(host string) HandlerOption { 190 | return func(opts *HandlerOptions) { 191 | opts.Host = host 192 | } 193 | } 194 | 195 | // IPsHandlerOption sets the ip list for port forward. 196 | func IPsHandlerOption(ips []string) HandlerOption { 197 | return func(opts *HandlerOptions) { 198 | opts.IPs = ips 199 | } 200 | } 201 | 202 | // TCPModeHandlerOption sets the tcp mode for tun/tap device. 203 | func TCPModeHandlerOption(b bool) HandlerOption { 204 | return func(opts *HandlerOptions) { 205 | opts.TCPMode = b 206 | } 207 | } 208 | 209 | // IPRoutesHandlerOption sets the IP routes for tun tunnel. 210 | func IPRoutesHandlerOption(routes ...IPRoute) HandlerOption { 211 | return func(opts *HandlerOptions) { 212 | opts.IPRoutes = routes 213 | } 214 | } 215 | 216 | // ProxyAgentHandlerOption sets the proxy agent for http handler. 217 | func ProxyAgentHandlerOption(agent string) HandlerOption { 218 | return func(opts *HandlerOptions) { 219 | opts.ProxyAgent = agent 220 | } 221 | } 222 | 223 | // HTTPTunnelHandlerOption sets the Tunnel mode for HTTP client used in HTTP handler. 224 | func HTTPTunnelHandlerOption(tunnelMode bool) HandlerOption { 225 | return func(opts *HandlerOptions) { 226 | opts.HTTPTunnel = tunnelMode 227 | } 228 | } 229 | 230 | type autoHandler struct { 231 | options *HandlerOptions 232 | } 233 | 234 | // AutoHandler creates a server Handler for auto proxy server. 235 | func AutoHandler(opts ...HandlerOption) Handler { 236 | h := &autoHandler{} 237 | h.Init(opts...) 238 | return h 239 | } 240 | 241 | func (h *autoHandler) Init(options ...HandlerOption) { 242 | if h.options == nil { 243 | h.options = &HandlerOptions{} 244 | } 245 | for _, opt := range options { 246 | opt(h.options) 247 | } 248 | } 249 | 250 | func (h *autoHandler) Handle(conn net.Conn) { 251 | br := bufio.NewReader(conn) 252 | b, err := br.Peek(1) 253 | if err != nil { 254 | log.Logf("[auto] %s - %s: %s", conn.RemoteAddr(), conn.LocalAddr(), err) 255 | conn.Close() 256 | return 257 | } 258 | 259 | cc := &bufferdConn{Conn: conn, br: br} 260 | var handler Handler 261 | switch b[0] { 262 | case gosocks4.Ver4: 263 | // SOCKS4(a) does not suppport authentication method, 264 | // so we ignore it when credentials are specified for security reason. 265 | if len(h.options.Users) > 0 { 266 | cc.Close() 267 | return 268 | } 269 | handler = &socks4Handler{options: h.options} 270 | case gosocks5.Ver5: // socks5 271 | handler = &socks5Handler{options: h.options} 272 | default: // http 273 | handler = &httpHandler{options: h.options} 274 | } 275 | handler.Init() 276 | handler.Handle(cc) 277 | } 278 | 279 | type bufferdConn struct { 280 | net.Conn 281 | br *bufio.Reader 282 | } 283 | 284 | func (c *bufferdConn) Read(b []byte) (int, error) { 285 | return c.br.Read(b) 286 | } 287 | -------------------------------------------------------------------------------- /handler_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/tls" 6 | "net/http/httptest" 7 | "net/url" 8 | "testing" 9 | ) 10 | 11 | func autoHTTPProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { 12 | ln, err := TCPListener("") 13 | if err != nil { 14 | return err 15 | } 16 | 17 | client := &Client{ 18 | Connector: HTTPConnector(clientInfo), 19 | Transporter: TCPTransporter(), 20 | } 21 | server := &Server{ 22 | Listener: ln, 23 | Handler: AutoHandler( 24 | UsersHandlerOption(serverInfo...), 25 | ), 26 | } 27 | 28 | go server.Run() 29 | defer server.Close() 30 | 31 | return proxyRoundtrip(client, server, targetURL, data) 32 | } 33 | 34 | func TestAutoHTTPProxy(t *testing.T) { 35 | httpSrv := httptest.NewServer(httpTestHandler) 36 | defer httpSrv.Close() 37 | 38 | sendData := make([]byte, 128) 39 | rand.Read(sendData) 40 | 41 | for i, tc := range httpProxyTests { 42 | err := autoHTTPProxyRoundtrip(httpSrv.URL, sendData, tc.cliUser, tc.srvUsers) 43 | if err == nil { 44 | if tc.errStr != "" { 45 | t.Errorf("#%d should failed with error %s", i, tc.errStr) 46 | } 47 | } else { 48 | if tc.errStr == "" { 49 | t.Errorf("#%d got error %v", i, err) 50 | } 51 | if err.Error() != tc.errStr { 52 | t.Errorf("#%d got error %v, want %v", i, err, tc.errStr) 53 | } 54 | } 55 | } 56 | } 57 | 58 | func autoSocks5ProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo []*url.Userinfo) error { 59 | ln, err := TCPListener("") 60 | if err != nil { 61 | return err 62 | } 63 | 64 | client := &Client{ 65 | Connector: SOCKS5Connector(clientInfo), 66 | Transporter: TCPTransporter(), 67 | } 68 | 69 | server := &Server{ 70 | Handler: AutoHandler(UsersHandlerOption(serverInfo...)), 71 | Listener: ln, 72 | } 73 | 74 | go server.Run() 75 | defer server.Close() 76 | 77 | return proxyRoundtrip(client, server, targetURL, data) 78 | } 79 | 80 | func TestAutoSOCKS5Proxy(t *testing.T) { 81 | cert, err := GenCertificate() 82 | if err != nil { 83 | panic(err) 84 | } 85 | DefaultTLSConfig = &tls.Config{ 86 | Certificates: []tls.Certificate{cert}, 87 | } 88 | 89 | httpSrv := httptest.NewServer(httpTestHandler) 90 | defer httpSrv.Close() 91 | 92 | sendData := make([]byte, 128) 93 | rand.Read(sendData) 94 | 95 | for i, tc := range socks5ProxyTests { 96 | err := autoSocks5ProxyRoundtrip(httpSrv.URL, sendData, 97 | tc.cliUser, 98 | tc.srvUsers, 99 | ) 100 | if err == nil { 101 | if !tc.pass { 102 | t.Errorf("#%d should failed", i) 103 | } 104 | } else { 105 | // t.Logf("#%d %v", i, err) 106 | if tc.pass { 107 | t.Errorf("#%d got error: %v", i, err) 108 | } 109 | } 110 | } 111 | } 112 | 113 | func autoSOCKS4ProxyRoundtrip(targetURL string, data []byte, options ...HandlerOption) error { 114 | ln, err := TCPListener("") 115 | if err != nil { 116 | return err 117 | } 118 | 119 | client := &Client{ 120 | Connector: SOCKS4Connector(), 121 | Transporter: TCPTransporter(), 122 | } 123 | 124 | server := &Server{ 125 | Listener: ln, 126 | Handler: AutoHandler(options...), 127 | } 128 | go server.Run() 129 | defer server.Close() 130 | 131 | return proxyRoundtrip(client, server, targetURL, data) 132 | } 133 | 134 | func TestAutoSOCKS4Proxy(t *testing.T) { 135 | httpSrv := httptest.NewServer(httpTestHandler) 136 | defer httpSrv.Close() 137 | 138 | sendData := make([]byte, 128) 139 | rand.Read(sendData) 140 | 141 | if err := autoSOCKS4ProxyRoundtrip(httpSrv.URL, sendData); err != nil { 142 | t.Errorf("got error: %v", err) 143 | } 144 | 145 | if err := autoSOCKS4ProxyRoundtrip(httpSrv.URL, sendData, 146 | UsersHandlerOption(url.UserPassword("admin", "123456"))); err == nil { 147 | t.Errorf("authentication required auto handler for SOCKS4 should failed") 148 | } 149 | } 150 | 151 | func autoSocks4aProxyRoundtrip(targetURL string, data []byte, options ...HandlerOption) error { 152 | ln, err := TCPListener("") 153 | if err != nil { 154 | return err 155 | } 156 | 157 | client := &Client{ 158 | Connector: SOCKS4AConnector(), 159 | Transporter: TCPTransporter(), 160 | } 161 | 162 | server := &Server{ 163 | Listener: ln, 164 | Handler: AutoHandler(options...), 165 | } 166 | 167 | go server.Run() 168 | defer server.Close() 169 | 170 | return proxyRoundtrip(client, server, targetURL, data) 171 | } 172 | 173 | func TestAutoSOCKS4AProxy(t *testing.T) { 174 | httpSrv := httptest.NewServer(httpTestHandler) 175 | defer httpSrv.Close() 176 | 177 | sendData := make([]byte, 128) 178 | rand.Read(sendData) 179 | 180 | if err := autoSocks4aProxyRoundtrip(httpSrv.URL, sendData); err != nil { 181 | t.Errorf("got error: %v", err) 182 | } 183 | 184 | if err := autoSocks4aProxyRoundtrip(httpSrv.URL, sendData, 185 | UsersHandlerOption(url.UserPassword("admin", "123456"))); err == nil { 186 | t.Errorf("authentication required auto handler for SOCKS4A should failed") 187 | } 188 | } 189 | 190 | func autoSSProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error { 191 | ln, err := TCPListener("") 192 | if err != nil { 193 | return err 194 | } 195 | 196 | client := &Client{ 197 | Connector: ShadowConnector(clientInfo), 198 | Transporter: TCPTransporter(), 199 | } 200 | 201 | server := &Server{ 202 | Handler: AutoHandler(UsersHandlerOption(serverInfo)), 203 | Listener: ln, 204 | } 205 | 206 | go server.Run() 207 | defer server.Close() 208 | 209 | return proxyRoundtrip(client, server, targetURL, data) 210 | } 211 | 212 | func TestAutoSSProxy(t *testing.T) { 213 | httpSrv := httptest.NewServer(httpTestHandler) 214 | defer httpSrv.Close() 215 | 216 | sendData := make([]byte, 128) 217 | rand.Read(sendData) 218 | 219 | for i, tc := range ssTests { 220 | err := autoSSProxyRoundtrip(httpSrv.URL, sendData, 221 | tc.clientCipher, 222 | tc.serverCipher, 223 | ) 224 | if err == nil { 225 | t.Errorf("#%d should failed", i) 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /hosts.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "github.com/go-log/log" 11 | ) 12 | 13 | // Host is a static mapping from hostname to IP. 14 | type Host struct { 15 | IP net.IP 16 | Hostname string 17 | Aliases []string 18 | } 19 | 20 | // NewHost creates a Host. 21 | func NewHost(ip net.IP, hostname string, aliases ...string) Host { 22 | return Host{ 23 | IP: ip, 24 | Hostname: hostname, 25 | Aliases: aliases, 26 | } 27 | } 28 | 29 | // Hosts is a static table lookup for hostnames. 30 | // For each host a single line should be present with the following information: 31 | // IP_address canonical_hostname [aliases...] 32 | // Fields of the entry are separated by any number of blanks and/or tab characters. 33 | // Text from a "#" character until the end of the line is a comment, and is ignored. 34 | type Hosts struct { 35 | hosts []Host 36 | period time.Duration 37 | stopped chan struct{} 38 | mux sync.RWMutex 39 | } 40 | 41 | // NewHosts creates a Hosts with optional list of hosts. 42 | func NewHosts(hosts ...Host) *Hosts { 43 | return &Hosts{ 44 | hosts: hosts, 45 | stopped: make(chan struct{}), 46 | } 47 | } 48 | 49 | // AddHost adds host(s) to the host table. 50 | func (h *Hosts) AddHost(host ...Host) { 51 | h.mux.Lock() 52 | defer h.mux.Unlock() 53 | 54 | h.hosts = append(h.hosts, host...) 55 | } 56 | 57 | // Lookup searches the IP address corresponds to the given host from the host table. 58 | func (h *Hosts) Lookup(host string) (ip net.IP) { 59 | if h == nil || host == "" { 60 | return 61 | } 62 | 63 | h.mux.RLock() 64 | defer h.mux.RUnlock() 65 | 66 | for _, h := range h.hosts { 67 | if h.Hostname == host { 68 | ip = h.IP 69 | break 70 | } 71 | for _, alias := range h.Aliases { 72 | if alias == host { 73 | ip = h.IP 74 | break 75 | } 76 | } 77 | } 78 | if ip != nil && Debug { 79 | log.Logf("[hosts] hit: %s %s", host, ip.String()) 80 | } 81 | return 82 | } 83 | 84 | // Reload parses config from r, then live reloads the hosts. 85 | func (h *Hosts) Reload(r io.Reader) error { 86 | var period time.Duration 87 | var hosts []Host 88 | 89 | if r == nil || h.Stopped() { 90 | return nil 91 | } 92 | 93 | scanner := bufio.NewScanner(r) 94 | for scanner.Scan() { 95 | line := scanner.Text() 96 | ss := splitLine(line) 97 | if len(ss) < 2 { 98 | continue // invalid lines are ignored 99 | } 100 | 101 | switch ss[0] { 102 | case "reload": // reload option 103 | period, _ = time.ParseDuration(ss[1]) 104 | default: 105 | ip := net.ParseIP(ss[0]) 106 | if ip == nil { 107 | break // invalid IP addresses are ignored 108 | } 109 | host := Host{ 110 | IP: ip, 111 | Hostname: ss[1], 112 | } 113 | if len(ss) > 2 { 114 | host.Aliases = ss[2:] 115 | } 116 | hosts = append(hosts, host) 117 | } 118 | } 119 | if err := scanner.Err(); err != nil { 120 | return err 121 | } 122 | 123 | h.mux.Lock() 124 | h.period = period 125 | h.hosts = hosts 126 | h.mux.Unlock() 127 | 128 | return nil 129 | } 130 | 131 | // Period returns the reload period 132 | func (h *Hosts) Period() time.Duration { 133 | if h.Stopped() { 134 | return -1 135 | } 136 | 137 | h.mux.RLock() 138 | defer h.mux.RUnlock() 139 | 140 | return h.period 141 | } 142 | 143 | // Stop stops reloading. 144 | func (h *Hosts) Stop() { 145 | select { 146 | case <-h.stopped: 147 | default: 148 | close(h.stopped) 149 | } 150 | } 151 | 152 | // Stopped checks whether the reloader is stopped. 153 | func (h *Hosts) Stopped() bool { 154 | select { 155 | case <-h.stopped: 156 | return true 157 | default: 158 | return false 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /hosts_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | var hostsLookupTests = []struct { 12 | hosts []Host 13 | host string 14 | ip net.IP 15 | }{ 16 | {nil, "", nil}, 17 | {nil, "example.com", nil}, 18 | {[]Host{}, "", nil}, 19 | {[]Host{}, "example.com", nil}, 20 | {[]Host{NewHost(nil, "")}, "", nil}, 21 | {[]Host{NewHost(nil, "example.com")}, "example.com", nil}, 22 | {[]Host{NewHost(net.IPv4(192, 168, 1, 1), "")}, "", nil}, 23 | {[]Host{NewHost(net.IPv4(192, 168, 1, 1), "example.com")}, "example.com", net.IPv4(192, 168, 1, 1)}, 24 | {[]Host{NewHost(net.IPv4(192, 168, 1, 1), "example.com")}, "example", nil}, 25 | {[]Host{NewHost(net.IPv4(192, 168, 1, 1), "example.com", "example", "examples")}, "example", net.IPv4(192, 168, 1, 1)}, 26 | {[]Host{NewHost(net.IPv4(192, 168, 1, 1), "example.com", "example", "examples")}, "examples", net.IPv4(192, 168, 1, 1)}, 27 | } 28 | 29 | func TestHostsLookup(t *testing.T) { 30 | for i, tc := range hostsLookupTests { 31 | hosts := NewHosts() 32 | hosts.AddHost(tc.hosts...) 33 | ip := hosts.Lookup(tc.host) 34 | if !ip.Equal(tc.ip) { 35 | t.Errorf("#%d test failed: lookup should be %s, got %s", i, tc.ip, ip) 36 | } 37 | } 38 | } 39 | 40 | var HostsReloadTests = []struct { 41 | r io.Reader 42 | period time.Duration 43 | host string 44 | ip net.IP 45 | stopped bool 46 | }{ 47 | { 48 | r: nil, 49 | period: 0, 50 | host: "", 51 | ip: nil, 52 | }, 53 | { 54 | r: bytes.NewBufferString(""), 55 | period: 0, 56 | host: "example.com", 57 | ip: nil, 58 | }, 59 | { 60 | r: bytes.NewBufferString("reload 10s"), 61 | period: 10 * time.Second, 62 | host: "example.com", 63 | ip: nil, 64 | }, 65 | { 66 | r: bytes.NewBufferString("#reload 10s\ninvalid.ip.addr example.com"), 67 | period: 0, 68 | ip: nil, 69 | }, 70 | { 71 | r: bytes.NewBufferString("reload 10s\n192.168.1.1"), 72 | period: 10 * time.Second, 73 | host: "", 74 | ip: nil, 75 | }, 76 | { 77 | r: bytes.NewBufferString("#reload 10s\n192.168.1.1 example.com"), 78 | period: 0, 79 | host: "example.com", 80 | ip: net.IPv4(192, 168, 1, 1), 81 | }, 82 | { 83 | r: bytes.NewBufferString("#reload 10s\n#192.168.1.1 example.com"), 84 | period: 0, 85 | host: "example.com", 86 | ip: nil, 87 | stopped: true, 88 | }, 89 | { 90 | r: bytes.NewBufferString("#reload 10s\n192.168.1.1 example.com example examples"), 91 | period: 0, 92 | host: "example", 93 | ip: net.IPv4(192, 168, 1, 1), 94 | stopped: true, 95 | }, 96 | { 97 | r: bytes.NewBufferString("#reload 10s\n192.168.1.1 example.com example examples"), 98 | period: 0, 99 | host: "examples", 100 | ip: net.IPv4(192, 168, 1, 1), 101 | stopped: true, 102 | }, 103 | } 104 | 105 | func TestHostsReload(t *testing.T) { 106 | for i, tc := range HostsReloadTests { 107 | hosts := NewHosts() 108 | if err := hosts.Reload(tc.r); err != nil { 109 | t.Error(err) 110 | } 111 | if hosts.Period() != tc.period { 112 | t.Errorf("#%d test failed: period value should be %v, got %v", 113 | i, tc.period, hosts.Period()) 114 | } 115 | ip := hosts.Lookup(tc.host) 116 | if !ip.Equal(tc.ip) { 117 | t.Errorf("#%d test failed: lookup should be %s, got %s", i, tc.ip, ip) 118 | } 119 | if tc.stopped { 120 | hosts.Stop() 121 | if hosts.Period() >= 0 { 122 | t.Errorf("period of the stopped reloader should be minus value") 123 | } 124 | } 125 | if hosts.Stopped() != tc.stopped { 126 | t.Errorf("#%d test failed: stopped value should be %v, got %v", 127 | i, tc.stopped, hosts.Stopped()) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func init() { 9 | log.SetFlags(log.LstdFlags | log.Lshortfile) 10 | } 11 | 12 | // LogLogger uses the standard log package as the logger 13 | type LogLogger struct { 14 | } 15 | 16 | // Log uses the standard log library log.Output 17 | func (l *LogLogger) Log(v ...interface{}) { 18 | log.Output(3, fmt.Sprintln(v...)) 19 | } 20 | 21 | // Logf uses the standard log library log.Output 22 | func (l *LogLogger) Logf(format string, v ...interface{}) { 23 | log.Output(3, fmt.Sprintf(format, v...)) 24 | } 25 | 26 | // NopLogger is a dummy logger that discards the log outputs 27 | type NopLogger struct { 28 | } 29 | 30 | // Log does nothing 31 | func (l *NopLogger) Log(v ...interface{}) { 32 | } 33 | 34 | // Logf does nothing 35 | func (l *NopLogger) Logf(format string, v ...interface{}) { 36 | } 37 | -------------------------------------------------------------------------------- /mux.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "net" 5 | 6 | smux "github.com/xtaci/smux" 7 | ) 8 | 9 | type muxStreamConn struct { 10 | net.Conn 11 | stream *smux.Stream 12 | } 13 | 14 | func (c *muxStreamConn) Read(b []byte) (n int, err error) { 15 | return c.stream.Read(b) 16 | } 17 | 18 | func (c *muxStreamConn) Write(b []byte) (n int, err error) { 19 | return c.stream.Write(b) 20 | } 21 | 22 | func (c *muxStreamConn) Close() error { 23 | return c.stream.Close() 24 | } 25 | 26 | type muxSession struct { 27 | conn net.Conn 28 | session *smux.Session 29 | } 30 | 31 | func (session *muxSession) GetConn() (net.Conn, error) { 32 | stream, err := session.session.OpenStream() 33 | if err != nil { 34 | return nil, err 35 | } 36 | return &muxStreamConn{Conn: session.conn, stream: stream}, nil 37 | } 38 | 39 | func (session *muxSession) Accept() (net.Conn, error) { 40 | stream, err := session.session.AcceptStream() 41 | if err != nil { 42 | return nil, err 43 | } 44 | return &muxStreamConn{Conn: session.conn, stream: stream}, nil 45 | } 46 | 47 | func (session *muxSession) Close() error { 48 | if session.session == nil { 49 | return nil 50 | } 51 | return session.session.Close() 52 | } 53 | 54 | func (session *muxSession) IsClosed() bool { 55 | if session.session == nil { 56 | return true 57 | } 58 | return session.session.IsClosed() 59 | } 60 | 61 | func (session *muxSession) NumStreams() int { 62 | return session.session.NumStreams() 63 | } 64 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | // ErrInvalidNode is an error that implies the node is invalid. 15 | ErrInvalidNode = errors.New("invalid node") 16 | ) 17 | 18 | // Node is a proxy node, mainly used to construct a proxy chain. 19 | type Node struct { 20 | ID int 21 | Addr string 22 | Host string 23 | Protocol string 24 | Transport string 25 | Remote string // remote address, used by tcp/udp port forwarding 26 | url *url.URL // raw url 27 | User *url.Userinfo 28 | Values url.Values 29 | DialOptions []DialOption 30 | HandshakeOptions []HandshakeOption 31 | ConnectOptions []ConnectOption 32 | Client *Client 33 | marker *failMarker 34 | Bypass *Bypass 35 | } 36 | 37 | // ParseNode parses the node info. 38 | // The proxy node string pattern is [scheme://][user:pass@host]:port. 39 | // Scheme can be divided into two parts by character '+', such as: http+tls. 40 | func ParseNode(s string) (node Node, err error) { 41 | s = strings.TrimSpace(s) 42 | if s == "" { 43 | return Node{}, ErrInvalidNode 44 | } 45 | 46 | if !strings.Contains(s, "://") { 47 | s = "auto://" + s 48 | } 49 | u, err := url.Parse(s) 50 | if err != nil { 51 | return 52 | } 53 | 54 | node = Node{ 55 | Addr: u.Host, 56 | Host: u.Host, 57 | Remote: strings.Trim(u.EscapedPath(), "/"), 58 | Values: u.Query(), 59 | User: u.User, 60 | marker: &failMarker{}, 61 | url: u, 62 | } 63 | 64 | u.RawQuery = "" 65 | u.User = nil 66 | 67 | schemes := strings.Split(u.Scheme, "+") 68 | if len(schemes) == 1 { 69 | node.Protocol = schemes[0] 70 | node.Transport = schemes[0] 71 | } 72 | if len(schemes) == 2 { 73 | node.Protocol = schemes[0] 74 | node.Transport = schemes[1] 75 | } 76 | 77 | switch node.Transport { 78 | case "https": 79 | node.Transport = "tls" 80 | case "tls", "mtls": 81 | case "http2", "h2", "h2c": 82 | case "ws", "mws", "wss", "mwss": 83 | case "kcp", "ssh", "quic": 84 | case "ssu": 85 | node.Transport = "udp" 86 | case "ohttp", "otls", "obfs4": // obfs 87 | case "tcp", "udp": 88 | case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding 89 | case "tun", "tap": // tun/tap device 90 | case "ftcp": // fake TCP 91 | case "dns": 92 | case "redu", "redirectu": // UDP tproxy 93 | case "vsock": 94 | default: 95 | node.Transport = "tcp" 96 | } 97 | 98 | switch node.Protocol { 99 | case "http", "http2": 100 | case "https": 101 | node.Protocol = "http" 102 | case "socks4", "socks4a": 103 | case "socks", "socks5": 104 | node.Protocol = "socks5" 105 | case "ss", "ssu": 106 | case "ss2": // as of 2.10.1, ss2 is same as ss 107 | node.Protocol = "ss" 108 | case "sni": 109 | case "tcp", "udp", "rtcp", "rudp": // port forwarding 110 | case "direct", "remote", "forward": // forwarding 111 | case "red", "redirect", "redu", "redirectu": // TCP,UDP transparent proxy 112 | case "tun", "tap": // tun/tap device 113 | case "ftcp": // fake TCP 114 | case "dns", "dot", "doh": 115 | case "relay": 116 | default: 117 | node.Protocol = "" 118 | } 119 | 120 | return 121 | } 122 | 123 | // MarkDead marks the node fail status. 124 | func (node *Node) MarkDead() { 125 | if node.marker == nil { 126 | return 127 | } 128 | node.marker.Mark() 129 | } 130 | 131 | // ResetDead resets the node fail status. 132 | func (node *Node) ResetDead() { 133 | if node.marker == nil { 134 | return 135 | } 136 | node.marker.Reset() 137 | } 138 | 139 | // Clone clones the node, it will prevent data race. 140 | func (node *Node) Clone() Node { 141 | nd := *node 142 | if node.marker != nil { 143 | nd.marker = node.marker.Clone() 144 | } 145 | return nd 146 | } 147 | 148 | // Get returns node parameter specified by key. 149 | func (node *Node) Get(key string) string { 150 | return node.Values.Get(key) 151 | } 152 | 153 | // GetBool converts node parameter value to bool. 154 | func (node *Node) GetBool(key string) bool { 155 | b, _ := strconv.ParseBool(node.Values.Get(key)) 156 | return b 157 | } 158 | 159 | // GetInt converts node parameter value to int. 160 | func (node *Node) GetInt(key string) int { 161 | n, _ := strconv.Atoi(node.Get(key)) 162 | return n 163 | } 164 | 165 | // GetDuration converts node parameter value to time.Duration. 166 | func (node *Node) GetDuration(key string) time.Duration { 167 | d, err := time.ParseDuration(node.Get(key)) 168 | if err != nil { 169 | d = time.Duration(node.GetInt(key)) * time.Second 170 | } 171 | return d 172 | } 173 | 174 | func (node Node) String() string { 175 | var scheme string 176 | if node.url != nil { 177 | scheme = node.url.Scheme 178 | } 179 | if scheme == "" { 180 | scheme = fmt.Sprintf("%s+%s", node.Protocol, node.Transport) 181 | } 182 | return fmt.Sprintf("%s://%s", 183 | scheme, node.Addr) 184 | } 185 | 186 | // NodeGroup is a group of nodes. 187 | type NodeGroup struct { 188 | ID int 189 | nodes []Node 190 | selectorOptions []SelectOption 191 | selector NodeSelector 192 | mux sync.RWMutex 193 | } 194 | 195 | // NewNodeGroup creates a node group 196 | func NewNodeGroup(nodes ...Node) *NodeGroup { 197 | return &NodeGroup{ 198 | nodes: nodes, 199 | } 200 | } 201 | 202 | // AddNode appends node or node list into group node. 203 | func (group *NodeGroup) AddNode(node ...Node) { 204 | if group == nil { 205 | return 206 | } 207 | group.mux.Lock() 208 | defer group.mux.Unlock() 209 | 210 | group.nodes = append(group.nodes, node...) 211 | } 212 | 213 | // SetNodes replaces the group nodes to the specified nodes, 214 | // and returns the previous nodes. 215 | func (group *NodeGroup) SetNodes(nodes ...Node) []Node { 216 | if group == nil { 217 | return nil 218 | } 219 | 220 | group.mux.Lock() 221 | defer group.mux.Unlock() 222 | 223 | old := group.nodes 224 | group.nodes = nodes 225 | return old 226 | } 227 | 228 | // SetSelector sets node selector with options for the group. 229 | func (group *NodeGroup) SetSelector(selector NodeSelector, opts ...SelectOption) { 230 | if group == nil { 231 | return 232 | } 233 | group.mux.Lock() 234 | defer group.mux.Unlock() 235 | 236 | group.selector = selector 237 | group.selectorOptions = opts 238 | } 239 | 240 | // Nodes returns the node list in the group 241 | func (group *NodeGroup) Nodes() []Node { 242 | if group == nil { 243 | return nil 244 | } 245 | 246 | group.mux.RLock() 247 | defer group.mux.RUnlock() 248 | 249 | return group.nodes 250 | } 251 | 252 | // GetNode returns the node specified by index in the group. 253 | func (group *NodeGroup) GetNode(i int) Node { 254 | group.mux.RLock() 255 | defer group.mux.RUnlock() 256 | 257 | if i < 0 || group == nil || len(group.nodes) <= i { 258 | return Node{} 259 | } 260 | return group.nodes[i] 261 | } 262 | 263 | // Next selects a node from group. 264 | // It also selects IP if the IP list exists. 265 | func (group *NodeGroup) Next() (node Node, err error) { 266 | if group == nil { 267 | return 268 | } 269 | 270 | group.mux.RLock() 271 | defer group.mux.RUnlock() 272 | 273 | selector := group.selector 274 | if selector == nil { 275 | selector = &defaultSelector{} 276 | } 277 | 278 | // select node from node group 279 | node, err = selector.Select(group.nodes, group.selectorOptions...) 280 | if err != nil { 281 | return 282 | } 283 | 284 | return 285 | } 286 | -------------------------------------------------------------------------------- /node_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import "testing" 4 | import "net/url" 5 | 6 | var nodeTests = []struct { 7 | in string 8 | out Node 9 | hasError bool 10 | }{ 11 | {"", Node{}, true}, 12 | {"://", Node{}, true}, 13 | {"localhost", Node{Addr: "localhost", Transport: "tcp"}, false}, 14 | {":", Node{Addr: ":", Transport: "tcp"}, false}, 15 | {":8080", Node{Addr: ":8080", Transport: "tcp"}, false}, 16 | {"http://:8080", Node{Addr: ":8080", Protocol: "http", Transport: "tcp"}, false}, 17 | {"http://localhost:8080", Node{Addr: "localhost:8080", Protocol: "http", Transport: "tcp"}, false}, 18 | {"http://admin:123456@:8080", Node{Addr: ":8080", Protocol: "http", Transport: "tcp", User: url.UserPassword("admin", "123456")}, false}, 19 | {"http://admin@localhost:8080", Node{Addr: "localhost:8080", Protocol: "http", Transport: "tcp", User: url.User("admin")}, false}, 20 | {"http://:123456@localhost:8080", Node{Addr: "localhost:8080", Protocol: "http", Transport: "tcp", User: url.UserPassword("", "123456")}, false}, 21 | {"http://@localhost:8080", Node{Addr: "localhost:8080", Protocol: "http", Transport: "tcp", User: url.User("")}, false}, 22 | {"http://:@localhost:8080", Node{Addr: "localhost:8080", Protocol: "http", Transport: "tcp", User: url.UserPassword("", "")}, false}, 23 | {"https://:8080", Node{Addr: ":8080", Protocol: "http", Transport: "tls"}, false}, 24 | {"socks+tls://:8080", Node{Addr: ":8080", Protocol: "socks5", Transport: "tls"}, false}, 25 | {"tls://:8080", Node{Addr: ":8080", Transport: "tls"}, false}, 26 | {"tcp://:8080/:8081", Node{Addr: ":8080", Remote: ":8081", Protocol: "tcp", Transport: "tcp"}, false}, 27 | {"udp://:8080/:8081", Node{Addr: ":8080", Remote: ":8081", Protocol: "udp", Transport: "udp"}, false}, 28 | {"rtcp://:8080/:8081", Node{Addr: ":8080", Remote: ":8081", Protocol: "rtcp", Transport: "rtcp"}, false}, 29 | {"rudp://:8080/:8081", Node{Addr: ":8080", Remote: ":8081", Protocol: "rudp", Transport: "rudp"}, false}, 30 | {"redirect://:8080", Node{Addr: ":8080", Protocol: "redirect", Transport: "tcp"}, false}, 31 | } 32 | 33 | func TestParseNode(t *testing.T) { 34 | for _, test := range nodeTests { 35 | actual, err := ParseNode(test.in) 36 | if err != nil { 37 | if test.hasError { 38 | // t.Logf("ParseNode(%q) got expected error: %v", test.in, err) 39 | continue 40 | } 41 | t.Errorf("ParseNode(%q) got error: %v", test.in, err) 42 | } else { 43 | if test.hasError { 44 | t.Errorf("ParseNode(%q) got %v, but should return error", test.in, actual) 45 | continue 46 | } 47 | if actual.Addr != test.out.Addr || actual.Protocol != test.out.Protocol || 48 | actual.Transport != test.out.Transport || actual.Remote != test.out.Remote { 49 | t.Errorf("ParseNode(%q) got %v, want %v", test.in, actual, test.out) 50 | } 51 | if actual.User == nil { 52 | if test.out.User != nil { 53 | t.Errorf("ParseNode(%q) got %v, want %v", test.in, actual, test.out) 54 | } 55 | continue 56 | } 57 | if actual.User != nil { 58 | if test.out.User == nil { 59 | t.Errorf("ParseNode(%q) got %v, want %v", test.in, actual, test.out) 60 | continue 61 | } 62 | if *actual.User != *test.out.User { 63 | t.Errorf("ParseNode(%q) got %v, want %v", test.in, actual, test.out) 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /permissions.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | 10 | glob "github.com/ryanuber/go-glob" 11 | ) 12 | 13 | // Permission is a rule for blacklist and whitelist. 14 | type Permission struct { 15 | Actions StringSet 16 | Hosts StringSet 17 | Ports PortSet 18 | } 19 | 20 | // PortRange specifies the range of port, such as 1000-2000. 21 | type PortRange struct { 22 | Min, Max int 23 | } 24 | 25 | // ParsePortRange parses the s to a PortRange. 26 | // The s may be a '*' means 0-65535. 27 | func ParsePortRange(s string) (*PortRange, error) { 28 | if s == "*" { 29 | return &PortRange{Min: 0, Max: 65535}, nil 30 | } 31 | 32 | minmax := strings.Split(s, "-") 33 | switch len(minmax) { 34 | case 1: 35 | port, err := strconv.Atoi(s) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if port < 0 || port > 65535 { 40 | return nil, fmt.Errorf("invalid port: %s", s) 41 | } 42 | return &PortRange{Min: port, Max: port}, nil 43 | case 2: 44 | min, err := strconv.Atoi(minmax[0]) 45 | if err != nil { 46 | return nil, err 47 | } 48 | max, err := strconv.Atoi(minmax[1]) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | realmin := maxint(0, minint(min, max)) 54 | realmax := minint(65535, maxint(min, max)) 55 | 56 | return &PortRange{Min: realmin, Max: realmax}, nil 57 | default: 58 | return nil, fmt.Errorf("invalid range: %s", s) 59 | } 60 | } 61 | 62 | // Contains checks whether the value is within this range. 63 | func (ir *PortRange) Contains(value int) bool { 64 | return value >= ir.Min && value <= ir.Max 65 | } 66 | 67 | // PortSet is a set of PortRange 68 | type PortSet []PortRange 69 | 70 | // ParsePortSet parses the s to a PortSet. 71 | // The s shoud be a comma separated string. 72 | func ParsePortSet(s string) (*PortSet, error) { 73 | ps := &PortSet{} 74 | 75 | if s == "" { 76 | return nil, errors.New("must specify at least one port") 77 | } 78 | 79 | ranges := strings.Split(s, ",") 80 | 81 | for _, r := range ranges { 82 | portRange, err := ParsePortRange(r) 83 | 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | *ps = append(*ps, *portRange) 89 | } 90 | 91 | return ps, nil 92 | } 93 | 94 | // Contains checks whether the value is within this port set. 95 | func (ps *PortSet) Contains(value int) bool { 96 | for _, portRange := range *ps { 97 | if portRange.Contains(value) { 98 | return true 99 | } 100 | } 101 | 102 | return false 103 | } 104 | 105 | // StringSet is a set of string. 106 | type StringSet []string 107 | 108 | // ParseStringSet parses the s to a StringSet. 109 | // The s shoud be a comma separated string. 110 | func ParseStringSet(s string) (*StringSet, error) { 111 | ss := &StringSet{} 112 | if s == "" { 113 | return nil, errors.New("cannot be empty") 114 | } 115 | 116 | *ss = strings.Split(s, ",") 117 | 118 | return ss, nil 119 | } 120 | 121 | // Contains checks whether the string subj within this StringSet. 122 | func (ss *StringSet) Contains(subj string) bool { 123 | for _, s := range *ss { 124 | if glob.Glob(s, subj) { 125 | return true 126 | } 127 | } 128 | 129 | return false 130 | } 131 | 132 | // Permissions is a set of Permission. 133 | type Permissions []Permission 134 | 135 | // ParsePermissions parses the s to a Permissions. 136 | func ParsePermissions(s string) (*Permissions, error) { 137 | ps := &Permissions{} 138 | 139 | if s == "" { 140 | return &Permissions{}, nil 141 | } 142 | 143 | perms := strings.Split(s, " ") 144 | 145 | for _, perm := range perms { 146 | parts := strings.Split(perm, ":") 147 | 148 | switch len(parts) { 149 | case 3: 150 | actions, err := ParseStringSet(parts[0]) 151 | 152 | if err != nil { 153 | return nil, fmt.Errorf("action list must look like connect,bind given: %s", parts[0]) 154 | } 155 | 156 | hosts, err := ParseStringSet(parts[1]) 157 | 158 | if err != nil { 159 | return nil, fmt.Errorf("hosts list must look like google.pl,*.google.com given: %s", parts[1]) 160 | } 161 | 162 | ports, err := ParsePortSet(parts[2]) 163 | 164 | if err != nil { 165 | return nil, fmt.Errorf("ports list must look like 80,8000-9000, given: %s", parts[2]) 166 | } 167 | 168 | permission := Permission{Actions: *actions, Hosts: *hosts, Ports: *ports} 169 | 170 | *ps = append(*ps, permission) 171 | default: 172 | return nil, fmt.Errorf("permission must have format [actions]:[hosts]:[ports] given: %s", perm) 173 | } 174 | } 175 | 176 | return ps, nil 177 | } 178 | 179 | // Can tests whether the given action and host:port is allowed by this Permissions. 180 | func (ps *Permissions) Can(action string, host string, port int) bool { 181 | for _, p := range *ps { 182 | if p.Actions.Contains(action) && p.Hosts.Contains(host) && p.Ports.Contains(port) { 183 | return true 184 | } 185 | } 186 | 187 | return false 188 | } 189 | 190 | func minint(x, y int) int { 191 | if x < y { 192 | return x 193 | } 194 | return y 195 | } 196 | 197 | func maxint(x, y int) int { 198 | if x > y { 199 | return x 200 | } 201 | return y 202 | } 203 | 204 | // Can tests whether the given action and address is allowed by the whitelist and blacklist. 205 | func Can(action string, addr string, whitelist, blacklist *Permissions) bool { 206 | if !strings.Contains(addr, ":") { 207 | addr = addr + ":80" 208 | } 209 | host, strport, err := net.SplitHostPort(addr) 210 | 211 | if err != nil { 212 | return false 213 | } 214 | 215 | port, err := strconv.Atoi(strport) 216 | 217 | if err != nil { 218 | return false 219 | } 220 | 221 | return (whitelist == nil || whitelist.Can(action, host, port)) && 222 | (blacklist == nil || !blacklist.Can(action, host, port)) 223 | } 224 | -------------------------------------------------------------------------------- /permissions_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var portRangeTests = []struct { 9 | in string 10 | out *PortRange 11 | }{ 12 | {"1", &PortRange{Min: 1, Max: 1}}, 13 | {"1-3", &PortRange{Min: 1, Max: 3}}, 14 | {"3-1", &PortRange{Min: 1, Max: 3}}, 15 | {"0-100000", &PortRange{Min: 0, Max: 65535}}, 16 | {"*", &PortRange{Min: 0, Max: 65535}}, 17 | } 18 | 19 | var stringSetTests = []struct { 20 | in string 21 | out *StringSet 22 | }{ 23 | {"*", &StringSet{"*"}}, 24 | {"google.pl,google.com", &StringSet{"google.pl", "google.com"}}, 25 | } 26 | 27 | var portSetTests = []struct { 28 | in string 29 | out *PortSet 30 | }{ 31 | {"1,3", &PortSet{PortRange{Min: 1, Max: 1}, PortRange{Min: 3, Max: 3}}}, 32 | {"1-3,7-5", &PortSet{PortRange{Min: 1, Max: 3}, PortRange{Min: 5, Max: 7}}}, 33 | {"0-100000", &PortSet{PortRange{Min: 0, Max: 65535}}}, 34 | {"*", &PortSet{PortRange{Min: 0, Max: 65535}}}, 35 | } 36 | 37 | var permissionsTests = []struct { 38 | in string 39 | out *Permissions 40 | }{ 41 | {"", &Permissions{}}, 42 | {"*:*:*", &Permissions{ 43 | Permission{ 44 | Actions: StringSet{"*"}, 45 | Hosts: StringSet{"*"}, 46 | Ports: PortSet{PortRange{Min: 0, Max: 65535}}, 47 | }, 48 | }}, 49 | {"bind:127.0.0.1,localhost:80,443,8000-8100 connect:*.google.pl:80,443", &Permissions{ 50 | Permission{ 51 | Actions: StringSet{"bind"}, 52 | Hosts: StringSet{"127.0.0.1", "localhost"}, 53 | Ports: PortSet{ 54 | PortRange{Min: 80, Max: 80}, 55 | PortRange{Min: 443, Max: 443}, 56 | PortRange{Min: 8000, Max: 8100}, 57 | }, 58 | }, 59 | Permission{ 60 | Actions: StringSet{"connect"}, 61 | Hosts: StringSet{"*.google.pl"}, 62 | Ports: PortSet{ 63 | PortRange{Min: 80, Max: 80}, 64 | PortRange{Min: 443, Max: 443}, 65 | }, 66 | }, 67 | }}, 68 | } 69 | 70 | func TestPortRangeParse(t *testing.T) { 71 | for _, test := range portRangeTests { 72 | actual, err := ParsePortRange(test.in) 73 | if err != nil { 74 | t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err) 75 | } else if *actual != *test.out { 76 | t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out) 77 | } 78 | } 79 | } 80 | 81 | func TestPortRangeContains(t *testing.T) { 82 | actual, _ := ParsePortRange("5-10") 83 | 84 | if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) { 85 | t.Errorf("5-10 should contain 5, 7 and 10") 86 | } 87 | 88 | if actual.Contains(4) || actual.Contains(11) { 89 | t.Errorf("5-10 should not contain 4, 11") 90 | } 91 | } 92 | 93 | func TestStringSetParse(t *testing.T) { 94 | for _, test := range stringSetTests { 95 | actual, err := ParseStringSet(test.in) 96 | if err != nil { 97 | t.Errorf("ParseStringSet(%q) returned error: %v", test.in, err) 98 | } else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) { 99 | t.Errorf("ParseStringSet(%q): got %v, want %v", test.in, actual, test.out) 100 | } 101 | } 102 | } 103 | 104 | func TestStringSetContains(t *testing.T) { 105 | ss, _ := ParseStringSet("google.pl,*.google.com") 106 | 107 | if !ss.Contains("google.pl") || !ss.Contains("www.google.com") { 108 | t.Errorf("google.pl,*.google.com should contain google.pl and www.google.com") 109 | } 110 | 111 | if ss.Contains("www.google.pl") || ss.Contains("foobar.com") { 112 | t.Errorf("google.pl,*.google.com shound not contain www.google.pl and foobar.com") 113 | } 114 | } 115 | 116 | func TestPortSetParse(t *testing.T) { 117 | for _, test := range portSetTests { 118 | actual, err := ParsePortSet(test.in) 119 | if err != nil { 120 | t.Errorf("ParsePortRange(%q) returned error: %v", test.in, err) 121 | } else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) { 122 | t.Errorf("ParsePortRange(%q): got %v, want %v", test.in, actual, test.out) 123 | } 124 | } 125 | } 126 | 127 | func TestPortSetContains(t *testing.T) { 128 | actual, _ := ParsePortSet("5-10,20-30") 129 | 130 | if !actual.Contains(5) || !actual.Contains(7) || !actual.Contains(10) { 131 | t.Errorf("5-10,20-30 should contain 5, 7 and 10") 132 | } 133 | 134 | if !actual.Contains(20) || !actual.Contains(27) || !actual.Contains(30) { 135 | t.Errorf("5-10,20-30 should contain 20, 27 and 30") 136 | } 137 | 138 | if actual.Contains(4) || actual.Contains(11) || actual.Contains(31) { 139 | t.Errorf("5-10,20-30 should not contain 4, 11, 31") 140 | } 141 | } 142 | 143 | func TestPermissionsParse(t *testing.T) { 144 | for _, test := range permissionsTests { 145 | actual, err := ParsePermissions(test.in) 146 | if err != nil { 147 | t.Errorf("ParsePermissions(%q) returned error: %v", test.in, err) 148 | } else if fmt.Sprintln(actual) != fmt.Sprintln(test.out) { 149 | t.Errorf("ParsePermissions(%q): got %v, want %v", test.in, actual, test.out) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /redirect.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package gost 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "sync" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/LiamHaworth/go-tproxy" 16 | "github.com/go-log/log" 17 | ) 18 | 19 | type tcpRedirectHandler struct { 20 | options *HandlerOptions 21 | } 22 | 23 | // TCPRedirectHandler creates a server Handler for TCP transparent server. 24 | func TCPRedirectHandler(opts ...HandlerOption) Handler { 25 | h := &tcpRedirectHandler{} 26 | h.Init(opts...) 27 | 28 | return h 29 | } 30 | 31 | func (h *tcpRedirectHandler) Init(options ...HandlerOption) { 32 | if h.options == nil { 33 | h.options = &HandlerOptions{} 34 | } 35 | 36 | for _, opt := range options { 37 | opt(h.options) 38 | } 39 | } 40 | 41 | func (h *tcpRedirectHandler) Handle(c net.Conn) { 42 | conn, ok := c.(*net.TCPConn) 43 | if !ok { 44 | log.Log("[red-tcp] not a TCP connection") 45 | } 46 | 47 | srcAddr := conn.RemoteAddr() 48 | dstAddr, conn, err := h.getOriginalDstAddr(conn) 49 | if err != nil { 50 | log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err) 51 | return 52 | } 53 | defer conn.Close() 54 | 55 | log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr) 56 | 57 | cc, err := h.options.Chain.DialContext(context.Background(), 58 | "tcp", dstAddr.String(), 59 | RetryChainOption(h.options.Retries), 60 | TimeoutChainOption(h.options.Timeout), 61 | ) 62 | if err != nil { 63 | log.Logf("[red-tcp] %s -> %s : %s", srcAddr, dstAddr, err) 64 | return 65 | } 66 | defer cc.Close() 67 | 68 | log.Logf("[red-tcp] %s <-> %s", srcAddr, dstAddr) 69 | transport(conn, cc) 70 | log.Logf("[red-tcp] %s >-< %s", srcAddr, dstAddr) 71 | } 72 | 73 | func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) { 74 | defer conn.Close() 75 | 76 | fc, err := conn.File() 77 | if err != nil { 78 | return 79 | } 80 | defer fc.Close() 81 | 82 | mreq, err := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, 80) 83 | if err != nil { 84 | return 85 | } 86 | 87 | // only ipv4 support 88 | ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7]) 89 | port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3]) 90 | addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port)) 91 | if err != nil { 92 | return 93 | } 94 | 95 | cc, err := net.FileConn(fc) 96 | if err != nil { 97 | return 98 | } 99 | 100 | c, ok := cc.(*net.TCPConn) 101 | if !ok { 102 | err = errors.New("not a TCP connection") 103 | } 104 | return 105 | } 106 | 107 | type udpRedirectHandler struct { 108 | options *HandlerOptions 109 | } 110 | 111 | // UDPRedirectHandler creates a server Handler for UDP transparent server. 112 | func UDPRedirectHandler(opts ...HandlerOption) Handler { 113 | h := &udpRedirectHandler{} 114 | h.Init(opts...) 115 | 116 | return h 117 | } 118 | 119 | func (h *udpRedirectHandler) Init(options ...HandlerOption) { 120 | if h.options == nil { 121 | h.options = &HandlerOptions{} 122 | } 123 | 124 | for _, opt := range options { 125 | opt(h.options) 126 | } 127 | } 128 | 129 | func (h *udpRedirectHandler) Handle(conn net.Conn) { 130 | defer conn.Close() 131 | 132 | raddr, ok := conn.LocalAddr().(*net.UDPAddr) 133 | if !ok { 134 | log.Log("[red-udp] wrong connection type") 135 | return 136 | } 137 | 138 | cc, err := h.options.Chain.DialContext(context.Background(), 139 | "udp", raddr.String(), 140 | RetryChainOption(h.options.Retries), 141 | TimeoutChainOption(h.options.Timeout), 142 | ) 143 | if err != nil { 144 | log.Logf("[red-udp] %s - %s : %s", conn.RemoteAddr(), raddr, err) 145 | return 146 | } 147 | defer cc.Close() 148 | 149 | log.Logf("[red-udp] %s <-> %s", conn.RemoteAddr(), raddr) 150 | transport(conn, cc) 151 | log.Logf("[red-udp] %s >-< %s", conn.RemoteAddr(), raddr) 152 | } 153 | 154 | type udpRedirectListener struct { 155 | *net.UDPConn 156 | config *UDPListenConfig 157 | } 158 | 159 | // UDPRedirectListener creates a Listener for UDP transparent proxy server. 160 | func UDPRedirectListener(addr string, cfg *UDPListenConfig) (Listener, error) { 161 | laddr, err := net.ResolveUDPAddr("udp", addr) 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | ln, err := tproxy.ListenUDP("udp", laddr) 167 | if err != nil { 168 | return nil, err 169 | } 170 | 171 | if cfg == nil { 172 | cfg = &UDPListenConfig{} 173 | } 174 | return &udpRedirectListener{ 175 | UDPConn: ln, 176 | config: cfg, 177 | }, nil 178 | } 179 | 180 | func (l *udpRedirectListener) Accept() (conn net.Conn, err error) { 181 | b := make([]byte, mediumBufferSize) 182 | 183 | n, raddr, dstAddr, err := tproxy.ReadFromUDP(l.UDPConn, b) 184 | if err != nil { 185 | log.Logf("[red-udp] %s : %s", l.Addr(), err) 186 | return 187 | } 188 | log.Logf("[red-udp] %s: %s -> %s", l.Addr(), raddr, dstAddr) 189 | 190 | c, err := tproxy.DialUDP("udp", dstAddr, raddr) 191 | if err != nil { 192 | log.Logf("[red-udp] %s -> %s : %s", raddr, dstAddr, err) 193 | return 194 | } 195 | 196 | ttl := l.config.TTL 197 | if ttl <= 0 { 198 | ttl = defaultTTL 199 | } 200 | 201 | conn = &udpRedirectServerConn{ 202 | Conn: c, 203 | buf: b[:n], 204 | ttl: ttl, 205 | } 206 | return 207 | } 208 | 209 | func (l *udpRedirectListener) Addr() net.Addr { 210 | return l.UDPConn.LocalAddr() 211 | } 212 | 213 | type udpRedirectServerConn struct { 214 | net.Conn 215 | buf []byte 216 | ttl time.Duration 217 | once sync.Once 218 | } 219 | 220 | func (c *udpRedirectServerConn) Read(b []byte) (n int, err error) { 221 | if c.ttl > 0 { 222 | c.SetReadDeadline(time.Now().Add(c.ttl)) 223 | defer c.SetReadDeadline(time.Time{}) 224 | } 225 | c.once.Do(func() { 226 | n = copy(b, c.buf) 227 | c.buf = nil 228 | }) 229 | 230 | if n == 0 { 231 | n, err = c.Conn.Read(b) 232 | } 233 | return 234 | } 235 | 236 | func (c *udpRedirectServerConn) Write(b []byte) (n int, err error) { 237 | if c.ttl > 0 { 238 | c.SetWriteDeadline(time.Now().Add(c.ttl)) 239 | defer c.SetWriteDeadline(time.Time{}) 240 | } 241 | return c.Conn.Write(b) 242 | } 243 | -------------------------------------------------------------------------------- /redirect_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package gost 5 | 6 | import ( 7 | "errors" 8 | "net" 9 | 10 | "github.com/go-log/log" 11 | ) 12 | 13 | type tcpRedirectHandler struct { 14 | options *HandlerOptions 15 | } 16 | 17 | // TCPRedirectHandler creates a server Handler for TCP redirect server. 18 | func TCPRedirectHandler(opts ...HandlerOption) Handler { 19 | h := &tcpRedirectHandler{ 20 | options: &HandlerOptions{ 21 | Chain: new(Chain), 22 | }, 23 | } 24 | for _, opt := range opts { 25 | opt(h.options) 26 | } 27 | return h 28 | } 29 | 30 | func (h *tcpRedirectHandler) Init(options ...HandlerOption) { 31 | log.Log("[red-tcp] TCP redirect is not available on the Windows platform") 32 | } 33 | 34 | func (h *tcpRedirectHandler) Handle(c net.Conn) { 35 | log.Log("[red-tcp] TCP redirect is not available on the Windows platform") 36 | c.Close() 37 | } 38 | 39 | type udpRedirectHandler struct{} 40 | 41 | // UDPRedirectHandler creates a server Handler for UDP transparent server. 42 | func UDPRedirectHandler(opts ...HandlerOption) Handler { 43 | return &udpRedirectHandler{} 44 | } 45 | 46 | func (h *udpRedirectHandler) Init(options ...HandlerOption) { 47 | } 48 | 49 | func (h *udpRedirectHandler) Handle(conn net.Conn) { 50 | log.Log("[red-udp] UDP redirect is not available on the Windows platform") 51 | conn.Close() 52 | } 53 | 54 | // UDPRedirectListener creates a Listener for UDP transparent proxy server. 55 | func UDPRedirectListener(addr string, cfg *UDPListenConfig) (Listener, error) { 56 | return nil, errors.New("UDP redirect is not available on the Windows platform") 57 | } 58 | -------------------------------------------------------------------------------- /reload.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "time" 7 | 8 | "github.com/go-log/log" 9 | ) 10 | 11 | // Reloader is the interface for objects that support live reloading. 12 | type Reloader interface { 13 | Reload(r io.Reader) error 14 | Period() time.Duration 15 | } 16 | 17 | // Stoppable is the interface that indicates a Reloader can be stopped. 18 | type Stoppable interface { 19 | Stop() 20 | Stopped() bool 21 | } 22 | 23 | // PeriodReload reloads the config configFile periodically according to the period of the Reloader r. 24 | func PeriodReload(r Reloader, configFile string) error { 25 | if r == nil || configFile == "" { 26 | return nil 27 | } 28 | 29 | var lastMod time.Time 30 | for { 31 | if r.Period() < 0 { 32 | log.Log("[reload] stopped:", configFile) 33 | return nil 34 | } 35 | 36 | f, err := os.Open(configFile) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | mt := lastMod 42 | if finfo, err := f.Stat(); err == nil { 43 | mt = finfo.ModTime() 44 | } 45 | 46 | if !lastMod.IsZero() && !mt.Equal(lastMod) { 47 | log.Log("[reload]", configFile) 48 | if err := r.Reload(f); err != nil { 49 | log.Logf("[reload] %s: %s", configFile, err) 50 | } 51 | } 52 | f.Close() 53 | lastMod = mt 54 | 55 | period := r.Period() 56 | if period == 0 { 57 | log.Log("[reload] disabled:", configFile) 58 | return nil 59 | } 60 | if period < time.Second { 61 | period = time.Second 62 | } 63 | <-time.After(period) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resolver_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | var dnsTests = []struct { 13 | ns NameServer 14 | host string 15 | pass bool 16 | }{ 17 | {NameServer{Addr: "1.1.1.1"}, "192.168.1.1", true}, 18 | {NameServer{Addr: "1.1.1.1"}, "github", true}, 19 | {NameServer{Addr: "1.1.1.1"}, "github.com", true}, 20 | {NameServer{Addr: "1.1.1.1:53"}, "github.com", true}, 21 | {NameServer{Addr: "1.1.1.1:53", Protocol: "tcp"}, "github.com", true}, 22 | {NameServer{Addr: "1.1.1.1:853", Protocol: "tls"}, "github.com", true}, 23 | {NameServer{Addr: "1.1.1.1:853", Protocol: "tls", Hostname: "example.com"}, "github.com", false}, 24 | {NameServer{Addr: "1.1.1.1:853", Protocol: "tls", Hostname: "cloudflare-dns.com"}, "github.com", true}, 25 | {NameServer{Addr: "https://cloudflare-dns.com/dns-query", Protocol: "https"}, "github.com", true}, 26 | {NameServer{Addr: "https://1.0.0.1/dns-query", Protocol: "https"}, "github.com", true}, 27 | {NameServer{Addr: "1.1.1.1:12345"}, "github.com", false}, 28 | {NameServer{Addr: "1.1.1.1:12345", Protocol: "tcp"}, "github.com", false}, 29 | {NameServer{Addr: "1.1.1.1:12345", Protocol: "tls"}, "github.com", false}, 30 | {NameServer{Addr: "https://1.0.0.1:12345/dns-query", Protocol: "https"}, "github.com", false}, 31 | } 32 | 33 | func dnsResolverRoundtrip(t *testing.T, r Resolver, host string) error { 34 | ips, err := r.Resolve(host) 35 | t.Log(host, ips, err) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func TestDNSResolver(t *testing.T) { 44 | for i, tc := range dnsTests { 45 | tc := tc 46 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 47 | ns := tc.ns 48 | t.Log(ns) 49 | r := NewResolver(0, ns) 50 | resolv := r.(*resolver) 51 | resolv.domain = "com" 52 | if err := r.Init(); err != nil { 53 | t.Error("got error:", err) 54 | } 55 | err := dnsResolverRoundtrip(t, r, tc.host) 56 | if err != nil { 57 | if tc.pass { 58 | t.Error("got error:", err) 59 | } 60 | } else { 61 | if !tc.pass { 62 | t.Error("should failed") 63 | } 64 | } 65 | }) 66 | } 67 | } 68 | 69 | var resolverCacheTests = []struct { 70 | name string 71 | ips []net.IP 72 | ttl time.Duration 73 | result []net.IP 74 | }{ 75 | {"", nil, 0, nil}, 76 | {"", []net.IP{net.IPv4(192, 168, 1, 1)}, 0, nil}, 77 | {"", []net.IP{net.IPv4(192, 168, 1, 1)}, 10 * time.Second, nil}, 78 | {"example.com", nil, 10 * time.Second, nil}, 79 | {"example.com", []net.IP{}, 10 * time.Second, nil}, 80 | {"example.com", []net.IP{net.IPv4(192, 168, 1, 1)}, 0, nil}, 81 | {"example.com", []net.IP{net.IPv4(192, 168, 1, 1)}, -1, nil}, 82 | {"example.com", []net.IP{net.IPv4(192, 168, 1, 1)}, 10 * time.Second, 83 | []net.IP{net.IPv4(192, 168, 1, 1)}}, 84 | {"example.com", []net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}, 10 * time.Second, 85 | []net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}}, 86 | } 87 | 88 | /* 89 | func TestResolverCache(t *testing.T) { 90 | isEqual := func(a, b []net.IP) bool { 91 | if a == nil && b == nil { 92 | return true 93 | } 94 | 95 | if a == nil || b == nil || len(a) != len(b) { 96 | return false 97 | } 98 | 99 | for i := range a { 100 | if !a[i].Equal(b[i]) { 101 | return false 102 | } 103 | } 104 | return true 105 | } 106 | for i, tc := range resolverCacheTests { 107 | tc := tc 108 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 109 | r := newResolver(tc.ttl) 110 | r.cache.storeCache(tc.name, tc.ips, tc.ttl) 111 | ips := r.cache.loadCache(tc.name, tc.ttl) 112 | 113 | if !isEqual(tc.result, ips) { 114 | t.Error("unexpected cache value:", tc.name, ips, tc.ttl) 115 | } 116 | }) 117 | } 118 | } 119 | */ 120 | 121 | var resolverReloadTests = []struct { 122 | r io.Reader 123 | 124 | timeout time.Duration 125 | ttl time.Duration 126 | domain string 127 | period time.Duration 128 | ns *NameServer 129 | 130 | stopped bool 131 | }{ 132 | { 133 | r: nil, 134 | }, 135 | { 136 | r: bytes.NewBufferString(""), 137 | }, 138 | { 139 | r: bytes.NewBufferString("reload 10s"), 140 | period: 10 * time.Second, 141 | }, 142 | { 143 | r: bytes.NewBufferString("timeout 10s\nreload 10s\n"), 144 | timeout: 10 * time.Second, 145 | period: 10 * time.Second, 146 | }, 147 | { 148 | r: bytes.NewBufferString("ttl 10s\ntimeout 10s\nreload 10s\n"), 149 | timeout: 10 * time.Second, 150 | period: 10 * time.Second, 151 | ttl: 10 * time.Second, 152 | }, 153 | { 154 | r: bytes.NewBufferString("domain example.com\nttl 10s\ntimeout 10s\nreload 10s\n"), 155 | timeout: 10 * time.Second, 156 | period: 10 * time.Second, 157 | ttl: 10 * time.Second, 158 | domain: "example.com", 159 | }, 160 | { 161 | r: bytes.NewBufferString("1.1.1.1"), 162 | ns: &NameServer{ 163 | Addr: "1.1.1.1", 164 | }, 165 | stopped: true, 166 | }, 167 | { 168 | r: bytes.NewBufferString("\n# comment\ntimeout 10s\nsearch\nnameserver \nnameserver 1.1.1.1 udp"), 169 | ns: &NameServer{ 170 | Protocol: "udp", 171 | Addr: "1.1.1.1", 172 | }, 173 | timeout: 10 * time.Second, 174 | stopped: true, 175 | }, 176 | { 177 | r: bytes.NewBufferString("1.1.1.1 tcp"), 178 | ns: &NameServer{ 179 | Addr: "1.1.1.1", 180 | Protocol: "tcp", 181 | }, 182 | stopped: true, 183 | }, 184 | { 185 | r: bytes.NewBufferString("1.1.1.1:853 tls cloudflare-dns.com"), 186 | ns: &NameServer{ 187 | Addr: "1.1.1.1:853", 188 | Protocol: "tls", 189 | Hostname: "cloudflare-dns.com", 190 | }, 191 | stopped: true, 192 | }, 193 | { 194 | r: bytes.NewBufferString("1.1.1.1:853 tls"), 195 | ns: &NameServer{ 196 | Addr: "1.1.1.1:853", 197 | Protocol: "tls", 198 | }, 199 | stopped: true, 200 | }, 201 | { 202 | r: bytes.NewBufferString("1.0.0.1:53 https"), 203 | stopped: true, 204 | }, 205 | { 206 | r: bytes.NewBufferString("https://1.0.0.1/dns-query"), 207 | ns: &NameServer{ 208 | Addr: "https://1.0.0.1/dns-query", 209 | Protocol: "https", 210 | }, 211 | stopped: true, 212 | }, 213 | } 214 | 215 | func TestResolverReload(t *testing.T) { 216 | for i, tc := range resolverReloadTests { 217 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 218 | r := newResolver(0) 219 | if err := r.Reload(tc.r); err != nil { 220 | t.Error(err) 221 | } 222 | t.Log(r.String()) 223 | if r.TTL() != tc.ttl { 224 | t.Errorf("ttl value should be %v, got %v", 225 | tc.ttl, r.TTL()) 226 | } 227 | if r.Period() != tc.period { 228 | t.Errorf("period value should be %v, got %v", 229 | tc.period, r.period) 230 | } 231 | if r.domain != tc.domain { 232 | t.Errorf("domain value should be %v, got %v", 233 | tc.domain, r.domain) 234 | } 235 | 236 | var ns *NameServer 237 | if len(r.servers) > 0 { 238 | ns = &r.servers[0] 239 | } 240 | 241 | if !compareNameServer(ns, tc.ns) { 242 | t.Errorf("nameserver not equal, should be %v, got %v", 243 | tc.ns, r.servers) 244 | } 245 | 246 | if tc.stopped { 247 | r.Stop() 248 | if r.Period() >= 0 { 249 | t.Errorf("period of the stopped reloader should be minus value") 250 | } 251 | } 252 | if r.Stopped() != tc.stopped { 253 | t.Errorf("stopped value should be %v, got %v", 254 | tc.stopped, r.Stopped()) 255 | } 256 | }) 257 | } 258 | } 259 | 260 | func compareNameServer(n1, n2 *NameServer) bool { 261 | if n1 == n2 { 262 | return true 263 | } 264 | if n1 == nil || n2 == nil { 265 | return false 266 | } 267 | return n1.Addr == n2.Addr && 268 | n1.Hostname == n2.Hostname && 269 | n1.Protocol == n2.Protocol 270 | } 271 | -------------------------------------------------------------------------------- /selector_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestRoundStrategy(t *testing.T) { 9 | nodes := []Node{ 10 | Node{ID: 1}, 11 | Node{ID: 2}, 12 | Node{ID: 3}, 13 | } 14 | s := NewStrategy("round") 15 | t.Log(s.String()) 16 | 17 | if node := s.Apply(nil); node.ID > 0 { 18 | t.Error("unexpected node", node.String()) 19 | } 20 | for i := 0; i <= len(nodes); i++ { 21 | node := s.Apply(nodes) 22 | if node.ID != nodes[i%len(nodes)].ID { 23 | t.Error("unexpected node", node.String()) 24 | } 25 | } 26 | } 27 | 28 | func TestRandomStrategy(t *testing.T) { 29 | nodes := []Node{ 30 | Node{ID: 1}, 31 | Node{ID: 2}, 32 | Node{ID: 3}, 33 | } 34 | s := NewStrategy("random") 35 | t.Log(s.String()) 36 | 37 | if node := s.Apply(nil); node.ID > 0 { 38 | t.Error("unexpected node", node.String()) 39 | } 40 | for i := 0; i <= len(nodes); i++ { 41 | node := s.Apply(nodes) 42 | if node.ID == 0 { 43 | t.Error("unexpected node", node.String()) 44 | } 45 | } 46 | } 47 | 48 | func TestFIFOStrategy(t *testing.T) { 49 | nodes := []Node{ 50 | Node{ID: 1}, 51 | Node{ID: 2}, 52 | Node{ID: 3}, 53 | } 54 | s := NewStrategy("fifo") 55 | t.Log(s.String()) 56 | 57 | if node := s.Apply(nil); node.ID > 0 { 58 | t.Error("unexpected node", node.String()) 59 | } 60 | for i := 0; i <= len(nodes); i++ { 61 | node := s.Apply(nodes) 62 | if node.ID != 1 { 63 | t.Error("unexpected node", node.String()) 64 | } 65 | } 66 | } 67 | 68 | func TestFailFilter(t *testing.T) { 69 | nodes := []Node{ 70 | Node{ID: 1, marker: &failMarker{}}, 71 | Node{ID: 2, marker: &failMarker{}}, 72 | Node{ID: 3, marker: &failMarker{}}, 73 | } 74 | filter := &FailFilter{} 75 | t.Log(filter.String()) 76 | 77 | isEqual := func(a, b []Node) bool { 78 | if a == nil && b == nil { 79 | return true 80 | } 81 | if a == nil || b == nil || len(a) != len(b) { 82 | return false 83 | } 84 | 85 | for i := range a { 86 | if a[i].ID != b[i].ID { 87 | return false 88 | } 89 | } 90 | return true 91 | } 92 | if v := filter.Filter(nil); v != nil { 93 | t.Error("unexpected node", v) 94 | } 95 | 96 | if v := filter.Filter(nodes); !isEqual(v, nodes) { 97 | t.Error("unexpected node", v) 98 | } 99 | 100 | filter.MaxFails = -1 101 | nodes[0].MarkDead() 102 | if v := filter.Filter(nodes); !isEqual(v, nodes) { 103 | t.Error("unexpected node", v) 104 | } 105 | 106 | filter.MaxFails = 0 107 | if v := filter.Filter(nodes); isEqual(v, nodes) { 108 | t.Error("unexpected node", v) 109 | } 110 | 111 | filter.FailTimeout = 5 * time.Second 112 | if v := filter.Filter(nodes); isEqual(v, nodes) { 113 | t.Error("unexpected node", v) 114 | } 115 | 116 | nodes[1].MarkDead() 117 | nodes[2].MarkDead() 118 | if v := filter.Filter(nodes); len(v) > 0 { 119 | t.Error("unexpected node", v) 120 | } 121 | 122 | for i := range nodes { 123 | nodes[i].ResetDead() 124 | } 125 | if v := filter.Filter(nodes); !isEqual(v, nodes) { 126 | t.Error("unexpected node", v) 127 | } 128 | } 129 | 130 | func TestFastestFilter(t *testing.T) { 131 | nodes := []Node{ 132 | Node{ID: 1, marker: &failMarker{}, Addr: "1.0.0.1:80"}, 133 | Node{ID: 2, marker: &failMarker{}, Addr: "1.0.0.2:80"}, 134 | Node{ID: 3, marker: &failMarker{}, Addr: "1.0.0.3:80"}, 135 | } 136 | filter := NewFastestFilter(0, 2) 137 | 138 | var print = func(nodes []Node) []string { 139 | var rows []string 140 | for _, node := range nodes { 141 | rows = append(rows, node.Addr) 142 | } 143 | return rows 144 | } 145 | 146 | result1 := filter.Filter(nodes) 147 | t.Logf("result 1: %+v", print(result1)) 148 | 149 | time.Sleep(time.Second) 150 | result2 := filter.Filter(nodes) 151 | t.Logf("result 2: %+v", print(result2)) 152 | } 153 | 154 | func TestSelector(t *testing.T) { 155 | nodes := []Node{ 156 | Node{ID: 1, marker: &failMarker{}}, 157 | Node{ID: 2, marker: &failMarker{}}, 158 | Node{ID: 3, marker: &failMarker{}}, 159 | } 160 | selector := &defaultSelector{} 161 | if _, err := selector.Select(nil); err != ErrNoneAvailable { 162 | t.Error("got unexpected error:", err) 163 | } 164 | 165 | if node, _ := selector.Select(nodes); node.ID != 1 { 166 | t.Error("unexpected node:", node) 167 | } 168 | 169 | if node, _ := selector.Select(nodes, 170 | WithStrategy(NewStrategy("")), 171 | WithFilter(&FailFilter{MaxFails: 1, FailTimeout: 3 * time.Second}), 172 | ); node.ID != 1 { 173 | t.Error("unexpected node:", node) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | 8 | "github.com/go-log/log" 9 | ) 10 | 11 | // Accepter represents a network endpoint that can accept connection from peer. 12 | type Accepter interface { 13 | Accept() (net.Conn, error) 14 | } 15 | 16 | // Server is a proxy server. 17 | type Server struct { 18 | Listener Listener 19 | Handler Handler 20 | options *ServerOptions 21 | } 22 | 23 | // Init intializes server with given options. 24 | func (s *Server) Init(opts ...ServerOption) { 25 | if s.options == nil { 26 | s.options = &ServerOptions{} 27 | } 28 | for _, opt := range opts { 29 | opt(s.options) 30 | } 31 | } 32 | 33 | // Addr returns the address of the server 34 | func (s *Server) Addr() net.Addr { 35 | return s.Listener.Addr() 36 | } 37 | 38 | // Close closes the server 39 | func (s *Server) Close() error { 40 | return s.Listener.Close() 41 | } 42 | 43 | // Serve serves as a proxy server. 44 | func (s *Server) Serve(h Handler, opts ...ServerOption) error { 45 | s.Init(opts...) 46 | 47 | if s.Listener == nil { 48 | ln, err := TCPListener("") 49 | if err != nil { 50 | return err 51 | } 52 | s.Listener = ln 53 | } 54 | 55 | if h == nil { 56 | h = s.Handler 57 | } 58 | if h == nil { 59 | h = HTTPHandler() 60 | } 61 | 62 | l := s.Listener 63 | var tempDelay time.Duration 64 | for { 65 | conn, e := l.Accept() 66 | if e != nil { 67 | if ne, ok := e.(net.Error); ok && ne.Temporary() { 68 | if tempDelay == 0 { 69 | tempDelay = 5 * time.Millisecond 70 | } else { 71 | tempDelay *= 2 72 | } 73 | if max := 1 * time.Second; tempDelay > max { 74 | tempDelay = max 75 | } 76 | log.Logf("server: Accept error: %v; retrying in %v", e, tempDelay) 77 | time.Sleep(tempDelay) 78 | continue 79 | } 80 | return e 81 | } 82 | tempDelay = 0 83 | 84 | go h.Handle(conn) 85 | } 86 | } 87 | 88 | // Run starts to serve. 89 | func (s *Server) Run() error { 90 | return s.Serve(s.Handler) 91 | } 92 | 93 | // ServerOptions holds the options for Server. 94 | type ServerOptions struct { 95 | } 96 | 97 | // ServerOption allows a common way to set server options. 98 | type ServerOption func(opts *ServerOptions) 99 | 100 | // Listener is a proxy server listener, just like a net.Listener. 101 | type Listener interface { 102 | net.Listener 103 | } 104 | 105 | func transport(rw1, rw2 io.ReadWriter) error { 106 | errc := make(chan error, 1) 107 | go func() { 108 | errc <- copyBuffer(rw1, rw2) 109 | }() 110 | 111 | go func() { 112 | errc <- copyBuffer(rw2, rw1) 113 | }() 114 | 115 | if err := <-errc; err != nil && err != io.EOF { 116 | return err 117 | } 118 | 119 | return nil 120 | } 121 | 122 | func copyBuffer(dst io.Writer, src io.Reader) error { 123 | buf := lPool.Get().([]byte) 124 | defer lPool.Put(buf) 125 | 126 | _, err := io.CopyBuffer(dst, src, buf) 127 | return err 128 | } 129 | -------------------------------------------------------------------------------- /signal.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package gost 5 | 6 | func kcpSigHandler() {} 7 | -------------------------------------------------------------------------------- /signal_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package gost 5 | 6 | import ( 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "github.com/go-log/log" 12 | "github.com/xtaci/kcp-go/v5" 13 | ) 14 | 15 | func kcpSigHandler() { 16 | ch := make(chan os.Signal, 1) 17 | signal.Notify(ch, syscall.SIGUSR1) 18 | 19 | for { 20 | switch <-ch { 21 | case syscall.SIGUSR1: 22 | log.Logf("[kcp] SNMP: %+v", kcp.DefaultSnmp.Copy()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: gost 2 | base: core20 3 | version: '2.12.0' 4 | summary: A simple security tunnel written in golang 5 | description: | 6 | Project: https://github.com/ginuerzh/gost 7 | Wiki: https://v2.gost.run 8 | icon: gost.png 9 | website: https://v2.gost.run 10 | license: MIT 11 | 12 | 13 | confinement: strict 14 | grade: stable 15 | 16 | parts: 17 | gost: 18 | plugin: go 19 | go-channel: latest/stable 20 | source: https://github.com/ginuerzh/gost 21 | source-subdir: cmd/gost 22 | source-type: git 23 | source-tag: v2.12.0 24 | build-packages: 25 | - gcc 26 | 27 | apps: 28 | gost: 29 | command: bin/gost 30 | plugs: 31 | - home 32 | - network 33 | - network-bind 34 | 35 | -------------------------------------------------------------------------------- /sni_test.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/rand" 7 | "crypto/tls" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "net/http/httptest" 13 | "net/url" 14 | "testing" 15 | "time" 16 | ) 17 | 18 | func sniRoundtrip(client *Client, server *Server, targetURL string, data []byte) (err error) { 19 | conn, err := client.Dial(server.Addr().String()) 20 | if err != nil { 21 | return 22 | } 23 | 24 | conn, err = client.Handshake(conn, AddrHandshakeOption(server.Addr().String())) 25 | if err != nil { 26 | return 27 | } 28 | defer conn.Close() 29 | 30 | u, err := url.Parse(targetURL) 31 | if err != nil { 32 | return 33 | } 34 | 35 | conn.SetDeadline(time.Now().Add(3 * time.Second)) 36 | defer conn.SetDeadline(time.Time{}) 37 | 38 | conn, err = client.Connect(conn, u.Host) 39 | if err != nil { 40 | return 41 | } 42 | 43 | if u.Scheme == "https" { 44 | conn = tls.Client(conn, 45 | &tls.Config{ 46 | InsecureSkipVerify: true, 47 | // ServerName: u.Hostname(), 48 | }) 49 | u.Scheme = "http" 50 | } 51 | req, err := http.NewRequest( 52 | http.MethodGet, 53 | u.String(), 54 | bytes.NewReader(data), 55 | ) 56 | if err != nil { 57 | return 58 | } 59 | if err = req.WriteProxy(conn); err != nil { 60 | return 61 | } 62 | resp, err := http.ReadResponse(bufio.NewReader(conn), req) 63 | if err != nil { 64 | return 65 | } 66 | defer resp.Body.Close() 67 | 68 | if resp.StatusCode != http.StatusOK { 69 | return errors.New(resp.Status) 70 | } 71 | 72 | recv, err := io.ReadAll(resp.Body) 73 | if err != nil { 74 | return 75 | } 76 | 77 | if !bytes.Equal(data, recv) { 78 | return fmt.Errorf("data not equal") 79 | } 80 | 81 | return 82 | } 83 | 84 | func sniProxyRoundtrip(targetURL string, data []byte, host string) error { 85 | ln, err := TCPListener("") 86 | if err != nil { 87 | return err 88 | } 89 | 90 | u, err := url.Parse(targetURL) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | client := &Client{ 96 | Connector: SNIConnector(host), 97 | Transporter: TCPTransporter(), 98 | } 99 | 100 | server := &Server{ 101 | Listener: ln, 102 | Handler: SNIHandler(HostHandlerOption(u.Host)), 103 | } 104 | 105 | go server.Run() 106 | defer server.Close() 107 | 108 | return sniRoundtrip(client, server, targetURL, data) 109 | } 110 | 111 | func TestSNIProxy(t *testing.T) { 112 | httpSrv := httptest.NewServer(httpTestHandler) 113 | defer httpSrv.Close() 114 | 115 | httpsSrv := httptest.NewTLSServer(httpTestHandler) 116 | defer httpsSrv.Close() 117 | 118 | sendData := make([]byte, 128) 119 | rand.Read(sendData) 120 | 121 | var sniProxyTests = []struct { 122 | targetURL string 123 | host string 124 | pass bool 125 | }{ 126 | {httpSrv.URL, "", true}, 127 | {httpSrv.URL, "example.com", true}, 128 | {httpsSrv.URL, "", true}, 129 | {httpsSrv.URL, "example.com", true}, 130 | } 131 | 132 | for i, tc := range sniProxyTests { 133 | tc := tc 134 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 135 | err := sniProxyRoundtrip(tc.targetURL, sendData, tc.host) 136 | if err == nil { 137 | if !tc.pass { 138 | t.Errorf("#%d should failed", i) 139 | } 140 | } else { 141 | // t.Logf("#%d %v", i, err) 142 | if tc.pass { 143 | t.Errorf("#%d got error: %v", i, err) 144 | } 145 | } 146 | }) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /sockopts_linux.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import "syscall" 4 | 5 | func setSocketMark(fd int, value int) (e error) { 6 | return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, value) 7 | } 8 | 9 | func setSocketInterface(fd int, value string) (e error) { 10 | return syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, value) 11 | } 12 | -------------------------------------------------------------------------------- /sockopts_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package gost 5 | 6 | func setSocketMark(fd int, value int) (e error) { 7 | return nil 8 | } 9 | 10 | func setSocketInterface(fd int, value string) (e error) { 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import "net" 4 | 5 | // tcpTransporter is a raw TCP transporter. 6 | type tcpTransporter struct{} 7 | 8 | // TCPTransporter creates a raw TCP client. 9 | func TCPTransporter() Transporter { 10 | return &tcpTransporter{} 11 | } 12 | 13 | func (tr *tcpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) { 14 | opts := &DialOptions{} 15 | for _, option := range options { 16 | option(opts) 17 | } 18 | 19 | timeout := opts.Timeout 20 | if timeout <= 0 { 21 | timeout = DialTimeout 22 | } 23 | if opts.Chain == nil { 24 | return net.DialTimeout("tcp", addr, timeout) 25 | } 26 | return opts.Chain.Dial(addr) 27 | } 28 | 29 | func (tr *tcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { 30 | return conn, nil 31 | } 32 | 33 | func (tr *tcpTransporter) Multiplex() bool { 34 | return false 35 | } 36 | 37 | type tcpListener struct { 38 | net.Listener 39 | } 40 | 41 | // TCPListener creates a Listener for TCP proxy server. 42 | func TCPListener(addr string) (Listener, error) { 43 | laddr, err := net.ResolveTCPAddr("tcp", addr) 44 | if err != nil { 45 | return nil, err 46 | } 47 | ln, err := net.ListenTCP("tcp", laddr) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &tcpListener{Listener: tcpKeepAliveListener{ln}}, nil 52 | } 53 | 54 | type tcpKeepAliveListener struct { 55 | *net.TCPListener 56 | } 57 | 58 | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 59 | tc, err := ln.AcceptTCP() 60 | if err != nil { 61 | return 62 | } 63 | tc.SetKeepAlive(true) 64 | tc.SetKeepAlivePeriod(KeepAliveTime) 65 | return tc, nil 66 | } 67 | -------------------------------------------------------------------------------- /tuntap_darwin.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/go-log/log" 11 | "github.com/songgao/water" 12 | ) 13 | 14 | func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) { 15 | ip, _, err := net.ParseCIDR(cfg.Addr) 16 | if err != nil { 17 | return 18 | } 19 | 20 | ifce, err := water.New(water.Config{ 21 | DeviceType: water.TUN, 22 | }) 23 | if err != nil { 24 | return 25 | } 26 | 27 | mtu := cfg.MTU 28 | if mtu <= 0 { 29 | mtu = DefaultMTU 30 | } 31 | 32 | peer := cfg.Peer 33 | if peer == "" { 34 | peer = ip.String() 35 | } 36 | cmd := fmt.Sprintf("ifconfig %s inet %s %s mtu %d up", 37 | ifce.Name(), cfg.Addr, peer, mtu) 38 | log.Log("[tun]", cmd) 39 | args := strings.Split(cmd, " ") 40 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 41 | err = fmt.Errorf("%s: %v", cmd, er) 42 | return 43 | } 44 | 45 | if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil { 46 | return 47 | } 48 | 49 | itf, err = net.InterfaceByName(ifce.Name()) 50 | if err != nil { 51 | return 52 | } 53 | 54 | conn = &tunTapConn{ 55 | ifce: ifce, 56 | addr: &net.IPAddr{IP: ip}, 57 | } 58 | return 59 | } 60 | 61 | func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { 62 | err = errors.New("tap is not supported on darwin") 63 | return 64 | } 65 | 66 | func addTunRoutes(ifName string, routes ...IPRoute) error { 67 | for _, route := range routes { 68 | if route.Dest == nil { 69 | continue 70 | } 71 | cmd := fmt.Sprintf("route add -net %s -interface %s", route.Dest.String(), ifName) 72 | log.Log("[tun]", cmd) 73 | args := strings.Split(cmd, " ") 74 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 75 | return fmt.Errorf("%s: %v", cmd, er) 76 | } 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /tuntap_linux.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os/exec" 7 | "strings" 8 | 9 | "github.com/go-log/log" 10 | "github.com/songgao/water" 11 | ) 12 | 13 | func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) { 14 | ip, _, err := net.ParseCIDR(cfg.Addr) 15 | if err != nil { 16 | return 17 | } 18 | 19 | ifce, err := water.New(water.Config{ 20 | DeviceType: water.TUN, 21 | PlatformSpecificParams: water.PlatformSpecificParams{ 22 | Name: cfg.Name, 23 | }, 24 | }) 25 | if err != nil { 26 | return 27 | } 28 | 29 | mtu := cfg.MTU 30 | if mtu <= 0 { 31 | mtu = DefaultMTU 32 | } 33 | 34 | if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil { 35 | log.Log(err) 36 | } 37 | 38 | if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil { 39 | log.Log(err) 40 | } 41 | 42 | if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil { 43 | log.Log(err) 44 | } 45 | 46 | if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil { 47 | return 48 | } 49 | 50 | itf, err = net.InterfaceByName(ifce.Name()) 51 | if err != nil { 52 | return 53 | } 54 | 55 | conn = &tunTapConn{ 56 | ifce: ifce, 57 | addr: &net.IPAddr{IP: ip}, 58 | } 59 | return 60 | } 61 | 62 | func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { 63 | var ip net.IP 64 | if cfg.Addr != "" { 65 | ip, _, err = net.ParseCIDR(cfg.Addr) 66 | if err != nil { 67 | return 68 | } 69 | } 70 | 71 | ifce, err := water.New(water.Config{ 72 | DeviceType: water.TAP, 73 | PlatformSpecificParams: water.PlatformSpecificParams{ 74 | Name: cfg.Name, 75 | }, 76 | }) 77 | if err != nil { 78 | return 79 | } 80 | 81 | mtu := cfg.MTU 82 | if mtu <= 0 { 83 | mtu = DefaultMTU 84 | } 85 | 86 | if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil { 87 | log.Log(err) 88 | } 89 | 90 | if cfg.Addr != "" { 91 | if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil { 92 | log.Log(err) 93 | } 94 | } 95 | 96 | if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil { 97 | log.Log(err) 98 | } 99 | 100 | if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil { 101 | return 102 | } 103 | 104 | itf, err = net.InterfaceByName(ifce.Name()) 105 | if err != nil { 106 | return 107 | } 108 | 109 | conn = &tunTapConn{ 110 | ifce: ifce, 111 | addr: &net.IPAddr{IP: ip}, 112 | } 113 | return 114 | } 115 | 116 | func addTunRoutes(ifName string, routes ...IPRoute) error { 117 | for _, route := range routes { 118 | if route.Dest == nil { 119 | continue 120 | } 121 | cmd := fmt.Sprintf("ip route add %s dev %s", route.Dest.String(), ifName) 122 | log.Logf("[tun] %s", cmd) 123 | 124 | args := strings.Split(cmd, " ") 125 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 126 | log.Logf("[tun] %s: %v", cmd, er) 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | func addTapRoutes(ifName string, gw string, routes ...string) error { 133 | for _, route := range routes { 134 | if route == "" { 135 | continue 136 | } 137 | cmd := fmt.Sprintf("ip route add %s via %s dev %s", route, gw, ifName) 138 | log.Logf("[tap] %s", cmd) 139 | 140 | args := strings.Split(cmd, " ") 141 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 142 | log.Logf("[tap] %s: %v", cmd, er) 143 | } 144 | } 145 | return nil 146 | } 147 | 148 | func exeCmd(cmd string) error { 149 | log.Log(cmd) 150 | 151 | args := strings.Split(cmd, " ") 152 | if err := exec.Command(args[0], args[1:]...).Run(); err != nil { 153 | return fmt.Errorf("%s: %v", cmd, err) 154 | } 155 | 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /tuntap_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows && !darwin 2 | // +build !linux,!windows,!darwin 3 | 4 | package gost 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | "os/exec" 10 | "strings" 11 | 12 | "github.com/go-log/log" 13 | "github.com/songgao/water" 14 | ) 15 | 16 | func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) { 17 | ip, _, err := net.ParseCIDR(cfg.Addr) 18 | if err != nil { 19 | return 20 | } 21 | 22 | ifce, err := water.New(water.Config{ 23 | DeviceType: water.TUN, 24 | }) 25 | if err != nil { 26 | return 27 | } 28 | 29 | mtu := cfg.MTU 30 | if mtu <= 0 { 31 | mtu = DefaultMTU 32 | } 33 | 34 | cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu) 35 | log.Log("[tun]", cmd) 36 | args := strings.Split(cmd, " ") 37 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 38 | err = fmt.Errorf("%s: %v", cmd, er) 39 | return 40 | } 41 | 42 | if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil { 43 | return 44 | } 45 | 46 | itf, err = net.InterfaceByName(ifce.Name()) 47 | if err != nil { 48 | return 49 | } 50 | 51 | conn = &tunTapConn{ 52 | ifce: ifce, 53 | addr: &net.IPAddr{IP: ip}, 54 | } 55 | return 56 | } 57 | 58 | func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { 59 | ip, _, _ := net.ParseCIDR(cfg.Addr) 60 | 61 | ifce, err := water.New(water.Config{ 62 | DeviceType: water.TAP, 63 | }) 64 | if err != nil { 65 | return 66 | } 67 | 68 | mtu := cfg.MTU 69 | if mtu <= 0 { 70 | mtu = DefaultMTU 71 | } 72 | 73 | var cmd string 74 | if cfg.Addr != "" { 75 | cmd = fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu) 76 | } else { 77 | cmd = fmt.Sprintf("ifconfig %s mtu %d up", ifce.Name(), mtu) 78 | } 79 | log.Log("[tap]", cmd) 80 | args := strings.Split(cmd, " ") 81 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 82 | err = fmt.Errorf("%s: %v", cmd, er) 83 | return 84 | } 85 | 86 | if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil { 87 | return 88 | } 89 | 90 | itf, err = net.InterfaceByName(ifce.Name()) 91 | if err != nil { 92 | return 93 | } 94 | 95 | conn = &tunTapConn{ 96 | ifce: ifce, 97 | addr: &net.IPAddr{IP: ip}, 98 | } 99 | return 100 | } 101 | 102 | func addTunRoutes(ifName string, routes ...IPRoute) error { 103 | for _, route := range routes { 104 | if route.Dest == nil { 105 | continue 106 | } 107 | cmd := fmt.Sprintf("route add -net %s -interface %s", route.Dest.String(), ifName) 108 | log.Logf("[tun] %s", cmd) 109 | args := strings.Split(cmd, " ") 110 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 111 | return fmt.Errorf("%s: %v", cmd, er) 112 | } 113 | } 114 | return nil 115 | } 116 | 117 | func addTapRoutes(ifName string, gw string, routes ...string) error { 118 | for _, route := range routes { 119 | if route == "" { 120 | continue 121 | } 122 | cmd := fmt.Sprintf("route add -net %s dev %s", route, ifName) 123 | if gw != "" { 124 | cmd += " gw " + gw 125 | } 126 | log.Logf("[tap] %s", cmd) 127 | args := strings.Split(cmd, " ") 128 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 129 | return fmt.Errorf("%s: %v", cmd, er) 130 | } 131 | } 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /tuntap_windows.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os/exec" 7 | "strings" 8 | 9 | "github.com/go-log/log" 10 | "github.com/songgao/water" 11 | ) 12 | 13 | func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) { 14 | ip, ipNet, err := net.ParseCIDR(cfg.Addr) 15 | if err != nil { 16 | return 17 | } 18 | 19 | ifce, err := water.New(water.Config{ 20 | DeviceType: water.TUN, 21 | PlatformSpecificParams: water.PlatformSpecificParams{ 22 | ComponentID: "tap0901", 23 | InterfaceName: cfg.Name, 24 | Network: cfg.Addr, 25 | }, 26 | }) 27 | if err != nil { 28 | return 29 | } 30 | 31 | cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+ 32 | "source=static addr=%s mask=%s gateway=none", 33 | ifce.Name(), ip.String(), ipMask(ipNet.Mask)) 34 | log.Log("[tun]", cmd) 35 | args := strings.Split(cmd, " ") 36 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 37 | err = fmt.Errorf("%s: %v", cmd, er) 38 | return 39 | } 40 | 41 | if err = addTunRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil { 42 | return 43 | } 44 | 45 | itf, err = net.InterfaceByName(ifce.Name()) 46 | if err != nil { 47 | return 48 | } 49 | 50 | conn = &tunTapConn{ 51 | ifce: ifce, 52 | addr: &net.IPAddr{IP: ip}, 53 | } 54 | return 55 | } 56 | 57 | func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { 58 | ip, ipNet, _ := net.ParseCIDR(cfg.Addr) 59 | 60 | ifce, err := water.New(water.Config{ 61 | DeviceType: water.TAP, 62 | PlatformSpecificParams: water.PlatformSpecificParams{ 63 | ComponentID: "tap0901", 64 | InterfaceName: cfg.Name, 65 | Network: cfg.Addr, 66 | }, 67 | }) 68 | if err != nil { 69 | return 70 | } 71 | 72 | if ip != nil && ipNet != nil { 73 | cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+ 74 | "source=static addr=%s mask=%s gateway=none", 75 | ifce.Name(), ip.String(), ipMask(ipNet.Mask)) 76 | log.Log("[tap]", cmd) 77 | args := strings.Split(cmd, " ") 78 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 79 | err = fmt.Errorf("%s: %v", cmd, er) 80 | return 81 | } 82 | } 83 | 84 | if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil { 85 | return 86 | } 87 | 88 | itf, err = net.InterfaceByName(ifce.Name()) 89 | if err != nil { 90 | return 91 | } 92 | 93 | conn = &tunTapConn{ 94 | ifce: ifce, 95 | addr: &net.IPAddr{IP: ip}, 96 | } 97 | return 98 | } 99 | 100 | func addTunRoutes(ifName string, gw string, routes ...IPRoute) error { 101 | for _, route := range routes { 102 | if route.Dest == nil { 103 | continue 104 | } 105 | 106 | deleteRoute(ifName, route.Dest.String()) 107 | 108 | cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active", 109 | route.Dest.String(), ifName) 110 | if gw != "" { 111 | cmd += " nexthop=" + gw 112 | } 113 | log.Logf("[tun] %s", cmd) 114 | args := strings.Split(cmd, " ") 115 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 116 | return fmt.Errorf("%s: %v", cmd, er) 117 | } 118 | } 119 | return nil 120 | } 121 | 122 | func addTapRoutes(ifName string, gw string, routes ...string) error { 123 | for _, route := range routes { 124 | if route == "" { 125 | continue 126 | } 127 | 128 | deleteRoute(ifName, route) 129 | 130 | cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active", 131 | route, ifName) 132 | if gw != "" { 133 | cmd += " nexthop=" + gw 134 | } 135 | log.Logf("[tap] %s", cmd) 136 | args := strings.Split(cmd, " ") 137 | if er := exec.Command(args[0], args[1:]...).Run(); er != nil { 138 | return fmt.Errorf("%s: %v", cmd, er) 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | func deleteRoute(ifName string, route string) error { 145 | cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=\"%s\" store=active", 146 | route, ifName) 147 | args := strings.Split(cmd, " ") 148 | return exec.Command(args[0], args[1:]...).Run() 149 | } 150 | 151 | func ipMask(mask net.IPMask) string { 152 | return fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]) 153 | } 154 | -------------------------------------------------------------------------------- /vsock.go: -------------------------------------------------------------------------------- 1 | package gost 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | 7 | "github.com/mdlayher/vsock" 8 | ) 9 | 10 | // vsockTransporter is a raw VSOCK transporter. 11 | type vsockTransporter struct{} 12 | 13 | // VSOCKTransporter creates a raw VSOCK client. 14 | func VSOCKTransporter() Transporter { 15 | return &vsockTransporter{} 16 | } 17 | 18 | func (tr *vsockTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) { 19 | opts := &DialOptions{} 20 | for _, option := range options { 21 | option(opts) 22 | } 23 | if opts.Chain == nil { 24 | vAddr, err := parseAddr(addr) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return vsock.Dial(vAddr.ContextID, vAddr.Port, nil) 29 | } 30 | return opts.Chain.Dial(addr) 31 | } 32 | 33 | func parseUint32(s string) (uint32, error ) { 34 | n, err := strconv.ParseUint(s, 10, 32) 35 | if err != nil { 36 | return 0, err 37 | } 38 | return uint32(n), nil 39 | } 40 | 41 | func parseAddr(addr string) (*vsock.Addr, error) { 42 | hostStr, portStr, err := net.SplitHostPort(addr) 43 | if err != nil { 44 | return nil, err 45 | } 46 | host := uint32(0) 47 | if hostStr != "" { 48 | host, err = parseUint32(hostStr) 49 | if err != nil { 50 | return nil, err 51 | } 52 | } 53 | 54 | port, err := parseUint32(portStr) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return &vsock.Addr{ContextID: host, Port: port}, nil 59 | } 60 | 61 | func (tr *vsockTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { 62 | return conn, nil 63 | } 64 | 65 | func (tr *vsockTransporter) Multiplex() bool { 66 | return false 67 | } 68 | 69 | // VSOCKListener creates a Listener for VSOCK proxy server. 70 | func VSOCKListener(addr string) (Listener, error) { 71 | vAddr, err := parseAddr(addr) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return vsock.Listen(vAddr.Port, nil) 76 | } 77 | --------------------------------------------------------------------------------