Compare commits

...

128 Commits

Author SHA1 Message Date
ginuerzh
87d6a2fdc2 update Dockerfile 2024-10-11 16:02:44 +08:00
ginuerzh
91e12c4428 update github actions 2024-10-11 00:23:09 +08:00
ginuerzh
48c7970942
Merge pull request #1038 from ShuBo6/master
fix: file.Close()
2024-08-01 21:00:06 +08:00
ginuerzh
de49ba2aba
Merge pull request #1042 from mengzhuo/mengzhuo-patch-1
chore: add riscv64 in buildx
2024-08-01 20:59:30 +08:00
Meng Zhuo
2732481bfc
chore: add riscv64 in buildx
Tested on visionfive2
```
root@visionfive2-1:~/gost/cmd/gost# uname -a
Linux visionfive2-1 5.15.0-starfive #1 SMP Sun Jun 11 07:48:39 UTC 2023 riscv64 GNU/Linux
root@visionfive2-1:~/gost/cmd/gost# ./gost -V
gost 2.12.0 (go1.22.3 linux/riscv64)
```
2024-07-22 11:08:35 +08:00
ShuBo6
e30f120045 fix: file.Close() 2024-07-07 13:00:14 +08:00
ginuerzh
08c54cd8af update go.mod 2024-06-13 23:39:11 +08:00
ginuerzh
31a9a45495
Merge pull request #1021 from ginuerzh/dependabot/go_modules/github.com/quic-go/quic-go-0.42.0
Bump github.com/quic-go/quic-go from 0.32.0 to 0.42.0
2024-06-13 23:12:06 +08:00
ginuerzh
81128f9439
Merge branch 'master' into dependabot/go_modules/github.com/quic-go/quic-go-0.42.0 2024-06-13 23:11:45 +08:00
ginuerzh
8508797355
Merge pull request #1024 from ginuerzh/dependabot/go_modules/golang.org/x/net-0.23.0
Bump golang.org/x/net from 0.10.0 to 0.23.0
2024-06-13 23:07:27 +08:00
ginuerzh
654791e635
Merge branch 'master' into dependabot/go_modules/golang.org/x/net-0.23.0 2024-06-13 23:07:11 +08:00
ginuerzh
ac1e8968d3
Merge pull request #1030 from ge9/master
use outgoing interface's IP for UDP relay port
2024-06-13 23:04:54 +08:00
ginuerzh
ee07120254
Merge pull request #1014 from ChengDaqi2023/oscs_fix_cnbkq28au51oj0c3u3o0
fix(sec): upgrade github.com/quic-go/quic-go to 0.40.1
2024-06-13 23:02:27 +08:00
ginuerzh
32b79a37fa
Merge branch 'master' into oscs_fix_cnbkq28au51oj0c3u3o0 2024-06-13 23:02:17 +08:00
ginuerzh
44aac5a5d9
Merge pull request #983 from honwen/master
bump deps; bump sdk to 1.21
2024-06-13 22:58:39 +08:00
ginuerzh
17685b4eac
Merge branch 'master' into master 2024-06-13 22:55:58 +08:00
tiancheng91
77262f2454 fix: concurrent map access 2024-06-13 22:49:50 +08:00
tiancheng91
94f812b026 fix: ping result ttl random 2024-06-13 22:49:50 +08:00
tiancheng91
2faecc1be8 feat: support node sort by tcp ping latency 2024-06-13 22:49:50 +08:00
ge9
00c36ab83d use outgoing interface's IP for UDP relay port 2024-06-13 11:00:47 +09:00
dependabot[bot]
cfe4abe4f9
Bump golang.org/x/net from 0.10.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 11:42:07 +00:00
dependabot[bot]
45ebea56ce
Bump github.com/quic-go/quic-go from 0.32.0 to 0.42.0
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.32.0 to 0.42.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.32.0...v0.42.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 14:16:42 +00:00
ChengDaqi2023
5e6f5c3df2 update github.com/quic-go/quic-go v0.32.0 to 0.40.1 2024-02-22 21:33:03 +08:00
honwen.chan
0056fd6560 bump deps; bump sdk to 1.21 2024-02-14 23:08:24 +08:00
guoguangwu
fd57e80709 chore: remove refs to deprecated io/ioutil 2024-02-02 17:22:04 +08:00
zzq
7e1af1b557 fix 2024-02-02 17:20:25 +08:00
suguds
13b9748c9c update golang.org/x/crypto v0.5.0 to 0.17.0 2024-02-02 17:20:02 +08:00
Jeffrey Zhang
7a2490134a support vsock 2023-10-10 19:27:13 +08:00
guoguangwu
c5d9bc8907 chore: remove refs to deprecated io/ioutil 2023-06-19 19:27:03 +08:00
dependabot[bot]
1c62376e08 Bump golang.org/x/net from 0.5.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.5.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-07 23:52:55 +08:00
ginuerzh
0f7376bd10 go1.20 2023-02-04 18:37:14 +08:00
ginuerzh
8a29ddabea add goreleaser github action 2023-01-31 21:33:14 +08:00
ginuerzh
729d0e7000 v2.11.5 2023-01-20 14:43:13 +08:00
koaiwu
b7beb1729e toSocksAddr supports IPv6 2023-01-19 14:46:23 +08:00
koaiwu
847ee05fd4 parseIP supports IPv6 2023-01-19 14:46:23 +08:00
Anton Tolchanov
c07cdeff92 Do not exit the server loop on obfs4 connection errors
obfs4Listener.Accept() returns an error when obfs4 handshake fails, which in
practice happens routinely when someone scans a machine that has an open
listening port.

Currently the accept loop in server.go exits on first such error, which makes
further connections to the same port impossible.

This change wraps obfs4 handshake errors into a custom error type that
satisfies net.Error and presents itself as temporary, which will prevent such
errors from aborting the server.
2023-01-19 14:43:10 +08:00
ginuerzh
005cff5888 #889: add http tunnel mode for HTTP handler 2022-11-16 15:21:01 +08:00
ginuerzh
aa8312a902 fix issue #883 2022-11-16 14:30:42 +08:00
ginuerzh
b0bb26fc95 v2.11.4 2022-09-03 20:51:23 +08:00
ginuerzh
97dda762e0 fix #870 2022-08-31 17:52:52 +08:00
ginuerzh
81854a61e0 fix snapcraft 2022-08-19 22:30:16 +08:00
ginuerzh
910d58c8ab fix snap 2022-08-19 12:08:17 +08:00
ginuerzh
ac554f670c add proxyAgent options for http/http2 handler 2022-08-18 20:03:03 +08:00
ginuerzh
b9e61dca1a go1.19 2022-08-18 18:21:21 +08:00
Costin Manolache
3322613d3c Wait for the second copy error 2022-08-18 15:32:51 +08:00
PPPPP
7485f9a753 Update go.sum 2022-08-18 13:10:09 +08:00
PPPPP
6129f5e940 Update go.mod 2022-08-18 13:10:09 +08:00
Kebin Liu
a263a9f173 Add older style build comment
To compatible with older Go version on some cloud environment , such as Google App Engine using 1.16
2022-08-18 13:09:38 +08:00
ginuerzh
0247b941ac fix resolver fallback 2022-05-01 20:49:33 +08:00
ginuerzh
31aee78e79 fix issues #820 and #821 2022-05-01 20:15:57 +08:00
IndexDoge
f94293b454 feat: add bind interface
fix: clone route without interface and mark config
2022-04-19 20:02:16 +08:00
ginuerzh
45340b2845 update kcp 2022-04-13 21:06:29 +08:00
ginuerzh
74659324c8 update snapcraft build 2022-04-13 17:09:01 +08:00
ginuerzh
cc87118242 update README_en.md 2022-04-07 23:13:52 +08:00
ginuerzh
3df387579c update README.md 2022-04-07 22:51:42 +08:00
ginuerzh
ca632e8909 fix SO_MARK on non-linux OS 2022-04-07 22:43:12 +08:00
ginuerzh
bbeaafc897 update version 2022-04-07 22:28:32 +08:00
p_caiwfeng
ffecc464fe surround interface name with double quote in case of name have space 2022-04-07 22:08:00 +08:00
purerosefallen
11d4838804 remote dups 2022-04-07 22:07:20 +08:00
purerosefallen
79b086df90 add mark option 2022-04-07 22:07:20 +08:00
hulb
40ccfecb36 update quick-go dep to support golang-1.18 2022-04-05 17:49:36 +08:00
openwrt2223
937b27dd95 update:update package 2021-12-16 22:38:27 +08:00
sleshep
27dec2d2ac update: ugprade quic-go==v0.24.0 2021-11-23 07:45:12 -08:00
naison
8f08304b75 ignore linux error: File exists while add same route twice 2021-08-22 22:57:00 +08:00
guqing637
a4695ece2d Update go.mod and go.sum 2021-03-24 15:04:00 +08:00
guqing637
fc971d7f2d Update dependency ! 2021-03-20 17:41:23 +08:00
Guangming Li
8dd4d8d9a1 fix obfs-tls with shadowsocks aead issue #695 2021-02-06 13:13:40 +08:00
Hubix9
e16ac5a58a Add whitelist/blacklist support for relay 2021-02-06 13:10:58 +08:00
ginuerzh
4712d6c9dd
Merge pull request #689 from btwiuse/master
bump github.com/milosgajdos/tenus to v0.0.3
2021-02-06 13:09:14 +08:00
navigaid
5d69ecf203 bump github.com/milosgajdos/tenus to v0.0.3 2021-01-04 20:17:27 +08:00
Soff
1a56a878ac Add darwin-arm64 for Apple M1 devices 2020-12-07 07:42:31 +08:00
proxy666-dev
b5d8d44c19 fix issue https://github.com/ginuerzh/gost/issues/617 2020-12-06 18:22:53 +08:00
Yang.Liu
3ea5708819 Fix: relay udp forward 2020-11-18 21:43:50 +08:00
Meng Zhuo
1c32df37bb Close connection if authentication failed
libcurl and some proxy application will keep sending authentication in
same connection if auth failed which is not supported.

fixes #583
2020-11-18 21:42:38 +08:00
luyuhuang
fd079dd066 certificate pinning for servers without domain 2020-11-18 21:40:14 +08:00
ginuerzh
f7995ab564
Merge pull request #655 from moonfruit/homebrew
Add installation instructions for Homebrew
2020-10-20 23:29:42 +08:00
MoonFruit
b9a965f4c1
Add installation instructions for Homebrew 2020-10-20 14:43:15 +08:00
ginuerzh
2707a8f0a9 v2.11.1 2020-05-23 21:26:33 +08:00
ginuerzh
d11f824858 update go.mod 2020-05-23 21:19:29 +08:00
ginuerzh
b285bcd6ce support mutual TLS authentication 2020-05-23 21:07:37 +08:00
ginuerzh
60d7e01164 update go.mod 2020-05-23 13:25:09 +08:00
ginuerzh
de43d87af3 rm shadowstream used in tuntap 2020-05-23 11:19:22 +08:00
ginuerzh
2c0eb8df9a fix issue #520 2020-05-23 11:14:49 +08:00
Scott Edlund
6e46ac03c7 Build go binaries without QEMU 2020-04-14 21:43:16 +08:00
hwchan
f0c9079f0a sni: work with non-ssl 2020-04-14 21:31:25 +08:00
Raymond Liu
8ab2fe6f77 Print version info to Stdout instead of Stderr
Otherwise we can't get the right version information by this:
```shell
gost -V | cut -d' ' -f2
```
2020-03-20 21:31:41 +08:00
ginuerzh
d474a0c417 update workflow 2020-03-14 15:05:06 +08:00
ginuerzh
9f1f492b3c add workflow for github actions 2020-03-14 14:21:23 +08:00
ginuerzh
2c0e01a5dd v2.11.0 2020-03-03 19:42:38 +08:00
ginuerzh
2e0aa67faf fix typo 2020-03-03 18:58:28 +08:00
ginuerzh
ec5052e55f obfs: tls max data length limitation 2020-03-03 18:56:43 +08:00
ginuerzh
c1bac99a5d update shadowsocks package 2020-03-02 19:43:17 +08:00
ginuerzh
3a63210845 fix #352: add pubkey auth support for ssh 2020-03-02 19:42:27 +08:00
ginuerzh
0f8064470f update relay package 2020-03-01 20:25:26 +08:00
ginuerzh
b015ac660a fix buffer size 2020-03-01 11:03:42 +08:00
ginuerzh
d8af58cec7 reduce buffer allocation 2020-02-29 15:45:58 +08:00
ginuerzh
b2d5319d68 remove log 2020-02-28 20:11:34 +08:00
ginuerzh
ee8b5d572c fix type assert 2020-02-28 19:28:33 +08:00
ginuerzh
e203d7760e fix #501: update pkg golang.org/x/crypto 2020-02-28 19:24:47 +08:00
ginuerzh
1587c679e7 add relay proxy protocol 2020-02-27 14:56:48 +08:00
ginuerzh
a99ff4aa15 add otls 2020-02-24 14:53:51 +08:00
ginuerzh
c7568170ac v2.10.1 2020-02-09 19:16:11 +08:00
ginuerzh
3142a4283e update README 2020-02-09 14:48:41 +08:00
ginuerzh
cbc9c1f77e dns: add edns0 subnet option support 2020-02-09 13:46:37 +08:00
ginuerzh
8121e20cbd update func calls 2020-02-09 10:32:34 +08:00
ginuerzh
ece79946b3 fix tests 2020-02-08 19:08:33 +08:00
ginuerzh
94dcfcab8c rudp: fix panic 2020-02-08 17:27:30 +08:00
ginuerzh
abe4043413 add chain.DialContext 2020-02-08 15:02:04 +08:00
ginuerzh
425099a7ba merge ss2 and ss 2020-02-03 22:18:44 +08:00
ginuerzh
18a515a5eb fix tun for darwin 2020-02-03 21:05:42 +08:00
ginuerzh
be1f050250 fix redirect on non linux OS 2020-02-02 16:09:16 +08:00
ginuerzh
0de7b8fb0b v2.10.0 2020-02-02 15:33:26 +08:00
ginuerzh
bb4a7411ef fix test cases 2020-02-02 15:33:26 +08:00
ginuerzh
4b856214f7 update udp transparent proxy 2020-02-02 15:32:49 +08:00
ginuerzh
bd9fc76466 add udp transparent proxy 2020-02-02 15:32:49 +08:00
ginuerzh
6ce3639c02 add ssu connector 2020-02-02 15:32:49 +08:00
ginuerzh
4133cf30b4 add chain support for resolver 2020-02-02 15:31:32 +08:00
ginuerzh
f1bad4d07b close resolver connection 2020-02-02 15:31:32 +08:00
ginuerzh
694c05b50a add dot & doh 2020-02-02 15:31:32 +08:00
ginuerzh
99b141e5be add cache for dns 2020-02-02 15:31:32 +08:00
ginuerzh
8ec3d8cbcf add dns proxy server 2020-02-02 15:29:05 +08:00
ginuerzh
ae36ad0fdc update snapcraft.yaml 2020-01-22 13:54:25 +08:00
ginuerzh
bb1bba3126 v2.9.2 2020-01-22 13:52:14 +08:00
ginuerzh
56e2ac3566 add auth parameter (#467) 2020-01-22 12:25:42 +08:00
ginuerzh
990c7b56c3 tap: make net parameter optional (#472) 2020-01-22 10:39:23 +08:00
ginuerzh
08d2f7c516 add default gateway for routes 2020-01-21 20:47:03 +08:00
ginuerzh
a782cf5cf8 fix parseIPRoutes 2020-01-21 20:34:27 +08:00
ginuerzh
e16427c6c8 add gateway per route for tun server side 2020-01-21 19:32:23 +08:00
67 changed files with 5162 additions and 2604 deletions

72
.github/workflows/buildx.yml vendored Normal file
View File

@ -0,0 +1,72 @@
# ref: https://docs.docker.com/ci-cd/github-actions/
# https://blog.oddbit.com/post/2020-09-25-building-multi-architecture-im/
name: docker
on:
push:
branches:
- master
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Prepare
id: prepare
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_IMAGE }}
VERSION=latest
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
MAJOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[1]}'`
MINOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[2]}'`
TAGS="$TAGS,${DOCKER_IMAGE}:${MAJOR_VERSION},${DOCKER_IMAGE}:${MAJOR_VERSION}.${MINOR_VERSION},${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "docker_image=${DOCKER_IMAGE}" >> $GITHUB_OUTPUT
echo "docker_platforms=linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/s390x,linux/riscv64" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Environment
run: |
echo home=$HOME
echo git_ref=$GITHUB_REF
echo git_sha=$GITHUB_SHA
echo image=${{ steps.prepare.outputs.docker_image }}
echo tags=${{ steps.prepare.outputs.tags }}
echo platforms=${{ steps.prepare.outputs.docker_platforms }}
echo avail_platforms=${{ steps.buildx.outputs.platforms }}
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Buildx and push
uses: docker/build-push-action@v6
with:
platforms: ${{ steps.prepare.outputs.docker_platforms }}
push: true
tags: ${{ steps.prepare.outputs.tags }}

38
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: goreleaser
on:
push:
# run only against tags
tags:
- 'v*'
permissions:
contents: write
# packages: write
# issues: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v3
with:
go-version: '1.22'
cache: true
# More assembly might be required: Docker logins, GPG, etc. It all depends
# on your needs.
- uses: goreleaser/goreleaser-action@v4
with:
# either 'goreleaser' (default) or 'goreleaser-pro':
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro'
# distribution:
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}

3
.gitignore vendored
View File

@ -9,6 +9,7 @@ _test
release release
debian debian
bin bin
dist/
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]
@ -30,5 +31,5 @@ _testmain.go
*.bak *.bak
.vscode/
cmd/gost/gost cmd/gost/gost
snap

58
.goreleaser.yaml Normal file
View File

@ -0,0 +1,58 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
# - go generate ./...
builds:
- env:
- CGO_ENABLED=0
main: ./cmd/gost
targets:
- darwin_amd64
- darwin_arm64
- linux_386
- linux_amd64
- linux_amd64_v3
- linux_arm_5
- linux_arm_6
- linux_arm_7
- linux_arm64
- linux_mips_softfloat
- linux_mips_hardfloat
- linux_mipsle_softfloat
- linux_mipsle_hardfloat
- linux_mips64
- linux_mips64le
- linux_s390x
- linux_riscv64
- freebsd_386
- freebsd_amd64
- windows_386
- windows_amd64
- windows_amd64_v3
- windows_arm64
archives:
- format: tar.gz
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
# The lines beneath this are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

View File

@ -1,19 +1,32 @@
FROM golang:1-alpine as builder FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
RUN apk add --no-cache musl-dev git gcc FROM --platform=$BUILDPLATFORM golang:1.23-alpine3.20 AS builder
ADD . /src COPY --from=xx / /
WORKDIR /src ARG TARGETPLATFORM
ENV GO111MODULE=on RUN xx-info env
RUN cd cmd/gost && go build ENV CGO_ENABLED=0
FROM alpine:latest ENV XX_VERIFY_STATIC=1
WORKDIR /app
COPY . .
RUN cd cmd/gost && \
xx-go build && \
xx-verify gost
FROM alpine:3.20
# add iptables for tun/tap
RUN apk add --no-cache iptables
WORKDIR /bin/ WORKDIR /bin/
COPY --from=builder /src/cmd/gost/gost . COPY --from=builder /app/cmd/gost/gost .
ENTRYPOINT ["/bin/gost"] ENTRYPOINT ["/bin/gost"]

View File

