Compare commits

..

135 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
ginuerzh
c3aedc6d42 v2.9.1 2020-01-15 14:56:45 +08:00
ginuerzh
d1043cff83 add notls option for SOCKS5 2020-01-12 20:33:53 +08:00
ginuerzh
c5fc56d151 kcp: add fake TCP support 2020-01-11 18:43:54 +08:00
ginuerzh
62a17f07f7 tuntap: fake TCP mode restoration 2020-01-11 16:00:37 +08:00
ginuerzh
c56e4aa7c2 add fake TCP transport 2020-01-11 15:36:18 +08:00
ginuerzh
e8d5687d65 tap: add chain support 2020-01-10 22:20:41 +08:00
ginuerzh
42bf481ded tun: add chain support 2020-01-10 20:59:31 +08:00
67 changed files with 5630 additions and 2680 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
debian
bin
dist/
# Architecture specific extensions/prefixes
*.[568vq]
@ -30,5 +31,5 @@ _testmain.go
*.bak
.vscode/
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/
COPY --from=builder /src/cmd/gost/gost .
COPY --from=builder /app/cmd/gost/gost .
ENTRYPOINT ["/bin/gost"]

View File

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

View File

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

View File

@ -4,33 +4,32 @@ gost - GO Simple Tunnel
### A simple security tunnel written in Golang
[![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)
[![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)
[![Snap Status](https://build.snapcraft.io/badge/ginuerzh/gost.svg)](https://build.snapcraft.io/user/ginuerzh/gost)
[![Docker Build Status](https://img.shields.io/docker/build/ginuerzh/gost.svg)](https://hub.docker.com/r/ginuerzh/gost/)
[![Docker](https://img.shields.io/docker/pulls/ginuerzh/gost.svg)](https://hub.docker.com/r/ginuerzh/gost/)
[![gost](https://snapcraft.io/gost/badge.svg)](https://snapcraft.io/gost)
Features
------
* Listening on multiple ports
* Multi-level forward proxy - proxy chain
* Standard HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5 proxy protocols support
* [Probing resistance](https://docs.ginuerzh.xyz/gost/en/probe_resist/) support for web proxy
* [Support multiple tunnel types](https://docs.ginuerzh.xyz/gost/en/configuration/)
* [TLS encryption via negotiation support for SOCKS5 proxy](https://docs.ginuerzh.xyz/gost/en/socks/)
* [Tunnel UDP over TCP](https://docs.ginuerzh.xyz/gost/en/socks/)
* [Transparent TCP proxy](https://docs.ginuerzh.xyz/gost/en/redirect/)
* [Local/remote TCP/UDP port forwarding](https://docs.ginuerzh.xyz/gost/en/port-forwarding/)
* [Shadowsocks protocol](https://docs.ginuerzh.xyz/gost/en/ss/)
* [SNI proxy](https://docs.ginuerzh.xyz/gost/en/sni/)
* [Permission control](https://docs.ginuerzh.xyz/gost/en/permission/)
* [Load balancing](https://docs.ginuerzh.xyz/gost/en/load-balancing/)
* [Routing control](https://docs.ginuerzh.xyz/gost/en/bypass/)
* [DNS control](https://docs.ginuerzh.xyz/gost/en/dns/)
* [TUN/TAP device](https://docs.ginuerzh.xyz/gost/en/tuntap/)
* [Probing resistance](https://v2.gost.run/en/probe_resist/) support for web proxy
* [Support multiple tunnel types](https://v2.gost.run/en/configuration/)
* [TLS encryption via negotiation support for SOCKS5 proxy](https://v2.gost.run/en/socks/)
* [Tunnel UDP over TCP](https://v2.gost.run/en/socks/)
* [TCP/UDP Transparent proxy](https://v2.gost.run/en/redirect/)
* [Local/remote TCP/UDP port forwarding](https://v2.gost.run/en/port-forwarding/)
* [Shadowsocks protocol](https://v2.gost.run/en/ss/)
* [SNI proxy](https://v2.gost.run/en/sni/)
* [Permission control](https://v2.gost.run/en/permission/)
* [Load balancing](https://v2.gost.run/en/load-balancing/)
* [Routing control](https://v2.gost.run/en/bypass/)
* DNS [resolver](https://v2.gost.run/resolver/) and [proxy](https://v2.gost.run/dns/)
* [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>
@ -46,18 +45,27 @@ Installation
#### From source
```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
```bash
docker pull ginuerzh/gost
docker run --rm ginuerzh/gost -V
```
#### Homebrew
```bash
brew install gost
```
#### Ubuntu store
```bash
sudo snap install core
sudo snap install gost
```
@ -210,7 +218,7 @@ gost -L=:8080 -F=h2://server_ip:443
#### 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:

119
chain.go
View File

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

147
client.go
View File

@ -1,12 +1,13 @@
package gost
import (
"context"
"crypto/tls"
"net"
"net/url"
"time"
"github.com/ginuerzh/gosocks5"
"github.com/go-gost/gosocks5"
)
// Client is a proxy client.
@ -14,23 +15,8 @@ import (
// Connector is responsible for connecting to the destination address through this proxy.
// Transporter performs a handshake with this proxy.
type Client struct {
Connector Connector
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...)
Connector
Transporter
}
// 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.
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.
@ -64,72 +79,11 @@ type Transporter interface {
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.
type DialOptions struct {
Timeout time.Duration
Chain *Chain
Host string
}
// 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.
type HandshakeOptions struct {
Addr string
@ -161,6 +122,7 @@ type HandshakeOptions struct {
WSOptions *WSOptions
KCPConfig *KCPConfig
QUICConfig *QUICConfig
SSHConfig *SSHConfig
}
// 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.
type ConnectOptions struct {
Addr string
@ -243,6 +212,8 @@ type ConnectOptions struct {
User *url.Userinfo
Selector gosocks5.Selector
UserAgent string
NoTLS bool
NoDelay bool
}
// ConnectOption allows a common way to set ConnectOptions.
@ -282,3 +253,17 @@ func UserAgentConnectOption(ua string) ConnectOption {
opts.UserAgent = ua
}
}
// NoTLSConnectOption specifies the SOCKS5 method without TLS.
func NoTLSConnectOption(b bool) ConnectOption {
return func(opts *ConnectOptions) {
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/x509"
"encoding/json"
"errors"
"io/ioutil"
"fmt"
"net"
"net/url"
"os"
"strings"
@ -43,8 +43,9 @@ var (
defaultKeyFile = "key.pem"
)
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
// Load the certificate from cert & key files and optional client CA file,
// will use the default certificate if the provided info are invalid.
func tlsConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
if certFile == "" || keyFile == "" {
certFile, keyFile = defaultCertFile, defaultKeyFile
}
@ -53,7 +54,19 @@ func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
if err != nil {
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) {
@ -61,12 +74,12 @@ func loadCA(caFile string) (cp *x509.CertPool, err error) {
return
}
cp = x509.NewCertPool()
data, err := ioutil.ReadFile(caFile)
data, err := os.ReadFile(caFile)
if err != nil {
return nil, err
}
if !cp.AppendCertsFromPEM(data) {
return nil, errors.New("AppendCertsFromPEM failed")
return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile)
}
return
}
@ -97,6 +110,7 @@ func parseUsers(authFile string) (users []*url.Userinfo, err error) {
if err != nil {
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
@ -142,33 +156,38 @@ func parseIP(s string, port string) (ips []string) {
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)
if err != nil {
ss := strings.Split(s, ",")
for _, s := range ss {
s = strings.TrimSpace(s)
if s != "" {
// TODO: support IPv6
if !strings.Contains(s, ":") {
s = s + ":" + port
}
ips = append(ips, s)
ips = append(ips, addrFn(s, port))
}
}
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if !strings.Contains(line, ":") {
line = line + ":" + port
}
ips = append(ips, line)
ips = append(ips, addrFn(line, port))
}
return
}
@ -218,13 +237,19 @@ func parseResolver(cfg string) gost.Resolver {
continue
}
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{
Addr: s,
Protocol: "https",
Protocol: p,
}
if err := ns.Init(); err == nil {
nss = append(nss, ns)
}
continue
}
@ -233,20 +258,16 @@ func parseResolver(cfg string) gost.Resolver {
ns := gost.NameServer{
Addr: ss[0],
}
if err := ns.Init(); err == nil {
nss = append(nss, ns)
}
}
if len(ss) == 2 {
ns := gost.NameServer{
Addr: ss[0],
Protocol: ss[1],
}
if err := ns.Init(); err == nil {
nss = append(nss, ns)
}
}
}
return gost.NewResolver(0, nss...)
}
defer f.Close()
@ -273,3 +294,49 @@ func parseHosts(s string) *gost.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.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(&baseCfg.route.Interface, "I", "", "Interface to bind")
flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log")
flag.BoolVar(&printVersion, "V", false, "print version")
if pprofEnabled {
@ -40,7 +42,7 @@ func init() {
flag.Parse()
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)
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.
tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile)
tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile, "")
if err != nil {
// generate random self-signed certificate.
cert, err := gost.GenCertificate()

View File

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

View File

@ -3,8 +3,11 @@ package main
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"net"
"net/url"
"os"
"strings"
"time"
@ -27,11 +30,15 @@ type route struct {
ServeNodes stringList
ChainNodes stringList
Retries int
Mark int
Interface string
}
func (r *route) parseChain() (*gost.Chain, error) {
chain := gost.NewChain()
chain.Retries = r.Retries
chain.Mark = r.Mark
chain.Interface = r.Interface
gid := 1 // group ID
for _, ns := range r.ChainNodes {
@ -59,6 +66,7 @@ func (r *route) parseChain() (*gost.Chain, error) {
FailTimeout: nodes[0].GetDuration("fail_timeout"),
},
&gost.InvalidFilter{},
gost.NewFastestFilter(0, nodes[0].GetInt("fastest_count")),
),
gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))),
)
@ -90,13 +98,29 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
return
}
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:])
}
}
if node.User == nil {
users, err := parseUsers(node.Get("secrets"))
if err != nil {
return
return nil, err
}
if node.User == nil && len(users) > 0 {
if len(users) > 0 {
node.User = users[0]
}
}
serverName, sport, _ := net.SplitHostPort(node.Addr)
if serverName == "" {
serverName = "localhost" // default server name
@ -111,6 +135,35 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
InsecureSkipVerify: !node.GetBool("secure"),
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.EnableCompression = node.GetBool("compression")
wsOpts.ReadBufferSize = node.GetInt("rbuf")
@ -118,7 +171,7 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
wsOpts.UserAgent = node.Get("agent")
wsOpts.Path = node.Get("path")
var host string
timeout := node.GetDuration("timeout")
var tr gost.Transporter
switch node.Transport {
@ -139,6 +192,13 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
if err != nil {
return nil, err
}
if config == nil {
conf := gost.DefaultKCPConfig
if node.GetBool("tcp") {
conf.TCP = true
}
config = &conf
}
tr = gost.KCPTransporter(config)
case "ssh":
if node.Protocol == "direct" || node.Protocol == "remote" {
@ -150,8 +210,14 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
config := &gost.QUICConfig{
TLSConfig: tlsCfg,
KeepAlive: node.GetBool("keepalive"),
Timeout: time.Duration(node.GetInt("timeout")) * time.Second,
IdleTimeout: time.Duration(node.GetInt("idle")) * time.Second,
Timeout: timeout,
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 != "" {
@ -169,8 +235,15 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
case "obfs4":
tr = gost.Obfs4Transporter()
case "ohttp":
host = node.Get("host")
tr = gost.ObfsHTTPTransporter()
case "otls":
tr = gost.ObfsTLSTransporter()
case "ftcp":
tr = gost.FakeTCPTransporter()
case "udp":
tr = gost.UDPTransporter()
case "vsock":
tr = gost.VSOCKTransporter()
default:
tr = gost.TCPTransporter()
}
@ -187,8 +260,8 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
connector = gost.SOCKS4AConnector()
case "ss":
connector = gost.ShadowConnector(node.User)
case "ss2":
connector = gost.Shadow2Connector(node.User)
case "ssu":
connector = gost.ShadowUDPConnector(node.User)
case "direct":
connector = gost.SSHDirectForwardConnector()
case "remote":
@ -198,33 +271,48 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
case "sni":
connector = gost.SNIConnector(node.Get("host"))
case "http":
fallthrough
default:
node.Protocol = "http" // default protocol is HTTP
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,
gost.TimeoutDialOption(time.Duration(timeout)*time.Second),
gost.TimeoutDialOption(timeout),
gost.HostDialOption(host),
)
node.ConnectOptions = []gost.ConnectOption{
gost.UserAgentConnectOption(node.Get("agent")),
gost.NoTLSConnectOption(node.GetBool("notls")),
gost.NoDelayConnectOption(node.GetBool("nodelay")),
}
if host == "" {
host = node.Host
sshConfig := &gost.SSHConfig{}
if s := node.Get("ssh_key"); s != "" {
key, err := gost.ParseSSHKeyFile(s)
if err != nil {
return nil, err
}
sshConfig.Key = key
}
handshakeOptions := []gost.HandshakeOption{
gost.AddrHandshakeOption(node.Addr),
gost.HostHandshakeOption(host),
gost.UserHandshakeOption(node.User),
gost.TLSConfigHandshakeOption(tlsCfg),
gost.IntervalHandshakeOption(time.Duration(node.GetInt("ping")) * time.Second),
gost.TimeoutHandshakeOption(time.Duration(timeout) * time.Second),
gost.IntervalHandshakeOption(node.GetDuration("ping")),
gost.TimeoutHandshakeOption(timeout),
gost.RetryHandshakeOption(node.GetInt("retry")),
gost.SSHConfigHandshakeOption(sshConfig),
}
node.Client = &gost.Client{
Connector: connector,
Transporter: tr,
@ -270,6 +358,20 @@ func (r *route) GenRouters() ([]router, error) {
if err != nil {
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"))
if err != nil {
return nil, err
@ -285,7 +387,7 @@ func (r *route) GenRouters() ([]router, error) {
}
}
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 != "" {
return nil, err
}
@ -296,9 +398,15 @@ func (r *route) GenRouters() ([]router, error) {
wsOpts.WriteBufferSize = node.GetInt("wbuf")
wsOpts.Path = node.Get("path")
ttl, err := time.ParseDuration(node.Get("ttl"))
if err != nil {
ttl = time.Duration(node.GetInt("ttl")) * time.Second
ttl := node.GetDuration("ttl")
timeout := node.GetDuration("timeout")
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
@ -320,12 +428,33 @@ func (r *route) GenRouters() ([]router, error) {
if er != nil {
return nil, er
}
if config == nil {
conf := gost.DefaultKCPConfig
if node.GetBool("tcp") {
conf.TCP = true
}
config = &conf
}
ln, err = gost.KCPListener(node.Addr, config)
case "ssh":
config := &gost.SSHConfig{
Authenticator: authenticator,
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" {
ln, err = gost.TCPListener(node.Addr)
} else {
@ -335,8 +464,14 @@ func (r *route) GenRouters() ([]router, error) {
config := &gost.QUICConfig{
TLSConfig: tlsCfg,
KeepAlive: node.GetBool("keepalive"),
Timeout: time.Duration(node.GetInt("timeout")) * time.Second,
IdleTimeout: time.Duration(node.GetInt("idle")) * time.Second,
Timeout: timeout,
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 != "" {
sum := sha256.Sum256([]byte(cipher))
@ -357,6 +492,14 @@ func (r *route) GenRouters() ([]router, error) {
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
}
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":
// Directly use SSH port forwarding if the last chain node is forward+ssh
if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" {
@ -364,24 +507,10 @@ func (r *route) GenRouters() ([]router, error) {
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
}
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":
ln, err = gost.UDPRemoteForwardListener(node.Addr,
chain,
&gost.UDPForwardListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"),
})
case "ssu":
ln, err = gost.ShadowUDPListener(node.Addr,
node.User,
&gost.UDPForwardListenConfig{
&gost.UDPListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
QueueSize: node.GetInt("queue"),
@ -393,12 +522,15 @@ func (r *route) GenRouters() ([]router, error) {
ln, err = gost.Obfs4Listener(node.Addr)
case "ohttp":
ln, err = gost.ObfsHTTPListener(node.Addr)
case "otls":
ln, err = gost.ObfsTLSListener(node.Addr)
case "tun":
cfg := gost.TunConfig{
Name: node.Get("name"),
Addr: node.Get("net"),
Peer: node.Get("peer"),
MTU: node.GetInt("mtu"),
Routes: strings.Split(node.Get("route"), ","),
Routes: tunRoutes,
Gateway: node.Get("gw"),
}
ln, err = gost.TunListener(cfg)
@ -411,6 +543,29 @@ func (r *route) GenRouters() ([]router, error) {
Gateway: node.Get("gw"),
}
ln, err = gost.TapListener(cfg)
case "ftcp":
ln, err = gost.FakeTCPListener(
node.Addr,
&gost.FakeTCPListenConfig{
TTL: ttl,
Backlog: node.GetInt("backlog"),
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:
ln, err = gost.TCPListener(node.Addr)
}
@ -428,8 +583,6 @@ func (r *route) GenRouters() ([]router, error) {
handler = gost.SOCKS4Handler()
case "ss":
handler = gost.ShadowHandler()
case "ss2":
handler = gost.Shadow2Handler()
case "http":
handler = gost.HTTPHandler()
case "tcp":
@ -442,16 +595,22 @@ func (r *route) GenRouters() ([]router, error) {
handler = gost.UDPRemoteForwardHandler(node.Remote)
case "forward":
handler = gost.SSHForwardHandler()
case "redirect":
case "red", "redirect":
handler = gost.TCPRedirectHandler()
case "redu", "redirectu":
handler = gost.UDPRedirectHandler()
case "ssu":
handler = gost.ShadowUDPdHandler()
handler = gost.ShadowUDPHandler()
case "sni":
handler = gost.SNIHandler()
case "tun":
handler = gost.TunHandler()
case "tap":
handler = gost.TapHandler()
case "dns":
handler = gost.DNSHandler(node.Remote)
case "relay":
handler = gost.RelayHandler(node.Remote)
default:
// start from 2.5, if remote is not empty, then we assume that it is a forward tunnel.
if node.Remote != "" {
@ -474,10 +633,20 @@ func (r *route) GenRouters() ([]router, error) {
}
node.Bypass = parseBypass(node.Get("bypass"))
resolver := parseResolver(node.Get("dns"))
hosts := parseHosts(node.Get("hosts"))
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(
gost.AddrHandlerOption(ln.Addr().String()),
gost.ChainHandlerOption(chain),
@ -493,12 +662,15 @@ func (r *route) GenRouters() ([]router, error) {
gost.ResolverHandlerOption(resolver),
gost.HostsHandlerOption(hosts),
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.KnockingHandlerOption(node.Get("knock")),
gost.NodeHandlerOption(node),
gost.IPsHandlerOption(ips),
gost.TCPModeHandlerOption(node.GetBool("tcp")),
gost.IPRoutesHandlerOption(tunRoutes...),
gost.ProxyAgentHandlerOption(node.Get("proxyAgent")),
gost.HTTPTunnelHandlerOption(node.GetBool("httpTunnel")),
)
rt := router{

View File

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

View File

@ -1,18 +1,18 @@
package gost
import (
"context"
"errors"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"fmt"
"github.com/ginuerzh/gosocks5"
"github.com/go-gost/gosocks5"
"github.com/go-log/log"
smux "gopkg.in/xtaci/smux.v1"
smux "github.com/xtaci/smux"
)
type forwardConnector struct {
@ -23,7 +23,11 @@ func ForwardConnector() Connector {
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
}
@ -70,13 +74,6 @@ func (h *baseForwardHandler) Init(options ...HandlerOption) {
n++
}
if len(h.group.Nodes()) == 0 {
h.group.AddNode(Node{ // dummy address
ID: n,
Addr: ":0",
Host: ":0",
})
}
}
type tcpDirectForwardHandler struct {
@ -109,6 +106,8 @@ func (h *tcpDirectForwardHandler) Init(options ...HandlerOption) {
func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
defer conn.Close()
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
retries := 1
if h.options.Chain != nil && h.options.Chain.Retries > 0 {
retries = h.options.Chain.Retries
@ -121,19 +120,21 @@ func (h *tcpDirectForwardHandler) Handle(conn 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 {
log.Logf("[tcp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
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,
RetryChainOption(h.options.Retries),
TimeoutChainOption(h.options.Timeout),
ResolverChainOption(h.options.Resolver),
)
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()
} else {
break
@ -146,9 +147,13 @@ func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
node.ResetDead()
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)
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), node.Addr)
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), addr)
}
type udpDirectForwardHandler struct {
@ -181,42 +186,39 @@ func (h *udpDirectForwardHandler) Init(options ...HandlerOption) {
func (h *udpDirectForwardHandler) Handle(conn net.Conn) {
defer conn.Close()
node, err := h.group.Next()
if err != nil {
log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
return
}
log.Logf("[udp] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
var cc net.Conn
if h.options.Chain.IsEmpty() {
raddr, err := net.ResolveUDPAddr("udp", node.Addr)
if err != nil {
node.MarkDead()
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err)
return
}
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 node Node
var err error
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
if len(h.group.Nodes()) > 0 {
node, err = h.group.Next()
if err != nil {
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err)
log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
cc = &udpTunnelConn{Conn: cc, raddr: node.Addr}
}
cc, err := h.options.Chain.DialContext(
context.Background(),
"udp",
node.Addr,
ResolverChainOption(h.options.Resolver),
)
if err != nil {
node.MarkDead()
log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
defer cc.Close()
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)
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), node.Addr)
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), addr)
}
type tcpRemoteForwardHandler struct {
@ -258,11 +260,13 @@ func (h *tcpRemoteForwardHandler) Handle(conn 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 {
log.Logf("[rtcp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
return
}
}
cc, err = net.DialTimeout("tcp", node.Addr, h.options.Timeout)
if err != nil {
log.Logf("[rtcp] %s -> %s : %s", conn.LocalAddr(), node.Addr, err)
@ -313,11 +317,15 @@ func (h *udpRemoteForwardHandler) Init(options ...HandlerOption) {
func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
defer conn.Close()
node, err := h.group.Next()
var node Node
var err error
if len(h.group.Nodes()) > 0 {
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)
if err != nil {
@ -339,271 +347,6 @@ func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
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.RemoteAddr(), 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 {
addr net.Addr
chain *Chain
@ -613,7 +356,6 @@ type tcpRemoteForwardListener struct {
sessionMux sync.Mutex
closed chan struct{}
closeMux sync.Mutex
errChan chan error
}
// TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server.
@ -628,7 +370,6 @@ func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
chain: chain,
connChan: make(chan net.Conn, 1024),
closed: make(chan struct{}),
errChan: make(chan error),
}
if !ln.isChainValid() {
@ -642,9 +383,13 @@ func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
}
func (l *tcpRemoteForwardListener) isChainValid() bool {
if l.chain.IsEmpty() {
return false
}
lastNode := l.chain.LastNode()
if (lastNode.Protocol == "forward" && lastNode.Transport == "ssh") ||
lastNode.Protocol == "socks5" {
lastNode.Protocol == "socks5" || lastNode.Protocol == "" {
return true
}
return false
@ -710,7 +455,7 @@ func (l *tcpRemoteForwardListener) accept() (conn net.Conn, err error) {
return l.chain.Dial(l.addr.String())
}
if lastNode.Protocol == "socks5" {
if l.isChainValid() {
if lastNode.GetBool("mbind") {
return l.muxAccept() // multiplexing support for binding.
}
@ -763,7 +508,7 @@ func (l *tcpRemoteForwardListener) getSession() (s *muxSession, err error) {
conn.SetDeadline(time.Now().Add(HandshakeTimeout))
defer conn.SetDeadline(time.Time{})
conn, err = socks5Handshake(conn, nil, l.chain.LastNode().User)
conn, err = socks5Handshake(conn, userSocks5HandshakeOption(l.chain.LastNode().User))
if err != nil {
return nil, err
}
@ -798,7 +543,7 @@ func (l *tcpRemoteForwardListener) getSession() (s *muxSession, err error) {
}
func (l *tcpRemoteForwardListener) waitConnectSOCKS5(conn net.Conn) (net.Conn, error) {
conn, err := socks5Handshake(conn, nil, l.chain.LastNode().User)
conn, err := socks5Handshake(conn, userSocks5HandshakeOption(l.chain.LastNode().User))
if err != nil {
return nil, err
}
@ -864,26 +609,26 @@ func (l *tcpRemoteForwardListener) Close() error {
type udpRemoteForwardListener struct {
addr net.Addr
chain *Chain
connMap udpConnMap
connMap *udpConnMap
connChan chan net.Conn
ln *net.UDPConn
errChan chan error
ttl time.Duration
closed chan struct{}
closeMux sync.Mutex
ready chan struct{}
once sync.Once
config *UDPForwardListenConfig
closeMux sync.Mutex
config *UDPListenConfig
}
// 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)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &UDPForwardListenConfig{}
cfg = &UDPListenConfig{}
}
backlog := cfg.Backlog
@ -894,22 +639,27 @@ func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPForwardListenCo
ln := &udpRemoteForwardListener{
addr: laddr,
chain: chain,
connMap: new(udpConnMap),
connChan: make(chan net.Conn, backlog),
errChan: make(chan error, 1),
ready: make(chan struct{}),
closed: make(chan struct{}),
config: cfg,
}
go ln.listenLoop()
err = <-ln.errChan
<-ln.ready
return ln, err
}
func (l *udpRemoteForwardListener) isChainValid() bool {
if l.chain.IsEmpty() {
return false
}
lastNode := l.chain.LastNode()
return lastNode.Protocol == "socks5"
return lastNode.Protocol == "socks5" || lastNode.Protocol == ""
}
func (l *udpRemoteForwardListener) listenLoop() {
@ -920,6 +670,10 @@ func (l *udpRemoteForwardListener) listenLoop() {
return
}
l.once.Do(func() {
close(l.ready)
})
func() {
defer conn.Close()
@ -933,11 +687,14 @@ func (l *udpRemoteForwardListener) listenLoop() {
uc, ok := l.connMap.Get(raddr.String())
if !ok {
uc = newUDPServerConn(conn, raddr, l.config.TTL, l.config.QueueSize)
uc.onClose = func() {
uc = newUDPServerConn(conn, raddr, &udpServerConnConfig{
ttl: l.config.TTL,
qsize: l.config.QueueSize,
onClose: func() {
l.connMap.Delete(raddr.String())
log.Logf("[rudp] %s closed (%d)", raddr, l.connMap.Size())
}
},
})
select {
case l.connChan <- uc:
@ -973,14 +730,13 @@ func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) {
default:
}
lastNode := l.chain.LastNode()
if lastNode.Protocol == "socks5" {
if l.isChainValid() {
var cc net.Conn
cc, err = getSOCKS5UDPTunnel(l.chain, l.addr)
cc, err = getSocks5UDPTunnel(l.chain, l.addr)
if err != nil {
log.Logf("[rudp] %s : %s", l.Addr(), err)
} else {
conn = &udpTunnelConn{Conn: cc}
conn = cc.(net.PacketConn)
}
} else {
var uc *net.UDPConn
@ -991,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 tempDelay == 0 {
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 {
ln, err := UDPDirectForwardListener("localhost:0", nil)
ln, err := UDPListener("localhost:0", nil)
if err != nil {
return err
}
@ -172,7 +172,7 @@ func BenchmarkUDPDirectForward(b *testing.B) {
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := UDPDirectForwardListener("localhost:0", nil)
ln, err := UDPListener("localhost:0", nil)
if err != nil {
b.Error(err)
}
@ -207,7 +207,7 @@ func BenchmarkUDPDirectForwardParallel(b *testing.B) {
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := UDPDirectForwardListener("localhost:0", nil)
ln, err := UDPListener("localhost:0", nil)
if err != nil {
b.Error(err)
}

175
ftcp.go Normal file
View File

@ -0,0 +1,175 @@
package gost
import (
"errors"
"net"
"time"
"github.com/go-log/log"
"github.com/xtaci/tcpraw"
)
type fakeTCPTransporter struct{}
// FakeTCPTransporter creates a Transporter that is used by fake tcp client.
func FakeTCPTransporter() Transporter {
return &fakeTCPTransporter{}
}
func (tr *fakeTCPTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) {
opts := &DialOptions{}
for _, option := range options {
option(opts)
}
raddr, er := net.ResolveTCPAddr("tcp", addr)
if er != nil {
return nil, er
}
c, err := tcpraw.Dial("tcp", addr)
if err != nil {
return
}
conn = &fakeTCPConn{
raddr: raddr,
PacketConn: c,
}
return conn, nil
}
func (tr *fakeTCPTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
return conn, nil
}
func (tr *fakeTCPTransporter) Multiplex() bool {
return false
}
// FakeTCPListenConfig is config for fake TCP Listener.
type FakeTCPListenConfig struct {
TTL time.Duration
Backlog int
QueueSize int
}
type fakeTCPListener struct {
ln net.PacketConn
connChan chan net.Conn
errChan chan error
connMap udpConnMap
config *FakeTCPListenConfig
}
// FakeTCPListener creates a Listener for fake TCP server.
func FakeTCPListener(addr string, cfg *FakeTCPListenConfig) (Listener, error) {
ln, err := tcpraw.Listen("tcp", addr)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &FakeTCPListenConfig{}
}
backlog := cfg.Backlog
if backlog <= 0 {
backlog = defaultBacklog
}
l := &fakeTCPListener{
ln: ln,
connChan: make(chan net.Conn, backlog),
errChan: make(chan error, 1),
config: cfg,
}
go l.listenLoop()
return l, nil
}
func (l *fakeTCPListener) listenLoop() {
for {
b := make([]byte, mediumBufferSize)
n, raddr, err := l.ln.ReadFrom(b)
if err != nil {
log.Logf("[ftcp] 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("[ftcp] %s closed (%d)", raddr, l.connMap.Size())
},
})
select {
case l.connChan <- conn:
l.connMap.Set(raddr.String(), conn)
log.Logf("[ftcp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size())
default:
conn.Close()
log.Logf("[ftcp] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan))
}
}
select {
case conn.rChan <- b[:n]:
if Debug {
log.Logf("[ftcp] %s >>> %s : length %d", raddr, l.Addr(), n)
}
default:
log.Logf("[ftcp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
}
}
}
func (l *fakeTCPListener) 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 *fakeTCPListener) Addr() net.Addr {
return l.ln.LocalAddr()
}
func (l *fakeTCPListener) Close() error {
err := l.ln.Close()
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
v.Close()
return true
})
return err
}
type fakeTCPConn struct {
raddr net.Addr
net.PacketConn
}
func (c *fakeTCPConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *fakeTCPConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.raddr)
}
func (c *fakeTCPConn) RemoteAddr() net.Addr {
return c.raddr
}

95
go.mod
View File

@ -1,49 +1,60 @@
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 (
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/coreos/go-iptables v0.4.5 // indirect
github.com/dchest/siphash v1.2.1 // indirect
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
git.torproject.org/pluggable-transports/goptlib.git v1.3.0
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/go-gost/gosocks4 v0.0.1
github.com/go-gost/gosocks5 v0.3.0
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451
github.com/go-log/log v0.2.0
github.com/gobwas/glob v0.2.3
github.com/golang/mock v1.2.0 // indirect
github.com/google/gopacket v1.1.17 // indirect
github.com/gorilla/websocket v1.4.0 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/klauspost/compress v1.4.1
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.7.0 // indirect
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
github.com/lucas-clemente/quic-go v0.10.0
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
github.com/miekg/dns v1.1.3
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/gorilla/websocket v1.5.1
github.com/klauspost/compress v1.17.6
github.com/mdlayher/vsock v1.2.1
github.com/miekg/dns v1.1.58
github.com/quic-go/quic-go v0.45.0
github.com/ryanuber/go-glob v1.0.0
github.com/shadowsocks/go-shadowsocks2 v0.1.5
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/xtaci/kcp-go/v5 v5.6.7
github.com/xtaci/smux v1.5.24
github.com/xtaci/tcpraw v1.2.25
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
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
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d
golang.org/x/crypto v0.24.0
golang.org/x/net v0.26.0
)
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=
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e h1:c8h60PKrRxEB5debIHBmP7T+s/EUNXTklXqlmJfYiJQ=
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e/go.mod h1:jRZbfRcLIgFQoCw6tRmsnETVyIj54jOmXhHCYYa0jbs=
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0=
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 h1:iJoUgXvhagsNMrJrvavw7vu1eG8+hm6jLOxlLFcoODw=
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
git.torproject.org/pluggable-transports/goptlib.git v1.3.0 h1:G+iuRUblCCC2xnO+0ag1/4+aaM98D5mjWP1M0v9s8a0=
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/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/dchest/siphash v1.2.0 h1:YWOShuhvg0GqbQpMa60QlCGtEyf7O7HC1Jf0VjdQ60M=
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/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ginuerzh/gosocks4 v0.0.1 h1:ojDKUyz+uaEeRm2usY1cyQiXTqJqrKxfeE6SVBXq4m0=
github.com/ginuerzh/gosocks4 v0.0.1/go.mod h1:8SdwBMKjfJ9+BfP2vDJM1jcrgWUbWV6qxBPHHVrwptY=
github.com/ginuerzh/gosocks5 v0.2.0 h1:K0Ua23U9LU3BZrf3XpGDcs0mP8DiEpa6PJE4TA/MU3s=
github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno=
github.com/ginuerzh/tls-dissector v0.0.1 h1:yF6fIt78TO4CdjiLLn6R8r0XajQJE1Lbnuq6rP8mGW8=
github.com/ginuerzh/tls-dissector v0.0.1/go.mod h1:u/kbBOqIOgJv39gywuUb3VwyzdZG5DKquOqfToKE6lk=
github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U=
github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M=
github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I=
github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
github.com/go-gost/gosocks5 v0.3.0 h1:Hkmp9YDRBSCJd7xywW6dBPT6B9aQTkuWd+3WCheJiJA=
github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 h1:itaaJhQJ19kUXEB4Igb0EbY8m+1Py2AaNNSBds/9gk4=
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
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/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE=
github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8 h1:4WFQEfEJ7zaHYViIVM2Cd6tnQOOhiEHbmQtlcV7aOpc=
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8/go.mod h1:G95Wwn625/q6JCCytI4VR/a5VtPwrtI0B+Q1Gi38QLA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/shadowsocks/go-shadowsocks2 v0.0.11 h1:dXloqEhYnZV40jblWTK8kWeC0Eb+dgql4S0tj99e8j0=
github.com/shadowsocks/go-shadowsocks2 v0.0.11/go.mod h1:R+KWaoIwRRhnpw6XV+dZil0XHi64Hc1D7hXUyXTjUzQ=
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba h1:tJgNXb3S+RkB4kNPi6N5OmEWe3m+Y3Qs6LUMiNDAONM=
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c=
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/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/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb h1:qRSZHsODmAP5qDvb3YsO7Qnf3TRiVbGxNG/WYnlM4/o=
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb/go.mod h1:gvdJuZuO/tPZyhEV8K3Hmoxv/DWud5L4qEQxfYjEUTo=
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d h1:tJ8F7ABaQ3p3wjxwXiWSktVDgjZEXkvaRawd2rIq5ws=
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/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.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-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-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.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-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gorilla/websocket.v1 v1.4.0 h1:lREme3ezAGPCpxSHwjGkHhAJX+ed2B6vzAJ+kaqBEIM=
gopkg.in/gorilla/websocket.v1 v1.4.0/go.mod h1:Ons1i8d00TjvJPdla7bJyeXFsdOacUyrTYbg9IetsIE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/xtaci/kcp-go.v4 v4.3.2 h1:S9IF+L55Ugzl/hVA6wvuL3SuAtTUzH2cBBC88MXQxnE=
gopkg.in/xtaci/kcp-go.v4 v4.3.2/go.mod h1:fFYTlSOHNOHDNTKfoqarZMQsu7g7oXKwJ9wq0i9lODc=
gopkg.in/xtaci/smux.v1 v1.0.7 h1:qootIZs4ZPSx5blhvgaFpx2epdFSWkyw99xT+q0mRXI=
gopkg.in/xtaci/smux.v1 v1.0.7/go.mod h1:NbrPjLp8lNAYN8KqTplnFr2JjIBbr7CdHBkHtHsXtWA=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
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.
const Version = "2.9.0"
const Version = "2.12.0"
// Debug is a flag that enables the debug log.
var Debug bool
@ -80,7 +80,10 @@ var (
// DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket.
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.
@ -146,9 +149,7 @@ func (rw *readWriter) Write(p []byte) (n int, err error) {
return rw.w.Write(p)
}
var (
nopClientConn = &nopConn{}
)
var nopClientConn = &nopConn{}
// a nop connection implements net.Conn,
// 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"
"time"
"github.com/ginuerzh/gosocks4"
"github.com/ginuerzh/gosocks5"
"github.com/go-gost/gosocks4"
"github.com/go-gost/gosocks5"
"github.com/go-log/log"
)
@ -41,6 +41,9 @@ type HandlerOptions struct {
Host string
IPs []string
TCPMode bool
IPRoutes []IPRoute
ProxyAgent string
HTTPTunnel bool
}
// 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 {
options *HandlerOptions
}

102
http.go
View File

@ -3,8 +3,10 @@ package gost
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
@ -27,7 +29,16 @@ func HTTPConnector(user *url.Userinfo) Connector {
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{}
for _, option := range options {
option(opts)
@ -47,8 +58,8 @@ func (c *httpConnector) Connect(conn net.Conn, addr string, options ...ConnectOp
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: addr},
Host: addr,
URL: &url.URL{Host: address},
Host: address,
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
@ -163,7 +174,12 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
ProtoMinor: 1,
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) {
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
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)
if err == nil {
return
@ -275,27 +293,65 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
}
defer cc.Close()
if req.Method == http.MethodConnect {
if req.Method != http.MethodConnect {
h.handleProxy(conn, cc, req)
return
}
b := []byte("HTTP/1.1 200 Connection established\r\n" +
"Proxy-Agent: gost/" + Version + "\r\n\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)
} 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
}
}
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), host)
transport(conn, cc)
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) {
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
if Debug && (u != "" || p != "") {
@ -353,10 +409,16 @@ func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.
conn.RemoteAddr(), conn.LocalAddr())
resp.StatusCode = http.StatusProxyAuthRequired
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 {
resp.Header = http.Header{}
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 {
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
if user := route.LastNode().User; user != nil {
s := user.String()
if _, set := user.Password(); !set {
s += ":"
}
userpass = base64.StdEncoding.EncodeToString([]byte(s))
u := user.Username()
p, _ := user.Password()
userpass = base64.StdEncoding.EncodeToString([]byte(u + ":" + p))
}
cc, err := route.Conn()

View File

@ -3,12 +3,12 @@ package gost
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
@ -33,7 +33,16 @@ func HTTP2Connector(user *url.Userinfo) Connector {
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{}
for _, option := range options {
option(opts)
@ -57,7 +66,7 @@ func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectO
ProtoMajor: 2,
ProtoMinor: 0,
Body: pr,
Host: addr,
Host: address,
ContentLength: -1,
}
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{}),
}
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr)
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", address)
hc.localAddr, _ = net.ResolveTCPAddr("tcp", cc.addr)
return hc, nil
@ -224,7 +233,7 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
transport := http2.Transport{
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)
if err != nil {
return nil, err
@ -246,13 +255,13 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
pr, pw := io.Pipe()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: "https", Host: addr},
URL: &url.URL{Scheme: "https", Host: opts.Host},
Header: make(http.Header),
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
Body: pr,
Host: addr,
Host: opts.Host,
ContentLength: -1,
}
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))
}
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) {
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,
ProtoMinor: 0,
Header: http.Header{},
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
}
if !h.authenticate(w, r, resp) {
@ -525,7 +538,7 @@ func (h *http2Handler) authenticate(w http.ResponseWriter, r *http.Request, resp
} else {
resp.Header = http.Header{}
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 {
resp.Header.Set("Content-Type", "text/html")
}

View File

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

View File

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

115
kcp.go
View File

@ -15,8 +15,9 @@ import (
"github.com/go-log/log"
"github.com/klauspost/compress/snappy"
"gopkg.in/xtaci/kcp-go.v4"
"gopkg.in/xtaci/smux.v1"
"github.com/xtaci/kcp-go/v5"
"github.com/xtaci/smux"
"github.com/xtaci/tcpraw"
)
var (
@ -42,10 +43,14 @@ type KCPConfig struct {
Resend int `json:"resend"`
NoCongestion int `json:"nc"`
SockBuf int `json:"sockbuf"`
SmuxBuf int `json:"smuxbuf"`
StreamBuf int `json:"streambuf"`
SmuxVer int `json:"smuxver"`
KeepAlive int `json:"keepalive"`
SnmpLog string `json:"snmplog"`
SnmpPeriod int `json:"snmpperiod"`
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
TCP bool `json:"tcp"`
}
// Init initializes the KCP config.
@ -60,6 +65,16 @@ func (c *KCPConfig) Init() {
case "fast3":
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 (
@ -81,10 +96,14 @@ var (
Resend: 0,
NoCongestion: 0,
SockBuf: 4194304,
SmuxVer: 1,
SmuxBuf: 4194304,
StreamBuf: 2097152,
KeepAlive: 10,
SnmpLog: "",
SnmpPeriod: 60,
Signal: false,
TCP: false,
}
)
@ -129,13 +148,24 @@ func (tr *kcpTransporter) Dial(addr string, options ...DialOption) (conn net.Con
ok = false
}
if !ok {
timeout := opts.Timeout
if timeout <= 0 {
timeout = DialTimeout
}
conn, err = net.DialTimeout("udp", addr, timeout)
raddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return
return nil, err
}
if tr.config.TCP {
pc, err := tcpraw.Dial("tcp", addr)
if err != nil {
return nil, err
}
conn = &fakeTCPConn{
raddr: raddr,
PacketConn: pc,
}
} else {
conn, err = net.ListenUDP("udp", nil)
if err != nil {
return nil, err
}
}
session = &muxSession{conn: conn}
tr.sessions[addr] = session
@ -184,14 +214,14 @@ func (tr *kcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (
}
func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPConfig) (*muxSession, error) {
udpConn, ok := conn.(*net.UDPConn)
pc, ok := conn.(net.PacketConn)
if !ok {
return nil, errors.New("kcp: wrong connection type")
}
kcpconn, err := kcp.NewConn(addr,
blockCrypt(config.Key, config.Crypt, KCPSalt),
config.DataShard, config.ParityShard, &connectedUDPConn{udpConn})
config.DataShard, config.ParityShard, pc)
if err != nil {
return nil, err
}
@ -203,9 +233,11 @@ func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPCon
kcpconn.SetMtu(config.MTU)
kcpconn.SetACKNoDelay(config.AckNodelay)
// if err := kcpconn.SetDSCP(config.DSCP); err != nil {
// log.Log("[kcp]", err)
// }
if config.DSCP > 0 {
if err := kcpconn.SetDSCP(config.DSCP); err != nil {
log.Log("[kcp]", err)
}
}
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil {
log.Log("[kcp]", err)
}
@ -215,8 +247,14 @@ func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPCon
// stream multiplex
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
if err := smux.VerifyConfig(smuxConfig); err != nil {
return nil, err
}
var cc net.Conn = kcpconn
if !config.NoComp {
cc = newCompStreamConn(kcpconn)
@ -247,14 +285,31 @@ func KCPListener(addr string, config *KCPConfig) (Listener, error) {
}
config.Init()
ln, err := kcp.ListenWithOptions(addr,
blockCrypt(config.Key, config.Crypt, KCPSalt), config.DataShard, config.ParityShard)
var err error
var ln *kcp.Listener
if config.TCP {
var conn net.PacketConn
conn, err = tcpraw.Listen("tcp", addr)
if err != nil {
return nil, err
}
// if err = ln.SetDSCP(config.DSCP); err != nil {
// log.Log("[kcp]", err)
// }
ln, err = kcp.ServeConn(
blockCrypt(config.Key, config.Crypt, KCPSalt), config.DataShard, config.ParityShard, conn)
if err != nil {
return nil, err
}
} else {
ln, err = kcp.ListenWithOptions(addr,
blockCrypt(config.Key, config.Crypt, KCPSalt), config.DataShard, config.ParityShard)
}
if err != nil {
return nil, err
}
if config.DSCP > 0 {
if err = ln.SetDSCP(config.DSCP); err != nil {
log.Log("[kcp]", err)
}
}
if err = ln.SetReadBuffer(config.SockBuf); err != nil {
log.Log("[kcp]", err)
}
@ -299,7 +354,9 @@ func (l *kcpListener) listenLoop() {
func (l *kcpListener) mux(conn net.Conn) {
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
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
@ -440,9 +497,13 @@ func (c *compStreamConn) Read(b []byte) (n int, err error) {
}
func (c *compStreamConn) Write(b []byte) (n int, err error) {
n, err = c.w.Write(b)
err = c.w.Flush()
return n, err
if _, err = c.w.Write(b); err != nil {
return 0, err
}
if err = c.w.Flush(); err != nil {
return 0, err
}
return len(b), err
}
func (c *compStreamConn) Close() error {
@ -468,11 +529,3 @@ func (c *compStreamConn) SetReadDeadline(t time.Time) error {
func (c *compStreamConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
// to Write syscalls that are 4 times faster on some OS'es. This should only be
// used for connections that were produced by a net.Dial* call.
type connectedUDPConn struct{ *net.UDPConn }
// WriteTo redirects all writes to the Write syscall, which is 4 times faster.
func (c *connectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.Write(b) }

2
mux.go
View File

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

37
node.go
View File

@ -75,26 +75,44 @@ func ParseNode(s string) (node Node, err error) {
}
switch node.Transport {
case "tls", "mtls", "ws", "mws", "wss", "mwss", "kcp", "ssh", "quic", "ssu", "http2", "h2", "h2c", "obfs4":
case "https":
node.Protocol = "http"
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 "ohttp": // obfs-http
case "tun", "tap": // tun/tap device
case "ftcp": // fake TCP
case "dns":
case "redu", "redirectu": // UDP tproxy
case "vsock":
default:
node.Transport = "tcp"
}
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":
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 "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 "ftcp": // fake TCP
case "dns", "dot", "doh":
case "relay":
default:
node.Protocol = ""
}
@ -140,13 +158,16 @@ func (node *Node) GetBool(key string) bool {
// GetInt converts node parameter value to int.
func (node *Node) GetInt(key string) int {
n, _ := strconv.Atoi(node.Values.Get(key))
n, _ := strconv.Atoi(node.Get(key))
return n
}
// GetDuration converts node parameter value to 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
}

432
obfs.go
View File

@ -5,6 +5,8 @@ package gost
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/tls"
"errors"
"fmt"
"io"
@ -18,8 +20,13 @@ import (
"github.com/go-log/log"
pt "git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4"
dissector "github.com/go-gost/tls-dissector"
"gitlab.com/yawning/obfs4.git/transports/base"
"gitlab.com/yawning/obfs4.git/transports/obfs4"
)
const (
maxTLSDataLen = 16384
)
type obfsHTTPTransporter struct {
@ -249,6 +256,415 @@ func (c *obfsHTTPConn) Write(b []byte) (n int, err error) {
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 {
cf base.ClientFactory
cargs interface{} // type obfs4ClientArgs
@ -388,6 +804,16 @@ func Obfs4Listener(addr string) (Listener, error) {
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) {
conn, err := l.Listener.Accept()
if err != nil {
@ -396,7 +822,7 @@ func (l *obfs4Listener) Accept() (net.Conn, error) {
cc, err := obfs4ServerConn(l.addr, conn)
if err != nil {
conn.Close()
return nil, err
return nil, TempError{err}
}
return cc, nil
}

151
quic.go
View File

@ -1,6 +1,7 @@
package gost
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
@ -12,16 +13,15 @@ import (
"time"
"github.com/go-log/log"
quic "github.com/lucas-clemente/quic-go"
quic "github.com/quic-go/quic-go"
)
type quicSession struct {
conn net.Conn
session quic.Session
session quic.EarlyConnection
}
func (session *quicSession) GetConn() (*quicConn, error) {
stream, err := session.session.OpenStreamSync()
stream, err := session.session.OpenStreamSync(context.Background())
if err != nil {
return nil, err
}
@ -33,7 +33,7 @@ func (session *quicSession) GetConn() (*quicConn, error) {
}
func (session *quicSession) Close() error {
return session.session.Close()
return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
}
type quicTransporter struct {
@ -59,100 +59,71 @@ func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Co
option(opts)
}
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock()
session, ok := tr.sessions[addr]
if !ok {
var cc *net.UDPConn
cc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
var pc net.PacketConn
pc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
return
}
conn = cc
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
}
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) {
opts := &HandshakeOptions{}
for _, option := range options {
option(opts)
return conn, nil
}
func (tr *quicTransporter) initSession(addr net.Addr, conn net.PacketConn) (*quicSession, error) {
config := tr.config
if opts.QUICConfig != nil {
config = opts.QUICConfig
if config == nil {
config = &QUICConfig{}
}
if config.TLSConfig == nil {
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{
HandshakeTimeout: config.Timeout,
KeepAlive: config.KeepAlive,
IdleTimeout: config.IdleTimeout,
HandshakeIdleTimeout: config.Timeout,
MaxIdleTimeout: config.IdleTimeout,
KeepAlivePeriod: config.KeepAlivePeriod,
Versions: []quic.VersionNumber{
quic.VersionGQUIC43,
quic.VersionGQUIC39,
quic.Version1,
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 {
log.Logf("quic dial %s: %v", addr, err)
return nil, err
}
return &quicSession{conn: conn, session: session}, nil
return &quicSession{session: session}, nil
}
func (tr *quicTransporter) Multiplex() bool {
@ -164,12 +135,13 @@ type QUICConfig struct {
TLSConfig *tls.Config
Timeout time.Duration
KeepAlive bool
KeepAlivePeriod time.Duration
IdleTimeout time.Duration
Key []byte
}
type quicListener struct {
ln quic.Listener
ln quic.EarlyListener
connChan chan net.Conn
errChan chan error
}
@ -180,39 +152,41 @@ func QUICListener(addr string, config *QUICConfig) (Listener, error) {
config = &QUICConfig{}
}
quicConfig := &quic.Config{
HandshakeTimeout: config.Timeout,
KeepAlive: config.KeepAlive,
IdleTimeout: config.IdleTimeout,
HandshakeIdleTimeout: config.Timeout,
KeepAlivePeriod: config.KeepAlivePeriod,
MaxIdleTimeout: config.IdleTimeout,
Versions: []quic.VersionNumber{
quic.Version1,
quic.Version2,
},
}
tlsConfig := config.TLSConfig
if tlsConfig == nil {
tlsConfig = DefaultTLSConfig
}
var conn net.PacketConn
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
lconn, err := net.ListenUDP("udp", udpAddr)
conn, err = net.ListenUDP("udp", udpAddr)
if err != nil {
return nil, err
}
conn = lconn
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 {
return nil, err
}
l := &quicListener{
ln: ln,
ln: *ln,
connChan: make(chan net.Conn, 1024),
errChan: make(chan error, 1),
}
@ -223,7 +197,7 @@ func QUICListener(addr string, config *QUICConfig) (Listener, error) {
func (l *quicListener) listenLoop() {
for {
session, err := l.ln.Accept()
session, err := l.ln.Accept(context.Background())
if err != nil {
log.Log("[quic] accept:", 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())
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr())
for {
stream, err := session.AcceptStream()
stream, err := session.AcceptStream(context.Background())
if err != nil {
log.Log("[quic] accept stream:", err)
session.Close()
session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
return
}
@ -291,12 +265,12 @@ func (c *quicConn) RemoteAddr() net.Addr {
}
type quicCipherConn struct {
*net.UDPConn
net.PacketConn
key []byte
}
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 {
return
}
@ -316,7 +290,7 @@ func (conn *quicCipherConn) WriteTo(data []byte, addr net.Addr) (n int, err erro
return
}
_, err = conn.UDPConn.WriteTo(b, addr)
_, err = conn.PacketConn.WriteTo(b, addr)
if err != nil {
return
}
@ -362,3 +336,12 @@ func (conn *quicCipherConn) decrypt(data []byte) ([]byte, error) {
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
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
import (
"context"
"errors"
"fmt"
"net"
"sync"
"syscall"
"time"
"github.com/LiamHaworth/go-tproxy"
"github.com/go-log/log"
)
@ -15,7 +20,7 @@ type tcpRedirectHandler struct {
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 {
h := &tcpRedirectHandler{}
h.Init(opts...)
@ -49,7 +54,8 @@ func (h *tcpRedirectHandler) Handle(c net.Conn) {
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),
TimeoutChainOption(h.options.Timeout),
)
@ -97,3 +103,140 @@ func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Add
}
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: "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: "1.1.1.1:12345", Timeout: 1 * time.Second}, "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: "tls", Timeout: 1 * time.Second}, "github.com", false},
{NameServer{Addr: "https://1.0.0.1:12345/dns-query", Protocol: "https", 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"}, "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"}, "github.com", false},
}
func dnsResolverRoundtrip(t *testing.T, r Resolver, host string) error {
@ -45,13 +45,13 @@ func TestDNSResolver(t *testing.T) {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
ns := tc.ns
if err := ns.Init(); err != nil {
t.Error(err)
}
t.Log(ns)
r := NewResolver(0, ns)
resolv := r.(*resolver)
resolv.domain = "com"
if err := r.Init(); err != nil {
t.Error("got error:", err)
}
err := dnsResolverRoundtrip(t, r, tc.host)
if err != nil {
if tc.pass {
@ -85,6 +85,7 @@ var resolverCacheTests = []struct {
[]net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}},
}
/*
func TestResolverCache(t *testing.T) {
isEqual := func(a, b []net.IP) bool {
if a == nil && b == nil {
@ -106,8 +107,8 @@ func TestResolverCache(t *testing.T) {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
r := newResolver(tc.ttl)
r.storeCache(tc.name, tc.ips, tc.ttl)
ips := r.loadCache(tc.name, tc.ttl)
r.cache.storeCache(tc.name, tc.ips, tc.ttl)
ips := r.cache.loadCache(tc.name, tc.ttl)
if !isEqual(tc.result, ips) {
t.Error("unexpected cache value:", tc.name, ips, tc.ttl)
@ -115,6 +116,7 @@ func TestResolverCache(t *testing.T) {
})
}
}
*/
var resolverReloadTests = []struct {
r io.Reader
@ -167,7 +169,6 @@ var resolverReloadTests = []struct {
ns: &NameServer{
Protocol: "udp",
Addr: "1.1.1.1",
Timeout: 10 * time.Second,
},
timeout: 10 * time.Second,
stopped: true,
@ -219,9 +220,9 @@ func TestResolverReload(t *testing.T) {
t.Error(err)
}
t.Log(r.String())
if r.TTL != tc.ttl {
if r.TTL() != tc.ttl {
t.Errorf("ttl value should be %v, got %v",
tc.ttl, r.TTL)
tc.ttl, r.TTL())
}
if r.Period() != tc.period {
t.Errorf("period value should be %v, got %v",
@ -233,13 +234,13 @@ func TestResolverReload(t *testing.T) {
}
var ns *NameServer
if len(r.Servers) > 0 {
ns = &r.Servers[0]
if len(r.servers) > 0 {
ns = &r.servers[0]
}
if !compareNameServer(ns, tc.ns) {
t.Errorf("nameserver not equal, should be %v, got %v",
tc.ns, r.Servers)
tc.ns, r.servers)
}
if tc.stopped {
@ -265,6 +266,5 @@ func compareNameServer(n1, n2 *NameServer) bool {
}
return n1.Addr == n2.Addr &&
n1.Hostname == n2.Hostname &&
n1.Protocol == n2.Protocol &&
n1.Timeout == n2.Timeout
n1.Protocol == n2.Protocol
}

View File

@ -4,15 +4,18 @@ import (
"errors"
"math/rand"
"net"
"sort"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/go-log/log"
)
var (
// 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.
@ -205,6 +208,94 @@ func (f *FailFilter) String() string {
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.
// A node is invalid if its port is invalid (negative or zero value).
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) {
nodes := []Node{
Node{ID: 1, marker: &failMarker{}},

View File

@ -102,37 +102,6 @@ type Listener interface {
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 {
errc := make(chan error, 1)
go func() {
@ -143,13 +112,13 @@ func transport(rw1, rw2 io.ReadWriter) error {
errc <- copyBuffer(rw2, rw1)
}()
err := <-errc
if err != nil && err == io.EOF {
err = nil
}
if err := <-errc; err != nil && err != io.EOF {
return err
}
return nil
}
func copyBuffer(dst io.Writer, src io.Reader) error {
buf := lPool.Get().([]byte)
defer lPool.Put(buf)

View File

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

View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package gost
@ -8,7 +9,7 @@ import (
"syscall"
"github.com/go-log/log"
"gopkg.in/xtaci/kcp-go.v4"
"github.com/xtaci/kcp-go/v5"
)
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.0'
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 (
"bufio"
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"errors"
@ -16,7 +17,8 @@ import (
"strings"
"sync"
dissector "github.com/ginuerzh/tls-dissector"
"github.com/asaskevich/govalidator"
dissector "github.com/go-gost/tls-dissector"
"github.com/go-log/log"
)
@ -29,8 +31,17 @@ func SNIConnector(host string) Connector {
return &sniConnector{host: host}
}
func (c *sniConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
return &sniClientConn{addr: addr, host: c.host, Conn: conn}, nil
func (c *sniConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
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 {
@ -76,6 +87,10 @@ func (h *sniHandler) Handle(conn net.Conn) {
return
}
if !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) {
req.URL.Scheme = "http"
}
handler := &httpHandler{options: h.options}
handler.Init()
handler.handleRequest(conn, req)
@ -260,7 +275,7 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
if err != nil {
return nil, "", err
}
clientHello := &dissector.ClientHelloHandshake{}
clientHello := &dissector.ClientHelloMsg{}
if err := clientHello.Decode(record.Opaque); err != nil {
return nil, "", err
}
@ -270,7 +285,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
for _, ext := range clientHello.Extensions {
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
}
}
@ -286,8 +302,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
host = snExtension.Name
}
if isClient {
clientHello.Extensions = append(clientHello.Extensions,
dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name))))
e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
clientHello.Extensions = append(clientHello.Extensions, e)
}
if host != "" {
snExtension.Name = host

View File

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

387
socks.go
View File

@ -2,6 +2,7 @@ package gost
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
@ -9,13 +10,14 @@ import (
"net"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/ginuerzh/gosocks4"
"github.com/ginuerzh/gosocks5"
"github.com/go-gost/gosocks4"
"github.com/go-gost/gosocks5"
"github.com/go-log/log"
smux "gopkg.in/xtaci/smux.v1"
smux "github.com/xtaci/smux"
)
const (
@ -35,6 +37,10 @@ const (
CmdUDPTun uint8 = 0xF3
)
var (
_ net.PacketConn = (*socks5UDPTunnelConn)(nil)
)
type clientSelector struct {
methods []uint8
User *url.Userinfo
@ -42,6 +48,9 @@ type clientSelector struct {
}
func (selector *clientSelector) Methods() []uint8 {
if Debug {
log.Log("[socks5] methods:", selector.methods)
}
return selector.methods
}
@ -54,6 +63,9 @@ func (selector *clientSelector) Select(methods ...uint8) (method uint8) {
}
func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
if Debug {
log.Log("[socks5] method selected:", method)
}
switch method {
case MethodTLS:
conn = tls.Client(conn, selector.TLSConfig)
@ -195,7 +207,17 @@ func SOCKS5Connector(user *url.Userinfo) Connector {
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{}
for _, option := range options {
option(opts)
@ -213,13 +235,17 @@ func (c *socks5Connector) Connect(conn net.Conn, addr string, options ...Connect
if user == nil {
user = c.User
}
cc, err := socks5Handshake(conn, opts.Selector, user)
cc, err := socks5Handshake(conn,
selectorSocks5HandshakeOption(opts.Selector),
userSocks5HandshakeOption(user),
noTLSSocks5HandshakeOption(opts.NoTLS),
)
if err != nil {
return nil, err
}
conn = cc
host, port, err := net.SplitHostPort(addr)
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
@ -263,7 +289,16 @@ func SOCKS5BindConnector(user *url.Userinfo) Connector {
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{}
for _, option := range options {
option(opts)
@ -281,13 +316,17 @@ func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...Con
if user == nil {
user = c.User
}
cc, err := socks5Handshake(conn, opts.Selector, user)
cc, err := socks5Handshake(conn,
selectorSocks5HandshakeOption(opts.Selector),
userSocks5HandshakeOption(user),
noTLSSocks5HandshakeOption(opts.NoTLS),
)
if err != nil {
return nil, err
}
conn = cc
laddr, err := net.ResolveTCPAddr("tcp", addr)
laddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
log.Log(err)
return nil, err
@ -317,8 +356,8 @@ func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...Con
}
if reply.Rep != gosocks5.Succeeded {
log.Logf("[socks5] bind on %s failure", addr)
return nil, fmt.Errorf("SOCKS5 bind on %s failure", addr)
log.Logf("[socks5] bind on %s failure", address)
return nil, fmt.Errorf("SOCKS5 bind on %s failure", address)
}
baddr, err := net.ResolveTCPAddr("tcp", reply.Addr.String())
if err != nil {
@ -336,8 +375,17 @@ func Socks5MuxBindConnector() Connector {
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.
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)
if !ok {
return nil, errors.New("wrong connection type")
@ -431,7 +479,9 @@ func (tr *socks5MuxBindTransporter) initSession(conn net.Conn, addr string, opts
opts = &HandshakeOptions{}
}
cc, err := socks5Handshake(conn, nil, opts.User)
cc, err := socks5Handshake(conn,
userSocks5HandshakeOption(opts.User),
)
if err != nil {
return nil, err
}
@ -497,7 +547,16 @@ func SOCKS5UDPConnector(user *url.Userinfo) Connector {
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{}
for _, option := range options {
option(opts)
@ -515,13 +574,17 @@ func (c *socks5UDPConnector) Connect(conn net.Conn, addr string, options ...Conn
if user == nil {
user = c.User
}
cc, err := socks5Handshake(conn, opts.Selector, user)
cc, err := socks5Handshake(conn,
selectorSocks5HandshakeOption(opts.Selector),
userSocks5HandshakeOption(user),
noTLSSocks5HandshakeOption(opts.NoTLS),
)
if err != nil {
return nil, err
}
conn = cc
taddr, err := net.ResolveUDPAddr("udp", addr)
taddr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return nil, err
}
@ -576,67 +639,40 @@ func SOCKS5UDPTunConnector(user *url.Userinfo) Connector {
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{}
for _, option := range options {
option(opts)
}
user := opts.User
if user == nil {
user = c.User
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = ConnectTimeout
}
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
user := opts.User
if user == nil {
user = c.User
}
cc, err := socks5Handshake(conn, opts.Selector, user)
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.String()}, nil
taddr, _ := net.ResolveUDPAddr("udp", address)
return newSocks5UDPTunnelConn(conn,
nil, taddr,
selectorSocks5HandshakeOption(opts.Selector),
userSocks5HandshakeOption(user),
noTLSSocks5HandshakeOption(opts.NoTLS),
)
}
type socks4Connector struct{}
@ -646,7 +682,16 @@ func SOCKS4Connector() Connector {
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{}
for _, option := range options {
option(opts)
@ -660,7 +705,7 @@ func (c *socks4Connector) Connect(conn net.Conn, addr string, options ...Connect
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
taddr, err := net.ResolveTCPAddr("tcp4", addr)
taddr, err := net.ResolveTCPAddr("tcp4", address)
if err != nil {
return nil, err
}
@ -706,7 +751,16 @@ func SOCKS4AConnector() Connector {
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{}
for _, option := range options {
option(opts)
@ -720,7 +774,7 @@ func (c *socks4aConnector) Connect(conn net.Conn, addr string, options ...Connec
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
host, port, err := net.SplitHostPort(addr)
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
@ -1071,7 +1125,7 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
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 {
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
reply := gosocks5.NewReply(gosocks5.Failure, nil)
@ -1084,7 +1138,6 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
defer relay.Close()
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)
if err := reply.Write(conn); err != nil {
log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
@ -1122,7 +1175,7 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
}
defer cc.Close()
cc, err = socks5Handshake(cc, nil, h.options.Chain.LastNode().User)
cc, err = socks5Handshake(cc, userSocks5HandshakeOption(h.options.Chain.LastNode().User))
if err != nil {
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), socksAddr, err)
return
@ -1347,14 +1400,14 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
addr := req.Addr.String()
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
}
bindAddr, _ := net.ResolveUDPAddr("udp", addr)
uc, err := net.ListenUDP("udp", bindAddr)
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
}
defer uc.Close()
@ -1363,32 +1416,32 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
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
}
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)
log.Logf("[socks5-udp] %s >-< %s", conn.RemoteAddr(), socksAddr)
log.Logf("[socks5] udp-tun %s >-< %s", conn.RemoteAddr(), socksAddr)
return
}
cc, err := h.options.Chain.Conn()
// connection error
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.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
}
defer cc.Close()
cc, err = socks5Handshake(cc, nil, h.options.Chain.LastNode().User)
cc, err = socks5Handshake(cc, userSocks5HandshakeOption(h.options.Chain.LastNode().User))
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
}
// tunnel <-> tunnel, direct forwarding
@ -1396,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
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)
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) {
@ -1416,7 +1469,7 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
return
}
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
}
@ -1424,12 +1477,12 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
dgram := gosocks5.NewUDPDatagram(
gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n])
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
return
}
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))
}
}
}()
@ -1449,16 +1502,16 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
continue // drop silently
}
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
}
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
return
}
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))
}
}
}()
@ -1577,16 +1630,21 @@ func (h *socks5Handler) muxBindOn(conn net.Conn, addr string) {
}
}
// TODO: support domain
func toSocksAddr(addr net.Addr) *gosocks5.Addr {
host := "0.0.0.0"
port := 0
addrType := gosocks5.AddrIPv4
if addr != nil {
h, p, _ := net.SplitHostPort(addr.String())
host = h
port, _ = strconv.Atoi(p)
if strings.Count(host, ":") > 0 {
addrType = gosocks5.AddrIPv6
}
}
return &gosocks5.Addr{
Type: gosocks5.AddrIPv4,
Type: addrType,
Host: host,
Port: uint16(port),
}
@ -1771,59 +1829,51 @@ func (h *socks4Handler) handleBind(conn net.Conn, req *gosocks4.Request) {
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
type socks5HandshakeOptions struct {
selector gosocks5.Selector
user *url.Userinfo
tlsConfig *tls.Config
noTLS bool
}
conn.SetDeadline(time.Now().Add(HandshakeTimeout))
defer conn.SetDeadline(time.Time{})
type socks5HandshakeOption func(opts *socks5HandshakeOptions)
cc, err := socks5Handshake(conn, nil, chain.LastNode().User)
if err != nil {
conn.Close()
return nil, err
func selectorSocks5HandshakeOption(selector gosocks5.Selector) socks5HandshakeOption {
return func(opts *socks5HandshakeOptions) {
opts.selector = selector
}
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
func userSocks5HandshakeOption(user *url.Userinfo) socks5HandshakeOption {
return func(opts *socks5HandshakeOptions) {
opts.user = user
}
}
if Debug {
log.Log("[socks5]", reply)
func noTLSSocks5HandshakeOption(noTLS bool) socks5HandshakeOption {
return func(opts *socks5HandshakeOptions) {
opts.noTLS = noTLS
}
}
if reply.Rep != gosocks5.Succeeded {
conn.Close()
return nil, errors.New("UDP tunnel failure")
func socks5Handshake(conn net.Conn, opts ...socks5HandshakeOption) (net.Conn, error) {
options := socks5HandshakeOptions{}
for _, opt := range opts {
opt(&options)
}
return conn, nil
}
func socks5Handshake(conn net.Conn, selector gosocks5.Selector, user *url.Userinfo) (net.Conn, error) {
selector := options.selector
if selector == nil {
cs := &clientSelector{
TLSConfig: &tls.Config{InsecureSkipVerify: true},
User: user,
User: options.user,
}
cs.AddMethod(
gosocks5.MethodNoAuth,
gosocks5.MethodUserPass,
MethodTLS,
)
if !options.noTLS {
cs.AddMethod(MethodTLS)
}
selector = cs
}
@ -1834,21 +1884,74 @@ func socks5Handshake(conn net.Conn, selector gosocks5.Selector, user *url.Userin
return cc, nil
}
type udpTunnelConn struct {
raddr string
net.Conn
}
func (c *udpTunnelConn) Read(b []byte) (n int, err error) {
dgram, err := gosocks5.ReadUDPDatagram(c.Conn)
func getSocks5UDPTunnel(chain *Chain, addr net.Addr) (net.Conn, error) {
c, err := chain.Conn()
if err != nil {
return
return nil, err
}
n = copy(b, dgram.Data)
node := chain.LastNode()
conn, err := newSocks5UDPTunnelConn(c,
addr, nil,
userSocks5HandshakeOption(node.User),
noTLSSocks5HandshakeOption(node.GetBool("notls")),
)
if err != nil {
c.Close()
}
return conn, err
}
type socks5UDPTunnelConn struct {
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 {
return nil, err
}
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
}
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)
if err != nil {
return
@ -1858,19 +1961,11 @@ func (c *udpTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
return
}
func (c *udpTunnelConn) Write(b []byte) (n int, err error) {
addr, err := net.ResolveUDPAddr("udp", c.raddr)
if err != nil {
return
}
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(addr)), b)
if err = dgram.Write(c.Conn); err != nil {
return
}
return len(b), nil
func (c *socks5UDPTunnelConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
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)
if err = dgram.Write(c.Conn); err != nil {
return

653
ss.go
View File

@ -2,32 +2,51 @@ package gost
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/url"
"strconv"
"time"
"github.com/ginuerzh/gosocks5"
"github.com/go-gost/gosocks5"
"github.com/go-log/log"
"github.com/shadowsocks/go-shadowsocks2/core"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
)
const (
maxSocksAddrLen = 259
)
var (
_ net.Conn = (*shadowConn)(nil)
_ net.PacketConn = (*shadowUDPPacketConn)(nil)
)
type shadowConnector struct {
Cipher *url.Userinfo
cipher core.Cipher
}
// ShadowConnector creates a Connector for shadowsocks proxy client.
// It accepts a cipher info for shadowsocks data encryption/decryption.
// The cipher must not be nil.
func ShadowConnector(cipher *url.Userinfo) Connector {
return &shadowConnector{Cipher: cipher}
// It accepts an optional cipher info for shadowsocks data encryption/decryption.
func ShadowConnector(info *url.Userinfo) Connector {
return &shadowConnector{
cipher: initShadowCipher(info),
}
}
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)
}
func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
opts := &ConnectOptions{}
for _, option := range options {
option(opts)
@ -38,37 +57,43 @@ func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...Connect
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))
defer conn.SetDeadline(time.Time{})
rawaddr, err := ss.RawAddr(addr)
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 := ss.NewCipher(method, password)
if err != nil {
return nil, err
if c.cipher != nil {
conn = c.cipher.StreamConn(conn)
}
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 {
cipher core.Cipher
options *HandlerOptions
}
@ -88,37 +113,34 @@ func (h *shadowHandler) Init(options ...HandlerOption) {
for _, opt := range options {
opt(h.options)
}
if len(h.options.Users) > 0 {
h.cipher = initShadowCipher(h.options.Users[0])
}
}
func (h *shadowHandler) 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()
if h.cipher != nil {
conn = &shadowConn{
Conn: h.cipher.StreamConn(conn),
}
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))
host, err := h.getRequest(conn)
addr, err := readSocksAddr(conn)
if err != nil {
log.Logf("[ss] %s -> %s : %s",
conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
// clear timer
conn.SetReadDeadline(time.Time{})
log.Logf("[ss] %s -> %s -> %s",
conn.RemoteAddr(), h.options.Node.String(), host)
host := addr.String()
log.Logf("[ss] %s -> %s",
conn.RemoteAddr(), host)
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
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)
}
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 {
Cipher *url.Userinfo
cipher core.Cipher
}
// ShadowUDPConnector creates a Connector for shadowsocks UDP client.
// It accepts a cipher info for shadowsocks data encryption/decryption.
// The cipher must not be nil.
func ShadowUDPConnector(cipher *url.Userinfo) Connector {
return &shadowUDPConnector{Cipher: cipher}
// It accepts an optional cipher info for shadowsocks data encryption/decryption.
func ShadowUDPConnector(info *url.Userinfo) Connector {
return &shadowUDPConnector{
cipher: initShadowCipher(info),
}
}
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)
}
func (c *shadowUDPConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
opts := &ConnectOptions{}
for _, option := range options {
option(opts)
@ -272,223 +238,197 @@ func (c *shadowUDPConnector) Connect(conn net.Conn, addr string, options ...Conn
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
rawaddr, err := ss.RawAddr(addr)
if err != nil {
return nil, err
taddr, _ := net.ResolveUDPAddr(network, address)
if taddr == nil {
taddr = &net.UDPAddr{}
}
var method, password string
if c.Cipher != nil {
method = c.Cipher.Username()
password, _ = c.Cipher.Password()
pc, ok := conn.(net.PacketConn)
if ok {
if c.cipher != nil {
pc = c.cipher.PacketConn(pc)
}
cipher, err := ss.NewCipher(method, password)
if err != nil {
return nil, err
}
sc := ss.NewSecurePacketConn(&shadowPacketConn{conn}, cipher, false)
return &shadowUDPConn{
PacketConn: sc,
return &shadowUDPPacketConn{
PacketConn: pc,
raddr: conn.RemoteAddr(),
header: rawaddr,
taddr: taddr,
}, nil
}
type shadowUDPListener struct {
ln net.PacketConn
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))
if c.cipher != nil {
conn = &shadowConn{
Conn: c.cipher.StreamConn(conn),
}
}
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))
}
}
return &socks5UDPTunnelConn{
Conn: conn,
taddr: taddr,
}, nil
}
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
type shadowUDPHandler struct {
cipher core.Cipher
options *HandlerOptions
}
// ShadowUDPdHandler creates a server Handler for shadowsocks UDP relay server.
func ShadowUDPdHandler(opts ...HandlerOption) Handler {
h := &shadowUDPdHandler{}
// ShadowUDPHandler creates a server Handler for shadowsocks UDP relay server.
func ShadowUDPHandler(opts ...HandlerOption) Handler {
h := &shadowUDPHandler{}
h.Init(opts...)
return h
}
func (h *shadowUDPdHandler) Init(options ...HandlerOption) {
func (h *shadowUDPHandler) Init(options ...HandlerOption) {
if h.options == nil {
h.options = &HandlerOptions{}
}
for _, opt := range 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()
var err error
var cc net.PacketConn
if h.options.Chain.IsEmpty() {
cc, err = net.ListenUDP("udp", nil)
c, err := h.options.Chain.DialContext(context.Background(), "udp", "")
if err != nil {
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
log.Logf("[ssu] %s: %s", conn.LocalAddr(), err)
return
}
} else {
var c net.Conn
c, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
if err != nil {
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
var ok bool
cc, ok = c.(net.PacketConn)
if !ok {
log.Logf("[ssu] %s: not a packet connection", conn.LocalAddr())
return
}
cc = &udpTunnelConn{Conn: c}
}
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())
h.transportUDP(conn, cc)
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)
go func() {
for {
er := func() (err error) {
b := lPool.Get().([]byte)
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:])
dgram, err := gosocks5.ReadUDPDatagram(conn)
if err != nil {
// log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.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)
// log.Logf("[ssu] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
return
}
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())
if err != nil {
@ -512,28 +452,25 @@ func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error {
go func() {
for {
er := func() (err error) {
b := lPool.Get().([]byte)
defer lPool.Put(b)
b := mPool.Get().([]byte)
defer mPool.Put(b)
n, addr, err := cc.ReadFrom(b)
if err != nil {
return
}
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()) {
log.Log("[ssu] bypass", addr)
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{}
dgram.Write(&buf)
if buf.Len() < 10 {
log.Logf("[ssu] %s <- %s : invalid udp datagram", sc.RemoteAddr(), addr)
return // ignore invalid datagram
}
_, err = sc.Write(buf.Bytes()[3:])
_, err = conn.Write(buf.Bytes())
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.
type shadowConn struct {
net.Conn
wbuf bytes.Buffer
}
func (c *shadowConn) Write(b []byte) (n int, err error) {
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)
return
}
type shadowUDPConn struct {
type shadowUDPPacketConn struct {
net.PacketConn
raddr net.Addr
header []byte
taddr net.Addr
}
func (c *shadowUDPConn) Write(b []byte) (n int, 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) {
func (c *shadowUDPPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
buf := mPool.Get().([]byte)
defer mPool.Put(buf)
@ -600,23 +531,117 @@ func (c *shadowUDPConn) Read(b []byte) (n int, err error) {
return
}
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
}
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
}
type shadowPacketConn struct {
net.Conn
type shadowCipher struct {
cipher *ss.Cipher
}
func (c *shadowPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
n, err = c.Conn.Read(b)
addr = c.Conn.RemoteAddr()
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
return ss.NewConn(conn, c.cipher.Copy())
}
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
}
func (c *shadowPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return c.Conn.Write(b)
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
}
func readSocksAddr(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
}

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
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},
{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"), 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"), 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.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"), 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.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"), 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.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"), 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.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"), 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.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"), false},
{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"), false},
{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"), false},
{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"), false},
{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"), false},
{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"), false},
{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"), false},
{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},
}
var ssProxyTests = []struct {
@ -108,16 +138,20 @@ var ssProxyTests = []struct {
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},
{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"), 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("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 {
@ -142,7 +176,7 @@ func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, s
return proxyRoundtrip(client, server, targetURL, data)
}
func TestSSProxy(t *testing.T) {
func TestShadowTCP(t *testing.T) {
httpSrv := httptest.NewServer(httpTestHandler)
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,
clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
ln, err := ShadowUDPListener("localhost:0", serverInfo, nil)
ln, err := UDPListener("localhost:0", nil)
if err != nil {
return err
}
@ -313,7 +466,9 @@ func shadowUDPRoundtrip(t *testing.T, host string, data []byte,
}
server := &Server{
Handler: ShadowUDPdHandler(),
Handler: ShadowUDPHandler(
UsersHandlerOption(serverInfo),
),
Listener: ln,
}
@ -327,7 +482,7 @@ func TestShadowUDP(t *testing.T) {
sendData := make([]byte, 128)
rand.Read(sendData)
for i, tc := range ssTests {
for i, tc := range ssuTests {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
udpSrv := newUDPTestServer(udpTestHandler)
@ -352,7 +507,6 @@ func TestShadowUDP(t *testing.T) {
}
}
// TODO: fix shadowsocks UDP relay benchmark.
func BenchmarkShadowUDP(b *testing.B) {
udpSrv := newUDPTestServer(udpTestHandler)
udpSrv.Start()
@ -361,7 +515,7 @@ func BenchmarkShadowUDP(b *testing.B) {
sendData := make([]byte, 128)
rand.Read(sendData)
ln, err := ShadowUDPListener("localhost:0", url.UserPassword("chacha20-ietf", "123456"), nil)
ln, err := UDPListener("localhost:0", nil)
if err != nil {
b.Error(err)
}
@ -372,7 +526,9 @@ func BenchmarkShadowUDP(b *testing.B) {
}
server := &Server{
Handler: ShadowUDPdHandler(),
Handler: ShadowUDPHandler(
UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456")),
),
Listener: ln,
}

131
ssh.go
View File

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"net"
"os"
"strconv"
"strings"
"sync"
@ -26,12 +27,37 @@ const (
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
)
var (
errSessionDead = errors.New("session is dead")
)
var 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.
func SSHDirectForwardConnector() Connector {
@ -39,6 +65,15 @@ func SSHDirectForwardConnector() Connector {
}
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{}
for _, option := range options {
option(opts)
@ -65,15 +100,23 @@ func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options
return conn, nil
}
type sshRemoteForwardConnector struct {
}
type sshRemoteForwardConnector struct{}
// SSHRemoteForwardConnector creates a Connector for SSH TCP remote port forwarding.
func SSHRemoteForwardConnector() Connector {
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.
if !ok {
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 {
return
}
if strings.HasPrefix(addr, ":") {
addr = "0.0.0.0" + addr
if strings.HasPrefix(address, ":") {
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 {
return
}
@ -99,7 +142,7 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
for {
rc, err := ln.Accept()
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
}
// 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:
default:
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 {
config.User = opts.User.Username()
password, _ := opts.User.Password()
if password, _ := opts.User.Password(); password != "" {
config.Auth = []ssh.AuthMethod{
ssh.Password(password),
}
}
}
if opts.SSHConfig != nil && opts.SSHConfig.Key != nil {
config.Auth = append(config.Auth, ssh.PublicKeys(opts.SSHConfig.Key))
}
tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock()
@ -199,6 +246,7 @@ func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOp
if !ok || session.client == nil {
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
if err != nil {
log.Log("ssh", err)
conn.Close()
delete(tr.sessions, opts.Addr)
return nil, err
@ -287,16 +335,20 @@ func (tr *sshTunnelTransporter) Handshake(conn net.Conn, options ...HandshakeOpt
}
config := ssh.ClientConfig{
Timeout: timeout,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// TODO: support pubkey auth.
if opts.User != nil {
config.User = opts.User.Username()
password, _ := opts.User.Password()
if password, _ := opts.User.Password(); password != "" {
config.Auth = []ssh.AuthMethod{
ssh.Password(password),
}
}
}
if opts.SSHConfig != nil && opts.SSHConfig.Key != nil {
config.Auth = append(config.Auth, ssh.PublicKeys(opts.SSHConfig.Key))
}
tr.sessionMutex.Lock()
defer tr.sessionMutex.Unlock()
@ -666,6 +718,8 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque
type SSHConfig struct {
Authenticator Authenticator
TLSConfig *tls.Config
Key ssh.Signer
AuthorizedKeys map[string]bool
}
type sshTunnelListener struct {
@ -686,21 +740,22 @@ func SSHTunnelListener(addr string, config *SSHConfig) (Listener, error) {
config = &SSHConfig{}
}
sshConfig := &ssh.ServerConfig{}
sshConfig.PasswordCallback = defaultSSHPasswordCallback(config.Authenticator)
if config.Authenticator == nil {
sshConfig.NoClientAuth = true
}
tlsConfig := config.TLSConfig
if tlsConfig == nil {
tlsConfig = DefaultTLSConfig
sshConfig := &ssh.ServerConfig{
PasswordCallback: defaultSSHPasswordCallback(config.Authenticator),
PublicKeyCallback: defaultSSHPublicKeyCallback(config.AuthorizedKeys),
}
signer, err := ssh.NewSignerFromKey(tlsConfig.Certificates[0].PrivateKey)
if config.Authenticator == nil && len(config.AuthorizedKeys) == 0 {
sshConfig.NoClientAuth = true
}
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)
@ -805,9 +860,13 @@ func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
}
// 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)
func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc {
if au == nil {
return nil
}
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
if au.Authenticate(conn.User(), string(password)) {
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 {
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
}

7
tls.go
View File

@ -2,7 +2,6 @@ package gost
import (
"crypto/tls"
"crypto/x509"
"errors"
"net"
"sync"
@ -10,7 +9,7 @@ import (
"github.com/go-log/log"
smux "gopkg.in/xtaci/smux.v1"
smux "github.com/xtaci/smux"
)
type tlsTransporter struct {
@ -290,6 +289,9 @@ func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration)
return nil, err
}
// We can do this in `tls.Config.VerifyConnection`, which effective for
// other TLS protocols such as WebSocket. See `route.go:parseChainNode`
/*
// If crypto/tls is doing verification, there's no need to do our own.
if tlsConfig.InsecureSkipVerify == false {
return tlsConn, nil
@ -320,6 +322,7 @@ func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration)
tlsConn.Close()
return nil, err
}
*/
return tlsConn, err
}

270
tuntap.go
View File

@ -1,6 +1,7 @@
package gost
import (
"context"
"errors"
"fmt"
"io"
@ -12,7 +13,6 @@ import (
"github.com/go-log/log"
"github.com/shadowsocks/go-shadowsocks2/core"
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
"github.com/shadowsocks/go-shadowsocks2/shadowstream"
"github.com/songgao/water"
"github.com/songgao/water/waterutil"
"github.com/xtaci/tcpraw"
@ -39,11 +39,19 @@ func ipProtocol(p waterutil.IPProtocol) string {
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 {
Name string
Addr string
Peer string // peer addr of point-to-point on MacOS
MTU int
Routes []string
Routes []IPRoute
Gateway string
}
@ -114,12 +122,14 @@ func (l *tunListener) Close() error {
type tunHandler struct {
options *HandlerOptions
routes sync.Map
chExit chan struct{}
}
// TunHandler creates a handler for tun tunnel.
func TunHandler(opts ...HandlerOption) Handler {
h := &tunHandler{
options: &HandlerOptions{},
chExit: make(chan struct{}, 1),
}
for _, opt := range opts {
opt(h.options)
@ -141,44 +151,107 @@ func (h *tunHandler) Handle(conn net.Conn) {
defer os.Exit(0)
defer conn.Close()
laddr, raddr := h.options.Node.Addr, h.options.Node.Remote
var pc net.PacketConn
var err error
if h.options.TCPMode {
if raddr != "" {
pc, err = tcpraw.Dial("tcp", raddr)
} else {
pc, err = tcpraw.Listen("tcp", laddr)
}
} else {
addr, _ := net.ResolveUDPAddr("udp", laddr)
pc, err = net.ListenUDP("udp", addr)
}
var raddr net.Addr
if addr := h.options.Node.Remote; addr != "" {
raddr, err = net.ResolveUDPAddr("udp", addr)
if err != nil {
log.Logf("[tun] %s: %v", conn.LocalAddr(), err)
log.Logf("[tun] %s: remote addr: %v", conn.LocalAddr(), err)
return
}
}
var tempDelay time.Duration
for {
err := func() error {
var err error
var pc net.PacketConn
// fake tcp mode will be ignored when the client specifies a chain.
if raddr != nil && !h.options.Chain.IsEmpty() {
cc, err := h.options.Chain.DialContext(context.Background(), "udp", raddr.String())
if err != nil {
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 {
if h.options.TCPMode {
if raddr != nil {
pc, err = tcpraw.Dial("tcp", raddr.String())
} else {
pc, err = tcpraw.Listen("tcp", h.options.Node.Addr)
}
} else {
laddr, _ := net.ResolveUDPAddr("udp", h.options.Node.Addr)
pc, err = net.ListenUDP("udp", laddr)
}
}
if err != nil {
return err
}
pc, err = h.initTunnelConn(pc)
if err != nil {
return err
}
return h.transportTun(conn, pc, raddr)
}()
if err != nil {
log.Logf("[tun] %s: %v", conn.LocalAddr(), err)
}
select {
case <-h.chExit:
return
default:
}
if err != nil {
if tempDelay == 0 {
tempDelay = 1000 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 6 * time.Second; tempDelay > max {
tempDelay = max
}
time.Sleep(tempDelay)
continue
}
tempDelay = 0
}
}
func (h *tunHandler) initTunnelConn(pc net.PacketConn) (net.PacketConn, error) {
if len(h.options.Users) > 0 && h.options.Users[0] != nil {
passwd, _ := h.options.Users[0].Password()
cipher, err := core.PickCipher(h.options.Users[0].Username(), nil, passwd)
if err != nil {
log.Logf("[tun] %s - %s cipher: %v", conn.LocalAddr(), pc.LocalAddr(), err)
return
return nil, err
}
pc = cipher.PacketConn(pc)
}
var ra net.Addr
if raddr != "" {
ra, err = net.ResolveUDPAddr("udp", raddr)
if err != nil {
log.Logf("[tun] %s - %s: remote addr: %v", conn.LocalAddr(), pc.LocalAddr(), err)
return
}
return pc, nil
}
h.transportTun(conn, pc, ra)
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 {
@ -192,6 +265,10 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
n, err := tun.Read(b)
if err != nil {
select {
case h.chExit <- struct{}{}:
default:
}
return err
}
@ -232,15 +309,15 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
return err
}
var addr net.Addr
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
addr = v.(net.Addr)
}
addr := h.findRouteFor(dst)
if addr == nil {
log.Logf("[tun] no route for %s -> %s", src, dst)
return nil
}
if Debug {
log.Logf("[tun] find route: %s -> %s", dst, addr)
}
if _, err := conn.WriteTo(b[:n], addr); err != nil {
return err
}
@ -258,11 +335,11 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
for {
err := func() error {
b := sPool.Get().([]byte)
defer mPool.Put(b)
defer sPool.Put(b)
n, addr, err := conn.ReadFrom(b)
if err != nil &&
err != shadowaead.ErrShortPacket && err != shadowstream.ErrShortPacket {
err != shadowaead.ErrShortPacket {
return err
}
@ -314,15 +391,19 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
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 {
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
}
if _, err := tun.Write(b[:n]); err != nil {
select {
case h.chExit <- struct{}{}:
default:
}
return err
}
return nil
@ -339,7 +420,6 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
if err != nil && err == io.EOF {
err = nil
}
log.Logf("[tun] %s - %s: %v", tun.LocalAddr(), conn.LocalAddr(), err)
return err
}
@ -357,6 +437,7 @@ func etherType(et waterutil.Ethertype) string {
return fmt.Sprintf("unknown(%v)", et)
}
// TapConfig is the config for TAP device.
type TapConfig struct {
Name string
Addr string
@ -431,12 +512,14 @@ func (l *tapListener) Close() error {
type tapHandler struct {
options *HandlerOptions
routes sync.Map
chExit chan struct{}
}
// TapHandler creates a handler for tap tunnel.
func TapHandler(opts ...HandlerOption) Handler {
h := &tapHandler{
options: &HandlerOptions{},
chExit: make(chan struct{}, 1),
}
for _, opt := range opts {
opt(h.options)
@ -458,44 +541,93 @@ func (h *tapHandler) Handle(conn net.Conn) {
defer os.Exit(0)
defer conn.Close()
laddr, raddr := h.options.Node.Addr, h.options.Node.Remote
var pc net.PacketConn
var err error
if h.options.TCPMode {
if raddr != "" {
pc, err = tcpraw.Dial("tcp", raddr)
} else {
pc, err = tcpraw.Listen("tcp", laddr)
}
} else {
addr, _ := net.ResolveUDPAddr("udp", laddr)
pc, err = net.ListenUDP("udp", addr)
}
var raddr net.Addr
if addr := h.options.Node.Remote; addr != "" {
raddr, err = net.ResolveUDPAddr("udp", addr)
if err != nil {
log.Logf("[tap] %s: %v", conn.LocalAddr(), err)
log.Logf("[tap] %s: remote addr: %v", conn.LocalAddr(), err)
return
}
}
var tempDelay time.Duration
for {
err := func() error {
var err error
var pc net.PacketConn
// fake tcp mode will be ignored when the client specifies a chain.
if raddr != nil && !h.options.Chain.IsEmpty() {
cc, err := h.options.Chain.DialContext(context.Background(), "udp", raddr.String())
if err != nil {
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 {
if h.options.TCPMode {
if raddr != nil {
pc, err = tcpraw.Dial("tcp", raddr.String())
} else {
pc, err = tcpraw.Listen("tcp", h.options.Node.Addr)
}
} else {
laddr, _ := net.ResolveUDPAddr("udp", h.options.Node.Addr)
pc, err = net.ListenUDP("udp", laddr)
}
}
if err != nil {
return err
}
pc, err = h.initTunnelConn(pc)
if err != nil {
return err
}
return h.transportTap(conn, pc, raddr)
}()
if err != nil {
log.Logf("[tap] %s: %v", conn.LocalAddr(), err)
}
select {
case <-h.chExit:
return
default:
}
if err != nil {
if tempDelay == 0 {
tempDelay = 1000 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 6 * time.Second; tempDelay > max {
tempDelay = max
}
time.Sleep(tempDelay)
continue
}
tempDelay = 0
}
}
func (h *tapHandler) initTunnelConn(pc net.PacketConn) (net.PacketConn, error) {
if len(h.options.Users) > 0 && h.options.Users[0] != nil {
passwd, _ := h.options.Users[0].Password()
cipher, err := core.PickCipher(h.options.Users[0].Username(), nil, passwd)
if err != nil {
log.Logf("[tap] %s - %s cipher: %v", conn.LocalAddr(), pc.LocalAddr(), err)
return
return nil, err
}
pc = cipher.PacketConn(pc)
}
var ra net.Addr
if raddr != "" {
ra, err = net.ResolveUDPAddr("udp", raddr)
if err != nil {
log.Logf("[tap] %s - %s: remote addr: %v", conn.LocalAddr(), pc.LocalAddr(), err)
return
}
}
h.transportTap(conn, pc, ra)
return pc, nil
}
func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.Addr) error {
@ -509,6 +641,10 @@ func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.A
n, err := tap.Read(b)
if err != nil {
select {
case h.chExit <- struct{}{}:
default:
}
return err
}
@ -561,11 +697,11 @@ func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.A
for {
err := func() error {
b := sPool.Get().([]byte)
defer mPool.Put(b)
defer sPool.Put(b)
n, addr, err := conn.ReadFrom(b)
if err != nil &&
err != shadowaead.ErrShortPacket && err != shadowstream.ErrShortPacket {
err != shadowaead.ErrShortPacket {
return err
}
@ -613,6 +749,10 @@ func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.A
}
if _, err := tap.Write(b[:n]); err != nil {
select {
case h.chExit <- struct{}{}:
default:
}
return err
}
return nil
@ -629,7 +769,6 @@ func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.A
if err != nil && err == io.EOF {
err = nil
}
log.Logf("[tap] %s - %s: %v", tap.LocalAddr(), conn.LocalAddr(), err)
return err
}
@ -670,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")}
}
// IsIPv6Multicast reports whether the address addr is an IPv6 multicast address.
func IsIPv6Multicast(addr net.HardwareAddr) bool {
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
}
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)
args := strings.Split(cmd, " ")
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
}
if err = addRoutes(ifce.Name(), cfg.Routes...); err != nil {
if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
return
}
@ -58,12 +63,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return
}
func addRoutes(ifName string, routes ...string) error {
func addTunRoutes(ifName string, routes ...IPRoute) error {
for _, route := range routes {
if route == "" {
if route.Dest == nil {
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)
args := strings.Split(cmd, " ")
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {

View File

@ -3,15 +3,15 @@ package gost
import (
"fmt"
"net"
"os/exec"
"strings"
"github.com/docker/libcontainer/netlink"
"github.com/go-log/log"
"github.com/milosgajdos83/tenus"
"github.com/songgao/water"
)
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 {
return
}
@ -26,35 +26,21 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
return
}
link, err := tenus.NewLinkFrom(ifce.Name())
if err != nil {
return
}
mtu := cfg.MTU
if mtu <= 0 {
mtu = DefaultMTU
}
cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)
log.Log("[tun]", cmd)
if er := link.SetLinkMTU(mtu); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil {
log.Log(err)
}
cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())
log.Log("[tun]", cmd)
if er := link.SetLinkIp(ip, ipNet); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil {
log.Log(err)
}
cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name())
log.Log("[tun]", cmd)
if er := link.SetLinkUp(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil {
log.Log(err)
}
if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
@ -74,10 +60,13 @@ func createTun(cfg TunConfig) (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 cfg.Addr != "" {
ip, _, err = net.ParseCIDR(cfg.Addr)
if err != nil {
return
}
}
ifce, err := water.New(water.Config{
DeviceType: water.TAP,
@ -89,35 +78,23 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return
}
link, err := tenus.NewLinkFrom(ifce.Name())
if err != nil {
return
}
mtu := cfg.MTU
if mtu <= 0 {
mtu = DefaultMTU
}
cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)
log.Log("[tap]", cmd)
if er := link.SetLinkMTU(mtu); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil {
log.Log(err)
}
cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())
log.Log("[tap]", cmd)
if er := link.SetLinkIp(ip, ipNet); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
if cfg.Addr != "" {
if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil {
log.Log(err)
}
}
cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name())
log.Log("[tap]", cmd)
if er := link.SetLinkUp(); er != nil {
err = fmt.Errorf("%s: %v", cmd, er)
return
if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil {
log.Log(err)
}
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
}
func addTunRoutes(ifName string, routes ...string) error {
func addTunRoutes(ifName string, routes ...IPRoute) error {
for _, route := range routes {
if route == "" {
if route.Dest == nil {
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)
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
@ -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)
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
}
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
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) {
ip, _, err := net.ParseCIDR(cfg.Addr)
if err != nil {
return
}
ip, _, _ := net.ParseCIDR(cfg.Addr)
ifce, err := water.New(water.Config{
DeviceType: water.TAP,
@ -72,7 +70,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
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)
args := strings.Split(cmd, " ")
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
}
func addTunRoutes(ifName string, routes ...string) error {
func addTunRoutes(ifName string, routes ...IPRoute) error {
for _, route := range routes {
if route == "" {
if route.Dest == nil {
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)
args := strings.Split(cmd, " ")
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
}
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",
ifce.Name(), ip.String(), ipMask(ipNet.Mask))
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) {
ip, ipNet, err := net.ParseCIDR(cfg.Addr)
if err != nil {
return
}
ip, ipNet, _ := net.ParseCIDR(cfg.Addr)
ifce, err := water.New(water.Config{
DeviceType: water.TAP,
@ -72,7 +69,8 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return
}
cmd := fmt.Sprintf("netsh interface ip set address name=%s "+
if ip != nil && ipNet != nil {
cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+
"source=static addr=%s mask=%s gateway=none",
ifce.Name(), ip.String(), ipMask(ipNet.Mask))
log.Log("[tap]", cmd)
@ -81,6 +79,7 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
err = fmt.Errorf("%s: %v", cmd, er)
return
}
}
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
return
@ -98,16 +97,16 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
return
}
func addTunRoutes(ifName string, gw string, routes ...string) error {
func addTunRoutes(ifName string, gw string, routes ...IPRoute) error {
for _, route := range routes {
if route == "" {
if route.Dest == nil {
continue
}
deleteRoute(ifName, route)
deleteRoute(ifName, route.Dest.String())
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active",
route, ifName)
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active",
route.Dest.String(), ifName)
if gw != "" {
cmd += " nexthop=" + gw
}
@ -128,7 +127,7 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
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)
if gw != "" {
cmd += " nexthop=" + gw
@ -143,7 +142,7 @@ func addTapRoutes(ifName string, gw string, routes ...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)
args := strings.Split(cmd, " ")
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"
"github.com/go-log/log"
"gopkg.in/gorilla/websocket.v1"
smux "gopkg.in/xtaci/smux.v1"
"github.com/gorilla/websocket"
smux "github.com/xtaci/smux"
)
const (