@ -2,10 +2,11 @@ NAME=gost
BINDIR=bin BINDIR=bin
VERSION=$(shell cat gost.go | grep 'Version =' | sed 's/.*\"\(.*\)\".*/\1/g') VERSION=$(shell cat gost.go | grep 'Version =' | sed 's/.*\"\(.*\)\".*/\1/g')
GOBUILD=CGO_ENABLED=0 go build --ldflags="-s -w" -v -x -a GOBUILD=CGO_ENABLED=0 go build --ldflags="-s -w" -v -x -a
GOFILES=cmd/gost/* GOFILES=cmd/gost/*.go
PLATFORM_LIST = \ PLATFORM_LIST = \
darwin-amd64 \ darwin-amd64 \
darwin-arm64 \
linux-386 \ linux-386 \
linux-amd64 \ linux-amd64 \
linux-armv5 \ linux-armv5 \
@ -18,18 +19,24 @@ PLATFORM_LIST = \
linux-mipsle-hardfloat \ linux-mipsle-hardfloat \
linux-mips64 \ linux-mips64 \
linux-mips64le \ linux-mips64le \
linux-s390x \
linux-riscv64 \
freebsd-386 \ freebsd-386 \
freebsd-amd64 freebsd-amd64
WINDOWS_ARCH_LIST = \ WINDOWS_ARCH_LIST = \
windows-386 \ windows-386 \
windows-amd64 windows-amd64 \
windows-arm64
all: linux-amd64 darwin-amd64 windows-amd64 # Most used all: linux-amd64 darwin-amd64 windows-amd64 # Most used
darwin-amd64: darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
linux-386: linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
@ -66,6 +73,12 @@ linux-mips64:
linux-mips64le: linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
linux-s390x:
GOARCH=s390x GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
linux-riscv64:
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
freebsd-386: freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES) GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
@ -78,6 +91,9 @@ windows-386:
windows-amd64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES) GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))

View File

@ -1,39 +1,40 @@
gost - GO Simple Tunnel GO Simple Tunnel
====== ======
### GO语言实现的安全隧道 ### GO语言实现的安全隧道
[![GoDoc](https://godoc.org/github.com/ginuerzh/gost?status.svg)](https://godoc.org/github.com/ginuerzh/gost) [![GoDoc](https://godoc.org/github.com/ginuerzh/gost?status.svg)](https://godoc.org/github.com/ginuerzh/gost)
[![Build Status](https://travis-ci.org/ginuerzh/gost.svg?branch=master)](https://travis-ci.org/ginuerzh/gost)
[![Go Report Card](https://goreportcard.com/badge/github.com/ginuerzh/gost)](https://goreportcard.com/report/github.com/ginuerzh/gost) [![Go Report Card](https://goreportcard.com/badge/github.com/ginuerzh/gost)](https://goreportcard.com/report/github.com/ginuerzh/gost)
[![codecov](https://codecov.io/gh/ginuerzh/gost/branch/master/graphs/badge.svg)](https://codecov.io/gh/ginuerzh/gost/branch/master) [![codecov](https://codecov.io/gh/ginuerzh/gost/branch/master/graphs/badge.svg)](https://codecov.io/gh/ginuerzh/gost/branch/master)
[![GitHub release](https://img.shields.io/github/release/ginuerzh/gost.svg)](https://github.com/ginuerzh/gost/releases/latest) [![GitHub release](https://img.shields.io/github/release/ginuerzh/gost.svg)](https://github.com/ginuerzh/gost/releases/latest)
[![Snap Status](https://build.snapcraft.io/badge/ginuerzh/gost.svg)](https://build.snapcraft.io/user/ginuerzh/gost) [![Docker](https://img.shields.io/docker/pulls/ginuerzh/gost.svg)](https://hub.docker.com/r/ginuerzh/gost/)
[![Docker Build Status](https://img.shields.io/docker/build/ginuerzh/gost.svg)](https://hub.docker.com/r/ginuerzh/gost/) [![gost](https://snapcraft.io/gost/badge.svg)](https://snapcraft.io/gost)
[English README](README_en.md) [English README](README_en.md)
### [V3版本已经可用欢迎抢先体验](https://latest.gost.run)
特性 特性
------ ------
* 多端口监听 * 多端口监听
* 可设置转发代理,支持多级转发(代理链) * 可设置转发代理,支持多级转发(代理链)
* 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议 * 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议
* Web代理支持[探测防御](https://docs.ginuerzh.xyz/gost/probe_resist/) * Web代理支持[探测防御](https://v2.gost.run/probe_resist/)
* [支持多种隧道类型](https://docs.ginuerzh.xyz/gost/configuration/) * [支持多种隧道类型](https://v2.gost.run/configuration/)
* [SOCKS5代理支持TLS协商加密](https://docs.ginuerzh.xyz/gost/socks/) * [SOCKS5代理支持TLS协商加密](https://v2.gost.run/socks/)
* [Tunnel UDP over TCP](https://docs.ginuerzh.xyz/gost/socks/) * [Tunnel UDP over TCP](https://v2.gost.run/socks/)
* [TCP透明代理](https://docs.ginuerzh.xyz/gost/redirect/) * [TCP/UDP透明代理](https://v2.gost.run/redirect/)
* [本地/远程TCP/UDP端口转发](https://docs.ginuerzh.xyz/gost/port-forwarding/) * [本地/远程TCP/UDP端口转发](https://v2.gost.run/port-forwarding/)
* [支持Shadowsocks(TCP/UDP)协议](https://docs.ginuerzh.xyz/gost/ss/) * [支持Shadowsocks(TCP/UDP)协议](https://v2.gost.run/ss/)
* [支持SNI代理](https://docs.ginuerzh.xyz/gost/sni/) * [支持SNI代理](https://v2.gost.run/sni/)
* [权限控制](https://docs.ginuerzh.xyz/gost/permission/) * [权限控制](https://v2.gost.run/permission/)
* [负载均衡](https://docs.ginuerzh.xyz/gost/load-balancing/) * [负载均衡](https://v2.gost.run/load-balancing/)
* [路由控制](https://docs.ginuerzh.xyz/gost/bypass/) * [路由控制](https://v2.gost.run/bypass/)
* [DNS控制](https://docs.ginuerzh.xyz/gost/dns/) * DNS[解析](https://v2.gost.run/resolver/)和[代理](https://v2.gost.run/dns/)
* [TUN/TAP设备](https://docs.ginuerzh.xyz/gost/tuntap/) * [TUN/TAP设备](https://v2.gost.run/tuntap/)
Wiki站点: <https://docs.ginuerzh.xyz/gost/> Wiki站点: [v2.gost.run](https://v2.gost.run)
Telegram讨论群: <https://t.me/gogost> Telegram讨论群: <https://t.me/gogost>
@ -49,18 +50,28 @@ Google讨论组: <https://groups.google.com/d/forum/go-gost>
#### 源码编译 #### 源码编译
```bash ```bash
go get -u github.com/ginuerzh/gost/cmd/gost git clone https://github.com/ginuerzh/gost.git
cd gost/cmd/gost
go build
``` ```
#### Docker #### Docker
```bash ```bash
docker pull ginuerzh/gost docker run --rm ginuerzh/gost -V
```
#### Homebrew
```bash
brew install gost
``` ```
#### Ubuntu商店 #### Ubuntu商店
```bash ```bash
sudo snap install core
sudo snap install gost sudo snap install gost
``` ```
@ -184,7 +195,7 @@ gost -L=:8080 -F=h2://server_ip:443
``` ```
#### QUIC #### QUIC
gost对QUIC的支持是基于[quic-go](https://github.com/lucas-clemente/quic-go)库。 gost对QUIC的支持是基于[quic-go](https://github.com/quic-go/quic-go)库。
服务端: 服务端:
```bash ```bash

View File

@ -4,33 +4,32 @@ gost - GO Simple Tunnel
### A simple security tunnel written in Golang ### A simple security tunnel written in Golang
[![GoDoc](https://godoc.org/github.com/ginuerzh/gost?status.svg)](https://godoc.org/github.com/ginuerzh/gost) [![GoDoc](https://godoc.org/github.com/ginuerzh/gost?status.svg)](https://godoc.org/github.com/ginuerzh/gost)
[![Build Status](https://travis-ci.org/ginuerzh/gost.svg?branch=master)](https://travis-ci.org/ginuerzh/gost)
[![Go Report Card](https://goreportcard.com/badge/github.com/ginuerzh/gost)](https://goreportcard.com/report/github.com/ginuerzh/gost) [![Go Report Card](https://goreportcard.com/badge/github.com/ginuerzh/gost)](https://goreportcard.com/report/github.com/ginuerzh/gost)
[![codecov](https://codecov.io/gh/ginuerzh/gost/branch/master/graphs/badge.svg)](https://codecov.io/gh/ginuerzh/gost/branch/master) [![codecov](https://codecov.io/gh/ginuerzh/gost/branch/master/graphs/badge.svg)](https://codecov.io/gh/ginuerzh/gost/branch/master)
[![GitHub release](https://img.shields.io/github/release/ginuerzh/gost.svg)](https://github.com/ginuerzh/gost/releases/latest) [![GitHub release](https://img.shields.io/github/release/ginuerzh/gost.svg)](https://github.com/ginuerzh/gost/releases/latest)
[![Snap Status](https://build.snapcraft.io/badge/ginuerzh/gost.svg)](https://build.snapcraft.io/user/ginuerzh/gost) [![Docker](https://img.shields.io/docker/pulls/ginuerzh/gost.svg)](https://hub.docker.com/r/ginuerzh/gost/)
[![Docker Build Status](https://img.shields.io/docker/build/ginuerzh/gost.svg)](https://hub.docker.com/r/ginuerzh/gost/) [![gost](https://snapcraft.io/gost/badge.svg)](https://snapcraft.io/gost)
Features Features
------ ------
* Listening on multiple ports * Listening on multiple ports
* Multi-level forward proxy - proxy chain * Multi-level forward proxy - proxy chain
* Standard HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5 proxy protocols support * Standard HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5 proxy protocols support
* [Probing resistance](https://docs.ginuerzh.xyz/gost/en/probe_resist/) support for web proxy * [Probing resistance](https://v2.gost.run/en/probe_resist/) support for web proxy
* [Support multiple tunnel types](https://docs.ginuerzh.xyz/gost/en/configuration/) * [Support multiple tunnel types](https://v2.gost.run/en/configuration/)
* [TLS encryption via negotiation support for SOCKS5 proxy](https://docs.ginuerzh.xyz/gost/en/socks/) * [TLS encryption via negotiation support for SOCKS5 proxy](https://v2.gost.run/en/socks/)
* [Tunnel UDP over TCP](https://docs.ginuerzh.xyz/gost/en/socks/) * [Tunnel UDP over TCP](https://v2.gost.run/en/socks/)
* [Transparent TCP proxy](https://docs.ginuerzh.xyz/gost/en/redirect/) * [TCP/UDP Transparent proxy](https://v2.gost.run/en/redirect/)
* [Local/remote TCP/UDP port forwarding](https://docs.ginuerzh.xyz/gost/en/port-forwarding/) * [Local/remote TCP/UDP port forwarding](https://v2.gost.run/en/port-forwarding/)
* [Shadowsocks protocol](https://docs.ginuerzh.xyz/gost/en/ss/) * [Shadowsocks protocol](https://v2.gost.run/en/ss/)
* [SNI proxy](https://docs.ginuerzh.xyz/gost/en/sni/) * [SNI proxy](https://v2.gost.run/en/sni/)
* [Permission control](https://docs.ginuerzh.xyz/gost/en/permission/) * [Permission control](https://v2.gost.run/en/permission/)
* [Load balancing](https://docs.ginuerzh.xyz/gost/en/load-balancing/) * [Load balancing](https://v2.gost.run/en/load-balancing/)
* [Routing control](https://docs.ginuerzh.xyz/gost/en/bypass/) * [Routing control](https://v2.gost.run/en/bypass/)
* [DNS control](https://docs.ginuerzh.xyz/gost/en/dns/) * DNS [resolver](https://v2.gost.run/resolver/) and [proxy](https://v2.gost.run/dns/)
* [TUN/TAP device](https://docs.ginuerzh.xyz/gost/en/tuntap/) * [TUN/TAP device](https://v2.gost.run/en/tuntap/)
Wiki: <https://docs.ginuerzh.xyz/gost/en/> Wiki: [v2.gost.run](https://v2.gost.run/en/)
Telegram group: <https://t.me/gogost> Telegram group: <https://t.me/gogost>
@ -46,18 +45,27 @@ Installation
#### From source #### From source
```bash ```bash
go get -u github.com/ginuerzh/gost/cmd/gost git clone https://github.com/ginuerzh/gost.git
cd gost/cmd/gost
go build
``` ```
#### Docker #### Docker
```bash ```bash
docker pull ginuerzh/gost docker run --rm ginuerzh/gost -V
```
#### Homebrew
```bash
brew install gost
``` ```
#### Ubuntu store #### Ubuntu store
```bash ```bash
sudo snap install core
sudo snap install gost sudo snap install gost
``` ```
@ -210,7 +218,7 @@ gost -L=:8080 -F=h2://server_ip:443
#### QUIC #### QUIC
Support for QUIC is based on library [quic-go](https://github.com/lucas-clemente/quic-go). Support for QUIC is based on library [quic-go](https://github.com/quic-go/quic-go).
Server: Server:

119
chain.go
View File

@ -1,8 +1,11 @@
package gost package gost
import ( import (
"context"
"errors" "errors"
"fmt"
"net" "net"
"syscall"
"time" "time"
"github.com/go-log/log" "github.com/go-log/log"
@ -17,6 +20,8 @@ var (
type Chain struct { type Chain struct {
isRoute bool isRoute bool
Retries int Retries int
Mark int
Interface string
nodeGroups []*NodeGroup nodeGroups []*NodeGroup
route []Node // nodes in the selected route route []Node // nodes in the selected route
} }
@ -33,10 +38,14 @@ func NewChain(nodes ...Node) *Chain {
// newRoute creates a chain route. // newRoute creates a chain route.
// a chain route is the final route after node selection. // a chain route is the final route after node selection.
func newRoute(nodes ...Node) *Chain { func (c *Chain) newRoute(nodes ...Node) *Chain {
chain := NewChain(nodes...) route := NewChain(nodes...)
chain.isRoute = true route.isRoute = true
return chain if !c.IsEmpty() {
route.Interface = c.Interface
route.Mark = c.Mark
}
return route
} }
// Nodes returns the proxy nodes that the chain holds. // Nodes returns the proxy nodes that the chain holds.
@ -100,9 +109,14 @@ func (c *Chain) IsEmpty() bool {
return c == nil || len(c.nodeGroups) == 0 return c == nil || len(c.nodeGroups) == 0
} }
// Dial connects to the target address addr through the chain. // Dial connects to the target TCP address addr through the chain.
// If the chain is empty, it will use the net.Dial directly. // Deprecated: use DialContext instead.
func (c *Chain) Dial(addr string, opts ...ChainOption) (conn net.Conn, err error) { func (c *Chain) Dial(address string, opts ...ChainOption) (conn net.Conn, err error) {
return c.DialContext(context.Background(), "tcp", address, opts...)
}
// DialContext connects to the address on the named network using the provided context.
func (c *Chain) DialContext(ctx context.Context, network, address string, opts ...ChainOption) (conn net.Conn, err error) {
options := &ChainOptions{} options := &ChainOptions{}
for _, opt := range opts { for _, opt := range opts {
opt(options) opt(options)
@ -117,7 +131,7 @@ func (c *Chain) Dial(addr string, opts ...ChainOption) (conn net.Conn, err error
} }
for i := 0; i < retries; i++ { for i := 0; i < retries; i++ {
conn, err = c.dialWithOptions(addr, options) conn, err = c.dialWithOptions(ctx, network, address, options)
if err == nil { if err == nil {
break break
} }
@ -125,33 +139,80 @@ func (c *Chain) Dial(addr string, opts ...ChainOption) (conn net.Conn, err error
return return
} }
func (c *Chain) dialWithOptions(addr string, options *ChainOptions) (net.Conn, error) { func (c *Chain) dialWithOptions(ctx context.Context, network, address string, options *ChainOptions) (net.Conn, error) {
if options == nil { if options == nil {
options = &ChainOptions{} options = &ChainOptions{}
} }
route, err := c.selectRouteFor(addr) if c == nil {
c = &Chain{}
}
route, err := c.selectRouteFor(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ipAddr := c.resolve(addr, options.Resolver, options.Hosts) ipAddr := address
if address != "" {
ipAddr = c.resolve(address, options.Resolver, options.Hosts)
if ipAddr == "" {
return nil, fmt.Errorf("resolver: domain %s does not exists", address)
}
}
timeout := options.Timeout timeout := options.Timeout
if timeout <= 0 { if timeout <= 0 {
timeout = DialTimeout timeout = DialTimeout
} }
if route.IsEmpty() { var controlFunction func(_ string, _ string, c syscall.RawConn) error = nil
return net.DialTimeout("tcp", ipAddr, timeout) if c.Mark > 0 {
controlFunction = func(_, _ string, cc syscall.RawConn) error {
return cc.Control(func(fd uintptr) {
ex := setSocketMark(int(fd), c.Mark)
if ex != nil {
log.Logf("net dialer set mark %d error: %s", c.Mark, ex)
} else {
// log.Logf("net dialer set mark %d success", options.Mark)
}
})
}
} }
conn, err := route.getConn() if c.Interface != "" {
controlFunction = func(_, _ string, cc syscall.RawConn) error {
return cc.Control(func(fd uintptr) {
err := setSocketInterface(int(fd), c.Interface)
if err != nil {
log.Logf("net dialer set interface %s error: %s", c.Interface, err)
}
})
}
}
if route.IsEmpty() {
switch network {
case "udp", "udp4", "udp6":
if address == "" {
return net.ListenUDP(network, nil)
}
default:
}
d := &net.Dialer{
Timeout: timeout,
Control: controlFunction,
// LocalAddr: laddr, // TODO: optional local address
}
return d.DialContext(ctx, network, ipAddr)
}
conn, err := route.getConn(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cOpts := append([]ConnectOption{AddrConnectOption(addr)}, route.LastNode().ConnectOptions...) cOpts := append([]ConnectOption{AddrConnectOption(address)}, route.LastNode().ConnectOptions...)
cc, err := route.LastNode().Client.Connect(conn, ipAddr, cOpts...) cc, err := route.LastNode().Client.ConnectContext(ctx, conn, network, ipAddr, cOpts...)
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, err return nil, err
@ -173,9 +234,11 @@ func (*Chain) resolve(addr string, resolver Resolver, hosts *Hosts) string {
if err != nil { if err != nil {
log.Logf("[resolver] %s: %v", host, err) log.Logf("[resolver] %s: %v", host, err)
} }
if len(ips) > 0 { if len(ips) == 0 {
return net.JoinHostPort(ips[0].String(), port) log.Logf("[resolver] %s: domain does not exists", host)
return ""
} }
return net.JoinHostPort(ips[0].String(), port)
} }
return addr return addr
} }
@ -187,6 +250,8 @@ func (c *Chain) Conn(opts ...ChainOption) (conn net.Conn, err error) {
opt(options) opt(options)
} }
ctx := context.Background()
retries := 1 retries := 1
if c != nil && c.Retries > 0 { if c != nil && c.Retries > 0 {
retries = c.Retries retries = c.Retries
@ -201,7 +266,7 @@ func (c *Chain) Conn(opts ...ChainOption) (conn net.Conn, err error) {
if err != nil { if err != nil {
continue continue
} }
conn, err = route.getConn() conn, err = route.getConn(ctx)
if err == nil { if err == nil {
break break
} }
@ -210,7 +275,7 @@ func (c *Chain) Conn(opts ...ChainOption) (conn net.Conn, err error) {
} }
// getConn obtains a connection to the last node of the chain. // getConn obtains a connection to the last node of the chain.
func (c *Chain) getConn() (conn net.Conn, err error) { func (c *Chain) getConn(ctx context.Context) (conn net.Conn, err error) {
if c.IsEmpty() { if c.IsEmpty() {
err = ErrEmptyChain err = ErrEmptyChain
return return
@ -218,14 +283,15 @@ func (c *Chain) getConn() (conn net.Conn, err error) {
nodes := c.Nodes() nodes := c.Nodes()
node := nodes[0] node := nodes[0]
cn, err := node.Client.Dial(node.Addr, node.DialOptions...) cc, err := node.Client.Dial(node.Addr, node.DialOptions...)
if err != nil { if err != nil {
node.MarkDead() node.MarkDead()
return return
} }
cn, err = node.Client.Handshake(cn, node.HandshakeOptions...) cn, err := node.Client.Handshake(cc, node.HandshakeOptions...)
if err != nil { if err != nil {
cc.Close()
node.MarkDead() node.MarkDead()
return return
} }
@ -234,7 +300,7 @@ func (c *Chain) getConn() (conn net.Conn, err error) {
preNode := node preNode := node
for _, node := range nodes[1:] { for _, node := range nodes[1:] {
var cc net.Conn var cc net.Conn
cc, err = preNode.Client.Connect(cn, node.Addr, preNode.ConnectOptions...) cc, err = preNode.Client.ConnectContext(ctx, cn, "tcp", node.Addr, preNode.ConnectOptions...)
if err != nil { if err != nil {
cn.Close() cn.Close()
node.MarkDead() node.MarkDead()
@ -263,13 +329,13 @@ func (c *Chain) selectRoute() (route *Chain, err error) {
// selectRouteFor selects route with bypass testing. // selectRouteFor selects route with bypass testing.
func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) { func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) {
if c.IsEmpty() { if c.IsEmpty() {
return newRoute(), nil return c.newRoute(), nil
} }
if c.isRoute { if c.isRoute {
return c, nil return c, nil
} }
route = newRoute() route = c.newRoute()
var nl []Node var nl []Node
for _, group := range c.nodeGroups { for _, group := range c.nodeGroups {
@ -287,7 +353,7 @@ func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) {
node.DialOptions = append(node.DialOptions, node.DialOptions = append(node.DialOptions,
ChainDialOption(route), ChainDialOption(route),
) )
route = newRoute() // cutoff the chain for multiplex node. route = c.newRoute() // cutoff the chain for multiplex node.
} }
route.AddNode(node) route.AddNode(node)
@ -305,6 +371,7 @@ type ChainOptions struct {
Timeout time.Duration Timeout time.Duration
Hosts *Hosts Hosts *Hosts
Resolver Resolver Resolver Resolver
Mark int
} }
// ChainOption allows a common way to set chain options. // ChainOption allows a common way to set chain options.

139
client.go
View File

@ -1,12 +1,13 @@
package gost package gost
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
"net/url" "net/url"
"time" "time"
"github.com/ginuerzh/gosocks5" "github.com/go-gost/gosocks5"
) )
// Client is a proxy client. // Client is a proxy client.
@ -14,23 +15,8 @@ import (
// Connector is responsible for connecting to the destination address through this proxy. // Connector is responsible for connecting to the destination address through this proxy.
// Transporter performs a handshake with this proxy. // Transporter performs a handshake with this proxy.
type Client struct { type Client struct {
Connector Connector Connector
Transporter Transporter Transporter
}
// Dial connects to the target address.
func (c *Client) Dial(addr string, options ...DialOption) (net.Conn, error) {
return c.Transporter.Dial(addr, options...)
}
// Handshake performs a handshake with the proxy over connection conn.
func (c *Client) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return c.Transporter.Handshake(conn, options...)
}
// Connect connects to the address addr via the proxy over connection conn.
func (c *Client) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
return c.Connector.Connect(conn, addr, options...)
} }
// DefaultClient is a standard HTTP proxy client. // DefaultClient is a standard HTTP proxy client.
@ -53,7 +39,36 @@ func Connect(conn net.Conn, addr string) (net.Conn, error) {
// Connector is responsible for connecting to the destination address. // Connector is responsible for connecting to the destination address.
type Connector interface { type Connector interface {
Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) // Deprecated: use ConnectContext instead.
Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error)
ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error)
}
type autoConnector struct {
User *url.Userinfo
}
// AutoConnector is a Connector.
func AutoConnector(user *url.Userinfo) Connector {
return &autoConnector{
User: user,
}
}
func (c *autoConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *autoConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
var cnr Connector
switch network {
case "tcp", "tcp4", "tcp6":
cnr = &httpConnector{User: c.User}
default:
cnr = &socks5UDPTunConnector{User: c.User}
}
return cnr.ConnectContext(ctx, conn, network, address, options...)
} }
// Transporter is responsible for handshaking with the proxy server. // Transporter is responsible for handshaking with the proxy server.
@ -64,72 +79,11 @@ type Transporter interface {
Multiplex() bool Multiplex() bool
} }
// tcpTransporter is a raw TCP transporter.
type tcpTransporter struct{}
// TCPTransporter creates a raw TCP client.
func TCPTransporter() Transporter {
return &tcpTransporter{}
}
func (tr *tcpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
opts := &DialOptions{}
for _, option := range options {
option(opts)
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = DialTimeout
}
if opts.Chain == nil {
return net.DialTimeout("tcp", addr, timeout)
}
return opts.Chain.Dial(addr)
}
func (tr *tcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return conn, nil
}
func (tr *tcpTransporter) Multiplex() bool {
return false
}
// udpTransporter is a raw UDP transporter.
type udpTransporter struct{}
// UDPTransporter creates a raw UDP client.
func UDPTransporter() Transporter {
return &udpTransporter{}
}
func (tr *udpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
opts := &DialOptions{}
for _, option := range options {
option(opts)
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = DialTimeout
}
return net.DialTimeout("udp", addr, timeout)
}
func (tr *udpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return conn, nil
}
func (tr *udpTransporter) Multiplex() bool {
return false
}
// DialOptions describes the options for Transporter.Dial. // DialOptions describes the options for Transporter.Dial.
type DialOptions struct { type DialOptions struct {
Timeout time.Duration Timeout time.Duration
Chain *Chain Chain *Chain
Host string
} }
// DialOption allows a common way to set DialOptions. // DialOption allows a common way to set DialOptions.
@ -149,6 +103,13 @@ func ChainDialOption(chain *Chain) DialOption {
} }
} }
// HostDialOption specifies the host used by Transporter.Dial
func HostDialOption(host string) DialOption {
return func(opts *DialOptions) {
opts.Host = host
}
}
// HandshakeOptions describes the options for handshake. // HandshakeOptions describes the options for handshake.
type HandshakeOptions struct { type HandshakeOptions struct {
Addr string Addr string
@ -161,6 +122,7 @@ type HandshakeOptions struct {
WSOptions *WSOptions WSOptions *WSOptions
KCPConfig *KCPConfig KCPConfig *KCPConfig
QUICConfig *QUICConfig QUICConfig *QUICConfig
SSHConfig *SSHConfig
} }
// HandshakeOption allows a common way to set HandshakeOptions. // HandshakeOption allows a common way to set HandshakeOptions.
@ -236,6 +198,13 @@ func QUICConfigHandshakeOption(config *QUICConfig) HandshakeOption {
} }
} }
// SSHConfigHandshakeOption specifies the ssh config used by SSH client handshake.
func SSHConfigHandshakeOption(config *SSHConfig) HandshakeOption {
return func(opts *HandshakeOptions) {
opts.SSHConfig = config
}
}
// ConnectOptions describes the options for Connector.Connect. // ConnectOptions describes the options for Connector.Connect.
type ConnectOptions struct { type ConnectOptions struct {
Addr string Addr string
@ -244,6 +213,7 @@ type ConnectOptions struct {
Selector gosocks5.Selector Selector gosocks5.Selector
UserAgent string UserAgent string
NoTLS bool NoTLS bool
NoDelay bool
} }
// ConnectOption allows a common way to set ConnectOptions. // ConnectOption allows a common way to set ConnectOptions.
@ -290,3 +260,10 @@ func NoTLSConnectOption(b bool) ConnectOption {
opts.NoTLS = b opts.NoTLS = b
} }
} }
// NoDelayConnectOption specifies the NoDelay option for ss.Connect.
func NoDelayConnectOption(b bool) ConnectOption {
return func(opts *ConnectOptions) {
opts.NoDelay = b
}
}

View File

@ -5,8 +5,8 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"errors" "fmt"
"io/ioutil" "net"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@ -43,8 +43,9 @@ var (
defaultKeyFile = "key.pem" defaultKeyFile = "key.pem"
) )
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid. // Load the certificate from cert & key files and optional client CA file,
func tlsConfig(certFile, keyFile string) (*tls.Config, error) { // will use the default certificate if the provided info are invalid.
func tlsConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
if certFile == "" || keyFile == "" { if certFile == "" || keyFile == "" {
certFile, keyFile = defaultCertFile, defaultKeyFile certFile, keyFile = defaultCertFile, defaultKeyFile
} }
@ -53,7 +54,19 @@ func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
pool, err := loadCA(caFile)
if err != nil {
return nil, err
}
if pool != nil {
cfg.ClientCAs = pool
cfg.ClientAuth = tls.RequireAndVerifyClientCert
}
return cfg, nil
} }
func loadCA(caFile string) (cp *x509.CertPool, err error) { func loadCA(caFile string) (cp *x509.CertPool, err error) {
@ -61,12 +74,12 @@ func loadCA(caFile string) (cp *x509.CertPool, err error) {
return return
} }
cp = x509.NewCertPool() cp = x509.NewCertPool()
data, err := ioutil.ReadFile(caFile) data, err := os.ReadFile(caFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !cp.AppendCertsFromPEM(data) { if !cp.AppendCertsFromPEM(data) {
return nil, errors.New("AppendCertsFromPEM failed") return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile)
} }
return return
} }
@ -97,6 +110,7 @@ func parseUsers(authFile string) (users []*url.Userinfo, err error) {
if err != nil { if err != nil {
return return
} }
defer file.Close()
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := strings.TrimSpace(scanner.Text()) line := strings.TrimSpace(scanner.Text())
@ -142,33 +156,38 @@ func parseIP(s string, port string) (ips []string) {
port = "8080" // default port port = "8080" // default port
} }
addrFn := func(s, port string) string {
c := strings.Count(s, ":")
if c == 0 || //ipv4 or domain
s[len(s)-1] == ']' { //[ipv6]
return s + ":" + port
}
if c > 1 && s[0] != '[' { // ipv6
return "[" + s + "]:" + port
}
return s //ipv4:port or [ipv6]:port
}
file, err := os.Open(s) file, err := os.Open(s)
if err != nil { if err != nil {
ss := strings.Split(s, ",") ss := strings.Split(s, ",")
for _, s := range ss { for _, s := range ss {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
if s != "" { if s != "" {
// TODO: support IPv6 ips = append(ips, addrFn(s, port))
if !strings.Contains(s, ":") {
s = s + ":" + port
}
ips = append(ips, s)
} }
} }
return return
} }
defer file.Close()
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := strings.TrimSpace(scanner.Text()) line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") { if line == "" || strings.HasPrefix(line, "#") {
continue continue
} }
if !strings.Contains(line, ":") { ips = append(ips, addrFn(line, port))
line = line + ":" + port
}
ips = append(ips, line)
} }
return return
} }
@ -218,13 +237,19 @@ func parseResolver(cfg string) gost.Resolver {
continue continue
} }
if strings.HasPrefix(s, "https") { if strings.HasPrefix(s, "https") {
p := "https"
u, _ := url.Parse(s)
if u == nil || u.Scheme == "" {
continue
}
if u.Scheme == "https-chain" {
p = u.Scheme
}
ns := gost.NameServer{ ns := gost.NameServer{
Addr: s, Addr: s,
Protocol: "https", Protocol: p,
}
if err := ns.Init(); err == nil {
nss = append(nss, ns)
} }
nss = append(nss, ns)
continue continue
} }
@ -233,18 +258,14 @@ func parseResolver(cfg string) gost.Resolver {
ns := gost.NameServer{ ns := gost.NameServer{
Addr: ss[0], Addr: ss[0],
} }
if err := ns.Init(); err == nil { nss = append(nss, ns)
nss = append(nss, ns)
}
} }
if len(ss) == 2 { if len(ss) == 2 {
ns := gost.NameServer{ ns := gost.NameServer{
Addr: ss[0], Addr: ss[0],
Protocol: ss[1], Protocol: ss[1],
} }
if err := ns.Init(); err == nil { nss = append(nss, ns)
nss = append(nss, ns)
}
} }
} }
return gost.NewResolver(0, nss...) return gost.NewResolver(0, nss...)
@ -273,3 +294,49 @@ func parseHosts(s string) *gost.Hosts {
return hosts return hosts
} }
func parseIPRoutes(s string) (routes []gost.IPRoute) {
if s == "" {
return
}
file, err := os.Open(s)
if err != nil {
ss := strings.Split(s, ",")
for _, s := range ss {
if _, inet, _ := net.ParseCIDR(strings.TrimSpace(s)); inet != nil {
routes = append(routes, gost.IPRoute{Dest: inet})
}
}
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.Replace(scanner.Text(), "\t", " ", -1)
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
var route gost.IPRoute
var ss []string
for _, s := range strings.Split(line, " ") {
if s = strings.TrimSpace(s); s != "" {
ss = append(ss, s)
}
}
if len(ss) > 0 && ss[0] != "" {
_, route.Dest, _ = net.ParseCIDR(strings.TrimSpace(ss[0]))
if route.Dest == nil {
continue
}
}
if len(ss) > 1 && ss[1] != "" {
route.Gateway = net.ParseIP(ss[1])
}
routes = append(routes, route)
}
return routes
}

View File

@ -31,7 +31,9 @@ func init() {
flag.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain") flag.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain")
flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)") flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)")
flag.IntVar(&baseCfg.route.Mark, "M", 0, "Specify out connection mark")
flag.StringVar(&configureFile, "C", "", "configure file") flag.StringVar(&configureFile, "C", "", "configure file")
flag.StringVar(&baseCfg.route.Interface, "I", "", "Interface to bind")
flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log") flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log")
flag.BoolVar(&printVersion, "V", false, "print version") flag.BoolVar(&printVersion, "V", false, "print version")
if pprofEnabled { if pprofEnabled {
@ -40,7 +42,7 @@ func init() {
flag.Parse() flag.Parse()
if printVersion { if printVersion {
fmt.Fprintf(os.Stderr, "gost %s (%s %s/%s)\n", fmt.Fprintf(os.Stdout, "gost %s (%s %s/%s)\n",
gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
os.Exit(0) os.Exit(0)
} }
@ -67,7 +69,7 @@ func main() {
} }
// NOTE: as of 2.6, you can use custom cert/key files to initialize the default certificate. // NOTE: as of 2.6, you can use custom cert/key files to initialize the default certificate.
tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile) tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile, "")
if err != nil { if err != nil {
// generate random self-signed certificate. // generate random self-signed certificate.
cert, err := gost.GenCertificate() cert, err := gost.GenCertificate()

View File

@ -5,7 +5,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io" "io"
"io/ioutil"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -14,14 +13,16 @@ import (
) )
type peerConfig struct { type peerConfig struct {
Strategy string `json:"strategy"` Strategy string `json:"strategy"`
MaxFails int `json:"max_fails"` MaxFails int `json:"max_fails"`
FailTimeout time.Duration FastestCount int `json:"fastest_count"` // topN fastest node count
period time.Duration // the period for live reloading FailTimeout time.Duration
Nodes []string `json:"nodes"` period time.Duration // the period for live reloading
group *gost.NodeGroup
baseNodes []gost.Node Nodes []string `json:"nodes"`
stopped chan struct{} group *gost.NodeGroup
baseNodes []gost.Node
stopped chan struct{}
} }
func newPeerConfig() *peerConfig { func newPeerConfig() *peerConfig {
@ -52,6 +53,7 @@ func (cfg *peerConfig) Reload(r io.Reader) error {
FailTimeout: cfg.FailTimeout, FailTimeout: cfg.FailTimeout,
}, },
&gost.InvalidFilter{}, &gost.InvalidFilter{},
gost.NewFastestFilter(0, cfg.FastestCount),
), ),
gost.WithStrategy(gost.NewStrategy(cfg.Strategy)), gost.WithStrategy(gost.NewStrategy(cfg.Strategy)),
) )
@ -83,7 +85,7 @@ func (cfg *peerConfig) Reload(r io.Reader) error {
} }
func (cfg *peerConfig) parse(r io.Reader) error { func (cfg *peerConfig) parse(r io.Reader) error {
data, err := ioutil.ReadAll(r) data, err := io.ReadAll(r)
if err != nil { if err != nil {
return err return err
} }
@ -126,6 +128,8 @@ func (cfg *peerConfig) parse(r io.Reader) error {
cfg.Strategy = ss[1] cfg.Strategy = ss[1]
case "max_fails": case "max_fails":
cfg.MaxFails, _ = strconv.Atoi(ss[1]) cfg.MaxFails, _ = strconv.Atoi(ss[1])
case "fastest_count":
cfg.FastestCount, _ = strconv.Atoi(ss[1])
case "fail_timeout": case "fail_timeout":
cfg.FailTimeout, _ = time.ParseDuration(ss[1]) cfg.FailTimeout, _ = time.ParseDuration(ss[1])
case "reload": case "reload":

View File

@ -3,8 +3,11 @@ package main
import ( import (
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt" "fmt"
"net" "net"
"net/url"
"os" "os"
"strings" "strings"
"time" "time"
@ -27,11 +30,15 @@ type route struct {
ServeNodes stringList ServeNodes stringList
ChainNodes stringList ChainNodes stringList
Retries int Retries int
Mark int
Interface string
} }
func (r *route) parseChain() (*gost.Chain, error) { func (r *route) parseChain() (*gost.Chain, error) {
chain := gost.NewChain() chain := gost.NewChain()
chain.Retries = r.Retries chain.Retries = r.Retries
chain.Mark = r.Mark
chain.Interface = r.Interface
gid := 1 // group ID gid := 1 // group ID
for _, ns := range r.ChainNodes { for _, ns := range r.ChainNodes {
@ -59,6 +66,7 @@ func (r *route) parseChain() (*gost.Chain, error) {
FailTimeout: nodes[0].GetDuration("fail_timeout"), FailTimeout: nodes[0].GetDuration("fail_timeout"),
}, },
&gost.InvalidFilter{}, &gost.InvalidFilter{},
gost.NewFastestFilter(0, nodes[0].GetInt("fastest_count")),
), ),
gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))), gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))),
) )
@ -90,13 +98,29 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
return return
} }
users, err := parseUsers(node.Get("secrets")) if auth := node.Get("auth"); auth != "" && node.User == nil {
if err != nil { c, err := base64.StdEncoding.DecodeString(auth)
return if err != nil {
return nil, err
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
node.User = url.User(cs)
} else {
node.User = url.UserPassword(cs[:s], cs[s+1:])
}
} }
if node.User == nil && len(users) > 0 { if node.User == nil {
node.User = users[0] users, err := parseUsers(node.Get("secrets"))
if err != nil {
return nil, err
}
if len(users) > 0 {
node.User = users[0]
}
} }
serverName, sport, _ := net.SplitHostPort(node.Addr) serverName, sport, _ := net.SplitHostPort(node.Addr)
if serverName == "" { if serverName == "" {
serverName = "localhost" // default server name serverName = "localhost" // default server name
@ -111,6 +135,35 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
InsecureSkipVerify: !node.GetBool("secure"), InsecureSkipVerify: !node.GetBool("secure"),
RootCAs: rootCAs, RootCAs: rootCAs,
} }
// If the argument `ca` is given, but not open `secure`, we verify the
// certificate manually.
if rootCAs != nil && !node.GetBool("secure") {
tlsCfg.VerifyConnection = func(state tls.ConnectionState) error {
opts := x509.VerifyOptions{
Roots: rootCAs,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}
certs := state.PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
return err
}
}
if cert, err := tls.LoadX509KeyPair(node.Get("cert"), node.Get("key")); err == nil {
tlsCfg.Certificates = []tls.Certificate{cert}
}
wsOpts := &gost.WSOptions{} wsOpts := &gost.WSOptions{}
wsOpts.EnableCompression = node.GetBool("compression") wsOpts.EnableCompression = node.GetBool("compression")
wsOpts.ReadBufferSize = node.GetInt("rbuf") wsOpts.ReadBufferSize = node.GetInt("rbuf")
@ -118,7 +171,7 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
wsOpts.UserAgent = node.Get("agent") wsOpts.UserAgent = node.Get("agent")
wsOpts.Path = node.Get("path") wsOpts.Path = node.Get("path")
var host string timeout := node.GetDuration("timeout")
var tr gost.Transporter var tr gost.Transporter
switch node.Transport { switch node.Transport {
@ -157,8 +210,14 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
config := &gost.QUICConfig{ config := &gost.QUICConfig{
TLSConfig: tlsCfg, TLSConfig: tlsCfg,
KeepAlive: node.GetBool("keepalive"), KeepAlive: node.GetBool("keepalive"),
Timeout: time.Duration(node.GetInt("timeout")) * time.Second, Timeout: timeout,
IdleTimeout: time.Duration(node.GetInt("idle")) * time.Second, IdleTimeout: node.GetDuration("idle"),
}
if config.KeepAlive {
config.KeepAlivePeriod = node.GetDuration("ttl")
if config.KeepAlivePeriod == 0 {
config.KeepAlivePeriod = 10 * time.Second
}
} }
if cipher := node.Get("cipher"); cipher != "" { if cipher := node.Get("cipher"); cipher != "" {
@ -176,10 +235,15 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
case "obfs4": case "obfs4":
tr = gost.Obfs4Transporter() tr = gost.Obfs4Transporter()
case "ohttp": case "ohttp":
host = node.Get("host")
tr = gost.ObfsHTTPTransporter() tr = gost.ObfsHTTPTransporter()
case "otls":
tr = gost.ObfsTLSTransporter()
case "ftcp": case "ftcp":
tr = gost.FakeTCPTransporter() tr = gost.FakeTCPTransporter()
case "udp":
tr = gost.UDPTransporter()
case "vsock":
tr = gost.VSOCKTransporter()
default: default:
tr = gost.TCPTransporter() tr = gost.TCPTransporter()
} }
@ -196,8 +260,8 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
connector = gost.SOCKS4AConnector() connector = gost.SOCKS4AConnector()
case "ss": case "ss":
connector = gost.ShadowConnector(node.User) connector = gost.ShadowConnector(node.User)
case "ss2": case "ssu":
connector = gost.Shadow2Connector(node.User) connector = gost.ShadowUDPConnector(node.User)
case "direct": case "direct":
connector = gost.SSHDirectForwardConnector() connector = gost.SSHDirectForwardConnector()
case "remote": case "remote":
@ -207,34 +271,48 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
case "sni": case "sni":
connector = gost.SNIConnector(node.Get("host")) connector = gost.SNIConnector(node.Get("host"))
case "http": case "http":
fallthrough
default:
node.Protocol = "http" // default protocol is HTTP
connector = gost.HTTPConnector(node.User) connector = gost.HTTPConnector(node.User)
case "relay":
connector = gost.RelayConnector(node.User)
default:
connector = gost.AutoConnector(node.User)
}
host := node.Get("host")
if host == "" {
host = node.Host
} }
timeout := node.GetInt("timeout")
node.DialOptions = append(node.DialOptions, node.DialOptions = append(node.DialOptions,
gost.TimeoutDialOption(time.Duration(timeout)*time.Second), gost.TimeoutDialOption(timeout),
gost.HostDialOption(host),
) )
node.ConnectOptions = []gost.ConnectOption{ node.ConnectOptions = []gost.ConnectOption{
gost.UserAgentConnectOption(node.Get("agent")), gost.UserAgentConnectOption(node.Get("agent")),
gost.NoTLSConnectOption(node.GetBool("notls")), gost.NoTLSConnectOption(node.GetBool("notls")),
gost.NoDelayConnectOption(node.GetBool("nodelay")),
} }
if host == "" { sshConfig := &gost.SSHConfig{}
host = node.Host if s := node.Get("ssh_key"); s != "" {
key, err := gost.ParseSSHKeyFile(s)
if err != nil {
return nil, err
}
sshConfig.Key = key
} }
handshakeOptions := []gost.HandshakeOption{ handshakeOptions := []gost.HandshakeOption{
gost.AddrHandshakeOption(node.Addr), gost.AddrHandshakeOption(node.Addr),
gost.HostHandshakeOption(host), gost.HostHandshakeOption(host),
gost.UserHandshakeOption(node.User), gost.UserHandshakeOption(node.User),
gost.TLSConfigHandshakeOption(tlsCfg), gost.TLSConfigHandshakeOption(tlsCfg),
gost.IntervalHandshakeOption(time.Duration(node.GetInt("ping")) * time.Second), gost.IntervalHandshakeOption(node.GetDuration("ping")),
gost.TimeoutHandshakeOption(time.Duration(timeout) * time.Second), gost.TimeoutHandshakeOption(timeout),
gost.RetryHandshakeOption(node.GetInt("retry")), gost.RetryHandshakeOption(node.GetInt("retry")),
gost.SSHConfigHandshakeOption(sshConfig),
} }
node.Client = &gost.Client{ node.Client = &gost.Client{
Connector: connector, Connector: connector,
Transporter: tr, Transporter: tr,
@ -280,6 +358,20 @@ func (r *route) GenRouters() ([]router, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if auth := node.Get("auth"); auth != "" && node.User == nil {
c, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
return nil, err
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
node.User = url.User(cs)
} else {
node.User = url.UserPassword(cs[:s], cs[s+1:])
}
}
authenticator, err := parseAuthenticator(node.Get("secrets")) authenticator, err := parseAuthenticator(node.Get("secrets"))
if err != nil { if err != nil {
return nil, err return nil, err
@ -295,7 +387,7 @@ func (r *route) GenRouters() ([]router, error) {
} }
} }
certFile, keyFile := node.Get("cert"), node.Get("key") certFile, keyFile := node.Get("cert"), node.Get("key")
tlsCfg, err := tlsConfig(certFile, keyFile) tlsCfg, err := tlsConfig(certFile, keyFile, node.Get("ca"))
if err != nil && certFile != "" && keyFile != "" { if err != nil && certFile != "" && keyFile != "" {
return nil, err return nil, err
} }
@ -306,9 +398,15 @@ func (r *route) GenRouters() ([]router, error) {
wsOpts.WriteBufferSize = node.GetInt("wbuf") wsOpts.WriteBufferSize = node.GetInt("wbuf")
wsOpts.Path = node.Get("path") wsOpts.Path = node.Get("path")
ttl, err := time.ParseDuration(node.Get("ttl")) ttl := node.GetDuration("ttl")
if err != nil { timeout := node.GetDuration("timeout")
ttl = time.Duration(node.GetInt("ttl")) * time.Second
tunRoutes := parseIPRoutes(node.Get("route"))
gw := net.ParseIP(node.Get("gw")) // default gateway
for i := range tunRoutes {
if tunRoutes[i].Gateway == nil {
tunRoutes[i].Gateway = gw
}
} }
var ln gost.Listener var ln gost.Listener
@ -343,6 +441,20 @@ func (r *route) GenRouters() ([]router, error) {
Authenticator: authenticator, Authenticator: authenticator,
TLSConfig: tlsCfg, TLSConfig: tlsCfg,
} }
if s := node.Get("ssh_key"); s != "" {
key, err := gost.ParseSSHKeyFile(s)
if err != nil {
return nil, err
}
config.Key = key
}
if s := node.Get("ssh_authorized_keys"); s != "" {
keys, err := gost.ParseSSHAuthorizedKeysFile(s)
if err != nil {
return nil, err
}
config.AuthorizedKeys = keys
}
if node.Protocol == "forward" { if node.Protocol == "forward" {
ln, err = gost.TCPListener(node.Addr) ln, err = gost.TCPListener(node.Addr)
} else { } else {
@ -352,8 +464,14 @@ func (r *route) GenRouters() ([]router, error) {
config := &gost.QUICConfig{ config := &gost.QUICConfig{
TLSConfig: tlsCfg, TLSConfig: tlsCfg,
KeepAlive: node.GetBool("keepalive"), KeepAlive: node.GetBool("keepalive"),
Timeout: time.Duration(node.GetInt("timeout")) * time.Second, Timeout: timeout,
IdleTimeout: time.Duration(node.GetInt("idle")) * time.Second, IdleTimeout: node.GetDuration("idle"),
}
if config.KeepAlive {
config.KeepAlivePeriod = node.GetDuration("ttl")
if config.KeepAlivePeriod == 0 {
config.KeepAlivePeriod = 10 * time.Second
}
} }
if cipher := node.Get("cipher"); cipher != "" { if cipher := node.Get("cipher"); cipher != "" {
sum := sha256.Sum256([]byte(cipher)) sum := sha256.Sum256([]byte(cipher))
@ -374,6 +492,14 @@ func (r *route) GenRouters() ([]router, error) {
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter() chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
} }
ln, err = gost.TCPListener(node.Addr) ln, err = gost.TCPListener(node.Addr)
case "vsock":
ln, err = gost.VSOCKListener(node.Addr)
case "udp":
ln, err = gost.UDPListener(node.Addr, &gost.UDPListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"),
})
case "rtcp": case "rtcp":
// Directly use SSH port forwarding if the last chain node is forward+ssh // Directly use SSH port forwarding if the last chain node is forward+ssh
if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" { if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" {
@ -381,24 +507,10 @@ func (r *route) GenRouters() ([]router, error) {
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter() chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
} }
ln, err = gost.TCPRemoteForwardListener(node.Addr, chain) ln, err = gost.TCPRemoteForwardListener(node.Addr, chain)
case "udp":
ln, err = gost.UDPDirectForwardListener(node.Addr, &gost.UDPForwardListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"),
})
case "rudp": case "rudp":
ln, err = gost.UDPRemoteForwardListener(node.Addr, ln, err = gost.UDPRemoteForwardListener(node.Addr,
chain, chain,
&gost.UDPForwardListenConfig{ &gost.UDPListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"),
})
case "ssu":
ln, err = gost.ShadowUDPListener(node.Addr,
node.User,
&gost.UDPForwardListenConfig{
TTL: ttl, TTL: ttl,
Backlog: node.GetInt("backlog"), Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"), QueueSize: node.GetInt("queue"),
@ -410,12 +522,15 @@ func (r *route) GenRouters() ([]router, error) {
ln, err = gost.Obfs4Listener(node.Addr) ln, err = gost.Obfs4Listener(node.Addr)
case "ohttp": case "ohttp":
ln, err = gost.ObfsHTTPListener(node.Addr) ln, err = gost.ObfsHTTPListener(node.Addr)
case "otls":
ln, err = gost.ObfsTLSListener(node.Addr)
case "tun": case "tun":
cfg := gost.TunConfig{ cfg := gost.TunConfig{
Name: node.Get("name"), Name: node.Get("name"),
Addr: node.Get("net"), Addr: node.Get("net"),
Peer: node.Get("peer"),
MTU: node.GetInt("mtu"), MTU: node.GetInt("mtu"),
Routes: strings.Split(node.Get("route"), ","), Routes: tunRoutes,
Gateway: node.Get("gw"), Gateway: node.Get("gw"),
} }
ln, err = gost.TunListener(cfg) ln, err = gost.TunListener(cfg)
@ -437,6 +552,20 @@ func (r *route) GenRouters() ([]router, error) {
QueueSize: node.GetInt("queue"), QueueSize: node.GetInt("queue"),
}, },
) )
case "dns":
ln, err = gost.DNSListener(
node.Addr,
&gost.DNSOptions{
Mode: node.Get("mode"),
TLSConfig: tlsCfg,
},
)
case "redu", "redirectu":
ln, err = gost.UDPRedirectListener(node.Addr, &gost.UDPListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"),
})
default: default:
ln, err = gost.TCPListener(node.Addr) ln, err = gost.TCPListener(node.Addr)
} }
@ -454,8 +583,6 @@ func (r *route) GenRouters() ([]router, error) {
handler = gost.SOCKS4Handler() handler = gost.SOCKS4Handler()
case "ss": case "ss":
handler = gost.ShadowHandler() handler = gost.ShadowHandler()
case "ss2":
handler = gost.Shadow2Handler()
case "http": case "http":
handler = gost.HTTPHandler() handler = gost.HTTPHandler()
case "tcp": case "tcp":
@ -468,16 +595,22 @@ func (r *route) GenRouters() ([]router, error) {
handler = gost.UDPRemoteForwardHandler(node.Remote) handler = gost.UDPRemoteForwardHandler(node.Remote)
case "forward": case "forward":
handler = gost.SSHForwardHandler() handler = gost.SSHForwardHandler()
case "redirect": case "red", "redirect":
handler = gost.TCPRedirectHandler() handler = gost.TCPRedirectHandler()
case "redu", "redirectu":
handler = gost.UDPRedirectHandler()
case "ssu": case "ssu":
handler = gost.ShadowUDPdHandler() handler = gost.ShadowUDPHandler()
case "sni": case "sni":
handler = gost.SNIHandler() handler = gost.SNIHandler()
case "tun": case "tun":
handler = gost.TunHandler() handler = gost.TunHandler()
case "tap": case "tap":
handler = gost.TapHandler() handler = gost.TapHandler()
case "dns":
handler = gost.DNSHandler(node.Remote)
case "relay":
handler = gost.RelayHandler(node.Remote)
default: default:
// start from 2.5, if remote is not empty, then we assume that it is a forward tunnel. // start from 2.5, if remote is not empty, then we assume that it is a forward tunnel.
if node.Remote != "" { if node.Remote != "" {
@ -500,10 +633,20 @@ func (r *route) GenRouters() ([]router, error) {
} }
node.Bypass = parseBypass(node.Get("bypass")) node.Bypass = parseBypass(node.Get("bypass"))
resolver := parseResolver(node.Get("dns"))
hosts := parseHosts(node.Get("hosts")) hosts := parseHosts(node.Get("hosts"))
ips := parseIP(node.Get("ip"), "") ips := parseIP(node.Get("ip"), "")
resolver := parseResolver(node.Get("dns"))
if resolver != nil {
resolver.Init(
gost.ChainResolverOption(chain),
gost.TimeoutResolverOption(timeout),
gost.TTLResolverOption(ttl),
gost.PreferResolverOption(node.Get("prefer")),
gost.SrcIPResolverOption(net.ParseIP(node.Get("ip"))),
)
}
handler.Init( handler.Init(
gost.AddrHandlerOption(ln.Addr().String()), gost.AddrHandlerOption(ln.Addr().String()),
gost.ChainHandlerOption(chain), gost.ChainHandlerOption(chain),
@ -519,12 +662,15 @@ func (r *route) GenRouters() ([]router, error) {
gost.ResolverHandlerOption(resolver), gost.ResolverHandlerOption(resolver),
gost.HostsHandlerOption(hosts), gost.HostsHandlerOption(hosts),
gost.RetryHandlerOption(node.GetInt("retry")), // override the global retry option. gost.RetryHandlerOption(node.GetInt("retry")), // override the global retry option.
gost.TimeoutHandlerOption(time.Duration(node.GetInt("timeout"))*time.Second), gost.TimeoutHandlerOption(timeout),
gost.ProbeResistHandlerOption(node.Get("probe_resist")), gost.ProbeResistHandlerOption(node.Get("probe_resist")),
gost.KnockingHandlerOption(node.Get("knock")), gost.KnockingHandlerOption(node.Get("knock")),
gost.NodeHandlerOption(node), gost.NodeHandlerOption(node),
gost.IPsHandlerOption(ips), gost.IPsHandlerOption(ips),
gost.TCPModeHandlerOption(node.GetBool("tcp")), gost.TCPModeHandlerOption(node.GetBool("tcp")),
gost.IPRoutesHandlerOption(tunRoutes...),
gost.ProxyAgentHandlerOption(node.Get("proxyAgent")),
gost.HTTPTunnelHandlerOption(node.GetBool("httpTunnel")),
) )
rt := router{ rt := router{

View File

@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -36,7 +35,7 @@ func init() {
var ( var (
httpTestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpTestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data, _ := ioutil.ReadAll(r.Body) data, _ := io.ReadAll(r.Body)
if len(data) == 0 { if len(data) == 0 {
data = []byte("Hello World!") data = []byte("Hello World!")
} }
@ -87,7 +86,7 @@ func httpRoundtrip(conn net.Conn, targetURL string, data []byte) (err error) {
return errors.New(resp.Status) return errors.New(resp.Status)
} }
recv, err := ioutil.ReadAll(resp.Body) recv, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return return
} }

421
dns.go Normal file
View File

@ -0,0 +1,421 @@
package gost
import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"io"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/go-log/log"
"github.com/miekg/dns"
)
var (
defaultResolver Resolver
)
func init() {
defaultResolver = NewResolver(
DefaultResolverTimeout,
NameServer{
Addr: "127.0.0.1:53",
Protocol: "udp",
})
defaultResolver.Init()
}
type dnsHandler struct {
options *HandlerOptions
}
// DNSHandler creates a Handler for DNS server.
func DNSHandler(raddr string, opts ...HandlerOption) Handler {
h := &dnsHandler{}
for _, opt := range opts {
opt(h.options)
}
return h
}
func (h *dnsHandler) Init(opts ...HandlerOption) {
if h.options == nil {
h.options = &HandlerOptions{}
}
for _, opt := range opts {
opt(h.options)
}
}
func (h *dnsHandler) Handle(conn net.Conn) {
defer conn.Close()
b := mPool.Get().([]byte)
defer mPool.Put(b)
n, err := conn.Read(b)
if err != nil {
log.Logf("[dns] %s - %s: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
}
mq := &dns.Msg{}
if err = mq.Unpack(b[:n]); err != nil {
log.Logf("[dns] %s - %s request unpack: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
log.Logf("[dns] %s -> %s: %s", conn.RemoteAddr(), conn.LocalAddr(), h.dumpMsgHeader(mq))
if Debug {
log.Logf("[dns] %s >>> %s: %s", conn.RemoteAddr(), conn.LocalAddr(), mq.String())
}
start := time.Now()
resolver := h.options.Resolver
if resolver == nil {
resolver = defaultResolver
}
reply, err := resolver.Exchange(context.Background(), b[:n])
if err != nil {
log.Logf("[dns] %s - %s exchange: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
rtt := time.Since(start)
mr := &dns.Msg{}
if err = mr.Unpack(reply); err != nil {
log.Logf("[dns] %s - %s reply unpack: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
log.Logf("[dns] %s <- %s: %s [%s]",
conn.RemoteAddr(), conn.LocalAddr(), h.dumpMsgHeader(mr), rtt)
if Debug {
log.Logf("[dns] %s <<< %s: %s", conn.RemoteAddr(), conn.LocalAddr(), mr.String())
}
if _, err = conn.Write(reply); err != nil {
log.Logf("[dns] %s - %s reply unpack: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
}
}
func (h *dnsHandler) dumpMsgHeader(m *dns.Msg) string {
buf := new(bytes.Buffer)
buf.WriteString(m.MsgHdr.String() + " ")
buf.WriteString("QUERY: " + strconv.Itoa(len(m.Question)) + ", ")
buf.WriteString("ANSWER: " + strconv.Itoa(len(m.Answer)) + ", ")
buf.WriteString("AUTHORITY: " + strconv.Itoa(len(m.Ns)) + ", ")
buf.WriteString("ADDITIONAL: " + strconv.Itoa(len(m.Extra)))
return buf.String()
}
// DNSOptions is options for DNS Listener.
type DNSOptions struct {
Mode string
UDPSize int
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config
}
type dnsListener struct {
addr net.Addr
server dnsServer
connChan chan net.Conn
errc chan error
}
// DNSListener creates a Listener for DNS proxy server.
func DNSListener(addr string, options *DNSOptions) (Listener, error) {
if options == nil {
options = &DNSOptions{}
}
tlsConfig := options.TLSConfig
if tlsConfig == nil {
tlsConfig = DefaultTLSConfig
}
ln := &dnsListener{
connChan: make(chan net.Conn, 128),
errc: make(chan error, 1),
}
var srv dnsServer
var err error
switch strings.ToLower(options.Mode) {
case "tcp":
srv = &dns.Server{
Net: "tcp",
Addr: addr,
Handler: ln,
ReadTimeout: options.ReadTimeout,
WriteTimeout: options.WriteTimeout,
}
case "tls":
srv = &dns.Server{
Net: "tcp-tls",
Addr: addr,
Handler: ln,
TLSConfig: tlsConfig,
ReadTimeout: options.ReadTimeout,
WriteTimeout: options.WriteTimeout,
}
case "https":
srv = &dohServer{
addr: addr,
tlsConfig: tlsConfig,
server: &http.Server{
Handler: ln,
ReadTimeout: options.ReadTimeout,
WriteTimeout: options.WriteTimeout,
},
}
default:
ln.addr, err = net.ResolveTCPAddr("tcp", addr)
srv = &dns.Server{
Net: "udp",
Addr: addr,
Handler: ln,
UDPSize: options.UDPSize,
ReadTimeout: options.ReadTimeout,
WriteTimeout: options.WriteTimeout,
}
}
if err != nil {
return nil, err
}
if ln.addr == nil {
ln.addr, err = net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
}
ln.server = srv
go func() {
if err := ln.server.ListenAndServe(); err != nil {
ln.errc <- err
return
}
}()
select {
case err := <-ln.errc:
return nil, err
default:
}
return ln, nil
}
func (l *dnsListener) serve(w dnsResponseWriter, mq []byte) (err error) {
conn := newDNSServerConn(l.addr, w.RemoteAddr())
conn.mq <- mq
select {
case l.connChan <- conn:
default:
return errors.New("connection queue is full")
}
select {
case mr := <-conn.mr:
_, err = w.Write(mr)
case <-conn.cclose:
err = io.EOF
}
return
}
func (l *dnsListener) ServeDNS(w dns.ResponseWriter, m *dns.Msg) {
b, err := m.Pack()
if err != nil {
log.Logf("[dns] %s: %v", l.addr, err)
return
}
if err := l.serve(w, b); err != nil {
log.Logf("[dns] %s: %v", l.addr, err)
}
}
// Based on https://github.com/semihalev/sdns
func (l *dnsListener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var buf []byte
var err error
switch r.Method {
case http.MethodGet:
buf, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("dns"))
if len(buf) == 0 || err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
case http.MethodPost:
if r.Header.Get("Content-Type") != "application/dns-message" {
http.Error(w, http.StatusText(http.StatusUnsupportedMediaType), http.StatusUnsupportedMediaType)
return
}
buf, err = io.ReadAll(r.Body)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
default:
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
mq := &dns.Msg{}
if err := mq.Unpack(buf); err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
w.Header().Set("Server", "SDNS")
w.Header().Set("Content-Type", "application/dns-message")
raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err := l.serve(newDoHResponseWriter(raddr, w), buf); err != nil {
log.Logf("[dns] %s: %v", l.addr, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
func (l *dnsListener) Accept() (conn net.Conn, err error) {
select {
case conn = <-l.connChan:
case err = <-l.errc:
}
return
}
func (l *dnsListener) Close() error {
return l.server.Shutdown()
}
func (l *dnsListener) Addr() net.Addr {
return l.addr
}
type dnsServer interface {
ListenAndServe() error
Shutdown() error
}
type dohServer struct {
addr string
tlsConfig *tls.Config
server *http.Server
}
func (s *dohServer) ListenAndServe() error {
ln, err := net.Listen("tcp", s.addr)
if err != nil {
return err
}
ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, s.tlsConfig)
return s.server.Serve(ln)
}
func (s *dohServer) Shutdown() error {
return s.server.Shutdown(context.Background())
}
type dnsServerConn struct {
mq chan []byte
mr chan []byte
cclose chan struct{}
laddr, raddr net.Addr
}
func newDNSServerConn(laddr, raddr net.Addr) *dnsServerConn {
return &dnsServerConn{
mq: make(chan []byte, 1),
mr: make(chan []byte, 1),
laddr: laddr,
raddr: raddr,
cclose: make(chan struct{}),
}
}
func (c *dnsServerConn) Read(b []byte) (n int, err error) {
select {
case mb := <-c.mq:
n = copy(b, mb)
case <-c.cclose:
err = errors.New("connection is closed")
}
return
}
func (c *dnsServerConn) Write(b []byte) (n int, err error) {
select {
case c.mr <- b:
n = len(b)
case <-c.cclose:
err = errors.New("broken pipe")
}
return
}
func (c *dnsServerConn) Close() error {
select {
case <-c.cclose:
default:
close(c.cclose)
}
return nil
}
func (c *dnsServerConn) LocalAddr() net.Addr {
return c.laddr
}
func (c *dnsServerConn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *dnsServerConn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "dns", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *dnsServerConn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "dns", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *dnsServerConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "dns", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
type dnsResponseWriter interface {
io.Writer
RemoteAddr() net.Addr
}
type dohResponseWriter struct {
raddr net.Addr
http.ResponseWriter
}
func newDoHResponseWriter(raddr net.Addr, w http.ResponseWriter) dnsResponseWriter {
return &dohResponseWriter{
raddr: raddr,
ResponseWriter: w,
}
}
func (w *dohResponseWriter) RemoteAddr() net.Addr {
return w.raddr
}

View File

@ -6,7 +6,7 @@ import (
"net" "net"
"strconv" "strconv"
"github.com/ginuerzh/gosocks5" "github.com/go-gost/gosocks5"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
) )

View File

@ -1,18 +1,18 @@
package gost package gost
import ( import (
"context"
"errors" "errors"
"net" "net"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"fmt" "fmt"
"github.com/ginuerzh/gosocks5" "github.com/go-gost/gosocks5"
"github.com/go-log/log" "github.com/go-log/log"
smux "gopkg.in/xtaci/smux.v1" smux "github.com/xtaci/smux"
) )
type forwardConnector struct { type forwardConnector struct {
@ -23,7 +23,11 @@ func ForwardConnector() Connector {
return &forwardConnector{} return &forwardConnector{}
} }
func (c *forwardConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *forwardConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *forwardConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
return conn, nil return conn, nil
} }
@ -70,13 +74,6 @@ func (h *baseForwardHandler) Init(options ...HandlerOption) {
n++ n++
} }
if len(h.group.Nodes()) == 0 {
h.group.AddNode(Node{ // dummy address
ID: n,
Addr: ":0",
Host: ":0",
})
}
} }
type tcpDirectForwardHandler struct { type tcpDirectForwardHandler struct {
@ -109,6 +106,8 @@ func (h *tcpDirectForwardHandler) Init(options ...HandlerOption) {
func (h *tcpDirectForwardHandler) Handle(conn net.Conn) { func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
retries := 1 retries := 1
if h.options.Chain != nil && h.options.Chain.Retries > 0 { if h.options.Chain != nil && h.options.Chain.Retries > 0 {
retries = h.options.Chain.Retries retries = h.options.Chain.Retries
@ -121,19 +120,21 @@ func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
var node Node var node Node
var err error var err error
for i := 0; i < retries; i++ { for i := 0; i < retries; i++ {
node, err = h.group.Next() if len(h.group.Nodes()) > 0 {
if err != nil { node, err = h.group.Next()
log.Logf("[tcp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err) if err != nil {
return log.Logf("[tcp] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
} }
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), node.Addr)
cc, err = h.options.Chain.Dial(node.Addr, cc, err = h.options.Chain.Dial(node.Addr,
RetryChainOption(h.options.Retries), RetryChainOption(h.options.Retries),
TimeoutChainOption(h.options.Timeout), TimeoutChainOption(h.options.Timeout),
ResolverChainOption(h.options.Resolver),
) )
if err != nil { if err != nil {
log.Logf("[tcp] %s -> %s : %s", conn.RemoteAddr(), node.Addr, err) log.Logf("[tcp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
node.MarkDead() node.MarkDead()
} else { } else {
break break
@ -146,9 +147,13 @@ func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
node.ResetDead() node.ResetDead()
defer cc.Close() defer cc.Close()
log.Logf("[tcp] %s <-> %s", conn.RemoteAddr(), node.Addr) addr := node.Addr
if addr == "" {
addr = conn.LocalAddr().String()
}
log.Logf("[tcp] %s <-> %s", conn.RemoteAddr(), addr)
transport(conn, cc) transport(conn, cc)
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), node.Addr) log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), addr)
} }
type udpDirectForwardHandler struct { type udpDirectForwardHandler struct {
@ -181,44 +186,39 @@ func (h *udpDirectForwardHandler) Init(options ...HandlerOption) {
func (h *udpDirectForwardHandler) Handle(conn net.Conn) { func (h *udpDirectForwardHandler) Handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
node, err := h.group.Next() log.Logf("[udp] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
if err != nil {
log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err) var node Node
return var err error
if len(h.group.Nodes()) > 0 {
node, err = h.group.Next()
if err != nil {
log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
} }
raddr, err := net.ResolveUDPAddr("udp", node.Addr) cc, err := h.options.Chain.DialContext(
context.Background(),
"udp",
node.Addr,
ResolverChainOption(h.options.Resolver),
)
if err != nil { if err != nil {
node.MarkDead() node.MarkDead()
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err) log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return return
} }
var cc net.Conn
if h.options.Chain.IsEmpty() {
cc, err = net.DialUDP("udp", nil, raddr)
if err != nil {
node.MarkDead()
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err)
return
}
} else {
var err error
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
if err != nil {
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err)
return
}
cc = &udpTunnelConn{Conn: cc, raddr: raddr}
}
defer cc.Close() defer cc.Close()
node.ResetDead() node.ResetDead()
log.Logf("[udp] %s <-> %s", conn.RemoteAddr(), node.Addr) addr := node.Addr
if addr == "" {
addr = conn.LocalAddr().String()
}
log.Logf("[udp] %s <-> %s", conn.RemoteAddr(), addr)
transport(conn, cc) transport(conn, cc)
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), node.Addr) log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), addr)
} }
type tcpRemoteForwardHandler struct { type tcpRemoteForwardHandler struct {
@ -260,10 +260,12 @@ func (h *tcpRemoteForwardHandler) Handle(conn net.Conn) {
var node Node var node Node
var err error var err error
for i := 0; i < retries; i++ { for i := 0; i < retries; i++ {
node, err = h.group.Next() if len(h.group.Nodes()) > 0 {
if err != nil { node, err = h.group.Next()
log.Logf("[rtcp] %s - %s : %s", conn.LocalAddr(), h.raddr, err) if err != nil {
return log.Logf("[rtcp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
return
}
} }
cc, err = net.DialTimeout("tcp", node.Addr, h.options.Timeout) cc, err = net.DialTimeout("tcp", node.Addr, h.options.Timeout)
if err != nil { if err != nil {
@ -315,10 +317,14 @@ func (h *udpRemoteForwardHandler) Init(options ...HandlerOption) {
func (h *udpRemoteForwardHandler) Handle(conn net.Conn) { func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
node, err := h.group.Next() var node Node
if err != nil { var err error
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err) if len(h.group.Nodes()) > 0 {
return node, err = h.group.Next()
if err != nil {
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
return
}
} }
raddr, err := net.ResolveUDPAddr("udp", node.Addr) raddr, err := net.ResolveUDPAddr("udp", node.Addr)
@ -341,271 +347,6 @@ func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
log.Logf("[rudp] %s >-< %s", conn.RemoteAddr(), node.Addr) log.Logf("[rudp] %s >-< %s", conn.RemoteAddr(), node.Addr)
} }
type udpConnMap struct {
m sync.Map
size int64
}
func (m *udpConnMap) Get(key interface{}) (conn *udpServerConn, ok bool) {
v, ok := m.m.Load(key)
if ok {
conn, ok = v.(*udpServerConn)
}
return
}
func (m *udpConnMap) Set(key interface{}, conn *udpServerConn) {
m.m.Store(key, conn)
atomic.AddInt64(&m.size, 1)
}
func (m *udpConnMap) Delete(key interface{}) {
m.m.Delete(key)
atomic.AddInt64(&m.size, -1)
}
func (m *udpConnMap) Range(f func(key interface{}, value *udpServerConn) bool) {
m.m.Range(func(k, v interface{}) bool {
return f(k, v.(*udpServerConn))
})
}
func (m *udpConnMap) Size() int64 {
return atomic.LoadInt64(&m.size)
}
type UDPForwardListenConfig struct {
TTL time.Duration
Backlog int
QueueSize int
}
type udpDirectForwardListener struct {
ln net.PacketConn
connChan chan net.Conn
errChan chan error
connMap udpConnMap
config *UDPForwardListenConfig
}
// UDPDirectForwardListener creates a Listener for UDP port forwarding server.
func UDPDirectForwardListener(addr string, cfg *UDPForwardListenConfig) (Listener, error) {
laddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenUDP("udp", laddr)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &UDPForwardListenConfig{}
}
backlog := cfg.Backlog
if backlog <= 0 {
backlog = defaultBacklog
}
l := &udpDirectForwardListener{
ln: ln,
connChan: make(chan net.Conn, backlog),
errChan: make(chan error, 1),
config: cfg,
}
go l.listenLoop()
return l, nil
}
func (l *udpDirectForwardListener) listenLoop() {
for {
b := make([]byte, mediumBufferSize)
n, raddr, err := l.ln.ReadFrom(b)
if err != nil {
log.Logf("[udp] peer -> %s : %s", l.Addr(), err)
l.Close()
l.errChan <- err
close(l.errChan)
return
}
conn, ok := l.connMap.Get(raddr.String())
if !ok {
conn = newUDPServerConn(l.ln, raddr, l.config.TTL, l.config.QueueSize)
conn.onClose = func() {
l.connMap.Delete(raddr.String())
log.Logf("[udp] %s closed (%d)", raddr, l.connMap.Size())
}
select {
case l.connChan <- conn:
l.connMap.Set(raddr.String(), conn)
log.Logf("[udp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size())
default:
conn.Close()
log.Logf("[udp] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan))
}
}
select {
case conn.rChan <- b[:n]:
if Debug {
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n)
}
default:
log.Logf("[udp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
}
}
}
func (l *udpDirectForwardListener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.connChan:
case err, ok = <-l.errChan:
if !ok {
err = errors.New("accpet on closed listener")
}
}
return
}
func (l *udpDirectForwardListener) Addr() net.Addr {
return l.ln.LocalAddr()
}
func (l *udpDirectForwardListener) Close() error {
err := l.ln.Close()
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
v.Close()
return true
})
return err
}
type udpServerConn struct {
conn net.PacketConn
raddr net.Addr
rChan chan []byte
closed chan struct{}
closeMutex sync.Mutex
ttl time.Duration
nopChan chan int
onClose func()
}
func newUDPServerConn(conn net.PacketConn, raddr net.Addr, ttl time.Duration, qsize int) *udpServerConn {
if qsize <= 0 {
qsize = defaultQueueSize
}
c := &udpServerConn{
conn: conn,
raddr: raddr,
rChan: make(chan []byte, qsize),
closed: make(chan struct{}),
nopChan: make(chan int),
ttl: ttl,
}
go c.ttlWait()
return c
}
func (c *udpServerConn) Read(b []byte) (n int, err error) {
select {
case bb := <-c.rChan:
n = copy(b, bb)
case <-c.closed:
err = errors.New("read from closed connection")
return
}
select {
case c.nopChan <- n:
default:
}
return
}
func (c *udpServerConn) Write(b []byte) (n int, err error) {
n, err = c.conn.WriteTo(b, c.raddr)
if n > 0 {
if Debug {
log.Logf("[udp] %s <<< %s : length %d", c.raddr, c.LocalAddr(), n)
}
select {
case c.nopChan <- n:
default:
}
}
return
}
func (c *udpServerConn) Close() error {
c.closeMutex.Lock()
defer c.closeMutex.Unlock()
select {
case <-c.closed:
return errors.New("connection is closed")
default:
if c.onClose != nil {
c.onClose()
}
close(c.closed)
}
return nil
}
func (c *udpServerConn) ttlWait() {
ttl := c.ttl
if ttl == 0 {
ttl = defaultTTL
}
timer := time.NewTimer(ttl)
defer timer.Stop()
for {
select {
case <-c.nopChan:
if !timer.Stop() {
<-timer.C
}
timer.Reset(ttl)
case <-timer.C:
c.Close()
return
case <-c.closed:
return
}
}
}
func (c *udpServerConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *udpServerConn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *udpServerConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *udpServerConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *udpServerConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
type tcpRemoteForwardListener struct { type tcpRemoteForwardListener struct {
addr net.Addr addr net.Addr
chain *Chain chain *Chain
@ -615,7 +356,6 @@ type tcpRemoteForwardListener struct {
sessionMux sync.Mutex sessionMux sync.Mutex
closed chan struct{} closed chan struct{}
closeMux sync.Mutex closeMux sync.Mutex
errChan chan error
} }
// TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server. // TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server.
@ -630,7 +370,6 @@ func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
chain: chain, chain: chain,
connChan: make(chan net.Conn, 1024), connChan: make(chan net.Conn, 1024),
closed: make(chan struct{}), closed: make(chan struct{}),
errChan: make(chan error),
} }
if !ln.isChainValid() { if !ln.isChainValid() {
@ -644,9 +383,13 @@ func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
} }
func (l *tcpRemoteForwardListener) isChainValid() bool { func (l *tcpRemoteForwardListener) isChainValid() bool {
if l.chain.IsEmpty() {
return false
}
lastNode := l.chain.LastNode() lastNode := l.chain.LastNode()
if (lastNode.Protocol == "forward" && lastNode.Transport == "ssh") || if (lastNode.Protocol == "forward" && lastNode.Transport == "ssh") ||
lastNode.Protocol == "socks5" { lastNode.Protocol == "socks5" || lastNode.Protocol == "" {
return true return true
} }
return false return false
@ -712,7 +455,7 @@ func (l *tcpRemoteForwardListener) accept() (conn net.Conn, err error) {
return l.chain.Dial(l.addr.String()) return l.chain.Dial(l.addr.String())
} }
if lastNode.Protocol == "socks5" { if l.isChainValid() {
if lastNode.GetBool("mbind") { if lastNode.GetBool("mbind") {
return l.muxAccept() // multiplexing support for binding. return l.muxAccept() // multiplexing support for binding.
} }
@ -866,26 +609,26 @@ func (l *tcpRemoteForwardListener) Close() error {
type udpRemoteForwardListener struct { type udpRemoteForwardListener struct {
addr net.Addr addr net.Addr
chain *Chain chain *Chain
connMap udpConnMap connMap *udpConnMap
connChan chan net.Conn connChan chan net.Conn
ln *net.UDPConn ln *net.UDPConn
errChan chan error
ttl time.Duration ttl time.Duration
closed chan struct{} closed chan struct{}
closeMux sync.Mutex ready chan struct{}
once sync.Once once sync.Once
config *UDPForwardListenConfig closeMux sync.Mutex
config *UDPListenConfig
} }
// UDPRemoteForwardListener creates a Listener for UDP remote port forwarding server. // UDPRemoteForwardListener creates a Listener for UDP remote port forwarding server.
func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPForwardListenConfig) (Listener, error) { func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPListenConfig) (Listener, error) {
laddr, err := net.ResolveUDPAddr("udp", addr) laddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cfg == nil { if cfg == nil {
cfg = &UDPForwardListenConfig{} cfg = &UDPListenConfig{}
} }
backlog := cfg.Backlog backlog := cfg.Backlog
@ -896,22 +639,27 @@ func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPForwardListenCo
ln := &udpRemoteForwardListener{ ln := &udpRemoteForwardListener{
addr: laddr, addr: laddr,
chain: chain, chain: chain,
connMap: new(udpConnMap),
connChan: make(chan net.Conn, backlog), connChan: make(chan net.Conn, backlog),
errChan: make(chan error, 1), ready: make(chan struct{}),
closed: make(chan struct{}), closed: make(chan struct{}),
config: cfg, config: cfg,
} }
go ln.listenLoop() go ln.listenLoop()
err = <-ln.errChan <-ln.ready
return ln, err return ln, err
} }
func (l *udpRemoteForwardListener) isChainValid() bool { func (l *udpRemoteForwardListener) isChainValid() bool {
if l.chain.IsEmpty() {
return false
}
lastNode := l.chain.LastNode() lastNode := l.chain.LastNode()
return lastNode.Protocol == "socks5" return lastNode.Protocol == "socks5" || lastNode.Protocol == ""
} }
func (l *udpRemoteForwardListener) listenLoop() { func (l *udpRemoteForwardListener) listenLoop() {
@ -922,6 +670,10 @@ func (l *udpRemoteForwardListener) listenLoop() {
return return
} }
l.once.Do(func() {
close(l.ready)
})
func() { func() {
defer conn.Close() defer conn.Close()
@ -935,11 +687,14 @@ func (l *udpRemoteForwardListener) listenLoop() {
uc, ok := l.connMap.Get(raddr.String()) uc, ok := l.connMap.Get(raddr.String())
if !ok { if !ok {
uc = newUDPServerConn(conn, raddr, l.config.TTL, l.config.QueueSize) uc = newUDPServerConn(conn, raddr, &udpServerConnConfig{
uc.onClose = func() { ttl: l.config.TTL,
l.connMap.Delete(raddr.String()) qsize: l.config.QueueSize,
log.Logf("[rudp] %s closed (%d)", raddr, l.connMap.Size()) onClose: func() {
} l.connMap.Delete(raddr.String())
log.Logf("[rudp] %s closed (%d)", raddr, l.connMap.Size())
},
})
select { select {
case l.connChan <- uc: case l.connChan <- uc:
@ -975,14 +730,13 @@ func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) {
default: default:
} }
lastNode := l.chain.LastNode() if l.isChainValid() {
if lastNode.Protocol == "socks5" {
var cc net.Conn var cc net.Conn
cc, err = getSOCKS5UDPTunnel(l.chain, l.addr) cc, err = getSocks5UDPTunnel(l.chain, l.addr)
if err != nil { if err != nil {
log.Logf("[rudp] %s : %s", l.Addr(), err) log.Logf("[rudp] %s : %s", l.Addr(), err)
} else { } else {
conn = &udpTunnelConn{Conn: cc} conn = cc.(net.PacketConn)
} }
} else { } else {
var uc *net.UDPConn var uc *net.UDPConn
@ -993,11 +747,6 @@ func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) {
} }
} }
l.once.Do(func() {
l.errChan <- err
close(l.errChan)
})
if err != nil { if err != nil {
if tempDelay == 0 { if tempDelay == 0 {
tempDelay = 1000 * time.Millisecond tempDelay = 1000 * time.Millisecond

View File

@ -128,7 +128,7 @@ func BenchmarkTCPDirectForwardParallel(b *testing.B) {
} }
func udpDirectForwardRoundtrip(t *testing.T, host string, data []byte) error { func udpDirectForwardRoundtrip(t *testing.T, host string, data []byte) error {
ln, err := UDPDirectForwardListener("localhost:0", nil) ln, err := UDPListener("localhost:0", nil)
if err != nil { if err != nil {
return err return err
} }
@ -172,7 +172,7 @@ func BenchmarkUDPDirectForward(b *testing.B) {
sendData := make([]byte, 128) sendData := make([]byte, 128)
rand.Read(sendData) rand.Read(sendData)
ln, err := UDPDirectForwardListener("localhost:0", nil) ln, err := UDPListener("localhost:0", nil)
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }
@ -207,7 +207,7 @@ func BenchmarkUDPDirectForwardParallel(b *testing.B) {
sendData := make([]byte, 128) sendData := make([]byte, 128)
rand.Read(sendData) rand.Read(sendData)
ln, err := UDPDirectForwardListener("localhost:0", nil) ln, err := UDPListener("localhost:0", nil)
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }

14
ftcp.go
View File

@ -45,6 +45,7 @@ func (tr *fakeTCPTransporter) Multiplex() bool {
return false return false
} }
// FakeTCPListenConfig is config for fake TCP Listener.
type FakeTCPListenConfig struct { type FakeTCPListenConfig struct {
TTL time.Duration TTL time.Duration
Backlog int Backlog int
@ -99,11 +100,14 @@ func (l *fakeTCPListener) listenLoop() {
conn, ok := l.connMap.Get(raddr.String()) conn, ok := l.connMap.Get(raddr.String())
if !ok { if !ok {
conn = newUDPServerConn(l.ln, raddr, l.config.TTL, l.config.QueueSize) conn = newUDPServerConn(l.ln, raddr, &udpServerConnConfig{
conn.onClose = func() { ttl: l.config.TTL,
l.connMap.Delete(raddr.String()) qsize: l.config.QueueSize,
log.Logf("[ftcp] %s closed (%d)", raddr, l.connMap.Size()) onClose: func() {
} l.connMap.Delete(raddr.String())
log.Logf("[ftcp] %s closed (%d)", raddr, l.connMap.Size())
},
})
select { select {
case l.connChan <- conn: case l.connChan <- conn:

95
go.mod
View File

@ -1,49 +1,60 @@
module github.com/ginuerzh/gost module github.com/ginuerzh/gost
go 1.13 go 1.22
replace github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a
require ( require (
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e git.torproject.org/pluggable-transports/goptlib.git v1.3.0
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/go-gost/gosocks4 v0.0.1
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect github.com/go-gost/gosocks5 v0.3.0
github.com/cheekybits/genny v1.0.0 // indirect github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
github.com/coreos/go-iptables v0.4.5 // indirect github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451
github.com/dchest/siphash v1.2.1 // indirect github.com/go-log/log v0.2.0
github.com/docker/libcontainer v2.2.1+incompatible
github.com/ginuerzh/gosocks4 v0.0.1
github.com/ginuerzh/gosocks5 v0.2.0
github.com/ginuerzh/tls-dissector v0.0.1
github.com/go-log/log v0.1.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/golang/mock v1.2.0 // indirect github.com/gorilla/websocket v1.5.1
github.com/google/gopacket v1.1.17 // indirect github.com/klauspost/compress v1.17.6
github.com/gorilla/websocket v1.4.0 // indirect github.com/mdlayher/vsock v1.2.1
github.com/hashicorp/golang-lru v0.5.0 // indirect github.com/miekg/dns v1.1.58
github.com/klauspost/compress v1.4.1 github.com/quic-go/quic-go v0.45.0
github.com/klauspost/cpuid v1.2.0 // indirect github.com/ryanuber/go-glob v1.0.0
github.com/klauspost/reedsolomon v1.7.0 // indirect github.com/shadowsocks/go-shadowsocks2 v0.1.5
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
github.com/lucas-clemente/quic-go v0.10.0 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect github.com/xtaci/kcp-go/v5 v5.6.7
github.com/miekg/dns v1.1.3 github.com/xtaci/smux v1.5.24
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735
github.com/shadowsocks/go-shadowsocks2 v0.0.11
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
github.com/tjfoc/gmsm v1.0.1 // indirect
github.com/xtaci/tcpraw v1.2.25 github.com/xtaci/tcpraw v1.2.25
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 golang.org/x/crypto v0.24.0
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect golang.org/x/net v0.26.0
gopkg.in/gorilla/websocket.v1 v1.4.0 )
gopkg.in/xtaci/kcp-go.v4 v4.3.2
gopkg.in/xtaci/smux.v1 v1.0.7 require (
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/coreos/go-iptables v0.6.0 // indirect
github.com/dchest/siphash v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/templexxx/cpu v0.1.0 // indirect
github.com/templexxx/xorsimd v0.4.2 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.22.0 // indirect
) )

299
go.sum
View File

@ -1,121 +1,204 @@
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e h1:PYcONLFUhr00kGrq7Mf14JRtoXHG7BOSKIfIha0Hu5Q= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q= filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 h1:iJoUgXvhagsNMrJrvavw7vu1eG8+hm6jLOxlLFcoODw=
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e h1:c8h60PKrRxEB5debIHBmP7T+s/EUNXTklXqlmJfYiJQ= filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e/go.mod h1:jRZbfRcLIgFQoCw6tRmsnETVyIj54jOmXhHCYYa0jbs= git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= git.torproject.org/pluggable-transports/goptlib.git v1.3.0 h1:G+iuRUblCCC2xnO+0ag1/4+aaM98D5mjWP1M0v9s8a0=
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= git.torproject.org/pluggable-transports/goptlib.git v1.3.0/go.mod h1:4PBMl1dg7/3vMWSoWb46eGWlrxkUyn/CAJmxhDLAlDs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4=
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dchest/siphash v1.2.0 h1:YWOShuhvg0GqbQpMa60QlCGtEyf7O7HC1Jf0VjdQ60M= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0= github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw= github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/ginuerzh/gosocks4 v0.0.1 h1:ojDKUyz+uaEeRm2usY1cyQiXTqJqrKxfeE6SVBXq4m0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ginuerzh/gosocks4 v0.0.1/go.mod h1:8SdwBMKjfJ9+BfP2vDJM1jcrgWUbWV6qxBPHHVrwptY= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
github.com/ginuerzh/gosocks5 v0.2.0 h1:K0Ua23U9LU3BZrf3XpGDcs0mP8DiEpa6PJE4TA/MU3s= github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno= github.com/go-gost/gosocks5 v0.3.0 h1:Hkmp9YDRBSCJd7xywW6dBPT6B9aQTkuWd+3WCheJiJA=
github.com/ginuerzh/tls-dissector v0.0.1 h1:yF6fIt78TO4CdjiLLn6R8r0XajQJE1Lbnuq6rP8mGW8= github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
github.com/ginuerzh/tls-dissector v0.0.1/go.mod h1:u/kbBOqIOgJv39gywuUb3VwyzdZG5DKquOqfToKE6lk= github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 h1:itaaJhQJ19kUXEB4Igb0EbY8m+1Py2AaNNSBds/9gk4=
github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I=
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs=
github.com/go-log/log v0.2.0 h1:z8i91GBudxD5L3RmF0KVpetCbcGWAV7q1Tw1eRwQM9Q=
github.com/go-log/log v0.2.0/go.mod h1:xzCnwajcues/6w7lne3yK2QU7DBPW7kqbgPGG5AF65U=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc= github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY= github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8 h1:4WFQEfEJ7zaHYViIVM2Cd6tnQOOhiEHbmQtlcV7aOpc= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8/go.mod h1:G95Wwn625/q6JCCytI4VR/a5VtPwrtI0B+Q1Gi38QLA= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/shadowsocks/go-shadowsocks2 v0.0.11 h1:dXloqEhYnZV40jblWTK8kWeC0Eb+dgql4S0tj99e8j0= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/shadowsocks/go-shadowsocks2 v0.0.11/go.mod h1:R+KWaoIwRRhnpw6XV+dZil0XHi64Hc1D7hXUyXTjUzQ= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba h1:tJgNXb3S+RkB4kNPi6N5OmEWe3m+Y3Qs6LUMiNDAONM= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM= github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ=
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/xtaci/kcp-go/v5 v5.6.7 h1:7+rnxNFIsjEwTXQk4cSZpXM4pO0hqtpwE1UFFoJBffA=
github.com/xtaci/kcp-go/v5 v5.6.7/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk= github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk=
github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk= github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb h1:qRSZHsODmAP5qDvb3YsO7Qnf3TRiVbGxNG/WYnlM4/o=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8= gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb/go.mod h1:gvdJuZuO/tPZyhEV8K3Hmoxv/DWud5L4qEQxfYjEUTo=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d h1:tJ8F7ABaQ3p3wjxwXiWSktVDgjZEXkvaRawd2rIq5ws=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d/go.mod h1:9GcM8QNU9/wXtEEH2q8bVOnPI7FtIF6VVLzZ1l6Hgf8=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
gopkg.in/gorilla/websocket.v1 v1.4.0 h1:lREme3ezAGPCpxSHwjGkHhAJX+ed2B6vzAJ+kaqBEIM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/gorilla/websocket.v1 v1.4.0/go.mod h1:Ons1i8d00TjvJPdla7bJyeXFsdOacUyrTYbg9IetsIE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
gopkg.in/xtaci/kcp-go.v4 v4.3.2 h1:S9IF+L55Ugzl/hVA6wvuL3SuAtTUzH2cBBC88MXQxnE= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/xtaci/kcp-go.v4 v4.3.2/go.mod h1:fFYTlSOHNOHDNTKfoqarZMQsu7g7oXKwJ9wq0i9lODc= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
gopkg.in/xtaci/smux.v1 v1.0.7 h1:qootIZs4ZPSx5blhvgaFpx2epdFSWkyw99xT+q0mRXI= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
gopkg.in/xtaci/smux.v1 v1.0.7/go.mod h1:NbrPjLp8lNAYN8KqTplnFr2JjIBbr7CdHBkHtHsXtWA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

11
gost.go
View File

@ -20,7 +20,7 @@ import (
) )
// Version is the gost version. // Version is the gost version.
const Version = "2.9.1" const Version = "2.12.0"
// Debug is a flag that enables the debug log. // Debug is a flag that enables the debug log.
var Debug bool var Debug bool
@ -80,7 +80,10 @@ var (
// DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket. // DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket.
DefaultUserAgent = "Chrome/78.0.3904.106" DefaultUserAgent = "Chrome/78.0.3904.106"
DefaultMTU = 1350 // default mtu for tun/tap device DefaultProxyAgent = "gost/" + Version
// DefaultMTU is the default mtu for tun/tap device
DefaultMTU = 1350
) )
// SetLogger sets a new logger for internal log system. // SetLogger sets a new logger for internal log system.
@ -146,9 +149,7 @@ func (rw *readWriter) Write(p []byte) (n int, err error) {
return rw.w.Write(p) return rw.w.Write(p)
} }
var ( var nopClientConn = &nopConn{}
nopClientConn = &nopConn{}
)
// a nop connection implements net.Conn, // a nop connection implements net.Conn,
// it does nothing. // it does nothing.

BIN
gost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -7,8 +7,8 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/ginuerzh/gosocks4" "github.com/go-gost/gosocks4"
"github.com/ginuerzh/gosocks5" "github.com/go-gost/gosocks5"
"github.com/go-log/log" "github.com/go-log/log"
) )
@ -41,6 +41,9 @@ type HandlerOptions struct {
Host string Host string
IPs []string IPs []string
TCPMode bool TCPMode bool
IPRoutes []IPRoute
ProxyAgent string
HTTPTunnel bool
} }
// HandlerOption allows a common way to set handler options. // HandlerOption allows a common way to set handler options.
@ -203,6 +206,27 @@ func TCPModeHandlerOption(b bool) HandlerOption {
} }
} }
// IPRoutesHandlerOption sets the IP routes for tun tunnel.
func IPRoutesHandlerOption(routes ...IPRoute) HandlerOption {
return func(opts *HandlerOptions) {
opts.IPRoutes = routes
}
}
// ProxyAgentHandlerOption sets the proxy agent for http handler.
func ProxyAgentHandlerOption(agent string) HandlerOption {
return func(opts *HandlerOptions) {
opts.ProxyAgent = agent
}
}
// HTTPTunnelHandlerOption sets the Tunnel mode for HTTP client used in HTTP handler.
func HTTPTunnelHandlerOption(tunnelMode bool) HandlerOption {
return func(opts *HandlerOptions) {
opts.HTTPTunnel = tunnelMode
}
}
type autoHandler struct { type autoHandler struct {
options *HandlerOptions options *HandlerOptions
} }

110
http.go
View File

@ -3,8 +3,10 @@ package gost
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -27,7 +29,16 @@ func HTTPConnector(user *url.Userinfo) Connector {
return &httpConnector{User: user} return &httpConnector{User: user}
} }
func (c *httpConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *httpConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *httpConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -47,8 +58,8 @@ func (c *httpConnector) Connect(conn net.Conn, addr string, options ...ConnectOp
req := &http.Request{ req := &http.Request{
Method: http.MethodConnect, Method: http.MethodConnect,
URL: &url.URL{Host: addr}, URL: &url.URL{Host: address},
Host: addr, Host: address,
ProtoMajor: 1, ProtoMajor: 1,
ProtoMinor: 1, ProtoMinor: 1,
Header: make(http.Header), Header: make(http.Header),
@ -163,7 +174,12 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
ProtoMinor: 1, ProtoMinor: 1,
Header: http.Header{}, Header: http.Header{},
} }
resp.Header.Add("Proxy-Agent", "gost/"+Version)
proxyAgent := DefaultProxyAgent
if h.options.ProxyAgent != "" {
proxyAgent = h.options.ProxyAgent
}
resp.Header.Add("Proxy-Agent", proxyAgent)
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s", log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s",
@ -242,7 +258,9 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
// forward http request // forward http request
lastNode := route.LastNode() lastNode := route.LastNode()
if req.Method != http.MethodConnect && lastNode.Protocol == "http" { if req.Method != http.MethodConnect &&
lastNode.Protocol == "http" &&
!h.options.HTTPTunnel {
err = h.forwardRequest(conn, req, route) err = h.forwardRequest(conn, req, route)
if err == nil { if err == nil {
return return
@ -275,27 +293,65 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
} }
defer cc.Close() defer cc.Close()
if req.Method == http.MethodConnect { if req.Method != http.MethodConnect {
b := []byte("HTTP/1.1 200 Connection established\r\n" + h.handleProxy(conn, cc, req)
"Proxy-Agent: gost/" + Version + "\r\n\r\n") return
if Debug {
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(b))
}
conn.Write(b)
} else {
req.Header.Del("Proxy-Connection")
if err = req.Write(cc); err != nil {
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
} }
b := []byte("HTTP/1.1 200 Connection established\r\n" +
"Proxy-Agent: " + proxyAgent + "\r\n\r\n")
if Debug {
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(b))
}
conn.Write(b)
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), host) log.Logf("[http] %s <-> %s", conn.RemoteAddr(), host)
transport(conn, cc) transport(conn, cc)
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), host) log.Logf("[http] %s >-< %s", conn.RemoteAddr(), host)
} }
func (h *httpHandler) handleProxy(rw, cc io.ReadWriter, req *http.Request) (err error) {
req.Header.Del("Proxy-Connection")
if err = req.Write(cc); err != nil {
return err
}
ch := make(chan error, 1)
go func() {
ch <- copyBuffer(rw, cc)
}()
for {
err := func() error {
req, err := http.ReadRequest(bufio.NewReader(rw))
if err != nil {
return err
}
if Debug {
dump, _ := httputil.DumpRequest(req, false)
log.Log(string(dump))
}
req.Header.Del("Proxy-Connection")
if err = req.Write(cc); err != nil {
return err
}
return nil
}()
ch <- err
if err != nil {
break
}
}
return <-ch
}
func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) { func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) {
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
if Debug && (u != "" || p != "") { if Debug && (u != "" || p != "") {
@ -353,10 +409,16 @@ func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.
conn.RemoteAddr(), conn.LocalAddr()) conn.RemoteAddr(), conn.LocalAddr())
resp.StatusCode = http.StatusProxyAuthRequired resp.StatusCode = http.StatusProxyAuthRequired
resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"") resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"")
if strings.ToLower(req.Header.Get("Proxy-Connection")) == "keep-alive" {
// XXX libcurl will keep sending auth request in same conn
// which we don't supported yet.
resp.Header.Add("Connection", "close")
resp.Header.Add("Proxy-Connection", "close")
}
} else { } else {
resp.Header = http.Header{} resp.Header = http.Header{}
resp.Header.Set("Server", "nginx/1.14.1") resp.Header.Set("Server", "nginx/1.14.1")
resp.Header.Set("Date", time.Now().Format(http.TimeFormat)) resp.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
resp.Header.Set("Connection", "keep-alive") resp.Header.Set("Connection", "keep-alive")
} }
@ -381,11 +443,9 @@ func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request, route *Ch
var userpass string var userpass string
if user := route.LastNode().User; user != nil { if user := route.LastNode().User; user != nil {
s := user.String() u := user.Username()
if _, set := user.Password(); !set { p, _ := user.Password()
s += ":" userpass = base64.StdEncoding.EncodeToString([]byte(u + ":" + p))
}
userpass = base64.StdEncoding.EncodeToString([]byte(s))
} }
cc, err := route.Conn() cc, err := route.Conn()

View File

@ -3,12 +3,12 @@ package gost
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -33,7 +33,16 @@ func HTTP2Connector(user *url.Userinfo) Connector {
return &http2Connector{User: user} return &http2Connector{User: user}
} }
func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *http2Connector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *http2Connector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -57,7 +66,7 @@ func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectO
ProtoMajor: 2, ProtoMajor: 2,
ProtoMinor: 0, ProtoMinor: 0,
Body: pr, Body: pr,
Host: addr, Host: address,
ContentLength: -1, ContentLength: -1,
} }
req.Header.Set("User-Agent", ua) req.Header.Set("User-Agent", ua)
@ -97,7 +106,7 @@ func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectO
closed: make(chan struct{}), closed: make(chan struct{}),
} }
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr) hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", address)
hc.localAddr, _ = net.ResolveTCPAddr("tcp", cc.addr) hc.localAddr, _ = net.ResolveTCPAddr("tcp", cc.addr)
return hc, nil return hc, nil
@ -224,7 +233,7 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
transport := http2.Transport{ transport := http2.Transport{
TLSClientConfig: tr.tlsConfig, TLSClientConfig: tr.tlsConfig,
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { DialTLS: func(network, adr string, cfg *tls.Config) (net.Conn, error) {
conn, err := opts.Chain.Dial(addr) conn, err := opts.Chain.Dial(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -246,13 +255,13 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
pr, pw := io.Pipe() pr, pw := io.Pipe()
req := &http.Request{ req := &http.Request{
Method: http.MethodConnect, Method: http.MethodConnect,
URL: &url.URL{Scheme: "https", Host: addr}, URL: &url.URL{Scheme: "https", Host: opts.Host},
Header: make(http.Header), Header: make(http.Header),
Proto: "HTTP/2.0", Proto: "HTTP/2.0",
ProtoMajor: 2, ProtoMajor: 2,
ProtoMinor: 0, ProtoMinor: 0,
Body: pr, Body: pr,
Host: addr, Host: opts.Host,
ContentLength: -1, ContentLength: -1,
} }
if tr.path != "" { if tr.path != "" {
@ -355,7 +364,11 @@ func (h *http2Handler) roundTrip(w http.ResponseWriter, r *http.Request) {
log.Logf("[http2] %s - %s\n%s", r.RemoteAddr, laddr, string(dump)) log.Logf("[http2] %s - %s\n%s", r.RemoteAddr, laddr, string(dump))
} }
w.Header().Set("Proxy-Agent", "gost/"+Version) proxyAgent := DefaultProxyAgent
if h.options.ProxyAgent != "" {
proxyAgent = h.options.ProxyAgent
}
w.Header().Set("Proxy-Agent", proxyAgent)
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
log.Logf("[http2] %s - %s : Unauthorized to tcp connect to %s", log.Logf("[http2] %s - %s : Unauthorized to tcp connect to %s",
@ -375,7 +388,7 @@ func (h *http2Handler) roundTrip(w http.ResponseWriter, r *http.Request) {
ProtoMajor: 2, ProtoMajor: 2,
ProtoMinor: 0, ProtoMinor: 0,
Header: http.Header{}, Header: http.Header{},
Body: ioutil.NopCloser(bytes.NewReader([]byte{})), Body: io.NopCloser(bytes.NewReader([]byte{})),
} }
if !h.authenticate(w, r, resp) { if !h.authenticate(w, r, resp) {
@ -525,7 +538,7 @@ func (h *http2Handler) authenticate(w http.ResponseWriter, r *http.Request, resp
} else { } else {
resp.Header = http.Header{} resp.Header = http.Header{}
resp.Header.Set("Server", "nginx/1.14.1") resp.Header.Set("Server", "nginx/1.14.1")
resp.Header.Set("Date", time.Now().Format(http.TimeFormat)) resp.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
if resp.ContentLength > 0 { if resp.ContentLength > 0 {
resp.Header.Set("Content-Type", "text/html") resp.Header.Set("Content-Type", "text/html")
} }

View File

@ -5,7 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -997,7 +997,7 @@ func TestHTTP2ProxyWithWebProbeResist(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
recv, _ := ioutil.ReadAll(conn) recv, _ := io.ReadAll(conn)
if !bytes.Equal(recv, []byte("Hello World!")) { if !bytes.Equal(recv, []byte("Hello World!")) {
t.Error("data not equal") t.Error("data not equal")
} }
@ -1053,7 +1053,7 @@ func TestHTTP2ProxyWithHostProbeResist(t *testing.T) {
Proto: "HTTP/2.0", Proto: "HTTP/2.0",
ProtoMajor: 2, ProtoMajor: 2,
ProtoMinor: 0, ProtoMinor: 0,
Body: ioutil.NopCloser(bytes.NewReader(sendData)), Body: io.NopCloser(bytes.NewReader(sendData)),
Host: "github.com:443", Host: "github.com:443",
ContentLength: int64(len(sendData)), ContentLength: int64(len(sendData)),
} }
@ -1068,7 +1068,7 @@ func TestHTTP2ProxyWithHostProbeResist(t *testing.T) {
t.Error("got non-200 status:", resp.Status) t.Error("got non-200 status:", resp.Status)
} }
recv, _ := ioutil.ReadAll(resp.Body) recv, _ := io.ReadAll(resp.Body)
if !bytes.Equal(sendData, recv) { if !bytes.Equal(sendData, recv) {
t.Error("data not equal") t.Error("data not equal")
} }
@ -1105,7 +1105,7 @@ func TestHTTP2ProxyWithFileProbeResist(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
recv, _ := ioutil.ReadAll(conn) recv, _ := io.ReadAll(conn)
if !bytes.Equal(recv, []byte("Hello World!")) { if !bytes.Equal(recv, []byte("Hello World!")) {
t.Error("data not equal") t.Error("data not equal")
} }

View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io/ioutil" "io"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -249,7 +249,7 @@ func TestHTTPProxyWithWebProbeResist(t *testing.T) {
t.Error("got status:", resp.Status) t.Error("got status:", resp.Status)
} }
recv, _ := ioutil.ReadAll(resp.Body) recv, _ := io.ReadAll(resp.Body)
if !bytes.Equal(recv, []byte("Hello World!")) { if !bytes.Equal(recv, []byte("Hello World!")) {
t.Error("data not equal") t.Error("data not equal")
} }
@ -296,7 +296,7 @@ func TestHTTPProxyWithHostProbeResist(t *testing.T) {
t.Error("got status:", resp.Status) t.Error("got status:", resp.Status)
} }
recv, _ := ioutil.ReadAll(resp.Body) recv, _ := io.ReadAll(resp.Body)
if !bytes.Equal(sendData, recv) { if !bytes.Equal(sendData, recv) {
t.Error("data not equal") t.Error("data not equal")
} }
@ -332,7 +332,7 @@ func TestHTTPProxyWithFileProbeResist(t *testing.T) {
t.Error("got status:", resp.Status) t.Error("got status:", resp.Status)
} }
recv, _ := ioutil.ReadAll(resp.Body) recv, _ := io.ReadAll(resp.Body)
if !bytes.Equal(recv, []byte("Hello World!")) { if !bytes.Equal(recv, []byte("Hello World!")) {
t.Error("data not equal, got:", string(recv)) t.Error("data not equal, got:", string(recv))
} }

42
kcp.go
View File

@ -15,9 +15,9 @@ import (
"github.com/go-log/log" "github.com/go-log/log"
"github.com/klauspost/compress/snappy" "github.com/klauspost/compress/snappy"
"github.com/xtaci/kcp-go/v5"
"github.com/xtaci/smux"
"github.com/xtaci/tcpraw" "github.com/xtaci/tcpraw"
"gopkg.in/xtaci/kcp-go.v4"
"gopkg.in/xtaci/smux.v1"
) )
var ( var (
@ -43,6 +43,9 @@ type KCPConfig struct {
Resend int `json:"resend"` Resend int `json:"resend"`
NoCongestion int `json:"nc"` NoCongestion int `json:"nc"`
SockBuf int `json:"sockbuf"` SockBuf int `json:"sockbuf"`
SmuxBuf int `json:"smuxbuf"`
StreamBuf int `json:"streambuf"`
SmuxVer int `json:"smuxver"`
KeepAlive int `json:"keepalive"` KeepAlive int `json:"keepalive"`
SnmpLog string `json:"snmplog"` SnmpLog string `json:"snmplog"`
SnmpPeriod int `json:"snmpperiod"` SnmpPeriod int `json:"snmpperiod"`
@ -62,6 +65,16 @@ func (c *KCPConfig) Init() {
case "fast3": case "fast3":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1 c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
} }
if c.SmuxVer <= 0 {
c.SmuxVer = 1
}
if c.SmuxBuf <= 0 {
c.SmuxBuf = c.SockBuf
}
if c.StreamBuf <= 0 {
c.StreamBuf = c.SockBuf / 2
}
log.Logf("%#v", c)
} }
var ( var (
@ -83,6 +96,9 @@ var (
Resend: 0, Resend: 0,
NoCongestion: 0, NoCongestion: 0,
SockBuf: 4194304, SockBuf: 4194304,
SmuxVer: 1,
SmuxBuf: 4194304,
StreamBuf: 2097152,
KeepAlive: 10, KeepAlive: 10,
SnmpLog: "", SnmpLog: "",
SnmpPeriod: 60, SnmpPeriod: 60,
@ -231,8 +247,14 @@ func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPCon
// stream multiplex // stream multiplex
smuxConfig := smux.DefaultConfig() smuxConfig := smux.DefaultConfig()
smuxConfig.MaxReceiveBuffer = config.SockBuf smuxConfig.Version = config.SmuxVer
smuxConfig.MaxReceiveBuffer = config.SmuxBuf
smuxConfig.MaxStreamBuffer = config.StreamBuf
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
if err := smux.VerifyConfig(smuxConfig); err != nil {
return nil, err
}
var cc net.Conn = kcpconn var cc net.Conn = kcpconn
if !config.NoComp { if !config.NoComp {
cc = newCompStreamConn(kcpconn) cc = newCompStreamConn(kcpconn)
@ -332,7 +354,9 @@ func (l *kcpListener) listenLoop() {
func (l *kcpListener) mux(conn net.Conn) { func (l *kcpListener) mux(conn net.Conn) {
smuxConfig := smux.DefaultConfig() smuxConfig := smux.DefaultConfig()
smuxConfig.MaxReceiveBuffer = l.config.SockBuf smuxConfig.Version = l.config.SmuxVer
smuxConfig.MaxReceiveBuffer = l.config.SmuxBuf
smuxConfig.MaxStreamBuffer = l.config.StreamBuf
smuxConfig.KeepAliveInterval = time.Duration(l.config.KeepAlive) * time.Second smuxConfig.KeepAliveInterval = time.Duration(l.config.KeepAlive) * time.Second
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr()) log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
@ -473,9 +497,13 @@ func (c *compStreamConn) Read(b []byte) (n int, err error) {
} }
func (c *compStreamConn) Write(b []byte) (n int, err error) { func (c *compStreamConn) Write(b []byte) (n int, err error) {
n, err = c.w.Write(b) if _, err = c.w.Write(b); err != nil {
err = c.w.Flush() return 0, err
return n, err }
if err = c.w.Flush(); err != nil {
return 0, err
}
return len(b), err
} }
func (c *compStreamConn) Close() error { func (c *compStreamConn) Close() error {

2
mux.go
View File

@ -3,7 +3,7 @@ package gost
import ( import (
"net" "net"
smux "gopkg.in/xtaci/smux.v1" smux "github.com/xtaci/smux"
) )
type muxStreamConn struct { type muxStreamConn struct {

35
node.go
View File

@ -75,28 +75,44 @@ func ParseNode(s string) (node Node, err error) {
} }
switch node.Transport { switch node.Transport {
case "tls", "mtls", "ws", "mws", "wss", "mwss", "kcp", "ssh", "quic", "ssu", "http2", "h2", "h2c", "obfs4":
case "https": case "https":
node.Protocol = "http"
node.Transport = "tls" node.Transport = "tls"
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding case "tls", "mtls":
case "http2", "h2", "h2c":
case "ws", "mws", "wss", "mwss":
case "kcp", "ssh", "quic":
case "ssu":
node.Transport = "udp"
case "ohttp", "otls", "obfs4": // obfs
case "tcp", "udp":
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
case "ohttp": // obfs-http
case "tun", "tap": // tun/tap device case "tun", "tap": // tun/tap device
case "ftcp": // fake TCP case "ftcp": // fake TCP
case "dns":
case "redu", "redirectu": // UDP tproxy
case "vsock":
default: default:
node.Transport = "tcp" node.Transport = "tcp"
} }
switch node.Protocol { switch node.Protocol {
case "http", "http2", "socks4", "socks4a", "ss", "ss2", "ssu", "sni": case "http", "http2":
case "https":
node.Protocol = "http"
case "socks4", "socks4a":
case "socks", "socks5": case "socks", "socks5":
node.Protocol = "socks5" node.Protocol = "socks5"
case "ss", "ssu":
case "ss2": // as of 2.10.1, ss2 is same as ss
node.Protocol = "ss"
case "sni":
case "tcp", "udp", "rtcp", "rudp": // port forwarding case "tcp", "udp", "rtcp", "rudp": // port forwarding
case "direct", "remote", "forward": // forwarding case "direct", "remote", "forward": // forwarding
case "redirect": // TCP transparent proxy case "red", "redirect", "redu", "redirectu": // TCP,UDP transparent proxy
case "tun", "tap": // tun/tap device case "tun", "tap": // tun/tap device
case "ftcp": // fake TCP case "ftcp": // fake TCP
case "dns", "dot", "doh":
case "relay":
default: default:
node.Protocol = "" node.Protocol = ""
} }
@ -142,13 +158,16 @@ func (node *Node) GetBool(key string) bool {
// GetInt converts node parameter value to int. // GetInt converts node parameter value to int.
func (node *Node) GetInt(key string) int { func (node *Node) GetInt(key string) int {
n, _ := strconv.Atoi(node.Values.Get(key)) n, _ := strconv.Atoi(node.Get(key))
return n return n
} }
// GetDuration converts node parameter value to time.Duration. // GetDuration converts node parameter value to time.Duration.
func (node *Node) GetDuration(key string) time.Duration { func (node *Node) GetDuration(key string) time.Duration {
d, _ := time.ParseDuration(node.Values.Get(key)) d, err := time.ParseDuration(node.Get(key))
if err != nil {
d = time.Duration(node.GetInt(key)) * time.Second
}
return d return d
} }

432
obfs.go
View File

@ -5,6 +5,8 @@ package gost
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/rand"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -18,8 +20,13 @@ import (
"github.com/go-log/log" "github.com/go-log/log"
pt "git.torproject.org/pluggable-transports/goptlib.git" pt "git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base" dissector "github.com/go-gost/tls-dissector"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4" "gitlab.com/yawning/obfs4.git/transports/base"
"gitlab.com/yawning/obfs4.git/transports/obfs4"
)
const (
maxTLSDataLen = 16384
) )
type obfsHTTPTransporter struct { type obfsHTTPTransporter struct {
@ -249,6 +256,415 @@ func (c *obfsHTTPConn) Write(b []byte) (n int, err error) {
return c.Conn.Write(b) return c.Conn.Write(b)
} }
type obfsTLSTransporter struct {
tcpTransporter
}
// ObfsTLSTransporter creates a Transporter that is used by TLS obfuscating.
func ObfsTLSTransporter() Transporter {
return &obfsTLSTransporter{}
}
func (tr *obfsTLSTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
opts := &HandshakeOptions{}
for _, option := range options {
option(opts)
}
return ClientObfsTLSConn(conn, opts.Host), nil
}
type obfsTLSListener struct {
net.Listener
}
// ObfsTLSListener creates a Listener for TLS obfuscating server.
func ObfsTLSListener(addr string) (Listener, error) {
laddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenTCP("tcp", laddr)
if err != nil {
return nil, err
}
return &obfsTLSListener{Listener: tcpKeepAliveListener{ln}}, nil
}
func (l *obfsTLSListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return ServerObfsTLSConn(conn, ""), nil
}
var (
cipherSuites = []uint16{
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8, 0xccaa, 0xc02b, 0xc02f,
0x009e, 0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067, 0xc00a,
0xc014, 0x0039, 0xc009, 0xc013, 0x0033, 0x009d, 0x009c, 0x003d,
0x003c, 0x0035, 0x002f, 0x00ff,
}
compressionMethods = []uint8{0x00}
algorithms = []uint16{
0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402,
0x0403, 0x0301, 0x0302, 0x0303, 0x0201, 0x0202, 0x0203,
}
tlsRecordTypes = []uint8{0x16, 0x14, 0x16, 0x17}
tlsVersionMinors = []uint8{0x01, 0x03, 0x03, 0x03}
ErrBadType = errors.New("bad type")
ErrBadMajorVersion = errors.New("bad major version")
ErrBadMinorVersion = errors.New("bad minor version")
ErrMaxDataLen = errors.New("bad tls data len")
)
const (
tlsRecordStateType = iota
tlsRecordStateVersion0
tlsRecordStateVersion1
tlsRecordStateLength0
tlsRecordStateLength1
tlsRecordStateData
)
type obfsTLSParser struct {
step uint8
state uint8
length uint16
}
type obfsTLSConn struct {
net.Conn
rbuf bytes.Buffer
wbuf bytes.Buffer
host string
isServer bool
handshaked chan struct{}
parser *obfsTLSParser
handshakeMutex sync.Mutex
}
func (r *obfsTLSParser) Parse(b []byte) (int, error) {
i := 0
last := 0
length := len(b)
for i < length {
ch := b[i]
switch r.state {
case tlsRecordStateType:
if tlsRecordTypes[r.step] != ch {
return 0, ErrBadType
}
r.state = tlsRecordStateVersion0
i++
case tlsRecordStateVersion0:
if ch != 0x03 {
return 0, ErrBadMajorVersion
}
r.state = tlsRecordStateVersion1
i++
case tlsRecordStateVersion1:
if ch != tlsVersionMinors[r.step] {
return 0, ErrBadMinorVersion
}
r.state = tlsRecordStateLength0
i++
case tlsRecordStateLength0:
r.length = uint16(ch) << 8
r.state = tlsRecordStateLength1
i++
case tlsRecordStateLength1:
r.length |= uint16(ch)
if r.step == 0 {
r.length = 91
} else if r.step == 1 {
r.length = 1
} else if r.length > maxTLSDataLen {
return 0, ErrMaxDataLen
}
if r.length > 0 {
r.state = tlsRecordStateData
} else {
r.state = tlsRecordStateType
r.step++
}
i++
case tlsRecordStateData:
left := uint16(length - i)
if left > r.length {
left = r.length
}
if r.step >= 2 {
skip := i - last
copy(b[last:], b[i:length])
length -= int(skip)
last += int(left)
i = last
} else {
i += int(left)
}
r.length -= left
if r.length == 0 {
if r.step < 3 {
r.step++
}
r.state = tlsRecordStateType
}
}
}
if last == 0 {
return 0, nil
} else if last < length {
length -= last
}
return length, nil
}
// ClientObfsTLSConn creates a connection for obfs-tls client.
func ClientObfsTLSConn(conn net.Conn, host string) net.Conn {
return &obfsTLSConn{
Conn: conn,
host: host,
handshaked: make(chan struct{}),
parser: &obfsTLSParser{},
}
}
// ServerObfsTLSConn creates a connection for obfs-tls server.
func ServerObfsTLSConn(conn net.Conn, host string) net.Conn {
return &obfsTLSConn{
Conn: conn,
host: host,
isServer: true,
handshaked: make(chan struct{}),
}
}
func (c *obfsTLSConn) Handshaked() bool {
select {
case <-c.handshaked:
return true
default:
return false
}
}
func (c *obfsTLSConn) Handshake(payload []byte) (err error) {
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
if c.Handshaked() {
return
}
if c.isServer {
err = c.serverHandshake()
} else {
err = c.clientHandshake(payload)
}
if err != nil {
return
}
close(c.handshaked)
return nil
}
func (c *obfsTLSConn) clientHandshake(payload []byte) error {
clientMsg := &dissector.ClientHelloMsg{
Version: tls.VersionTLS12,
SessionID: make([]byte, 32),
CipherSuites: cipherSuites,
CompressionMethods: compressionMethods,
Extensions: []dissector.Extension{
&dissector.SessionTicketExtension{
Data: payload,
},
&dissector.ServerNameExtension{
Name: c.host,
},
&dissector.ECPointFormatsExtension{
Formats: []uint8{0x01, 0x00, 0x02},
},
&dissector.SupportedGroupsExtension{
Groups: []uint16{0x001d, 0x0017, 0x0019, 0x0018},
},
&dissector.SignatureAlgorithmsExtension{
Algorithms: algorithms,
},
&dissector.EncryptThenMacExtension{},
&dissector.ExtendedMasterSecretExtension{},
},
}
clientMsg.Random.Time = uint32(time.Now().Unix())
rand.Read(clientMsg.Random.Opaque[:])
rand.Read(clientMsg.SessionID)
b, err := clientMsg.Encode()
if err != nil {
return err
}
record := &dissector.Record{
Type: dissector.Handshake,
Version: tls.VersionTLS10,
Opaque: b,
}
if _, err := record.WriteTo(c.Conn); err != nil {
return err
}
return err
}
func (c *obfsTLSConn) serverHandshake() error {
record := &dissector.Record{}
if _, err := record.ReadFrom(c.Conn); err != nil {
log.Log(err)
return err
}
if record.Type != dissector.Handshake {
return dissector.ErrBadType
}
clientMsg := &dissector.ClientHelloMsg{}
if err := clientMsg.Decode(record.Opaque); err != nil {
log.Log(err)
return err
}
for _, ext := range clientMsg.Extensions {
if ext.Type() == dissector.ExtSessionTicket {
b, err := ext.Encode()
if err != nil {
log.Log(err)
return err
}
c.rbuf.Write(b)
break
}
}
serverMsg := &dissector.ServerHelloMsg{
Version: tls.VersionTLS12,
SessionID: clientMsg.SessionID,
CipherSuite: 0xcca8,
CompressionMethod: 0x00,
Extensions: []dissector.Extension{
&dissector.RenegotiationInfoExtension{},
&dissector.ExtendedMasterSecretExtension{},
&dissector.ECPointFormatsExtension{
Formats: []uint8{0x00},
},
},
}
serverMsg.Random.Time = uint32(time.Now().Unix())
rand.Read(serverMsg.Random.Opaque[:])
b, err := serverMsg.Encode()
if err != nil {
return err
}
record = &dissector.Record{
Type: dissector.Handshake,
Version: tls.VersionTLS10,
Opaque: b,
}
if _, err := record.WriteTo(&c.wbuf); err != nil {
return err
}
record = &dissector.Record{
Type: dissector.ChangeCipherSpec,
Version: tls.VersionTLS12,
Opaque: []byte{0x01},
}
if _, err := record.WriteTo(&c.wbuf); err != nil {
return err
}
return nil
}
func (c *obfsTLSConn) Read(b []byte) (n int, err error) {
if c.isServer { // NOTE: only Write performs the handshake operation on client side.
if err = c.Handshake(nil); err != nil {
return
}
}
select {
case <-c.handshaked:
}
if c.isServer {
if c.rbuf.Len() > 0 {
return c.rbuf.Read(b)
}
record := &dissector.Record{}
if _, err = record.ReadFrom(c.Conn); err != nil {
return
}
n = copy(b, record.Opaque)
_, err = c.rbuf.Write(record.Opaque[n:])
} else {
n, err = c.Conn.Read(b)
if err != nil {
return
}
if n > 0 {
n, err = c.parser.Parse(b[:n])
}
}
return
}
func (c *obfsTLSConn) Write(b []byte) (n int, err error) {
n = len(b)
if !c.Handshaked() {
if err = c.Handshake(b); err != nil {
return
}
if !c.isServer { // the data b has been sended during handshake phase.
return
}
}
for len(b) > 0 {
data := b
if len(b) > maxTLSDataLen {
data = b[:maxTLSDataLen]
b = b[maxTLSDataLen:]
} else {
b = b[:0]
}
record := &dissector.Record{
Type: dissector.AppData,
Version: tls.VersionTLS12,
Opaque: data,
}
if c.wbuf.Len() > 0 {
record.Type = dissector.Handshake
record.WriteTo(&c.wbuf)
_, err = c.wbuf.WriteTo(c.Conn)
return
}
if _, err = record.WriteTo(c.Conn); err != nil {
return
}
}
return
}
type obfs4Context struct { type obfs4Context struct {
cf base.ClientFactory cf base.ClientFactory
cargs interface{} // type obfs4ClientArgs cargs interface{} // type obfs4ClientArgs
@ -388,6 +804,16 @@ func Obfs4Listener(addr string) (Listener, error) {
return l, nil return l, nil
} }
// TempError satisfies the net.Error interface and presents itself
// as temporary to make sure that it gets retried by the Accept loop
// in server.go.
type TempError struct {
error
}
func (e TempError) Timeout() bool { return false }
func (e TempError) Temporary() bool { return true }
func (l *obfs4Listener) Accept() (net.Conn, error) { func (l *obfs4Listener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept() conn, err := l.Listener.Accept()
if err != nil { if err != nil {
@ -396,7 +822,7 @@ func (l *obfs4Listener) Accept() (net.Conn, error) {
cc, err := obfs4ServerConn(l.addr, conn) cc, err := obfs4ServerConn(l.addr, conn)
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, err return nil, TempError{err}
} }
return cc, nil return cc, nil
} }

163
quic.go
View File

@ -1,6 +1,7 @@
package gost package gost
import ( import (
"context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
@ -12,16 +13,15 @@ import (
"time" "time"
"github.com/go-log/log" "github.com/go-log/log"
quic "github.com/lucas-clemente/quic-go" quic "github.com/quic-go/quic-go"
) )
type quicSession struct { type quicSession struct {
conn net.Conn session quic.EarlyConnection
session quic.Session
} }
func (session *quicSession) GetConn() (*quicConn, error) { func (session *quicSession) GetConn() (*quicConn, error) {
stream, err := session.session.OpenStreamSync() stream, err := session.session.OpenStreamSync(context.Background())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -33,7 +33,7 @@ func (session *quicSession) GetConn() (*quicConn, error) {
} }
func (session *quicSession) Close() error { func (session *quicSession) Close() error {
return session.session.Close() return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
} }
type quicTransporter struct { type quicTransporter struct {
@ -59,100 +59,71 @@ func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Co
option(opts) option(opts)
} }
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
tr.sessionMutex.Lock() tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock() defer tr.sessionMutex.Unlock()
session, ok := tr.sessions[addr] session, ok := tr.sessions[addr]
if !ok { if !ok {
var cc *net.UDPConn var pc net.PacketConn
cc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) pc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil { if err != nil {
return return
} }
conn = cc
if tr.config != nil && tr.config.Key != nil { if tr.config != nil && tr.config.Key != nil {
conn = &quicCipherConn{UDPConn: cc, key: tr.config.Key} pc = &quicCipherConn{PacketConn: pc, key: tr.config.Key}
} }
session = &quicSession{conn: conn} session, err = tr.initSession(udpAddr, pc)
if err != nil {
pc.Close()
return nil, err
}
tr.sessions[addr] = session tr.sessions[addr] = session
} }
return session.conn, nil
conn, err = session.GetConn()
if err != nil {
session.Close()
delete(tr.sessions, addr)
return nil, err
}
return conn, nil
} }
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
opts := &HandshakeOptions{} return conn, nil
for _, option := range options { }
option(opts)
} func (tr *quicTransporter) initSession(addr net.Addr, conn net.PacketConn) (*quicSession, error) {
config := tr.config config := tr.config
if opts.QUICConfig != nil { if config == nil {
config = opts.QUICConfig config = &QUICConfig{}
} }
if config.TLSConfig == nil { if config.TLSConfig == nil {
config.TLSConfig = &tls.Config{InsecureSkipVerify: true} config.TLSConfig = &tls.Config{InsecureSkipVerify: true}
} }
tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock()
timeout := opts.Timeout
if timeout <= 0 {
timeout = HandshakeTimeout
}
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
session, ok := tr.sessions[opts.Addr]
if session != nil && session.conn != conn {
conn.Close()
return nil, errors.New("quic: unrecognized connection")
}
if !ok || session.session == nil {
s, err := tr.initSession(opts.Addr, conn, config)
if err != nil {
conn.Close()
delete(tr.sessions, opts.Addr)
return nil, err
}
session = s
tr.sessions[opts.Addr] = session
}
cc, err := session.GetConn()
if err != nil {
session.Close()
delete(tr.sessions, opts.Addr)
return nil, err
}
return cc, nil
}
func (tr *quicTransporter) initSession(addr string, conn net.Conn, config *QUICConfig) (*quicSession, error) {
udpConn, ok := conn.(net.PacketConn)
if !ok {
return nil, errors.New("quic: wrong connection type")
}
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
quicConfig := &quic.Config{ quicConfig := &quic.Config{
HandshakeTimeout: config.Timeout, HandshakeIdleTimeout: config.Timeout,
KeepAlive: config.KeepAlive, MaxIdleTimeout: config.IdleTimeout,
IdleTimeout: config.IdleTimeout, KeepAlivePeriod: config.KeepAlivePeriod,
Versions: []quic.VersionNumber{ Versions: []quic.VersionNumber{
quic.VersionGQUIC43, quic.Version1,
quic.VersionGQUIC39, quic.Version2,
}, },
} }
session, err := quic.Dial(udpConn, udpAddr, addr, config.TLSConfig, quicConfig) session, err := quic.DialEarly(context.Background(), conn, addr, tlsConfigQUICALPN(config.TLSConfig), quicConfig)
if err != nil { if err != nil {
log.Logf("quic dial %s: %v", addr, err) log.Logf("quic dial %s: %v", addr, err)
return nil, err return nil, err
} }
return &quicSession{conn: conn, session: session}, nil return &quicSession{session: session}, nil
} }
func (tr *quicTransporter) Multiplex() bool { func (tr *quicTransporter) Multiplex() bool {
@ -161,15 +132,16 @@ func (tr *quicTransporter) Multiplex() bool {
// QUICConfig is the config for QUIC client and server // QUICConfig is the config for QUIC client and server
type QUICConfig struct { type QUICConfig struct {
TLSConfig *tls.Config TLSConfig *tls.Config
Timeout time.Duration Timeout time.Duration
KeepAlive bool KeepAlive bool
IdleTimeout time.Duration KeepAlivePeriod time.Duration
Key []byte IdleTimeout time.Duration
Key []byte
} }
type quicListener struct { type quicListener struct {
ln quic.Listener ln quic.EarlyListener
connChan chan net.Conn connChan chan net.Conn
errChan chan error errChan chan error
} }
@ -180,39 +152,41 @@ func QUICListener(addr string, config *QUICConfig) (Listener, error) {
config = &QUICConfig{} config = &QUICConfig{}
} }
quicConfig := &quic.Config{ quicConfig := &quic.Config{
HandshakeTimeout: config.Timeout, HandshakeIdleTimeout: config.Timeout,
KeepAlive: config.KeepAlive, KeepAlivePeriod: config.KeepAlivePeriod,
IdleTimeout: config.IdleTimeout, MaxIdleTimeout: config.IdleTimeout,
Versions: []quic.VersionNumber{
quic.Version1,
quic.Version2,
},
} }
tlsConfig := config.TLSConfig tlsConfig := config.TLSConfig
if tlsConfig == nil { if tlsConfig == nil {
tlsConfig = DefaultTLSConfig tlsConfig = DefaultTLSConfig
} }
var conn net.PacketConn var conn net.PacketConn
udpAddr, err := net.ResolveUDPAddr("udp", addr) udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
lconn, err := net.ListenUDP("udp", udpAddr) conn, err = net.ListenUDP("udp", udpAddr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
conn = lconn
if config.Key != nil { if config.Key != nil {
conn = &quicCipherConn{UDPConn: lconn, key: config.Key} conn = &quicCipherConn{PacketConn: conn, key: config.Key}
} }
ln, err := quic.Listen(conn, tlsConfig, quicConfig) ln, err := quic.ListenEarly(conn, tlsConfigQUICALPN(tlsConfig), quicConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
l := &quicListener{ l := &quicListener{
ln: ln, ln: *ln,
connChan: make(chan net.Conn, 1024), connChan: make(chan net.Conn, 1024),
errChan: make(chan error, 1), errChan: make(chan error, 1),
} }
@ -223,7 +197,7 @@ func QUICListener(addr string, config *QUICConfig) (Listener, error) {
func (l *quicListener) listenLoop() { func (l *quicListener) listenLoop() {
for { for {
session, err := l.ln.Accept() session, err := l.ln.Accept(context.Background())
if err != nil { if err != nil {
log.Log("[quic] accept:", err) log.Log("[quic] accept:", err)
l.errChan <- err l.errChan <- err
@ -234,15 +208,15 @@ func (l *quicListener) listenLoop() {
} }
} }
func (l *quicListener) sessionLoop(session quic.Session) { func (l *quicListener) sessionLoop(session quic.Connection) {
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr()) log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr())
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr()) defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr())
for { for {
stream, err := session.AcceptStream() stream, err := session.AcceptStream(context.Background())
if err != nil { if err != nil {
log.Log("[quic] accept stream:", err) log.Log("[quic] accept stream:", err)
session.Close() session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
return return
} }
@ -291,12 +265,12 @@ func (c *quicConn) RemoteAddr() net.Addr {
} }
type quicCipherConn struct { type quicCipherConn struct {
*net.UDPConn net.PacketConn
key []byte key []byte
} }
func (conn *quicCipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) { func (conn *quicCipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) {
n, addr, err = conn.UDPConn.ReadFrom(data) n, addr, err = conn.PacketConn.ReadFrom(data)
if err != nil { if err != nil {
return return
} }
@ -316,7 +290,7 @@ func (conn *quicCipherConn) WriteTo(data []byte, addr net.Addr) (n int, err erro
return return
} }
_, err = conn.UDPConn.WriteTo(b, addr) _, err = conn.PacketConn.WriteTo(b, addr)
if err != nil { if err != nil {
return return
} }
@ -362,3 +336,12 @@ func (conn *quicCipherConn) decrypt(data []byte) ([]byte, error) {
nonce, ciphertext := data[:nonceSize], data[nonceSize:] nonce, ciphertext := data[:nonceSize], data[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil) return gcm.Open(nil, nonce, ciphertext, nil)
} }
func tlsConfigQUICALPN(tlsConfig *tls.Config) *tls.Config {
if tlsConfig == nil {
panic("quic: tlsconfig is nil")
}
tlsConfigQUIC := tlsConfig.Clone()
tlsConfigQUIC.NextProtos = []string{"http/3", "quic/v1"}
return tlsConfigQUIC
}

View File

@ -1,13 +1,18 @@
// +build !windows //go:build linux
// +build linux
package gost package gost
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sync"
"syscall" "syscall"
"time"
"github.com/LiamHaworth/go-tproxy"
"github.com/go-log/log" "github.com/go-log/log"
) )
@ -15,7 +20,7 @@ type tcpRedirectHandler struct {
options *HandlerOptions options *HandlerOptions
} }
// TCPRedirectHandler creates a server Handler for TCP redirect server. // TCPRedirectHandler creates a server Handler for TCP transparent server.
func TCPRedirectHandler(opts ...HandlerOption) Handler { func TCPRedirectHandler(opts ...HandlerOption) Handler {
h := &tcpRedirectHandler{} h := &tcpRedirectHandler{}
h.Init(opts...) h.Init(opts...)
@ -49,7 +54,8 @@ func (h *tcpRedirectHandler) Handle(c net.Conn) {
log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr) log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr)
cc, err := h.options.Chain.Dial(dstAddr.String(), cc, err := h.options.Chain.DialContext(context.Background(),
"tcp", dstAddr.String(),
RetryChainOption(h.options.Retries), RetryChainOption(h.options.Retries),
TimeoutChainOption(h.options.Timeout), TimeoutChainOption(h.options.Timeout),
) )
@ -97,3 +103,140 @@ func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Add
} }
return return
} }
type udpRedirectHandler struct {
options *HandlerOptions
}
// UDPRedirectHandler creates a server Handler for UDP transparent server.
func UDPRedirectHandler(opts ...HandlerOption) Handler {
h := &udpRedirectHandler{}
h.Init(opts...)
return h
}
func (h *udpRedirectHandler) Init(options ...HandlerOption) {
if h.options == nil {
h.options = &HandlerOptions{}
}
for _, opt := range options {
opt(h.options)
}
}
func (h *udpRedirectHandler) Handle(conn net.Conn) {
defer conn.Close()
raddr, ok := conn.LocalAddr().(*net.UDPAddr)
if !ok {
log.Log("[red-udp] wrong connection type")
return
}
cc, err := h.options.Chain.DialContext(context.Background(),
"udp", raddr.String(),
RetryChainOption(h.options.Retries),
TimeoutChainOption(h.options.Timeout),
)
if err != nil {
log.Logf("[red-udp] %s - %s : %s", conn.RemoteAddr(), raddr, err)
return
}
defer cc.Close()
log.Logf("[red-udp] %s <-> %s", conn.RemoteAddr(), raddr)
transport(conn, cc)
log.Logf("[red-udp] %s >-< %s", conn.RemoteAddr(), raddr)
}
type udpRedirectListener struct {
*net.UDPConn
config *UDPListenConfig
}
// UDPRedirectListener creates a Listener for UDP transparent proxy server.
func UDPRedirectListener(addr string, cfg *UDPListenConfig) (Listener, error) {
laddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
ln, err := tproxy.ListenUDP("udp", laddr)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &UDPListenConfig{}
}
return &udpRedirectListener{
UDPConn: ln,
config: cfg,
}, nil
}
func (l *udpRedirectListener) Accept() (conn net.Conn, err error) {
b := make([]byte, mediumBufferSize)
n, raddr, dstAddr, err := tproxy.ReadFromUDP(l.UDPConn, b)
if err != nil {
log.Logf("[red-udp] %s : %s", l.Addr(), err)
return
}
log.Logf("[red-udp] %s: %s -> %s", l.Addr(), raddr, dstAddr)
c, err := tproxy.DialUDP("udp", dstAddr, raddr)
if err != nil {
log.Logf("[red-udp] %s -> %s : %s", raddr, dstAddr, err)
return
}
ttl := l.config.TTL
if ttl <= 0 {
ttl = defaultTTL
}
conn = &udpRedirectServerConn{
Conn: c,
buf: b[:n],
ttl: ttl,
}
return
}
func (l *udpRedirectListener) Addr() net.Addr {
return l.UDPConn.LocalAddr()
}
type udpRedirectServerConn struct {
net.Conn
buf []byte
ttl time.Duration
once sync.Once
}
func (c *udpRedirectServerConn) Read(b []byte) (n int, err error) {
if c.ttl > 0 {
c.SetReadDeadline(time.Now().Add(c.ttl))
defer c.SetReadDeadline(time.Time{})
}
c.once.Do(func() {
n = copy(b, c.buf)
c.buf = nil
})
if n == 0 {
n, err = c.Conn.Read(b)
}
return
}
func (c *udpRedirectServerConn) Write(b []byte) (n int, err error) {
if c.ttl > 0 {
c.SetWriteDeadline(time.Now().Add(c.ttl))
defer c.SetWriteDeadline(time.Time{})
}
return c.Conn.Write(b)
}

57
redirect_other.go Normal file
View File

@ -0,0 +1,57 @@
//go:build !linux
// +build !linux
package gost
import (
"errors"
"net"
"github.com/go-log/log"
)
type tcpRedirectHandler struct {
options *HandlerOptions
}
// TCPRedirectHandler creates a server Handler for TCP redirect server.
func TCPRedirectHandler(opts ...HandlerOption) Handler {
h := &tcpRedirectHandler{
options: &HandlerOptions{
Chain: new(Chain),
},
}
for _, opt := range opts {
opt(h.options)
}
return h
}
func (h *tcpRedirectHandler) Init(options ...HandlerOption) {
log.Log("[red-tcp] TCP redirect is not available on the Windows platform")
}
func (h *tcpRedirectHandler) Handle(c net.Conn) {
log.Log("[red-tcp] TCP redirect is not available on the Windows platform")
c.Close()
}
type udpRedirectHandler struct{}
// UDPRedirectHandler creates a server Handler for UDP transparent server.
func UDPRedirectHandler(opts ...HandlerOption) Handler {
return &udpRedirectHandler{}
}
func (h *udpRedirectHandler) Init(options ...HandlerOption) {
}
func (h *udpRedirectHandler) Handle(conn net.Conn) {
log.Log("[red-udp] UDP redirect is not available on the Windows platform")
conn.Close()
}
// UDPRedirectListener creates a Listener for UDP transparent proxy server.
func UDPRedirectListener(addr string, cfg *UDPListenConfig) (Listener, error) {
return nil, errors.New("UDP redirect is not available on the Windows platform")
}

View File

@ -1,35 +0,0 @@
// +build windows
package gost
import (
"net"
"github.com/go-log/log"
)
type tcpRedirectHandler struct {
options *HandlerOptions
}
// TCPRedirectHandler creates a server Handler for TCP redirect server.
func TCPRedirectHandler(opts ...HandlerOption) Handler {
h := &tcpRedirectHandler{
options: &HandlerOptions{
Chain: new(Chain),
},
}
for _, opt := range opts {
opt(h.options)
}
return h
}
func (h *tcpRedirectHandler) Init(options ...HandlerOption) {
log.Log("[red-tcp] TCP redirect is not available on the Windows platform")
}
func (h *tcpRedirectHandler) Handle(c net.Conn) {
log.Log("[red-tcp] TCP redirect is not available on the Windows platform")
c.Close()
}

369
relay.go Normal file
View File

@ -0,0 +1,369 @@
package gost
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/url"
"strconv"
"sync"
"time"
"github.com/go-gost/relay"
"github.com/go-log/log"
)
type relayConnector struct {
user *url.Userinfo
remoteAddr string
}
// RelayConnector creates a Connector for TCP/UDP data relay.
func RelayConnector(user *url.Userinfo) Connector {
return &relayConnector{
user: user,
}
}
func (c *relayConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return conn, nil
}
func (c *relayConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
opts := &ConnectOptions{}
for _, option := range options {
option(opts)
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = ConnectTimeout
}
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
var udp bool
if network == "udp" || network == "udp4" || network == "udp6" {
udp = true
}
req := &relay.Request{
Version: relay.Version1,
}
if udp {
req.Flags |= relay.FUDP
}
if c.user != nil {
pwd, _ := c.user.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.user.Username(),
Password: pwd,
})
}
if address != "" {
host, port, _ := net.SplitHostPort(address)
nport, _ := strconv.ParseUint(port, 10, 16)
if host == "" {
host = net.IPv4zero.String()
}
if nport > 0 {
var atype uint8
ip := net.ParseIP(host)
if ip == nil {
atype = relay.AddrDomain
} else if ip.To4() == nil {
atype = relay.AddrIPv6
} else {
atype = relay.AddrIPv4
}
req.Features = append(req.Features, &relay.AddrFeature{
AType: atype,
Host: host,
Port: uint16(nport),
})
}
}
rc := &relayConn{
udp: udp,
Conn: conn,
}
// write the header at once.
if opts.NoDelay {
if _, err := req.WriteTo(rc); err != nil {
return nil, err
}
} else {
if _, err := req.WriteTo(&rc.wbuf); err != nil {
return nil, err
}
}
return rc, nil
}
type relayHandler struct {
*baseForwardHandler
}
// RelayHandler creates a server Handler for TCP/UDP relay server.
func RelayHandler(raddr string, opts ...HandlerOption) Handler {
h := &relayHandler{
baseForwardHandler: &baseForwardHandler{
raddr: raddr,
group: NewNodeGroup(),
options: &HandlerOptions{},
},
}
for _, opt := range opts {
opt(h.options)
}
return h
}
func (h *relayHandler) Init(options ...HandlerOption) {
h.baseForwardHandler.Init(options...)
}
func (h *relayHandler) Handle(conn net.Conn) {
defer conn.Close()
req := &relay.Request{}
if _, err := req.ReadFrom(conn); err != nil {
log.Logf("[relay] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
if req.Version != relay.Version1 {
log.Logf("[relay] %s - %s : bad version", conn.RemoteAddr(), conn.LocalAddr())
return
}
var user, pass string
var raddr string
for _, f := range req.Features {
if f.Type() == relay.FeatureUserAuth {
feature := f.(*relay.UserAuthFeature)
user, pass = feature.Username, feature.Password
}
if f.Type() == relay.FeatureAddr {
feature := f.(*relay.AddrFeature)
raddr = net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port)))
}
}
resp := &relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
}
if h.options.Authenticator != nil && !h.options.Authenticator.Authenticate(user, pass) {
resp.Status = relay.StatusUnauthorized
resp.WriteTo(conn)
log.Logf("[relay] %s -> %s : %s unauthorized", conn.RemoteAddr(), conn.LocalAddr(), user)
return
}
if raddr != "" {
if len(h.group.Nodes()) > 0 {
resp.Status = relay.StatusForbidden
resp.WriteTo(conn)
log.Logf("[relay] %s -> %s : relay to %s is forbidden",
conn.RemoteAddr(), conn.LocalAddr(), raddr)
return
}
} else {
if len(h.group.Nodes()) == 0 {
resp.Status = relay.StatusBadRequest
resp.WriteTo(conn)
log.Logf("[relay] %s -> %s : bad request, target addr is needed",
conn.RemoteAddr(), conn.LocalAddr())
return
}
}
udp := (req.Flags & relay.FUDP) == relay.FUDP
retries := 1
if h.options.Chain != nil && h.options.Chain.Retries > 0 {
retries = h.options.Chain.Retries
}
if h.options.Retries > 0 {
retries = h.options.Retries
}
network := "tcp"
if udp {
network = "udp"
}
if !Can(network, raddr, h.options.Whitelist, h.options.Blacklist) {
resp.Status = relay.StatusForbidden
resp.WriteTo(conn)
log.Logf("[relay] %s -> %s : relay to %s is forbidden",
conn.RemoteAddr(), conn.LocalAddr(), raddr)
return
}
ctx := context.TODO()
var cc net.Conn
var node Node
var err error
for i := 0; i < retries; i++ {
if len(h.group.Nodes()) > 0 {
node, err = h.group.Next()
if err != nil {
resp.Status = relay.StatusServiceUnavailable
resp.WriteTo(conn)
log.Logf("[relay] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
raddr = node.Addr
}
log.Logf("[relay] %s -> %s -> %s", conn.RemoteAddr(), conn.LocalAddr(), raddr)
cc, err = h.options.Chain.DialContext(ctx,
network, raddr,
RetryChainOption(h.options.Retries),
TimeoutChainOption(h.options.Timeout),
)
if err != nil {
log.Logf("[relay] %s -> %s : %s", conn.RemoteAddr(), raddr, err)
node.MarkDead()
} else {
break
}
}
if err != nil {
resp.Status = relay.StatusServiceUnavailable
resp.WriteTo(conn)
return
}
node.ResetDead()
defer cc.Close()
sc := &relayConn{
Conn: conn,
isServer: true,
udp: udp,
}
resp.WriteTo(&sc.wbuf)
conn = sc
log.Logf("[relay] %s <-> %s", conn.RemoteAddr(), raddr)
transport(conn, cc)
log.Logf("[relay] %s >-< %s", conn.RemoteAddr(), raddr)
}
type relayConn struct {
net.Conn
isServer bool
udp bool
wbuf bytes.Buffer
once sync.Once
headerSent bool
}
func (c *relayConn) Read(b []byte) (n int, err error) {
c.once.Do(func() {
if c.isServer {
return
}
resp := new(relay.Response)
_, err = resp.ReadFrom(c.Conn)
if err != nil {
return
}
if resp.Version != relay.Version1 {
err = relay.ErrBadVersion
return
}
if resp.Status != relay.StatusOK {
err = fmt.Errorf("status %d", resp.Status)
return
}
})
if err != nil {
log.Logf("[relay] %s <- %s: %s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), err)
return
}
if !c.udp {
return c.Conn.Read(b)
}
var bb [2]byte
_, err = io.ReadFull(c.Conn, bb[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dlen {
return io.ReadFull(c.Conn, b[:dlen])
}
buf := make([]byte, dlen)
_, err = io.ReadFull(c.Conn, buf)
n = copy(b, buf)
return
}
func (c *relayConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
n, err = c.Read(b)
addr = c.Conn.RemoteAddr()
return
}
func (c *relayConn) Write(b []byte) (n int, err error) {
if len(b) > 0xFFFF {
err = errors.New("write: data maximum exceeded")
return
}
n = len(b) // force byte length consistent
if c.wbuf.Len() > 0 {
if c.udp {
var bb [2]byte
binary.BigEndian.PutUint16(bb[:2], uint16(len(b)))
c.wbuf.Write(bb[:])
c.headerSent = true
}
c.wbuf.Write(b) // append the data to the cached header
// _, err = c.Conn.Write(c.wbuf.Bytes())
// c.wbuf.Reset()
_, err = c.wbuf.WriteTo(c.Conn)
return
}
if !c.udp {
return c.Conn.Write(b)
}
if !c.headerSent {
c.headerSent = true
b2 := make([]byte, len(b)+2)
copy(b2, b)
_, err = c.Conn.Write(b2)
return
}
nsize := 2 + len(b)
var buf []byte
if nsize <= mediumBufferSize {
buf = mPool.Get().([]byte)
defer mPool.Put(buf)
} else {
buf = make([]byte, nsize)
}
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
_, err = c.Conn.Write(buf[:nsize])
return
}
func (c *relayConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return c.Write(b)
}

File diff suppressed because it is too large Load Diff

View File

@ -24,10 +24,10 @@ var dnsTests = []struct {
{NameServer{Addr: "1.1.1.1:853", Protocol: "tls", Hostname: "cloudflare-dns.com"}, "github.com", true}, {NameServer{Addr: "1.1.1.1:853", Protocol: "tls", Hostname: "cloudflare-dns.com"}, "github.com", true},
{NameServer{Addr: "https://cloudflare-dns.com/dns-query", Protocol: "https"}, "github.com", true}, {NameServer{Addr: "https://cloudflare-dns.com/dns-query", Protocol: "https"}, "github.com", true},
{NameServer{Addr: "https://1.0.0.1/dns-query", Protocol: "https"}, "github.com", true}, {NameServer{Addr: "https://1.0.0.1/dns-query", Protocol: "https"}, "github.com", true},
{NameServer{Addr: "1.1.1.1:12345", Timeout: 1 * time.Second}, "github.com", false}, {NameServer{Addr: "1.1.1.1:12345"}, "github.com", false},
{NameServer{Addr: "1.1.1.1:12345", Protocol: "tcp", Timeout: 1 * time.Second}, "github.com", false}, {NameServer{Addr: "1.1.1.1:12345", Protocol: "tcp"}, "github.com", false},
{NameServer{Addr: "1.1.1.1:12345", Protocol: "tls", Timeout: 1 * time.Second}, "github.com", false}, {NameServer{Addr: "1.1.1.1:12345", Protocol: "tls"}, "github.com", false},
{NameServer{Addr: "https://1.0.0.1:12345/dns-query", Protocol: "https", Timeout: 1 * time.Second}, "github.com", false}, {NameServer{Addr: "https://1.0.0.1:12345/dns-query", Protocol: "https"}, "github.com", false},
} }
func dnsResolverRoundtrip(t *testing.T, r Resolver, host string) error { func dnsResolverRoundtrip(t *testing.T, r Resolver, host string) error {
@ -45,13 +45,13 @@ func TestDNSResolver(t *testing.T) {
tc := tc tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
ns := tc.ns ns := tc.ns
if err := ns.Init(); err != nil {
t.Error(err)
}
t.Log(ns) t.Log(ns)
r := NewResolver(0, ns) r := NewResolver(0, ns)
resolv := r.(*resolver) resolv := r.(*resolver)
resolv.domain = "com" resolv.domain = "com"
if err := r.Init(); err != nil {
t.Error("got error:", err)
}
err := dnsResolverRoundtrip(t, r, tc.host) err := dnsResolverRoundtrip(t, r, tc.host)
if err != nil { if err != nil {
if tc.pass { if tc.pass {
@ -85,6 +85,7 @@ var resolverCacheTests = []struct {
[]net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}}, []net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}},
} }
/*
func TestResolverCache(t *testing.T) { func TestResolverCache(t *testing.T) {
isEqual := func(a, b []net.IP) bool { isEqual := func(a, b []net.IP) bool {
if a == nil && b == nil { if a == nil && b == nil {
@ -106,8 +107,8 @@ func TestResolverCache(t *testing.T) {
tc := tc tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
r := newResolver(tc.ttl) r := newResolver(tc.ttl)
r.storeCache(tc.name, tc.ips, tc.ttl) r.cache.storeCache(tc.name, tc.ips, tc.ttl)
ips := r.loadCache(tc.name, tc.ttl) ips := r.cache.loadCache(tc.name, tc.ttl)
if !isEqual(tc.result, ips) { if !isEqual(tc.result, ips) {
t.Error("unexpected cache value:", tc.name, ips, tc.ttl) t.Error("unexpected cache value:", tc.name, ips, tc.ttl)
@ -115,6 +116,7 @@ func TestResolverCache(t *testing.T) {
}) })
} }
} }
*/
var resolverReloadTests = []struct { var resolverReloadTests = []struct {
r io.Reader r io.Reader
@ -167,7 +169,6 @@ var resolverReloadTests = []struct {
ns: &NameServer{ ns: &NameServer{
Protocol: "udp", Protocol: "udp",
Addr: "1.1.1.1", Addr: "1.1.1.1",
Timeout: 10 * time.Second,
}, },
timeout: 10 * time.Second, timeout: 10 * time.Second,
stopped: true, stopped: true,
@ -219,9 +220,9 @@ func TestResolverReload(t *testing.T) {
t.Error(err) t.Error(err)
} }
t.Log(r.String()) t.Log(r.String())
if r.TTL != tc.ttl { if r.TTL() != tc.ttl {
t.Errorf("ttl value should be %v, got %v", t.Errorf("ttl value should be %v, got %v",
tc.ttl, r.TTL) tc.ttl, r.TTL())
} }
if r.Period() != tc.period { if r.Period() != tc.period {
t.Errorf("period value should be %v, got %v", t.Errorf("period value should be %v, got %v",
@ -233,13 +234,13 @@ func TestResolverReload(t *testing.T) {
} }
var ns *NameServer var ns *NameServer
if len(r.Servers) > 0 { if len(r.servers) > 0 {
ns = &r.Servers[0] ns = &r.servers[0]
} }
if !compareNameServer(ns, tc.ns) { if !compareNameServer(ns, tc.ns) {
t.Errorf("nameserver not equal, should be %v, got %v", t.Errorf("nameserver not equal, should be %v, got %v",
tc.ns, r.Servers) tc.ns, r.servers)
} }
if tc.stopped { if tc.stopped {
@ -265,6 +266,5 @@ func compareNameServer(n1, n2 *NameServer) bool {
} }
return n1.Addr == n2.Addr && return n1.Addr == n2.Addr &&
n1.Hostname == n2.Hostname && n1.Hostname == n2.Hostname &&
n1.Protocol == n2.Protocol && n1.Protocol == n2.Protocol
n1.Timeout == n2.Timeout
} }

View File

@ -4,15 +4,18 @@ import (
"errors" "errors"
"math/rand" "math/rand"
"net" "net"
"sort"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/go-log/log"
) )
var ( var (
// ErrNoneAvailable indicates there is no node available. // ErrNoneAvailable indicates there is no node available.
ErrNoneAvailable = errors.New("none available") ErrNoneAvailable = errors.New("none node available")
) )
// NodeSelector as a mechanism to pick nodes and mark their status. // NodeSelector as a mechanism to pick nodes and mark their status.
@ -205,6 +208,94 @@ func (f *FailFilter) String() string {
return "fail" return "fail"
} }
// FastestFilter filter the fastest node
type FastestFilter struct {
mu sync.Mutex
pinger *net.Dialer
pingResult map[int]int
pingResultTTL map[int]int64
topCount int
}
func NewFastestFilter(pingTimeOut int, topCount int) *FastestFilter {
if pingTimeOut == 0 {
pingTimeOut = 3000 // 3s
}
return &FastestFilter{
mu: sync.Mutex{},
pinger: &net.Dialer{Timeout: time.Millisecond * time.Duration(pingTimeOut)},
pingResult: make(map[int]int, 0),
pingResultTTL: make(map[int]int64, 0),
topCount: topCount,
}
}
func (f *FastestFilter) Filter(nodes []Node) []Node {
// disabled
if f.topCount == 0 {
return nodes
}
// get latency with ttl cache
now := time.Now().Unix()
var getNodeLatency = func(node Node) int {
f.mu.Lock()
defer f.mu.Unlock()
if f.pingResultTTL[node.ID] < now {
f.pingResultTTL[node.ID] = now + 5 // tmp
// get latency
go func(node Node) {
latency := f.doTcpPing(node.Addr)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
ttl := 300 - int64(120*r.Float64())
f.mu.Lock()
defer f.mu.Unlock()
f.pingResult[node.ID] = latency
f.pingResultTTL[node.ID] = now + ttl
}(node)
}
return f.pingResult[node.ID]
}
// sort
sort.Slice(nodes, func(i, j int) bool {
return getNodeLatency(nodes[i]) < getNodeLatency(nodes[j])
})
// split
if len(nodes) <= f.topCount {
return nodes
}
return nodes[0:f.topCount]
}
func (f *FastestFilter) String() string {
return "fastest"
}
// doTcpPing
func (f *FastestFilter) doTcpPing(address string) int {
start := time.Now()
conn, err := f.pinger.Dial("tcp", address)
elapsed := time.Since(start)
if err == nil {
_ = conn.Close()
}
latency := int(elapsed.Milliseconds())
log.Logf("pingDoTCP: %s, latency: %d", address, latency)
return latency
}
// InvalidFilter filters the invalid node. // InvalidFilter filters the invalid node.
// A node is invalid if its port is invalid (negative or zero value). // A node is invalid if its port is invalid (negative or zero value).
type InvalidFilter struct{} type InvalidFilter struct{}

View File

@ -127,6 +127,30 @@ func TestFailFilter(t *testing.T) {
} }
} }
func TestFastestFilter(t *testing.T) {
nodes := []Node{
Node{ID: 1, marker: &failMarker{}, Addr: "1.0.0.1:80"},
Node{ID: 2, marker: &failMarker{}, Addr: "1.0.0.2:80"},
Node{ID: 3, marker: &failMarker{}, Addr: "1.0.0.3:80"},
}
filter := NewFastestFilter(0, 2)
var print = func(nodes []Node) []string {
var rows []string
for _, node := range nodes {
rows = append(rows, node.Addr)
}
return rows
}
result1 := filter.Filter(nodes)
t.Logf("result 1: %+v", print(result1))
time.Sleep(time.Second)
result2 := filter.Filter(nodes)
t.Logf("result 2: %+v", print(result2))
}
func TestSelector(t *testing.T) { func TestSelector(t *testing.T) {
nodes := []Node{ nodes := []Node{
Node{ID: 1, marker: &failMarker{}}, Node{ID: 1, marker: &failMarker{}},

View File

@ -102,37 +102,6 @@ type Listener interface {
net.Listener net.Listener
} }
type tcpListener struct {
net.Listener
}
// TCPListener creates a Listener for TCP proxy server.
func TCPListener(addr string) (Listener, error) {
laddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenTCP("tcp", laddr)
if err != nil {
return nil, err
}
return &tcpListener{Listener: tcpKeepAliveListener{ln}}, nil
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(KeepAliveTime)
return tc, nil
}
func transport(rw1, rw2 io.ReadWriter) error { func transport(rw1, rw2 io.ReadWriter) error {
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
@ -143,11 +112,11 @@ func transport(rw1, rw2 io.ReadWriter) error {
errc <- copyBuffer(rw2, rw1) errc <- copyBuffer(rw2, rw1)
}() }()
err := <-errc if err := <-errc; err != nil && err != io.EOF {
if err != nil && err == io.EOF { return err
err = nil
} }
return err
return nil
} }
func copyBuffer(dst io.Writer, src io.Reader) error { func copyBuffer(dst io.Writer, src io.Reader) error {

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package gost package gost

View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
package gost package gost
@ -8,7 +9,7 @@ import (
"syscall" "syscall"
"github.com/go-log/log" "github.com/go-log/log"
"gopkg.in/xtaci/kcp-go.v4" "github.com/xtaci/kcp-go/v5"
) )
func kcpSigHandler() { func kcpSigHandler() {

34
snap/snapcraft.yaml Normal file
View File

@ -0,0 +1,34 @@
name: gost
base: core20
version: '2.12.0'
summary: A simple security tunnel written in golang
description: |
Project: https://github.com/ginuerzh/gost
Wiki: https://v2.gost.run
icon: gost.png
website: https://v2.gost.run
license: MIT
confinement: strict
grade: stable
parts:
gost:
plugin: go
go-channel: latest/stable
source: https://github.com/ginuerzh/gost
source-subdir: cmd/gost
source-type: git
source-tag: v2.12.0
build-packages:
- gcc
apps:
gost:
command: bin/gost
plugs:
- home
- network
- network-bind

View File

@ -1,43 +0,0 @@
name: gost
type: app
version: '2.9.1'
title: GO Simple Tunnel
summary: A simple security tunnel written in golang
description: |
https://github.com/ginuerzh/gost
confinement: strict
grade: stable
base: core18
license: MIT
parts:
gost:
plugin: nil
build-snaps: [go/1.13/stable]
source: https://github.com/ginuerzh/gost.git
source-subdir: cmd/gost
source-type: git
source-branch: '2.9'
build-packages:
- build-essential
override-build: |
set -ex
echo "Starting override-build:"
pwd
cd $SNAPCRAFT_PART_BUILD
GO111MODULE=on CGO_ENABLED=0 go build --ldflags="-s -w"
./gost -V
echo "Installing to ${SNAPCRAFT_PART_INSTALL}..."
install -d $SNAPCRAFT_PART_INSTALL/bin
cp -v gost $SNAPCRAFT_PART_INSTALL/bin/
echo "Override-build done!"
apps:
gost:
command: bin/gost
plugs:
- home
- network
- network-bind

30
sni.go
View File

@ -5,6 +5,7 @@ package gost
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "errors"
@ -16,7 +17,8 @@ import (
"strings" "strings"
"sync" "sync"
dissector "github.com/ginuerzh/tls-dissector" "github.com/asaskevich/govalidator"
dissector "github.com/go-gost/tls-dissector"
"github.com/go-log/log" "github.com/go-log/log"
) )
@ -29,8 +31,17 @@ func SNIConnector(host string) Connector {
return &sniConnector{host: host} return &sniConnector{host: host}
} }
func (c *sniConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *sniConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return &sniClientConn{addr: addr, host: c.host, Conn: conn}, nil return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *sniConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
return &sniClientConn{addr: address, host: c.host, Conn: conn}, nil
} }
type sniHandler struct { type sniHandler struct {
@ -76,6 +87,10 @@ func (h *sniHandler) Handle(conn net.Conn) {
return return
} }
if !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) {
req.URL.Scheme = "http"
}
handler := &httpHandler{options: h.options} handler := &httpHandler{options: h.options}
handler.Init() handler.Init()
handler.handleRequest(conn, req) handler.handleRequest(conn, req)
@ -260,7 +275,7 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
clientHello := &dissector.ClientHelloHandshake{} clientHello := &dissector.ClientHelloMsg{}
if err := clientHello.Decode(record.Opaque); err != nil { if err := clientHello.Decode(record.Opaque); err != nil {
return nil, "", err return nil, "", err
} }
@ -270,7 +285,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
for _, ext := range clientHello.Extensions { for _, ext := range clientHello.Extensions {
if ext.Type() == 0xFFFE { if ext.Type() == 0xFFFE {
if host, err = decodeServerName(string(ext.Bytes()[4:])); err == nil { b, _ := ext.Encode()
if host, err = decodeServerName(string(b)); err == nil {
continue continue
} }
} }
@ -286,8 +302,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
host = snExtension.Name host = snExtension.Name
} }
if isClient { if isClient {
clientHello.Extensions = append(clientHello.Extensions, e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))) clientHello.Extensions = append(clientHello.Extensions, e)
} }
if host != "" { if host != "" {
snExtension.Name = host snExtension.Name = host

View File

@ -7,7 +7,7 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -69,7 +69,7 @@ func sniRoundtrip(client *Client, server *Server, targetURL string, data []byte)
return errors.New(resp.Status) return errors.New(resp.Status)
} }
recv, err := ioutil.ReadAll(resp.Body) recv, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return return
} }

11
sockopts_linux.go Normal file
View File

@ -0,0 +1,11 @@
package gost
import "syscall"
func setSocketMark(fd int, value int) (e error) {
return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, value)
}
func setSocketInterface(fd int, value string) (e error) {
return syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, value)
}

12
sockopts_other.go Normal file
View File

@ -0,0 +1,12 @@
//go:build !linux
// +build !linux
package gost
func setSocketMark(fd int, value int) (e error) {
return nil
}
func setSocketInterface(fd int, value string) (e error) {
return nil
}

329
socks.go
View File

@ -2,6 +2,7 @@ package gost
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -9,13 +10,14 @@ import (
"net" "net"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
"github.com/ginuerzh/gosocks4" "github.com/go-gost/gosocks4"
"github.com/ginuerzh/gosocks5" "github.com/go-gost/gosocks5"
"github.com/go-log/log" "github.com/go-log/log"
smux "gopkg.in/xtaci/smux.v1" smux "github.com/xtaci/smux"
) )
const ( const (
@ -35,6 +37,10 @@ const (
CmdUDPTun uint8 = 0xF3 CmdUDPTun uint8 = 0xF3
) )
var (
_ net.PacketConn = (*socks5UDPTunnelConn)(nil)
)
type clientSelector struct { type clientSelector struct {
methods []uint8 methods []uint8
User *url.Userinfo User *url.Userinfo
@ -201,7 +207,17 @@ func SOCKS5Connector(user *url.Userinfo) Connector {
return &socks5Connector{User: user} return &socks5Connector{User: user}
} }
func (c *socks5Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks5Connector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *socks5Connector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
cnr := &socks5UDPTunConnector{User: c.User}
return cnr.ConnectContext(ctx, conn, network, address, options...)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -229,7 +245,7 @@ func (c *socks5Connector) Connect(conn net.Conn, addr string, options ...Connect
} }
conn = cc conn = cc
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -273,7 +289,16 @@ func SOCKS5BindConnector(user *url.Userinfo) Connector {
return &socks5BindConnector{User: user} return &socks5BindConnector{User: user}
} }
func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks5BindConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *socks5BindConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -301,7 +326,7 @@ func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...Con
} }
conn = cc conn = cc
laddr, err := net.ResolveTCPAddr("tcp", addr) laddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil { if err != nil {
log.Log(err) log.Log(err)
return nil, err return nil, err
@ -331,8 +356,8 @@ func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...Con
} }
if reply.Rep != gosocks5.Succeeded { if reply.Rep != gosocks5.Succeeded {
log.Logf("[socks5] bind on %s failure", addr) log.Logf("[socks5] bind on %s failure", address)
return nil, fmt.Errorf("SOCKS5 bind on %s failure", addr) return nil, fmt.Errorf("SOCKS5 bind on %s failure", address)
} }
baddr, err := net.ResolveTCPAddr("tcp", reply.Addr.String()) baddr, err := net.ResolveTCPAddr("tcp", reply.Addr.String())
if err != nil { if err != nil {
@ -350,8 +375,17 @@ func Socks5MuxBindConnector() Connector {
return &socks5MuxBindConnector{} return &socks5MuxBindConnector{}
} }
func (c *socks5MuxBindConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
// NOTE: the conn must be *muxBindClientConn. // NOTE: the conn must be *muxBindClientConn.
func (c *socks5MuxBindConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks5MuxBindConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
accepter, ok := conn.(Accepter) accepter, ok := conn.(Accepter)
if !ok { if !ok {
return nil, errors.New("wrong connection type") return nil, errors.New("wrong connection type")
@ -513,7 +547,16 @@ func SOCKS5UDPConnector(user *url.Userinfo) Connector {
return &socks5UDPConnector{User: user} return &socks5UDPConnector{User: user}
} }
func (c *socks5UDPConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks5UDPConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "udp", address, options...)
}
func (c *socks5UDPConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -541,7 +584,7 @@ func (c *socks5UDPConnector) Connect(conn net.Conn, addr string, options ...Conn
} }
conn = cc conn = cc
taddr, err := net.ResolveUDPAddr("udp", addr) taddr, err := net.ResolveUDPAddr("udp", address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -596,71 +639,40 @@ func SOCKS5UDPTunConnector(user *url.Userinfo) Connector {
return &socks5UDPTunConnector{User: user} return &socks5UDPTunConnector{User: user}
} }
func (c *socks5UDPTunConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks5UDPTunConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "udp", address, options...)
}
func (c *socks5UDPTunConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
} }
user := opts.User
if user == nil {
user = c.User
}
timeout := opts.Timeout timeout := opts.Timeout
if timeout <= 0 { if timeout <= 0 {
timeout = ConnectTimeout timeout = ConnectTimeout
} }
conn.SetDeadline(time.Now().Add(timeout)) conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{}) defer conn.SetDeadline(time.Time{})
user := opts.User taddr, _ := net.ResolveUDPAddr("udp", address)
if user == nil { return newSocks5UDPTunnelConn(conn,
user = c.User nil, taddr,
}
cc, err := socks5Handshake(conn,
selectorSocks5HandshakeOption(opts.Selector), selectorSocks5HandshakeOption(opts.Selector),
userSocks5HandshakeOption(user), userSocks5HandshakeOption(user),
noTLSSocks5HandshakeOption(opts.NoTLS), noTLSSocks5HandshakeOption(opts.NoTLS),
) )
if err != nil {
return nil, err
}
conn = cc
taddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
req := gosocks5.NewRequest(CmdUDPTun, &gosocks5.Addr{
Type: gosocks5.AddrIPv4,
})
if err := req.Write(conn); err != nil {
return nil, err
}
if Debug {
log.Log("[socks5] udp\n", req)
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
if Debug {
log.Log("[socks5] udp\n", reply)
}
if reply.Rep != gosocks5.Succeeded {
log.Logf("[socks5] udp relay failure")
return nil, fmt.Errorf("SOCKS5 udp relay failure")
}
baddr, err := net.ResolveUDPAddr("udp", reply.Addr.String())
if err != nil {
return nil, err
}
log.Logf("[socks5] udp-tun associate on %s OK", baddr)
return &udpTunnelConn{Conn: conn, raddr: taddr}, nil
} }
type socks4Connector struct{} type socks4Connector struct{}
@ -670,7 +682,16 @@ func SOCKS4Connector() Connector {
return &socks4Connector{} return &socks4Connector{}
} }
func (c *socks4Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks4Connector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *socks4Connector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -684,7 +705,7 @@ func (c *socks4Connector) Connect(conn net.Conn, addr string, options ...Connect
conn.SetDeadline(time.Now().Add(timeout)) conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{}) defer conn.SetDeadline(time.Time{})
taddr, err := net.ResolveTCPAddr("tcp4", addr) taddr, err := net.ResolveTCPAddr("tcp4", address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -730,7 +751,16 @@ func SOCKS4AConnector() Connector {
return &socks4aConnector{} return &socks4aConnector{}
} }
func (c *socks4aConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *socks4aConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *socks4aConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -744,7 +774,7 @@ func (c *socks4aConnector) Connect(conn net.Conn, addr string, options ...Connec
conn.SetDeadline(time.Now().Add(timeout)) conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{}) defer conn.SetDeadline(time.Time{})
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1095,7 +1125,7 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
return return
} }
relay, err := net.ListenUDP("udp", nil) relay, err := net.ListenUDP("udp", &net.UDPAddr{IP: conn.LocalAddr().(*net.TCPAddr).IP, Port: 0}) // use out-going interface's IP
if err != nil { if err != nil {
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
reply := gosocks5.NewReply(gosocks5.Failure, nil) reply := gosocks5.NewReply(gosocks5.Failure, nil)
@ -1108,7 +1138,6 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
defer relay.Close() defer relay.Close()
socksAddr := toSocksAddr(relay.LocalAddr()) socksAddr := toSocksAddr(relay.LocalAddr())
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) // replace the IP to the out-going interface's
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
if err := reply.Write(conn); err != nil { if err := reply.Write(conn); err != nil {
log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
@ -1371,14 +1400,14 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
addr := req.Addr.String() addr := req.Addr.String()
if !Can("rudp", addr, h.options.Whitelist, h.options.Blacklist) { if !Can("rudp", addr, h.options.Whitelist, h.options.Blacklist) {
log.Logf("[socks5-udp] Unauthorized to udp bind to %s", addr) log.Logf("[socks5] udp-tun Unauthorized to udp bind to %s", addr)
return return
} }
bindAddr, _ := net.ResolveUDPAddr("udp", addr) bindAddr, _ := net.ResolveUDPAddr("udp", addr)
uc, err := net.ListenUDP("udp", bindAddr) uc, err := net.ListenUDP("udp", bindAddr)
if err != nil { if err != nil {
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) log.Logf("[socks5] udp-tun %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
return return
} }
defer uc.Close() defer uc.Close()
@ -1387,32 +1416,32 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
if err := reply.Write(conn); err != nil { if err := reply.Write(conn); err != nil {
log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), socksAddr, err) log.Logf("[socks5] udp-tun %s <- %s : %s", conn.RemoteAddr(), socksAddr, err)
return return
} }
if Debug { if Debug {
log.Logf("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), socksAddr, reply) log.Logf("[socks5] udp-tun %s <- %s\n%s", conn.RemoteAddr(), socksAddr, reply)
} }
log.Logf("[socks5-udp] %s <-> %s", conn.RemoteAddr(), socksAddr) log.Logf("[socks5] udp-tun %s <-> %s", conn.RemoteAddr(), socksAddr)
h.tunnelServerUDP(conn, uc) h.tunnelServerUDP(conn, uc)
log.Logf("[socks5-udp] %s >-< %s", conn.RemoteAddr(), socksAddr) log.Logf("[socks5] udp-tun %s >-< %s", conn.RemoteAddr(), socksAddr)
return return
} }
cc, err := h.options.Chain.Conn() cc, err := h.options.Chain.Conn()
// connection error // connection error
if err != nil { if err != nil {
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) log.Logf("[socks5] udp-tun %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
reply := gosocks5.NewReply(gosocks5.Failure, nil) reply := gosocks5.NewReply(gosocks5.Failure, nil)
reply.Write(conn) reply.Write(conn)
log.Logf("[socks5-udp] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, reply) log.Logf("[socks5] udp-tun %s -> %s\n%s", conn.RemoteAddr(), req.Addr, reply)
return return
} }
defer cc.Close() defer cc.Close()
cc, err = socks5Handshake(cc, userSocks5HandshakeOption(h.options.Chain.LastNode().User)) cc, err = socks5Handshake(cc, userSocks5HandshakeOption(h.options.Chain.LastNode().User))
if err != nil { if err != nil {
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) log.Logf("[socks5] udp-tun %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
return return
} }
// tunnel <-> tunnel, direct forwarding // tunnel <-> tunnel, direct forwarding
@ -1420,9 +1449,9 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
// so we don't need to authenticate it, as it's as explicit as whitelisting // so we don't need to authenticate it, as it's as explicit as whitelisting
req.Write(cc) req.Write(cc)
log.Logf("[socks5-udp] %s <-> %s [tun]", conn.RemoteAddr(), cc.RemoteAddr()) log.Logf("[socks5] udp-tun %s <-> %s", conn.RemoteAddr(), cc.RemoteAddr())
transport(conn, cc) transport(conn, cc)
log.Logf("[socks5-udp] %s >-< %s [tun]", conn.RemoteAddr(), cc.RemoteAddr()) log.Logf("[socks5] udp-tun %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
} }
func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err error) { func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err error) {
@ -1440,7 +1469,7 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
return return
} }
if h.options.Bypass.Contains(addr.String()) { if h.options.Bypass.Contains(addr.String()) {
log.Log("[udp-tun] [bypass] read from", addr) log.Log("[socks5] udp-tun bypass read from", addr)
continue // bypass continue // bypass
} }
@ -1448,12 +1477,12 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
dgram := gosocks5.NewUDPDatagram( dgram := gosocks5.NewUDPDatagram(
gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n]) gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n])
if err := dgram.Write(cc); err != nil { if err := dgram.Write(cc); err != nil {
log.Logf("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), dgram.Header.Addr, err) log.Logf("[socks5] udp-tun %s <- %s : %s", cc.RemoteAddr(), dgram.Header.Addr, err)
errc <- err errc <- err
return return
} }
if Debug { if Debug {
log.Logf("[udp-tun] %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data)) log.Logf("[socks5] udp-tun %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data))
} }
} }
}() }()
@ -1473,16 +1502,16 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
continue // drop silently continue // drop silently
} }
if h.options.Bypass.Contains(addr.String()) { if h.options.Bypass.Contains(addr.String()) {
log.Log("[udp-tun] [bypass] write to", addr) log.Log("[socks5] udp-tun bypass write to", addr)
continue // bypass continue // bypass
} }
if _, err := pc.WriteTo(dgram.Data, addr); err != nil { if _, err := pc.WriteTo(dgram.Data, addr); err != nil {
log.Logf("[udp-tun] %s -> %s : %s", cc.RemoteAddr(), addr, err) log.Logf("[socks5] udp-tun %s -> %s : %s", cc.RemoteAddr(), addr, err)
errc <- err errc <- err
return return
} }
if Debug { if Debug {
log.Logf("[udp-tun] %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data)) log.Logf("[socks5] udp-tun %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data))
} }
} }
}() }()
@ -1601,16 +1630,21 @@ func (h *socks5Handler) muxBindOn(conn net.Conn, addr string) {
} }
} }
// TODO: support domain
func toSocksAddr(addr net.Addr) *gosocks5.Addr { func toSocksAddr(addr net.Addr) *gosocks5.Addr {
host := "0.0.0.0" host := "0.0.0.0"
port := 0 port := 0
addrType := gosocks5.AddrIPv4
if addr != nil { if addr != nil {
h, p, _ := net.SplitHostPort(addr.String()) h, p, _ := net.SplitHostPort(addr.String())
host = h host = h
port, _ = strconv.Atoi(p) port, _ = strconv.Atoi(p)
if strings.Count(host, ":") > 0 {
addrType = gosocks5.AddrIPv6
}
} }
return &gosocks5.Addr{ return &gosocks5.Addr{
Type: gosocks5.AddrIPv4, Type: addrType,
Host: host, Host: host,
Port: uint16(port), Port: uint16(port),
} }
@ -1795,52 +1829,6 @@ func (h *socks4Handler) handleBind(conn net.Conn, req *gosocks4.Request) {
log.Logf("[socks4-bind] %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) log.Logf("[socks4-bind] %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
} }
func getSOCKS5UDPTunnel(chain *Chain, addr net.Addr) (net.Conn, error) {
conn, err := chain.Conn()
if err != nil {
return nil, err
}
conn.SetDeadline(time.Now().Add(HandshakeTimeout))
defer conn.SetDeadline(time.Time{})
node := chain.LastNode()
cc, err := socks5Handshake(conn,
userSocks5HandshakeOption(node.User),
noTLSSocks5HandshakeOption(node.GetBool("notls")),
)
if err != nil {
conn.Close()
return nil, err
}
conn = cc
req := gosocks5.NewRequest(CmdUDPTun, toSocksAddr(addr))
if err := req.Write(conn); err != nil {
conn.Close()
return nil, err
}
if Debug {
log.Log("[socks5]", req)
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
conn.Close()
return nil, err
}
if Debug {
log.Log("[socks5]", reply)
}
if reply.Rep != gosocks5.Succeeded {
conn.Close()
return nil, errors.New("UDP tunnel failure")
}
return conn, nil
}
type socks5HandshakeOptions struct { type socks5HandshakeOptions struct {
selector gosocks5.Selector selector gosocks5.Selector
user *url.Userinfo user *url.Userinfo
@ -1896,21 +1884,74 @@ func socks5Handshake(conn net.Conn, opts ...socks5HandshakeOption) (net.Conn, er
return cc, nil return cc, nil
} }
type udpTunnelConn struct { func getSocks5UDPTunnel(chain *Chain, addr net.Addr) (net.Conn, error) {
raddr net.Addr c, err := chain.Conn()
net.Conn if err != nil {
return nil, err
}
node := chain.LastNode()
conn, err := newSocks5UDPTunnelConn(c,
addr, nil,
userSocks5HandshakeOption(node.User),
noTLSSocks5HandshakeOption(node.GetBool("notls")),
)
if err != nil {
c.Close()
}
return conn, err
} }
func (c *udpTunnelConn) Read(b []byte) (n int, err error) { type socks5UDPTunnelConn struct {
dgram, err := gosocks5.ReadUDPDatagram(c.Conn) net.Conn
taddr net.Addr
}
func newSocks5UDPTunnelConn(conn net.Conn, raddr, taddr net.Addr, opts ...socks5HandshakeOption) (net.Conn, error) {
cc, err := socks5Handshake(conn, opts...)
if err != nil { if err != nil {
return return nil, err
} }
n = copy(b, dgram.Data)
req := gosocks5.NewRequest(CmdUDPTun, toSocksAddr(raddr))
if err := req.Write(cc); err != nil {
return nil, err
}
if Debug {
log.Log("[socks5] udp-tun", req)
}
reply, err := gosocks5.ReadReply(cc)
if err != nil {
return nil, err
}
if Debug {
log.Log("[socks5] udp-tun", reply)
}
if reply.Rep != gosocks5.Succeeded {
return nil, errors.New("socks5 UDP tunnel failure")
}
baddr, err := net.ResolveUDPAddr("udp", reply.Addr.String())
if err != nil {
return nil, err
}
log.Logf("[socks5] udp-tun associate on %s OK", baddr)
return &socks5UDPTunnelConn{
Conn: cc,
taddr: taddr,
}, nil
}
func (c *socks5UDPTunnelConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return return
} }
func (c *udpTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { func (c *socks5UDPTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
dgram, err := gosocks5.ReadUDPDatagram(c.Conn) dgram, err := gosocks5.ReadUDPDatagram(c.Conn)
if err != nil { if err != nil {
return return
@ -1920,15 +1961,11 @@ func (c *udpTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
return return
} }
func (c *udpTunnelConn) Write(b []byte) (n int, err error) { func (c *socks5UDPTunnelConn) Write(b []byte) (n int, err error) {
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(c.raddr)), b) return c.WriteTo(b, c.taddr)
if err = dgram.Write(c.Conn); err != nil {
return
}
return len(b), nil
} }
func (c *udpTunnelConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { func (c *socks5UDPTunnelConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(addr)), b) dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(addr)), b)
if err = dgram.Write(c.Conn); err != nil { if err = dgram.Write(c.Conn); err != nil {
return return

673
ss.go
View File

@ -2,32 +2,51 @@ package gost
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/url" "net/url"
"strconv"
"time" "time"
"github.com/ginuerzh/gosocks5" "github.com/go-gost/gosocks5"
"github.com/go-log/log" "github.com/go-log/log"
"github.com/shadowsocks/go-shadowsocks2/core"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
) )
const (
maxSocksAddrLen = 259
)
var (
_ net.Conn = (*shadowConn)(nil)
_ net.PacketConn = (*shadowUDPPacketConn)(nil)
)
type shadowConnector struct { type shadowConnector struct {
Cipher *url.Userinfo cipher core.Cipher
} }
// ShadowConnector creates a Connector for shadowsocks proxy client. // ShadowConnector creates a Connector for shadowsocks proxy client.
// It accepts a cipher info for shadowsocks data encryption/decryption. // It accepts an optional cipher info for shadowsocks data encryption/decryption.
// The cipher must not be nil. func ShadowConnector(info *url.Userinfo) Connector {
func ShadowConnector(cipher *url.Userinfo) Connector { return &shadowConnector{
return &shadowConnector{Cipher: cipher} cipher: initShadowCipher(info),
}
} }
func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *shadowConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *shadowConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -38,37 +57,43 @@ func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...Connect
timeout = ConnectTimeout timeout = ConnectTimeout
} }
socksAddr, err := gosocks5.NewAddr(address)
if err != nil {
return nil, err
}
rawaddr := sPool.Get().([]byte)
defer sPool.Put(rawaddr)
n, err := socksAddr.Encode(rawaddr)
if err != nil {
return nil, err
}
conn.SetDeadline(time.Now().Add(timeout)) conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{}) defer conn.SetDeadline(time.Time{})
rawaddr, err := ss.RawAddr(addr) if c.cipher != nil {
if err != nil { conn = c.cipher.StreamConn(conn)
return nil, err
}
var method, password string
cp := opts.User
if cp == nil {
cp = c.Cipher
}
if cp != nil {
method = cp.Username()
password, _ = cp.Password()
}
cipher, err := ss.NewCipher(method, password)
if err != nil {
return nil, err
} }
sc := &shadowConn{ sc := &shadowConn{
Conn: ss.NewConn(conn, cipher), Conn: conn,
} }
_, err = sc.Write(rawaddr)
return sc, err // write the addr at once.
if opts.NoDelay {
if _, err := sc.Write(rawaddr[:n]); err != nil {
return nil, err
}
} else {
sc.wbuf.Write(rawaddr[:n]) // cache the header
}
return sc, nil
} }
type shadowHandler struct { type shadowHandler struct {
cipher core.Cipher
options *HandlerOptions options *HandlerOptions
} }
@ -88,37 +113,34 @@ func (h *shadowHandler) Init(options ...HandlerOption) {
for _, opt := range options { for _, opt := range options {
opt(h.options) opt(h.options)
} }
if len(h.options.Users) > 0 {
h.cipher = initShadowCipher(h.options.Users[0])
}
} }
func (h *shadowHandler) Handle(conn net.Conn) { func (h *shadowHandler) Handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
var method, password string if h.cipher != nil {
users := h.options.Users conn = &shadowConn{
if len(users) > 0 { Conn: h.cipher.StreamConn(conn),
method = users[0].Username() }
password, _ = users[0].Password()
} }
cipher, err := ss.NewCipher(method, password)
if err != nil {
log.Logf("[ss] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
conn = &shadowConn{Conn: ss.NewConn(conn, cipher)}
conn.SetReadDeadline(time.Now().Add(ReadTimeout)) conn.SetReadDeadline(time.Now().Add(ReadTimeout))
host, err := h.getRequest(conn)
addr, err := readSocksAddr(conn)
if err != nil { if err != nil {
log.Logf("[ss] %s -> %s : %s", log.Logf("[ss] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err) conn.RemoteAddr(), conn.LocalAddr(), err)
return return
} }
// clear timer
conn.SetReadDeadline(time.Time{}) conn.SetReadDeadline(time.Time{})
log.Logf("[ss] %s -> %s -> %s", host := addr.String()
conn.RemoteAddr(), h.options.Node.String(), host) log.Logf("[ss] %s -> %s",
conn.RemoteAddr(), host)
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) { if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
log.Logf("[ss] %s - %s : Unauthorized to tcp connect to %s", log.Logf("[ss] %s - %s : Unauthorized to tcp connect to %s",
@ -181,84 +203,28 @@ func (h *shadowHandler) Handle(conn net.Conn) {
log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), host) log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), host)
} }
const (
idType = 0 // address type index
idIP0 = 1 // ip address start index
idDmLen = 1 // domain address length index
idDm0 = 2 // domain address start index
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
lenHmacSha1 = 10
)
// This function is copied from shadowsocks library with some modification.
func (h *shadowHandler) getRequest(r io.Reader) (host string, err error) {
// buf size should at least have the same size with the largest possible
// request size (when addrType is 3, domain name has at most 256 bytes)
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
buf := make([]byte, smallBufferSize)
// read till we get possible domain length field
if _, err = io.ReadFull(r, buf[:idType+1]); err != nil {
return
}
var reqStart, reqEnd int
addrType := buf[idType]
switch addrType & ss.AddrMask {
case typeIPv4:
reqStart, reqEnd = idIP0, idIP0+lenIPv4
case typeIPv6:
reqStart, reqEnd = idIP0, idIP0+lenIPv6
case typeDm:
if _, err = io.ReadFull(r, buf[idType+1:idDmLen+1]); err != nil {
return
}
reqStart, reqEnd = idDm0, idDm0+int(buf[idDmLen])+lenDmBase
default:
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
return
}
if _, err = io.ReadFull(r, buf[reqStart:reqEnd]); err != nil {
return
}
// Return string for typeIP is not most efficient, but browsers (Chrome,
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
// big problem.
switch addrType & ss.AddrMask {
case typeIPv4:
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
case typeIPv6:
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
case typeDm:
host = string(buf[idDm0 : idDm0+int(buf[idDmLen])])
}
// parse port
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
return
}
type shadowUDPConnector struct { type shadowUDPConnector struct {
Cipher *url.Userinfo cipher core.Cipher
} }
// ShadowUDPConnector creates a Connector for shadowsocks UDP client. // ShadowUDPConnector creates a Connector for shadowsocks UDP client.
// It accepts a cipher info for shadowsocks data encryption/decryption. // It accepts an optional cipher info for shadowsocks data encryption/decryption.
// The cipher must not be nil. func ShadowUDPConnector(info *url.Userinfo) Connector {
func ShadowUDPConnector(cipher *url.Userinfo) Connector { return &shadowUDPConnector{
return &shadowUDPConnector{Cipher: cipher} cipher: initShadowCipher(info),
}
} }
func (c *shadowUDPConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *shadowUDPConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "udp", address, options...)
}
func (c *shadowUDPConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "tcp", "tcp4", "tcp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -272,223 +238,197 @@ func (c *shadowUDPConnector) Connect(conn net.Conn, addr string, options ...Conn
conn.SetDeadline(time.Now().Add(timeout)) conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{}) defer conn.SetDeadline(time.Time{})
rawaddr, err := ss.RawAddr(addr) taddr, _ := net.ResolveUDPAddr(network, address)
if err != nil { if taddr == nil {
return nil, err taddr = &net.UDPAddr{}
} }
var method, password string pc, ok := conn.(net.PacketConn)
if c.Cipher != nil { if ok {
method = c.Cipher.Username() if c.cipher != nil {
password, _ = c.Cipher.Password() pc = c.cipher.PacketConn(pc)
}
return &shadowUDPPacketConn{
PacketConn: pc,
raddr: conn.RemoteAddr(),
taddr: taddr,
}, nil
} }
cipher, err := ss.NewCipher(method, password) if c.cipher != nil {
if err != nil { conn = &shadowConn{
return nil, err Conn: c.cipher.StreamConn(conn),
}
} }
sc := ss.NewSecurePacketConn(&shadowPacketConn{conn}, cipher, false) return &socks5UDPTunnelConn{
return &shadowUDPConn{ Conn: conn,
PacketConn: sc, taddr: taddr,
raddr: conn.RemoteAddr(),
header: rawaddr,
}, nil }, nil
} }
type shadowUDPListener struct { type shadowUDPHandler struct {
ln net.PacketConn cipher core.Cipher
connChan chan net.Conn
errChan chan error
ttl time.Duration
connMap udpConnMap
config *UDPForwardListenConfig
}
// ShadowUDPListener creates a Listener for shadowsocks UDP relay server.
func ShadowUDPListener(addr string, cipher *url.Userinfo, cfg *UDPForwardListenConfig) (Listener, error) {
laddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenUDP("udp", laddr)
if err != nil {
return nil, err
}
var method, password string
if cipher != nil {
method = cipher.Username()
password, _ = cipher.Password()
}
cp, err := ss.NewCipher(method, password)
if err != nil {
ln.Close()
return nil, err
}
if cfg == nil {
cfg = &UDPForwardListenConfig{}
}
backlog := cfg.Backlog
if backlog <= 0 {
backlog = defaultBacklog
}
l := &shadowUDPListener{
ln: ss.NewSecurePacketConn(ln, cp, false),
connChan: make(chan net.Conn, backlog),
errChan: make(chan error, 1),
config: cfg,
}
go l.listenLoop()
return l, nil
}
func (l *shadowUDPListener) listenLoop() {
for {
b := make([]byte, mediumBufferSize)
n, raddr, err := l.ln.ReadFrom(b)
if err != nil {
log.Logf("[ssu] peer -> %s : %s", l.Addr(), err)
l.ln.Close()
l.errChan <- err
close(l.errChan)
return
}
conn, ok := l.connMap.Get(raddr.String())
if !ok {
conn = newUDPServerConn(l.ln, raddr, l.config.TTL, l.config.QueueSize)
conn.onClose = func() {
l.connMap.Delete(raddr.String())
log.Logf("[ssu] %s closed (%d)", raddr, l.connMap.Size())
}
select {
case l.connChan <- conn:
l.connMap.Set(raddr.String(), conn)
log.Logf("[ssu] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size())
default:
conn.Close()
log.Logf("[ssu] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan))
}
}
select {
case conn.rChan <- b[:n]: // we keep the addr info so that the handler can identify the destination.
if Debug {
log.Logf("[ssu] %s >>> %s : length %d", raddr, l.Addr(), n)
}
default:
log.Logf("[ssu] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
}
}
}
func (l *shadowUDPListener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.connChan:
case err, ok = <-l.errChan:
if !ok {
err = errors.New("accpet on closed listener")
}
}
return
}
func (l *shadowUDPListener) Addr() net.Addr {
return l.ln.LocalAddr()
}
func (l *shadowUDPListener) Close() error {
err := l.ln.Close()
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
v.Close()
return true
})
return err
}
type shadowUDPdHandler struct {
ttl time.Duration
options *HandlerOptions options *HandlerOptions
} }
// ShadowUDPdHandler creates a server Handler for shadowsocks UDP relay server. // ShadowUDPHandler creates a server Handler for shadowsocks UDP relay server.
func ShadowUDPdHandler(opts ...HandlerOption) Handler { func ShadowUDPHandler(opts ...HandlerOption) Handler {
h := &shadowUDPdHandler{} h := &shadowUDPHandler{}
h.Init(opts...) h.Init(opts...)
return h return h
} }
func (h *shadowUDPdHandler) Init(options ...HandlerOption) { func (h *shadowUDPHandler) Init(options ...HandlerOption) {
if h.options == nil { if h.options == nil {
h.options = &HandlerOptions{} h.options = &HandlerOptions{}
} }
for _, opt := range options { for _, opt := range options {
opt(h.options) opt(h.options)
} }
if len(h.options.Users) > 0 {
h.cipher = initShadowCipher(h.options.Users[0])
}
} }
func (h *shadowUDPdHandler) Handle(conn net.Conn) { func (h *shadowUDPHandler) Handle(conn net.Conn) {
defer conn.Close() defer conn.Close()
var err error
var cc net.PacketConn var cc net.PacketConn
if h.options.Chain.IsEmpty() { c, err := h.options.Chain.DialContext(context.Background(), "udp", "")
cc, err = net.ListenUDP("udp", nil) if err != nil {
if err != nil { log.Logf("[ssu] %s: %s", conn.LocalAddr(), err)
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err) return
return
}
} else {
var c net.Conn
c, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
if err != nil {
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
return
}
cc = &udpTunnelConn{Conn: c}
} }
var ok bool
cc, ok = c.(net.PacketConn)
if !ok {
log.Logf("[ssu] %s: not a packet connection", conn.LocalAddr())
return
}
defer cc.Close() defer cc.Close()
pc, ok := conn.(net.PacketConn)
if ok {
if h.cipher != nil {
pc = h.cipher.PacketConn(pc)
}
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
h.transportPacket(pc, cc)
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
return
}
if h.cipher != nil {
conn = &shadowConn{
Conn: h.cipher.StreamConn(conn),
}
}
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
h.transportUDP(conn, cc) h.transportUDP(conn, cc)
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr()) log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
} }
func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error { func (h *shadowUDPHandler) transportPacket(conn, cc net.PacketConn) (err error) {
errc := make(chan error, 1)
var clientAddr net.Addr
go func() {
for {
err := func() error {
b := mPool.Get().([]byte)
defer mPool.Put(b)
n, addr, err := conn.ReadFrom(b)
if err != nil {
return err
}
if clientAddr == nil {
clientAddr = addr
}
r := bytes.NewBuffer(b[:n])
saddr, err := readSocksAddr(r)
if err != nil {
return err
}
taddr, err := net.ResolveUDPAddr("udp", saddr.String())
if err != nil {
return err
}
if Debug {
log.Logf("[ssu] %s >>> %s length: %d", addr, taddr, r.Len())
}
_, err = cc.WriteTo(r.Bytes(), taddr)
return err
}()
if err != nil {
errc <- err
return
}
}
}()
go func() {
for {
err := func() error {
b := mPool.Get().([]byte)
defer mPool.Put(b)
n, addr, err := cc.ReadFrom(b)
if err != nil {
return err
}
if clientAddr == nil {
return nil
}
if Debug {
log.Logf("[ssu] %s <<< %s length: %d", clientAddr, addr, n)
}
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n])
buf := bytes.Buffer{}
if err = dgram.Write(&buf); err != nil {
return err
}
_, err = conn.WriteTo(buf.Bytes()[3:], clientAddr)
return err
}()
if err != nil {
errc <- err
return
}
}
}()
select {
case err = <-errc:
}
return
}
func (h *shadowUDPHandler) transportUDP(conn net.Conn, cc net.PacketConn) error {
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
for { for {
er := func() (err error) { er := func() (err error) {
b := lPool.Get().([]byte) dgram, err := gosocks5.ReadUDPDatagram(conn)
defer lPool.Put(b)
b[0] = 0
b[1] = 0
b[2] = 0
// add rsv and frag fields to make it the standard SOCKS5 UDP datagram
n, err := sc.Read(b[3:])
if err != nil { if err != nil {
// log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err) // log.Logf("[ssu] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
if err != nil {
log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
return return
} }
if Debug { if Debug {
log.Logf("[ssu] %s >>> %s length: %d", sc.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data)) log.Logf("[ssu] %s >>> %s length: %d",
conn.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data))
} }
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
if err != nil { if err != nil {
@ -512,28 +452,25 @@ func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error {
go func() { go func() {
for { for {
er := func() (err error) { er := func() (err error) {
b := lPool.Get().([]byte) b := mPool.Get().([]byte)
defer lPool.Put(b) defer mPool.Put(b)
n, addr, err := cc.ReadFrom(b) n, addr, err := cc.ReadFrom(b)
if err != nil { if err != nil {
return return
} }
if Debug { if Debug {
log.Logf("[ssu] %s <<< %s length: %d", sc.RemoteAddr(), addr, n) log.Logf("[ssu] %s <<< %s length: %d", conn.RemoteAddr(), addr, n)
} }
if h.options.Bypass.Contains(addr.String()) { if h.options.Bypass.Contains(addr.String()) {
log.Log("[ssu] bypass", addr) log.Log("[ssu] bypass", addr)
return // bypass return // bypass
} }
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n]) dgram := gosocks5.NewUDPDatagram(
gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n])
buf := bytes.Buffer{} buf := bytes.Buffer{}
dgram.Write(&buf) dgram.Write(&buf)
if buf.Len() < 10 { _, err = conn.Write(buf.Bytes())
log.Logf("[ssu] %s <- %s : invalid udp datagram", sc.RemoteAddr(), addr)
return // ignore invalid datagram
}
_, err = sc.Write(buf.Bytes()[3:])
return return
}() }()
@ -555,34 +492,28 @@ func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error {
// we wrap around it to make io.Copy happy. // we wrap around it to make io.Copy happy.
type shadowConn struct { type shadowConn struct {
net.Conn net.Conn
wbuf bytes.Buffer
} }
func (c *shadowConn) Write(b []byte) (n int, err error) { func (c *shadowConn) Write(b []byte) (n int, err error) {
n = len(b) // force byte length consistent n = len(b) // force byte length consistent
if c.wbuf.Len() > 0 {
c.wbuf.Write(b) // append the data to the cached header
_, err = c.Conn.Write(c.wbuf.Bytes())
c.wbuf.Reset()
return
}
_, err = c.Conn.Write(b) _, err = c.Conn.Write(b)
return return
} }
type shadowUDPConn struct { type shadowUDPPacketConn struct {
net.PacketConn net.PacketConn
raddr net.Addr raddr net.Addr
header []byte taddr net.Addr
} }
func (c *shadowUDPConn) Write(b []byte) (n int, err error) { func (c *shadowUDPPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
n = len(b) // force byte length consistent
buf := bytes.Buffer{}
if _, err = buf.Write(c.header); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
return
}
func (c *shadowUDPConn) Read(b []byte) (n int, err error) {
buf := mPool.Get().([]byte) buf := mPool.Get().([]byte)
defer mPool.Put(buf) defer mPool.Put(buf)
@ -600,23 +531,117 @@ func (c *shadowUDPConn) Read(b []byte) (n int, err error) {
return return
} }
n = copy(b, dgram.Data) n = copy(b, dgram.Data)
addr, err = net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
return
}
func (c *shadowUDPPacketConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return return
} }
func (c *shadowUDPConn) RemoteAddr() net.Addr { func (c *shadowUDPPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
sa, err := gosocks5.NewAddr(addr.String())
if err != nil {
return
}
var rawaddr [maxSocksAddrLen]byte
nn, err := sa.Encode(rawaddr[:])
if err != nil {
return
}
buf := mPool.Get().([]byte)
defer mPool.Put(buf)
copy(buf, rawaddr[:nn])
n = copy(buf[nn:], b)
_, err = c.PacketConn.WriteTo(buf[:n+nn], c.raddr)
return
}
func (c *shadowUDPPacketConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
func (c *shadowUDPPacketConn) RemoteAddr() net.Addr {
return c.raddr return c.raddr
} }
type shadowPacketConn struct { type shadowCipher struct {
net.Conn cipher *ss.Cipher
} }
func (c *shadowPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
n, err = c.Conn.Read(b) return ss.NewConn(conn, c.cipher.Copy())
addr = c.Conn.RemoteAddr() }
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
}
func initShadowCipher(info *url.Userinfo) (cipher core.Cipher) {
var method, password string
if info != nil {
method = info.Username()
password, _ = info.Password()
}
if method == "" || password == "" {
return
}
cp, _ := ss.NewCipher(method, password)
if cp != nil {
cipher = &shadowCipher{cipher: cp}
}
if cipher == nil {
var err error
cipher, err = core.PickCipher(method, nil, password)
if err != nil {
log.Logf("[ss] %s", err)
return
}
}
return return
} }
func (c *shadowPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { func readSocksAddr(r io.Reader) (*gosocks5.Addr, error) {
return c.Conn.Write(b) addr := &gosocks5.Addr{}
b := sPool.Get().([]byte)
defer sPool.Put(b)
_, err := io.ReadFull(r, b[:1])
if err != nil {
return nil, err
}
addr.Type = b[0]
switch addr.Type {
case gosocks5.AddrIPv4:
_, err = io.ReadFull(r, b[:net.IPv4len])
addr.Host = net.IP(b[0:net.IPv4len]).String()
case gosocks5.AddrIPv6:
_, err = io.ReadFull(r, b[:net.IPv6len])
addr.Host = net.IP(b[0:net.IPv6len]).String()
case gosocks5.AddrDomain:
if _, err = io.ReadFull(r, b[:1]); err != nil {
return nil, err
}
addrlen := int(b[0])
_, err = io.ReadFull(r, b[:addrlen])
addr.Host = string(b[:addrlen])
default:
return nil, gosocks5.ErrBadAddrType
}
if err != nil {
return nil, err
}
_, err = io.ReadFull(r, b[:2])
addr.Port = binary.BigEndian.Uint16(b[:2])
return addr, err
} }

229
ss2.go
View File

@ -1,229 +0,0 @@
package gost
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"net/url"
"time"
"github.com/ginuerzh/gosocks5"
"github.com/go-log/log"
"github.com/shadowsocks/go-shadowsocks2/core"
)
type shadow2Connector struct {
Cipher *url.Userinfo
}
// Shadow2Connector creates a Connector for go-shadowsocks2 proxy client.
// It accepts a cipher info for shadowsocks data encryption/decryption.
// The cipher must not be nil.
func Shadow2Connector(cipher *url.Userinfo) Connector {
return &shadow2Connector{Cipher: cipher}
}
func (c *shadow2Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
opts := &ConnectOptions{}
for _, option := range options {
option(opts)
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = ConnectTimeout
}
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
socksAddr, err := gosocks5.NewAddr(addr)
if err != nil {
return nil, err
}
rawaddr := sPool.Get().([]byte)
defer sPool.Put(rawaddr)
n, err := socksAddr.Encode(rawaddr)
if err != nil {
return nil, err
}
var method, password string
cp := opts.User
if cp == nil {
cp = c.Cipher
}
if cp != nil {
method = cp.Username()
password, _ = cp.Password()
}
cipher, err := core.PickCipher(method, nil, password)
if err != nil {
return nil, err
}
conn = cipher.StreamConn(conn)
if _, err := conn.Write(rawaddr[:n]); err != nil {
return nil, err
}
return conn, nil
}
type shadow2Handler struct {
options *HandlerOptions
}
// Shadow2Handler creates a server Handler for go-shadowsocks2 proxy server.
func Shadow2Handler(opts ...HandlerOption) Handler {
h := &shadow2Handler{}
h.Init(opts...)
return h
}
func (h *shadow2Handler) Init(options ...HandlerOption) {
if h.options == nil {
h.options = &HandlerOptions{}
}
for _, opt := range options {
opt(h.options)
}
}
func (h *shadow2Handler) Handle(conn net.Conn) {
defer conn.Close()
var method, password string
users := h.options.Users
if len(users) > 0 {
method = users[0].Username()
password, _ = users[0].Password()
}
cipher, err := core.PickCipher(method, nil, password)
if err != nil {
log.Logf("[ss2] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
conn = cipher.StreamConn(conn)
conn.SetReadDeadline(time.Now().Add(ReadTimeout))
addr, err := readAddr(conn)
if err != nil {
log.Logf("[ss2] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
// clear timer
conn.SetReadDeadline(time.Time{})
host := addr.String()
log.Logf("[ss2] %s -> %s -> %s",
conn.RemoteAddr(), h.options.Node.String(), host)
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
log.Logf("[ss2] %s - %s : Unauthorized to tcp connect to %s",
conn.RemoteAddr(), conn.LocalAddr(), host)
return
}
if h.options.Bypass.Contains(host) {
log.Logf("[ss2] %s - %s : Bypass %s",
conn.RemoteAddr(), conn.LocalAddr(), host)
return
}
retries := 1
if h.options.Chain != nil && h.options.Chain.Retries > 0 {
retries = h.options.Chain.Retries
}
if h.options.Retries > 0 {
retries = h.options.Retries
}
var cc net.Conn
var route *Chain
for i := 0; i < retries; i++ {
route, err = h.options.Chain.selectRouteFor(host)
if err != nil {
log.Logf("[ss2] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err)
continue
}
buf := bytes.Buffer{}
fmt.Fprintf(&buf, "%s -> %s -> ",
conn.RemoteAddr(), h.options.Node.String())
for _, nd := range route.route {
fmt.Fprintf(&buf, "%d@%s -> ", nd.ID, nd.String())
}
fmt.Fprintf(&buf, "%s", host)
log.Log("[route]", buf.String())
cc, err = route.Dial(host,
TimeoutChainOption(h.options.Timeout),
HostsChainOption(h.options.Hosts),
ResolverChainOption(h.options.Resolver),
)
if err == nil {
break
}
log.Logf("[ss2] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err)
}
if err != nil {
return
}
defer cc.Close()
log.Logf("[ss2] %s <-> %s", conn.RemoteAddr(), host)
transport(conn, cc)
log.Logf("[ss2] %s >-< %s", conn.RemoteAddr(), host)
}
func readAddr(r io.Reader) (*gosocks5.Addr, error) {
addr := &gosocks5.Addr{}
b := sPool.Get().([]byte)
defer sPool.Put(b)
_, err := io.ReadFull(r, b[:1])
if err != nil {
return nil, err
}
addr.Type = b[0]
switch addr.Type {
case gosocks5.AddrIPv4:
_, err = io.ReadFull(r, b[:net.IPv4len])
addr.Host = net.IP(b[0:net.IPv4len]).String()
case gosocks5.AddrIPv6:
_, err = io.ReadFull(r, b[:net.IPv6len])
addr.Host = net.IP(b[0:net.IPv6len]).String()
case gosocks5.AddrDomain:
if _, err = io.ReadFull(r, b[:1]); err != nil {
return nil, err
}
addrlen := int(b[0])
_, err = io.ReadFull(r, b[:addrlen])
addr.Host = string(b[:addrlen])
default:
return nil, gosocks5.ErrBadAddrType
}
if err != nil {
return nil, err
}
_, err = io.ReadFull(r, b[:2])
addr.Port = binary.BigEndian.Uint16(b[:2])
return addr, err
}

View File

@ -1,425 +0,0 @@
package gost
import (
"crypto/rand"
"fmt"
"net/http/httptest"
"net/url"
"testing"
)
func init() {
// ss.Debug = true
}
var ss2Tests = []struct {
clientCipher *url.Userinfo
serverCipher *url.Userinfo
pass bool
}{
{nil, nil, false},
{&url.Userinfo{}, &url.Userinfo{}, false},
{url.User("abc"), url.User("abc"), false},
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false},
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
{url.User("aes-192-cfb"), url.User("aes-192-cfb"), true},
{url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true},
{url.User("aes-256-cfb"), url.User("aes-256-cfb"), true},
{url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true},
{url.User("aes-128-ctr"), url.User("aes-128-ctr"), true},
{url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true},
{url.User("aes-192-ctr"), url.User("aes-192-ctr"), true},
{url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true},
{url.User("aes-256-ctr"), url.User("aes-256-ctr"), true},
{url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true},
{url.User("chacha20-ietf"), url.User("chacha20-ietf"), true},
{url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true},
{url.User("xchacha20"), url.User("xchacha20"), true},
{url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false},
{url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false},
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false},
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true},
{url.User("AEAD_AES_128_GCM"), url.User("AEAD_AES_128_GCM"), true},
{url.User("AEAD_AES_128_GCM"), url.UserPassword("AEAD_AES_128_GCM", "123456"), false},
{url.UserPassword("AEAD_AES_128_GCM", "123456"), url.User("AEAD_AES_128_GCM"), false},
{url.UserPassword("AEAD_AES_128_GCM", "123456"), url.UserPassword("AEAD_AES_128_GCM", "abc"), false},
{url.UserPassword("AEAD_AES_128_GCM", "123456"), url.UserPassword("AEAD_AES_128_GCM", "123456"), true},
{url.User("AEAD_AES_192_GCM"), url.User("AEAD_AES_192_GCM"), true},
{url.User("AEAD_AES_192_GCM"), url.UserPassword("AEAD_AES_192_GCM", "123456"), false},
{url.UserPassword("AEAD_AES_192_GCM", "123456"), url.User("AEAD_AES_192_GCM"), false},
{url.UserPassword("AEAD_AES_192_GCM", "123456"), url.UserPassword("AEAD_AES_192_GCM", "abc"), false},
{url.UserPassword("AEAD_AES_192_GCM", "123456"), url.UserPassword("AEAD_AES_192_GCM", "123456"), true},
{url.User("AEAD_AES_256_GCM"), url.User("AEAD_AES_256_GCM"), true},
{url.User("AEAD_AES_256_GCM"), url.UserPassword("AEAD_AES_256_GCM", "123456"), false},
{url.UserPassword("AEAD_AES_256_GCM", "123456"), url.User("AEAD_AES_256_GCM"), false},
{url.UserPassword("AEAD_AES_256_GCM", "123456"), url.UserPassword("AEAD_AES_256_GCM", "abc"), false},
{url.UserPassword("AEAD_AES_256_GCM", "123456"), url.UserPassword("AEAD_AES_256_GCM", "123456"), true},
{url.User("AEAD_CHACHA20_POLY1305"), url.User("AEAD_CHACHA20_POLY1305"), true},
{url.User("AEAD_CHACHA20_POLY1305"), url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), false},
{url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), url.User("AEAD_CHACHA20_POLY1305"), false},
{url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), url.UserPassword("AEAD_CHACHA20_POLY1305", "abc"), false},
{url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"), true},
}
var ss2ProxyTests = []struct {
clientCipher *url.Userinfo
serverCipher *url.Userinfo
pass bool
}{
{nil, nil, false},
{&url.Userinfo{}, &url.Userinfo{}, false},
{url.User("abc"), url.User("abc"), false},
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false},
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), false},
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
// {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
}
func ss2ProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
ln, err := TCPListener("")
if err != nil {
return err
}
client := &Client{
Connector: Shadow2Connector(clientInfo),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(serverInfo)),
Listener: ln,
}
go server.Run()
defer server.Close()
return proxyRoundtrip(client, server, targetURL, data)
}
func TestSS2Proxy(t *testing.T) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
for i, tc := range ss2Tests {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
err := ss2ProxyRoundtrip(httpSrv.URL, sendData,
tc.clientCipher,
tc.serverCipher,
)
if err == nil {
if !tc.pass {
t.Errorf("#%d should failed", i)
}
} else {
// t.Logf("#%d %v", i, err)
if tc.pass {
t.Errorf("#%d got error: %v", i, err)
}
}
})
}
}
func BenchmarkSS2Proxy_AES256(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("aes-256-cfb", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("aes-256-cfb", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
for i := 0; i < b.N; i++ {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
}
func BenchmarkSS2Proxy_XChacha20(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("xchacha20", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("xchacha20", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
for i := 0; i < b.N; i++ {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
}
func BenchmarkSS2Proxy_Chacha20_ietf(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("chacha20-ietf", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
for i := 0; i < b.N; i++ {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
}
func BenchmarkSS2Proxy_CHACHA20_IETF_Parallel(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("chacha20-ietf", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
})
}
func BenchmarkSS2Proxy_AEAD_AES_256_GCM(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("AEAD_AES_256_GCM", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_AES_256_GCM", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
for i := 0; i < b.N; i++ {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
}
func BenchmarkSS2Proxy_AEAD_AES_256_GCM_Parallel(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("AEAD_AES_256_GCM", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_AES_256_GCM", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
})
}
func BenchmarkSS2Proxy_AEAD_CHACHA20_POLY1305(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
for i := 0; i < b.N; i++ {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
}
func BenchmarkSS2Proxy_AEAD_CHACHA20_POLY1305_Parallel(b *testing.B) {
httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close()
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := TCPListener("")
if err != nil {
b.Error(err)
}
client := &Client{
Connector: Shadow2Connector(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456")),
Transporter: TCPTransporter(),
}
server := &Server{
Handler: Shadow2Handler(UsersHandlerOption(url.UserPassword("AEAD_CHACHA20_POLY1305", "123456"))),
Listener: ln,
}
go server.Run()
defer server.Close()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if err := proxyRoundtrip(client, server, httpSrv.URL, sendData); err != nil {
b.Error(err)
}
}
})
}

View File

@ -19,88 +19,118 @@ var ssTests = []struct {
serverCipher *url.Userinfo serverCipher *url.Userinfo
pass bool pass bool
}{ }{
{nil, nil, false}, {nil, nil, true},
{&url.Userinfo{}, &url.Userinfo{}, false}, {&url.Userinfo{}, &url.Userinfo{}, true},
{url.User("abc"), url.User("abc"), false}, {url.User("abc"), url.User("abc"), true},
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true},
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), false}, {url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
{url.User("aes-192-cfb"), url.User("aes-192-cfb"), false}, {url.User("aes-192-cfb"), url.User("aes-192-cfb"), true},
{url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false}, {url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false}, {url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false}, {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true}, {url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true},
{url.User("aes-256-cfb"), url.User("aes-256-cfb"), false}, {url.User("aes-256-cfb"), url.User("aes-256-cfb"), true},
{url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false}, {url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false}, {url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false}, {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true}, {url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true},
{url.User("aes-128-ctr"), url.User("aes-128-ctr"), false}, {url.User("aes-128-ctr"), url.User("aes-128-ctr"), true},
{url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false}, {url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false}, {url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false}, {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true}, {url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true},
{url.User("aes-192-ctr"), url.User("aes-192-ctr"), false}, {url.User("aes-192-ctr"), url.User("aes-192-ctr"), true},
{url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false}, {url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false}, {url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false}, {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true}, {url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true},
{url.User("aes-256-ctr"), url.User("aes-256-ctr"), false}, {url.User("aes-256-ctr"), url.User("aes-256-ctr"), true},
{url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false}, {url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false}, {url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false}, {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true}, {url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true},
{url.User("des-cfb"), url.User("des-cfb"), false}, {url.User("des-cfb"), url.User("des-cfb"), true},
{url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false}, {url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false},
{url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false}, {url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false},
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false}, {url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false},
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true}, {url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true},
{url.User("bf-cfb"), url.User("bf-cfb"), false}, {url.User("bf-cfb"), url.User("bf-cfb"), true},
{url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false}, {url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false},
{url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false}, {url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false},
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false}, {url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false},
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true}, {url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true},
{url.User("cast5-cfb"), url.User("cast5-cfb"), false}, {url.User("cast5-cfb"), url.User("cast5-cfb"), true},
{url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false}, {url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false},
{url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false}, {url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false},
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false}, {url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false},
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true}, {url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true},
{url.User("rc4-md5"), url.User("rc4-md5"), false}, {url.User("rc4-md5"), url.User("rc4-md5"), true},
{url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false}, {url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false},
{url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false}, {url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false},
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false}, {url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false},
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true}, {url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true},
{url.User("chacha20"), url.User("chacha20"), false}, {url.User("chacha20"), url.User("chacha20"), true},
{url.User("chacha20"), url.UserPassword("chacha20", "123456"), false}, {url.User("chacha20"), url.UserPassword("chacha20", "123456"), false},
{url.UserPassword("chacha20", "123456"), url.User("chacha20"), false}, {url.UserPassword("chacha20", "123456"), url.User("chacha20"), false},
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false}, {url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false},
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true}, {url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true},
{url.User("chacha20-ietf"), url.User("chacha20-ietf"), false}, {url.User("chacha20-ietf"), url.User("chacha20-ietf"), true},
{url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false}, {url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false}, {url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false}, {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true}, {url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true},
{url.User("salsa20"), url.User("salsa20"), false}, {url.User("salsa20"), url.User("salsa20"), true},
{url.User("salsa20"), url.UserPassword("salsa20", "123456"), false}, {url.User("salsa20"), url.UserPassword("salsa20", "123456"), false},
{url.UserPassword("salsa20", "123456"), url.User("salsa20"), false}, {url.UserPassword("salsa20", "123456"), url.User("salsa20"), false},
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false}, {url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false},
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true}, {url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true},
{url.User("xchacha20"), url.User("xchacha20"), true},
{url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false},
{url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false},
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false},
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true},
{url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true},
{url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "abc"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true},
{url.User("AES-128-GCM"), url.User("AES-128-GCM"), true},
{url.User("AES-128-GCM"), url.UserPassword("AES-128-GCM", "123456"), false},
{url.UserPassword("AES-128-GCM", "123456"), url.User("AES-128-GCM"), false},
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "abc"), false},
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "123456"), true},
{url.User("AES-192-GCM"), url.User("AES-192-GCM"), true},
{url.User("AES-192-GCM"), url.UserPassword("AES-192-GCM", "123456"), false},
{url.UserPassword("AES-192-GCM", "123456"), url.User("AES-192-GCM"), false},
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "abc"), false},
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "123456"), true},
{url.User("AES-256-GCM"), url.User("AES-256-GCM"), true},
{url.User("AES-256-GCM"), url.UserPassword("AES-256-GCM", "123456"), false},
{url.UserPassword("AES-256-GCM", "123456"), url.User("AES-256-GCM"), false},
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "abc"), false},
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "123456"), true},
} }
var ssProxyTests = []struct { var ssProxyTests = []struct {
@ -108,16 +138,20 @@ var ssProxyTests = []struct {
serverCipher *url.Userinfo serverCipher *url.Userinfo
pass bool pass bool
}{ }{
{nil, nil, false}, {nil, nil, true},
{&url.Userinfo{}, &url.Userinfo{}, false}, {&url.Userinfo{}, &url.Userinfo{}, true},
{url.User("abc"), url.User("abc"), false}, {url.User("abc"), url.User("abc"), true},
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false}, {url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true},
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), false}, {url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false}, {url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false}, {url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
// {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true}, {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
{url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true},
{url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true},
} }
func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error { func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
@ -142,7 +176,7 @@ func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, s
return proxyRoundtrip(client, server, targetURL, data) return proxyRoundtrip(client, server, targetURL, data)
} }
func TestSSProxy(t *testing.T) { func TestShadowTCP(t *testing.T) {
httpSrv := httptest.NewServer(httpTestHandler) httpSrv := httptest.NewServer(httpTestHandler)
defer httpSrv.Close() defer httpSrv.Close()
@ -300,9 +334,128 @@ func BenchmarkSSProxyParallel(b *testing.B) {
}) })
} }
var ssuTests = []struct {
clientCipher *url.Userinfo
serverCipher *url.Userinfo
pass bool
}{
{nil, nil, true},
{&url.Userinfo{}, &url.Userinfo{}, true},
{url.User("abc"), url.User("abc"), true},
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true},
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
{url.User("aes-192-cfb"), url.User("aes-192-cfb"), true},
{url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false},
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true},
{url.User("aes-256-cfb"), url.User("aes-256-cfb"), true},
{url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false},
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true},
{url.User("aes-128-ctr"), url.User("aes-128-ctr"), true},
{url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false},
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true},
{url.User("aes-192-ctr"), url.User("aes-192-ctr"), true},
{url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false},
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true},
{url.User("aes-256-ctr"), url.User("aes-256-ctr"), true},
{url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false},
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true},
{url.User("des-cfb"), url.User("des-cfb"), true},
{url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false},
{url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false},
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false},
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true},
{url.User("bf-cfb"), url.User("bf-cfb"), true},
{url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false},
{url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false},
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false},
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true},
{url.User("cast5-cfb"), url.User("cast5-cfb"), true},
{url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false},
{url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false},
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false},
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true},
{url.User("rc4-md5"), url.User("rc4-md5"), true},
{url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false},
{url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false},
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false},
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true},
{url.User("chacha20"), url.User("chacha20"), true},
{url.User("chacha20"), url.UserPassword("chacha20", "123456"), false},
{url.UserPassword("chacha20", "123456"), url.User("chacha20"), false},
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false},
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true},
{url.User("chacha20-ietf"), url.User("chacha20-ietf"), true},
{url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false},
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true},
{url.User("salsa20"), url.User("salsa20"), true},
{url.User("salsa20"), url.UserPassword("salsa20", "123456"), false},
{url.UserPassword("salsa20", "123456"), url.User("salsa20"), false},
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false},
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true},
{url.User("xchacha20"), url.User("xchacha20"), true},
{url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false},
{url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false},
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false},
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true},
{url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true},
{url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "abc"), false},
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true},
{url.User("AES-128-GCM"), url.User("AES-128-GCM"), true},
{url.User("AES-128-GCM"), url.UserPassword("AES-128-GCM", "123456"), false},
{url.UserPassword("AES-128-GCM", "123456"), url.User("AES-128-GCM"), false},
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "abc"), false},
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "123456"), true},
{url.User("AES-192-GCM"), url.User("AES-192-GCM"), true},
{url.User("AES-192-GCM"), url.UserPassword("AES-192-GCM", "123456"), false},
{url.UserPassword("AES-192-GCM", "123456"), url.User("AES-192-GCM"), false},
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "abc"), false},
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "123456"), true},
{url.User("AES-256-GCM"), url.User("AES-256-GCM"), true},
{url.User("AES-256-GCM"), url.UserPassword("AES-256-GCM", "123456"), false},
{url.UserPassword("AES-256-GCM", "123456"), url.User("AES-256-GCM"), false},
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "abc"), false},
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "123456"), true},
}
func shadowUDPRoundtrip(t *testing.T, host string, data []byte, func shadowUDPRoundtrip(t *testing.T, host string, data []byte,
clientInfo *url.Userinfo, serverInfo *url.Userinfo) error { clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
ln, err := ShadowUDPListener("localhost:0", serverInfo, nil) ln, err := UDPListener("localhost:0", nil)
if err != nil { if err != nil {
return err return err
} }
@ -313,7 +466,9 @@ func shadowUDPRoundtrip(t *testing.T, host string, data []byte,
} }
server := &Server{ server := &Server{
Handler: ShadowUDPdHandler(), Handler: ShadowUDPHandler(
UsersHandlerOption(serverInfo),
),
Listener: ln, Listener: ln,
} }
@ -327,7 +482,7 @@ func TestShadowUDP(t *testing.T) {
sendData := make([]byte, 128) sendData := make([]byte, 128)
rand.Read(sendData) rand.Read(sendData)
for i, tc := range ssTests { for i, tc := range ssuTests {
tc := tc tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
udpSrv := newUDPTestServer(udpTestHandler) udpSrv := newUDPTestServer(udpTestHandler)
@ -352,7 +507,6 @@ func TestShadowUDP(t *testing.T) {
} }
} }
// TODO: fix shadowsocks UDP relay benchmark.
func BenchmarkShadowUDP(b *testing.B) { func BenchmarkShadowUDP(b *testing.B) {
udpSrv := newUDPTestServer(udpTestHandler) udpSrv := newUDPTestServer(udpTestHandler)
udpSrv.Start() udpSrv.Start()
@ -361,7 +515,7 @@ func BenchmarkShadowUDP(b *testing.B) {
sendData := make([]byte, 128) sendData := make([]byte, 128)
rand.Read(sendData) rand.Read(sendData)
ln, err := ShadowUDPListener("localhost:0", url.UserPassword("chacha20-ietf", "123456"), nil) ln, err := UDPListener("localhost:0", nil)
if err != nil { if err != nil {
b.Error(err) b.Error(err)
} }
@ -372,7 +526,9 @@ func BenchmarkShadowUDP(b *testing.B) {
} }
server := &Server{ server := &Server{
Handler: ShadowUDPdHandler(), Handler: ShadowUDPHandler(
UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456")),
),
Listener: ln, Listener: ln,
} }

149
ssh.go
View File

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -26,19 +27,53 @@ const (
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
) )
var ( var errSessionDead = errors.New("session is dead")
errSessionDead = errors.New("session is dead")
)
type sshDirectForwardConnector struct { // ParseSSHKeyFile parses ssh key file.
func ParseSSHKeyFile(fp string) (ssh.Signer, error) {
key, err := os.ReadFile(fp)
if err != nil {
return nil, err
}
return ssh.ParsePrivateKey(key)
} }
// ParseSSHAuthorizedKeysFile parses ssh Authorized Keys file.
func ParseSSHAuthorizedKeysFile(fp string) (map[string]bool, error) {
authorizedKeysBytes, err := os.ReadFile(fp)
if err != nil {
return nil, err
}
authorizedKeysMap := make(map[string]bool)
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
return nil, err
}
authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}
return authorizedKeysMap, nil
}
type sshDirectForwardConnector struct{}
// SSHDirectForwardConnector creates a Connector for SSH TCP direct port forwarding. // SSHDirectForwardConnector creates a Connector for SSH TCP direct port forwarding.
func SSHDirectForwardConnector() Connector { func SSHDirectForwardConnector() Connector {
return &sshDirectForwardConnector{} return &sshDirectForwardConnector{}
} }
func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options ...ConnectOption) (net.Conn, error) { func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", raddr, options...)
}
func (c *sshDirectForwardConnector) ConnectContext(ctx context.Context, conn net.Conn, network, raddr string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
opts := &ConnectOptions{} opts := &ConnectOptions{}
for _, option := range options { for _, option := range options {
option(opts) option(opts)
@ -65,15 +100,23 @@ func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options
return conn, nil return conn, nil
} }
type sshRemoteForwardConnector struct { type sshRemoteForwardConnector struct{}
}
// SSHRemoteForwardConnector creates a Connector for SSH TCP remote port forwarding. // SSHRemoteForwardConnector creates a Connector for SSH TCP remote port forwarding.
func SSHRemoteForwardConnector() Connector { func SSHRemoteForwardConnector() Connector {
return &sshRemoteForwardConnector{} return &sshRemoteForwardConnector{}
} }
func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) { func (c *sshRemoteForwardConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
}
func (c *sshRemoteForwardConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
switch network {
case "udp", "udp4", "udp6":
return nil, fmt.Errorf("%s unsupported", network)
}
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution. cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
if !ok { if !ok {
return nil, errors.New("ssh: wrong connection type") return nil, errors.New("ssh: wrong connection type")
@ -87,10 +130,10 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
if cc.session == nil || cc.session.client == nil { if cc.session == nil || cc.session.client == nil {
return return
} }
if strings.HasPrefix(addr, ":") { if strings.HasPrefix(address, ":") {
addr = "0.0.0.0" + addr address = "0.0.0.0" + address
} }
ln, err := cc.session.client.Listen("tcp", addr) ln, err := cc.session.client.Listen("tcp", address)
if err != nil { if err != nil {
return return
} }
@ -99,7 +142,7 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
for { for {
rc, err := ln.Accept() rc, err := ln.Accept()
if err != nil { if err != nil {
log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), addr, err) log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), address, err)
return return
} }
// log.Log("[ssh-rtcp] accept", rc.LocalAddr(), rc.RemoteAddr()) // log.Log("[ssh-rtcp] accept", rc.LocalAddr(), rc.RemoteAddr())
@ -107,7 +150,7 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
case cc.session.connChan <- rc: case cc.session.connChan <- rc:
default: default:
rc.Close() rc.Close()
log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), addr) log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), address)
} }
} }
}() }()
@ -183,11 +226,15 @@ func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOp
} }
if opts.User != nil { if opts.User != nil {
config.User = opts.User.Username() config.User = opts.User.Username()
password, _ := opts.User.Password() if password, _ := opts.User.Password(); password != "" {
config.Auth = []ssh.AuthMethod{ config.Auth = []ssh.AuthMethod{
ssh.Password(password), ssh.Password(password),
}
} }
} }
if opts.SSHConfig != nil && opts.SSHConfig.Key != nil {
config.Auth = append(config.Auth, ssh.PublicKeys(opts.SSHConfig.Key))
}
tr.sessionMutex.Lock() tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock() defer tr.sessionMutex.Unlock()
@ -199,6 +246,7 @@ func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOp
if !ok || session.client == nil { if !ok || session.client == nil {
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config) sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
if err != nil { if err != nil {
log.Log("ssh", err)
conn.Close() conn.Close()
delete(tr.sessions, opts.Addr) delete(tr.sessions, opts.Addr)
return nil, err return nil, err
@ -287,16 +335,20 @@ func (tr *sshTunnelTransporter) Handshake(conn net.Conn, options ...HandshakeOpt
} }
config := ssh.ClientConfig{ config := ssh.ClientConfig{
Timeout: timeout,
HostKeyCallback: ssh.InsecureIgnoreHostKey(), HostKeyCallback: ssh.InsecureIgnoreHostKey(),
} }
// TODO: support pubkey auth.
if opts.User != nil { if opts.User != nil {
config.User = opts.User.Username() config.User = opts.User.Username()
password, _ := opts.User.Password() if password, _ := opts.User.Password(); password != "" {
config.Auth = []ssh.AuthMethod{ config.Auth = []ssh.AuthMethod{
ssh.Password(password), ssh.Password(password),
}
} }
} }
if opts.SSHConfig != nil && opts.SSHConfig.Key != nil {
config.Auth = append(config.Auth, ssh.PublicKeys(opts.SSHConfig.Key))
}
tr.sessionMutex.Lock() tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock() defer tr.sessionMutex.Unlock()
@ -594,7 +646,7 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque
return return
} }
ln, err := net.Listen("tcp", addr) //tie to the client connection ln, err := net.Listen("tcp", addr) // tie to the client connection
if err != nil { if err != nil {
log.Log("[ssh-rtcp]", err) log.Log("[ssh-rtcp]", err)
req.Reply(false, nil) req.Reply(false, nil)
@ -664,8 +716,10 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque
// SSHConfig holds the SSH tunnel server config // SSHConfig holds the SSH tunnel server config
type SSHConfig struct { type SSHConfig struct {
Authenticator Authenticator Authenticator Authenticator
TLSConfig *tls.Config TLSConfig *tls.Config
Key ssh.Signer
AuthorizedKeys map[string]bool
} }
type sshTunnelListener struct { type sshTunnelListener struct {
@ -686,21 +740,22 @@ func SSHTunnelListener(addr string, config *SSHConfig) (Listener, error) {
config = &SSHConfig{} config = &SSHConfig{}
} }
sshConfig := &ssh.ServerConfig{} sshConfig := &ssh.ServerConfig{
sshConfig.PasswordCallback = defaultSSHPasswordCallback(config.Authenticator) PasswordCallback: defaultSSHPasswordCallback(config.Authenticator),
if config.Authenticator == nil { PublicKeyCallback: defaultSSHPublicKeyCallback(config.AuthorizedKeys),
}
if config.Authenticator == nil && len(config.AuthorizedKeys) == 0 {
sshConfig.NoClientAuth = true sshConfig.NoClientAuth = true
} }
tlsConfig := config.TLSConfig
if tlsConfig == nil {
tlsConfig = DefaultTLSConfig
}
signer, err := ssh.NewSignerFromKey(tlsConfig.Certificates[0].PrivateKey)
if err != nil {
ln.Close()
return nil, err
signer := config.Key
if signer == nil {
signer, err = ssh.NewSignerFromKey(DefaultTLSConfig.Certificates[0].PrivateKey)
if err != nil {
ln.Close()
return nil, err
}
} }
sshConfig.AddHostKey(signer) sshConfig.AddHostKey(signer)
@ -805,9 +860,13 @@ func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
} }
// PasswordCallbackFunc is a callback function used by SSH server. // PasswordCallbackFunc is a callback function used by SSH server.
// It authenticates user using a password.
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc { func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc {
if au == nil {
return nil
}
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
if au.Authenticate(conn.User(), string(password)) { if au.Authenticate(conn.User(), string(password)) {
return nil, nil return nil, nil
@ -817,6 +876,28 @@ func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc {
} }
} }
// PublicKeyCallbackFunc is a callback function used by SSH server.
// It offers a public key for authentication.
type PublicKeyCallbackFunc func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error)
func defaultSSHPublicKeyCallback(keys map[string]bool) PublicKeyCallbackFunc {
if len(keys) == 0 {
return nil
}
return func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if keys[string(pubKey.Marshal())] {
return &ssh.Permissions{
// Record the public key used for authentication.
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
}
}
type sshNopConn struct { type sshNopConn struct {
session *sshSession session *sshSession
} }

66
tcp.go Normal file
View File

@ -0,0 +1,66 @@
package gost
import "net"
// tcpTransporter is a raw TCP transporter.
type tcpTransporter struct{}
// TCPTransporter creates a raw TCP client.
func TCPTransporter() Transporter {
return &tcpTransporter{}
}
func (tr *tcpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
opts := &DialOptions{}
for _, option := range options {
option(opts)
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = DialTimeout
}
if opts.Chain == nil {
return net.DialTimeout("tcp", addr, timeout)
}
return opts.Chain.Dial(addr)
}
func (tr *tcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return conn, nil
}
func (tr *tcpTransporter) Multiplex() bool {
return false
}
type tcpListener struct {
net.Listener
}
// TCPListener creates a Listener for TCP proxy server.
func TCPListener(addr string) (Listener, error) {
laddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenTCP("tcp", laddr)
if err != nil {
return nil, err
}
return &tcpListener{Listener: tcpKeepAliveListener{ln}}, nil
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(KeepAliveTime)
return tc, nil
}

63
tls.go
View File

@ -2,7 +2,6 @@ package gost
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"errors" "errors"
"net" "net"
"sync" "sync"
@ -10,7 +9,7 @@ import (
"github.com/go-log/log" "github.com/go-log/log"
smux "gopkg.in/xtaci/smux.v1" smux "github.com/xtaci/smux"
) )
type tlsTransporter struct { type tlsTransporter struct {
@ -290,36 +289,40 @@ func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration)
return nil, err return nil, err
} }
// If crypto/tls is doing verification, there's no need to do our own. // We can do this in `tls.Config.VerifyConnection`, which effective for
if tlsConfig.InsecureSkipVerify == false { // other TLS protocols such as WebSocket. See `route.go:parseChainNode`
return tlsConn, nil /*
} // If crypto/tls is doing verification, there's no need to do our own.
if tlsConfig.InsecureSkipVerify == false {
// Similarly if we use host's CA, we can do full handshake return tlsConn, nil
if tlsConfig.RootCAs == nil {
return tlsConn, nil
}
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}
certs := tlsConn.ConnectionState().PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
} }
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts) // Similarly if we use host's CA, we can do full handshake
if err != nil { if tlsConfig.RootCAs == nil {
tlsConn.Close() return tlsConn, nil
return nil, err }
}
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}
certs := tlsConn.ConnectionState().PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
if err != nil {
tlsConn.Close()
return nil, err
}
*/
return tlsConn, err return tlsConn, err
} }

View File

@ -1,6 +1,7 @@
package gost package gost
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -12,7 +13,6 @@ import (
"github.com/go-log/log" "github.com/go-log/log"
"github.com/shadowsocks/go-shadowsocks2/core" "github.com/shadowsocks/go-shadowsocks2/core"
"github.com/shadowsocks/go-shadowsocks2/shadowaead" "github.com/shadowsocks/go-shadowsocks2/shadowaead"
"github.com/shadowsocks/go-shadowsocks2/shadowstream"
"github.com/songgao/water" "github.com/songgao/water"
"github.com/songgao/water/waterutil" "github.com/songgao/water/waterutil"
"github.com/xtaci/tcpraw" "github.com/xtaci/tcpraw"
@ -39,11 +39,19 @@ func ipProtocol(p waterutil.IPProtocol) string {
return fmt.Sprintf("unknown(%d)", p) return fmt.Sprintf("unknown(%d)", p)
} }
// IPRoute is an IP routing entry.
type IPRoute struct {
Dest *net.IPNet
Gateway net.IP
}
// TunConfig is the config for TUN device.
type TunConfig struct { type TunConfig struct {
Name string Name string
Addr string Addr string
Peer string // peer addr of point-to-point on MacOS
MTU int MTU int
Routes []string Routes []IPRoute
Gateway string Gateway string
} }
@ -160,9 +168,17 @@ func (h *tunHandler) Handle(conn net.Conn) {
var pc net.PacketConn var pc net.PacketConn
// fake tcp mode will be ignored when the client specifies a chain. // fake tcp mode will be ignored when the client specifies a chain.
if raddr != nil && !h.options.Chain.IsEmpty() { if raddr != nil && !h.options.Chain.IsEmpty() {
var cc net.Conn cc, err := h.options.Chain.DialContext(context.Background(), "udp", raddr.String())
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil) if err != nil {
pc = &udpTunnelConn{Conn: cc, raddr: raddr} return err
}
var ok bool
pc, ok = cc.(net.PacketConn)
if !ok {
err = errors.New("not a packet connection")
log.Logf("[tun] %s - %s: %s", conn.LocalAddr(), raddr, err)
return err
}
} else { } else {
if h.options.TCPMode { if h.options.TCPMode {
if raddr != nil { if raddr != nil {
@ -224,6 +240,20 @@ func (h *tunHandler) initTunnelConn(pc net.PacketConn) (net.PacketConn, error) {
return pc, nil return pc, nil
} }
func (h *tunHandler) findRouteFor(dst net.IP) net.Addr {
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
return v.(net.Addr)
}
for _, route := range h.options.IPRoutes {
if route.Dest.Contains(dst) && route.Gateway != nil {
if v, ok := h.routes.Load(ipToTunRouteKey(route.Gateway)); ok {
return v.(net.Addr)
}
}
}
return nil
}
func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.Addr) error { func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.Addr) error {
errc := make(chan error, 1) errc := make(chan error, 1)
@ -279,15 +309,15 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
return err return err
} }
var addr net.Addr addr := h.findRouteFor(dst)
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
addr = v.(net.Addr)
}
if addr == nil { if addr == nil {
log.Logf("[tun] no route for %s -> %s", src, dst) log.Logf("[tun] no route for %s -> %s", src, dst)
return nil return nil
} }
if Debug {
log.Logf("[tun] find route: %s -> %s", dst, addr)
}
if _, err := conn.WriteTo(b[:n], addr); err != nil { if _, err := conn.WriteTo(b[:n], addr); err != nil {
return err return err
} }
@ -305,11 +335,11 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
for { for {
err := func() error { err := func() error {
b := sPool.Get().([]byte) b := sPool.Get().([]byte)
defer mPool.Put(b) defer sPool.Put(b)
n, addr, err := conn.ReadFrom(b) n, addr, err := conn.ReadFrom(b)
if err != nil && if err != nil &&
err != shadowaead.ErrShortPacket && err != shadowstream.ErrShortPacket { err != shadowaead.ErrShortPacket {
return err return err
} }
@ -361,11 +391,11 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
log.Logf("[tun] new route: %s -> %s", src, addr) log.Logf("[tun] new route: %s -> %s", src, addr)
} }
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok { if addr := h.findRouteFor(dst); addr != nil {
if Debug { if Debug {
log.Logf("[tun] find route: %s -> %s", dst, v) log.Logf("[tun] find route: %s -> %s", dst, addr)
} }
_, err := conn.WriteTo(b[:n], v.(net.Addr)) _, err := conn.WriteTo(b[:n], addr)
return err return err
} }
@ -407,6 +437,7 @@ func etherType(et waterutil.Ethertype) string {
return fmt.Sprintf("unknown(%v)", et) return fmt.Sprintf("unknown(%v)", et)
} }
// TapConfig is the config for TAP device.
type TapConfig struct { type TapConfig struct {
Name string Name string
Addr string Addr string
@ -527,9 +558,17 @@ func (h *tapHandler) Handle(conn net.Conn) {
var pc net.PacketConn var pc net.PacketConn
// fake tcp mode will be ignored when the client specifies a chain. // fake tcp mode will be ignored when the client specifies a chain.
if raddr != nil && !h.options.Chain.IsEmpty() { if raddr != nil && !h.options.Chain.IsEmpty() {
var cc net.Conn cc, err := h.options.Chain.DialContext(context.Background(), "udp", raddr.String())
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil) if err != nil {
pc = &udpTunnelConn{Conn: cc, raddr: raddr} return err
}
var ok bool
pc, ok = cc.(net.PacketConn)
if !ok {
err = errors.New("not a packet connection")
log.Logf("[tap] %s - %s: %s", conn.LocalAddr(), raddr, err)
return err
}
} else { } else {
if h.options.TCPMode { if h.options.TCPMode {
if raddr != nil { if raddr != nil {
@ -658,11 +697,11 @@ func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.A
for { for {
err := func() error { err := func() error {
b := sPool.Get().([]byte) b := sPool.Get().([]byte)
defer mPool.Put(b) defer sPool.Put(b)
n, addr, err := conn.ReadFrom(b) n, addr, err := conn.ReadFrom(b)
if err != nil && if err != nil &&
err != shadowaead.ErrShortPacket && err != shadowstream.ErrShortPacket { err != shadowaead.ErrShortPacket {
return err return err
} }
@ -770,6 +809,7 @@ func (c *tunTapConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
} }
// IsIPv6Multicast reports whether the address addr is an IPv6 multicast address.
func IsIPv6Multicast(addr net.HardwareAddr) bool { func IsIPv6Multicast(addr net.HardwareAddr) bool {
return addr[0] == 0x33 && addr[1] == 0x33 return addr[0] == 0x33 && addr[1] == 0x33
} }

View File

@ -29,7 +29,12 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
mtu = DefaultMTU mtu = DefaultMTU
} }
cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu) peer := cfg.Peer
if peer == "" {
peer = ip.String()
}
cmd := fmt.Sprintf("ifconfig %s inet %s %s mtu %d up",
ifce.Name(), cfg.Addr, peer, mtu)
log.Log("[tun]", cmd) log.Log("[tun]", cmd)
args := strings.Split(cmd, " ") args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil { if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
@ -37,7 +42,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
if err = addRoutes(ifce.Name(), cfg.Routes...); err != nil { if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
return return
} }
@ -58,12 +63,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
func addRoutes(ifName string, routes ...string) error { func addTunRoutes(ifName string, routes ...IPRoute) error {
for _, route := range routes { for _, route := range routes {
if route == "" { if route.Dest == nil {
continue continue
} }
cmd := fmt.Sprintf("route add -net %s -interface %s", route, ifName) cmd := fmt.Sprintf("route add -net %s -interface %s", route.Dest.String(), ifName)
log.Log("[tun]", cmd) log.Log("[tun]", cmd)
args := strings.Split(cmd, " ") args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil { if er := exec.Command(args[0], args[1:]...).Run(); er != nil {

View File

@ -3,15 +3,15 @@ package gost
import ( import (
"fmt" "fmt"
"net" "net"
"os/exec"
"strings"
"github.com/docker/libcontainer/netlink"
"github.com/go-log/log" "github.com/go-log/log"
"github.com/milosgajdos83/tenus"
"github.com/songgao/water" "github.com/songgao/water"
) )
func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) { func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
ip, ipNet, err := net.ParseCIDR(cfg.Addr) ip, _, err := net.ParseCIDR(cfg.Addr)
if err != nil { if err != nil {
return return
} }
@ -26,35 +26,21 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
link, err := tenus.NewLinkFrom(ifce.Name())
if err != nil {
return
}
mtu := cfg.MTU mtu := cfg.MTU
if mtu <= 0 { if mtu <= 0 {
mtu = DefaultMTU mtu = DefaultMTU
} }
cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu) if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil {
log.Log("[tun]", cmd) log.Log(err)
if er := link.SetLinkMTU(mtu); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
} }
cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name()) if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil {
log.Log("[tun]", cmd) log.Log(err)
if er := link.SetLinkIp(ip, ipNet); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
} }
cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name()) if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil {
log.Log("[tun]", cmd) log.Log(err)
if er := link.SetLinkUp(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
} }
if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil { if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
@ -74,9 +60,12 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
} }
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
ip, ipNet, err := net.ParseCIDR(cfg.Addr) var ip net.IP
if err != nil { if cfg.Addr != "" {
return ip, _, err = net.ParseCIDR(cfg.Addr)
if err != nil {
return
}
} }
ifce, err := water.New(water.Config{ ifce, err := water.New(water.Config{
@ -89,35 +78,23 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
link, err := tenus.NewLinkFrom(ifce.Name())
if err != nil {
return
}
mtu := cfg.MTU mtu := cfg.MTU
if mtu <= 0 { if mtu <= 0 {
mtu = DefaultMTU mtu = DefaultMTU
} }
cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu) if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil {
log.Log("[tap]", cmd) log.Log(err)
if er := link.SetLinkMTU(mtu); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
} }
cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name()) if cfg.Addr != "" {
log.Log("[tap]", cmd) if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil {
if er := link.SetLinkIp(ip, ipNet); er != nil { log.Log(err)
err = fmt.Errorf("%s: %v", cmd, er) }
return
} }
cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name()) if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil {
log.Log("[tap]", cmd) log.Log(err)
if er := link.SetLinkUp(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
} }
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil { if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
@ -136,15 +113,17 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
func addTunRoutes(ifName string, routes ...string) error { func addTunRoutes(ifName string, routes ...IPRoute) error {
for _, route := range routes { for _, route := range routes {
if route == "" { if route.Dest == nil {
continue continue
} }
cmd := fmt.Sprintf("ip route add %s dev %s", route, ifName) cmd := fmt.Sprintf("ip route add %s dev %s", route.Dest.String(), ifName)
log.Logf("[tun] %s", cmd) log.Logf("[tun] %s", cmd)
if err := netlink.AddRoute(route, "", "", ifName); err != nil {
return fmt.Errorf("%s: %v", cmd, err) args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
log.Logf("[tun] %s: %v", cmd, er)
} }
} }
return nil return nil
@ -157,9 +136,22 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
} }
cmd := fmt.Sprintf("ip route add %s via %s dev %s", route, gw, ifName) cmd := fmt.Sprintf("ip route add %s via %s dev %s", route, gw, ifName)
log.Logf("[tap] %s", cmd) log.Logf("[tap] %s", cmd)
if err := netlink.AddRoute(route, "", gw, ifName); err != nil {
return fmt.Errorf("%s: %v", cmd, err) args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
log.Logf("[tap] %s: %v", cmd, er)
} }
} }
return nil return nil
} }
func exeCmd(cmd string) error {
log.Log(cmd)
args := strings.Split(cmd, " ")
if err := exec.Command(args[0], args[1:]...).Run(); err != nil {
return fmt.Errorf("%s: %v", cmd, err)
}
return nil
}

View File

@ -1,3 +1,4 @@
//go:build !linux && !windows && !darwin
// +build !linux,!windows,!darwin // +build !linux,!windows,!darwin
package gost package gost
@ -55,10 +56,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
} }
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
ip, _, err := net.ParseCIDR(cfg.Addr) ip, _, _ := net.ParseCIDR(cfg.Addr)
if err != nil {
return
}
ifce, err := water.New(water.Config{ ifce, err := water.New(water.Config{
DeviceType: water.TAP, DeviceType: water.TAP,
@ -72,7 +70,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
mtu = DefaultMTU mtu = DefaultMTU
} }
cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu) var cmd string
if cfg.Addr != "" {
cmd = fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu)
} else {
cmd = fmt.Sprintf("ifconfig %s mtu %d up", ifce.Name(), mtu)
}
log.Log("[tap]", cmd) log.Log("[tap]", cmd)
args := strings.Split(cmd, " ") args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil { if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
@ -96,12 +99,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
func addTunRoutes(ifName string, routes ...string) error { func addTunRoutes(ifName string, routes ...IPRoute) error {
for _, route := range routes { for _, route := range routes {
if route == "" { if route.Dest == nil {
continue continue
} }
cmd := fmt.Sprintf("route add -net %s -interface %s", route, ifName) cmd := fmt.Sprintf("route add -net %s -interface %s", route.Dest.String(), ifName)
log.Logf("[tun] %s", cmd) log.Logf("[tun] %s", cmd)
args := strings.Split(cmd, " ") args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil { if er := exec.Command(args[0], args[1:]...).Run(); er != nil {

View File

@ -28,7 +28,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
cmd := fmt.Sprintf("netsh interface ip set address name=%s "+ cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+
"source=static addr=%s mask=%s gateway=none", "source=static addr=%s mask=%s gateway=none",
ifce.Name(), ip.String(), ipMask(ipNet.Mask)) ifce.Name(), ip.String(), ipMask(ipNet.Mask))
log.Log("[tun]", cmd) log.Log("[tun]", cmd)
@ -55,10 +55,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
} }
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) { func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
ip, ipNet, err := net.ParseCIDR(cfg.Addr) ip, ipNet, _ := net.ParseCIDR(cfg.Addr)
if err != nil {
return
}
ifce, err := water.New(water.Config{ ifce, err := water.New(water.Config{
DeviceType: water.TAP, DeviceType: water.TAP,
@ -72,14 +69,16 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
cmd := fmt.Sprintf("netsh interface ip set address name=%s "+ if ip != nil && ipNet != nil {
"source=static addr=%s mask=%s gateway=none", cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+
ifce.Name(), ip.String(), ipMask(ipNet.Mask)) "source=static addr=%s mask=%s gateway=none",
log.Log("[tap]", cmd) ifce.Name(), ip.String(), ipMask(ipNet.Mask))
args := strings.Split(cmd, " ") log.Log("[tap]", cmd)
if er := exec.Command(args[0], args[1:]...).Run(); er != nil { args := strings.Split(cmd, " ")
err = fmt.Errorf("%s: %v", cmd, er) if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
return err = fmt.Errorf("%s: %v", cmd, er)
return
}
} }
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil { if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
@ -98,16 +97,16 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return return
} }
func addTunRoutes(ifName string, gw string, routes ...string) error { func addTunRoutes(ifName string, gw string, routes ...IPRoute) error {
for _, route := range routes { for _, route := range routes {
if route == "" { if route.Dest == nil {
continue continue
} }
deleteRoute(ifName, route) deleteRoute(ifName, route.Dest.String())
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active", cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active",
route, ifName) route.Dest.String(), ifName)
if gw != "" { if gw != "" {
cmd += " nexthop=" + gw cmd += " nexthop=" + gw
} }
@ -128,7 +127,7 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
deleteRoute(ifName, route) deleteRoute(ifName, route)
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active", cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active",
route, ifName) route, ifName)
if gw != "" { if gw != "" {
cmd += " nexthop=" + gw cmd += " nexthop=" + gw
@ -143,7 +142,7 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
} }
func deleteRoute(ifName string, route string) error { func deleteRoute(ifName string, route string) error {
cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=%s store=active", cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=\"%s\" store=active",
route, ifName) route, ifName)
args := strings.Split(cmd, " ") args := strings.Split(cmd, " ")
return exec.Command(args[0], args[1:]...).Run() return exec.Command(args[0], args[1:]...).Run()

356
udp.go Normal file
View File

@ -0,0 +1,356 @@
package gost
import (
"errors"
"net"
"sync"
"sync/atomic"
"time"
"github.com/go-log/log"
)
// udpTransporter is a raw UDP transporter.
type udpTransporter struct{}
// UDPTransporter creates a Transporter for UDP client.
func UDPTransporter() Transporter {
return &udpTransporter{}
}
func (tr *udpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
taddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", nil, taddr)
if err != nil {
return nil, err
}
return &udpClientConn{
UDPConn: conn,
}, nil
}
func (tr *udpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return conn, nil
}
func (tr *udpTransporter) Multiplex() bool {
return false
}
// UDPListenConfig is the config for UDP Listener.
type UDPListenConfig struct {
TTL time.Duration // timeout per connection
Backlog int // connection backlog
QueueSize int // recv queue size per connection
}
type udpListener struct {
ln net.PacketConn
connChan chan net.Conn
errChan chan error
connMap *udpConnMap
config *UDPListenConfig
}
// UDPListener creates a Listener for UDP server.
func UDPListener(addr string, cfg *UDPListenConfig) (Listener, error) {
laddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
ln, err := net.ListenUDP("udp", laddr)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &UDPListenConfig{}
}
backlog := cfg.Backlog
if backlog <= 0 {
backlog = defaultBacklog
}
l := &udpListener{
ln: ln,
connChan: make(chan net.Conn, backlog),
errChan: make(chan error, 1),
connMap: new(udpConnMap),
config: cfg,
}
go l.listenLoop()
return l, nil
}
func (l *udpListener) listenLoop() {
for {
// NOTE: this buffer will be released in the udpServerConn after read.
b := mPool.Get().([]byte)
n, raddr, err := l.ln.ReadFrom(b)
if err != nil {
log.Logf("[udp] peer -> %s : %s", l.Addr(), err)
l.Close()
l.errChan <- err
close(l.errChan)
return
}
conn, ok := l.connMap.Get(raddr.String())
if !ok {
conn = newUDPServerConn(l.ln, raddr, &udpServerConnConfig{
ttl: l.config.TTL,
qsize: l.config.QueueSize,
onClose: func() {
l.connMap.Delete(raddr.String())
log.Logf("[udp] %s closed (%d)", raddr, l.connMap.Size())
},
})
select {
case l.connChan <- conn:
l.connMap.Set(raddr.String(), conn)
log.Logf("[udp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size())
default:
conn.Close()
log.Logf("[udp] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan))
}
}
select {
case conn.rChan <- b[:n]:
if Debug {
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n)
}
default:
log.Logf("[udp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
}
}
}
func (l *udpListener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.connChan:
case err, ok = <-l.errChan:
if !ok {
err = errors.New("accpet on closed listener")
}
}
return
}
func (l *udpListener) Addr() net.Addr {
return l.ln.LocalAddr()
}
func (l *udpListener) Close() error {
err := l.ln.Close()
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
v.Close()
return true
})
return err
}
type udpConnMap struct {
size int64
m sync.Map
}
func (m *udpConnMap) Get(key interface{}) (conn *udpServerConn, ok bool) {
v, ok := m.m.Load(key)
if ok {
conn, ok = v.(*udpServerConn)
}
return
}
func (m *udpConnMap) Set(key interface{}, conn *udpServerConn) {
m.m.Store(key, conn)
atomic.AddInt64(&m.size, 1)
}
func (m *udpConnMap) Delete(key interface{}) {
m.m.Delete(key)
atomic.AddInt64(&m.size, -1)
}
func (m *udpConnMap) Range(f func(key interface{}, value *udpServerConn) bool) {
m.m.Range(func(k, v interface{}) bool {
return f(k, v.(*udpServerConn))
})
}
func (m *udpConnMap) Size() int64 {
return atomic.LoadInt64(&m.size)
}
// udpServerConn is a server side connection for UDP client peer, it implements net.Conn and net.PacketConn.
type udpServerConn struct {
conn net.PacketConn
raddr net.Addr
rChan chan []byte
closed chan struct{}
closeMutex sync.Mutex
nopChan chan int
config *udpServerConnConfig
}
type udpServerConnConfig struct {
ttl time.Duration
qsize int
onClose func()
}
func newUDPServerConn(conn net.PacketConn, raddr net.Addr, cfg *udpServerConnConfig) *udpServerConn {
if conn == nil || raddr == nil {
return nil
}
if cfg == nil {
cfg = &udpServerConnConfig{}
}
qsize := cfg.qsize
if qsize <= 0 {
qsize = defaultQueueSize
}
c := &udpServerConn{
conn: conn,
raddr: raddr,
rChan: make(chan []byte, qsize),
closed: make(chan struct{}),
nopChan: make(chan int),
config: cfg,
}
go c.ttlWait()
return c
}
func (c *udpServerConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpServerConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
select {
case bb := <-c.rChan:
n = copy(b, bb)
if cap(bb) == mediumBufferSize {
mPool.Put(bb[:cap(bb)])
}
case <-c.closed:
err = errors.New("read from closed connection")
return
}
select {
case c.nopChan <- n:
default:
}
addr = c.raddr
return
}
func (c *udpServerConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.raddr)
}
func (c *udpServerConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
n, err = c.conn.WriteTo(b, addr)
if n > 0 {
if Debug {
log.Logf("[udp] %s <<< %s : length %d", addr, c.LocalAddr(), n)
}
select {
case c.nopChan <- n:
default:
}
}
return
}
func (c *udpServerConn) Close() error {
c.closeMutex.Lock()
defer c.closeMutex.Unlock()
select {
case <-c.closed:
return errors.New("connection is closed")
default:
if c.config.onClose != nil {
c.config.onClose()
}
close(c.closed)
}
return nil
}
func (c *udpServerConn) ttlWait() {
ttl := c.config.ttl
if ttl == 0 {
ttl = defaultTTL
}
timer := time.NewTimer(ttl)
defer timer.Stop()
for {
select {
case <-c.nopChan:
if !timer.Stop() {
<-timer.C
}
timer.Reset(ttl)
case <-timer.C:
c.Close()
return
case <-c.closed:
return
}
}
}
func (c *udpServerConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *udpServerConn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *udpServerConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *udpServerConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *udpServerConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
type udpClientConn struct {
*net.UDPConn
}
func (c *udpClientConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return c.UDPConn.Write(b)
}
func (c *udpClientConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
n, err = c.UDPConn.Read(b)
addr = c.RemoteAddr()
return
}

76
vsock.go Normal file
View File

@ -0,0 +1,76 @@
package gost
import (
"net"
"strconv"
"github.com/mdlayher/vsock"
)
// vsockTransporter is a raw VSOCK transporter.
type vsockTransporter struct{}
// VSOCKTransporter creates a raw VSOCK client.
func VSOCKTransporter() Transporter {
return &vsockTransporter{}
}
func (tr *vsockTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
opts := &DialOptions{}
for _, option := range options {
option(opts)
}
if opts.Chain == nil {
vAddr, err := parseAddr(addr)
if err != nil {
return nil, err
}
return vsock.Dial(vAddr.ContextID, vAddr.Port, nil)
}
return opts.Chain.Dial(addr)
}
func parseUint32(s string) (uint32, error ) {
n, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return 0, err
}
return uint32(n), nil
}
func parseAddr(addr string) (*vsock.Addr, error) {
hostStr, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
host := uint32(0)
if hostStr != "" {
host, err = parseUint32(hostStr)
if err != nil {
return nil, err
}
}
port, err := parseUint32(portStr)
if err != nil {
return nil, err
}
return &vsock.Addr{ContextID: host, Port: port}, nil
}
func (tr *vsockTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return conn, nil
}
func (tr *vsockTransporter) Multiplex() bool {
return false
}
// VSOCKListener creates a Listener for VSOCK proxy server.
func VSOCKListener(addr string) (Listener, error) {
vAddr, err := parseAddr(addr)
if err != nil {
return nil, err
}
return vsock.Listen(vAddr.Port, nil)
}

4
ws.go
View File

@ -15,8 +15,8 @@ import (
"net/url" "net/url"
"github.com/go-log/log" "github.com/go-log/log"
"gopkg.in/gorilla/websocket.v1" "github.com/gorilla/websocket"
smux "gopkg.in/xtaci/smux.v1" smux "github.com/xtaci/smux"
) )
const ( const (