Compare commits
128 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
87d6a2fdc2 | ||
|
91e12c4428 | ||
|
48c7970942 | ||
|
de49ba2aba | ||
|
2732481bfc | ||
|
e30f120045 | ||
|
08c54cd8af | ||
|
31a9a45495 | ||
|
81128f9439 | ||
|
8508797355 | ||
|
654791e635 | ||
|
ac1e8968d3 | ||
|
ee07120254 | ||
|
32b79a37fa | ||
|
44aac5a5d9 | ||
|
17685b4eac | ||
|
77262f2454 | ||
|
94f812b026 | ||
|
2faecc1be8 | ||
|
00c36ab83d | ||
|
cfe4abe4f9 | ||
|
45ebea56ce | ||
|
5e6f5c3df2 | ||
|
0056fd6560 | ||
|
fd57e80709 | ||
|
7e1af1b557 | ||
|
13b9748c9c | ||
|
7a2490134a | ||
|
c5d9bc8907 | ||
|
1c62376e08 | ||
|
0f7376bd10 | ||
|
8a29ddabea | ||
|
729d0e7000 | ||
|
b7beb1729e | ||
|
847ee05fd4 | ||
|
c07cdeff92 | ||
|
005cff5888 | ||
|
aa8312a902 | ||
|
b0bb26fc95 | ||
|
97dda762e0 | ||
|
81854a61e0 | ||
|
910d58c8ab | ||
|
ac554f670c | ||
|
b9e61dca1a | ||
|
3322613d3c | ||
|
7485f9a753 | ||
|
6129f5e940 | ||
|
a263a9f173 | ||
|
0247b941ac | ||
|
31aee78e79 | ||
|
f94293b454 | ||
|
45340b2845 | ||
|
74659324c8 | ||
|
cc87118242 | ||
|
3df387579c | ||
|
ca632e8909 | ||
|
bbeaafc897 | ||
|
ffecc464fe | ||
|
11d4838804 | ||
|
79b086df90 | ||
|
40ccfecb36 | ||
|
937b27dd95 | ||
|
27dec2d2ac | ||
|
8f08304b75 | ||
|
a4695ece2d | ||
|
fc971d7f2d | ||
|
8dd4d8d9a1 | ||
|
e16ac5a58a | ||
|
4712d6c9dd | ||
|
5d69ecf203 | ||
|
1a56a878ac | ||
|
b5d8d44c19 | ||
|
3ea5708819 | ||
|
1c32df37bb | ||
|
fd079dd066 | ||
|
f7995ab564 | ||
|
b9a965f4c1 | ||
|
2707a8f0a9 | ||
|
d11f824858 | ||
|
b285bcd6ce | ||
|
60d7e01164 | ||
|
de43d87af3 | ||
|
2c0eb8df9a | ||
|
6e46ac03c7 | ||
|
f0c9079f0a | ||
|
8ab2fe6f77 | ||
|
d474a0c417 | ||
|
9f1f492b3c | ||
|
2c0e01a5dd | ||
|
2e0aa67faf | ||
|
ec5052e55f | ||
|
c1bac99a5d | ||
|
3a63210845 | ||
|
0f8064470f | ||
|
b015ac660a | ||
|
d8af58cec7 | ||
|
b2d5319d68 | ||
|
ee8b5d572c | ||
|
e203d7760e | ||
|
1587c679e7 | ||
|
a99ff4aa15 | ||
|
c7568170ac | ||
|
3142a4283e | ||
|
cbc9c1f77e | ||
|
8121e20cbd | ||
|
ece79946b3 | ||
|
94dcfcab8c | ||
|
abe4043413 | ||
|
425099a7ba | ||
|
18a515a5eb | ||
|
be1f050250 | ||
|
0de7b8fb0b | ||
|
bb4a7411ef | ||
|
4b856214f7 | ||
|
bd9fc76466 | ||
|
6ce3639c02 | ||
|
4133cf30b4 | ||
|
f1bad4d07b | ||
|
694c05b50a | ||
|
99b141e5be | ||
|
8ec3d8cbcf | ||
|
ae36ad0fdc | ||
|
bb1bba3126 | ||
|
56e2ac3566 | ||
|
990c7b56c3 | ||
|
08d2f7c516 | ||
|
a782cf5cf8 | ||
|
e16427c6c8 |
72
.github/workflows/buildx.yml
vendored
Normal file
72
.github/workflows/buildx.yml
vendored
Normal 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
38
.github/workflows/release.yml
vendored
Normal 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
3
.gitignore
vendored
@ -9,6 +9,7 @@ _test
|
|||||||
release
|
release
|
||||||
debian
|
debian
|
||||||
bin
|
bin
|
||||||
|
dist/
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
# Architecture specific extensions/prefixes
|
||||||
*.[568vq]
|
*.[568vq]
|
||||||
@ -30,5 +31,5 @@ _testmain.go
|
|||||||
|
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
.vscode/
|
||||||
cmd/gost/gost
|
cmd/gost/gost
|
||||||
snap
|
|
||||||
|
58
.goreleaser.yaml
Normal file
58
.goreleaser.yaml
Normal 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
|
29
Dockerfile
29
Dockerfile
@ -1,19 +1,32 @@
|
|||||||
FROM golang:1-alpine as builder
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
|
||||||
|
|
||||||
RUN apk add --no-cache musl-dev git gcc
|
FROM --platform=$BUILDPLATFORM golang:1.23-alpine3.20 AS builder
|
||||||
|
|
||||||
ADD . /src
|
COPY --from=xx / /
|
||||||
|
|
||||||
WORKDIR /src
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
ENV GO111MODULE=on
|
RUN xx-info env
|
||||||
|
|
||||||
RUN cd cmd/gost && go build
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
FROM alpine:latest
|
ENV XX_VERIFY_STATIC=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN cd cmd/gost && \
|
||||||
|
xx-go build && \
|
||||||
|
xx-verify gost
|
||||||
|
|
||||||
|
FROM alpine:3.20
|
||||||
|
|
||||||
|
# add iptables for tun/tap
|
||||||
|
RUN apk add --no-cache iptables
|
||||||
|
|
||||||
WORKDIR /bin/
|
WORKDIR /bin/
|
||||||
|
|
||||||
COPY --from=builder /src/cmd/gost/gost .
|
COPY --from=builder /app/cmd/gost/gost .
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/gost"]
|
ENTRYPOINT ["/bin/gost"]
|
20
Makefile
20
Makefile
@ -2,10 +2,11 @@ NAME=gost
|
|||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
VERSION=$(shell cat gost.go | grep 'Version =' | sed 's/.*\"\(.*\)\".*/\1/g')
|
VERSION=$(shell cat gost.go | grep 'Version =' | sed 's/.*\"\(.*\)\".*/\1/g')
|
||||||
GOBUILD=CGO_ENABLED=0 go build --ldflags="-s -w" -v -x -a
|
GOBUILD=CGO_ENABLED=0 go build --ldflags="-s -w" -v -x -a
|
||||||
GOFILES=cmd/gost/*
|
GOFILES=cmd/gost/*.go
|
||||||
|
|
||||||
PLATFORM_LIST = \
|
PLATFORM_LIST = \
|
||||||
darwin-amd64 \
|
darwin-amd64 \
|
||||||
|
darwin-arm64 \
|
||||||
linux-386 \
|
linux-386 \
|
||||||
linux-amd64 \
|
linux-amd64 \
|
||||||
linux-armv5 \
|
linux-armv5 \
|
||||||
@ -18,18 +19,24 @@ PLATFORM_LIST = \
|
|||||||
linux-mipsle-hardfloat \
|
linux-mipsle-hardfloat \
|
||||||
linux-mips64 \
|
linux-mips64 \
|
||||||
linux-mips64le \
|
linux-mips64le \
|
||||||
|
linux-s390x \
|
||||||
|
linux-riscv64 \
|
||||||
freebsd-386 \
|
freebsd-386 \
|
||||||
freebsd-amd64
|
freebsd-amd64
|
||||||
|
|
||||||
WINDOWS_ARCH_LIST = \
|
WINDOWS_ARCH_LIST = \
|
||||||
windows-386 \
|
windows-386 \
|
||||||
windows-amd64
|
windows-amd64 \
|
||||||
|
windows-arm64
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
||||||
|
|
||||||
darwin-amd64:
|
darwin-amd64:
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
|
darwin-arm64:
|
||||||
|
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
linux-386:
|
linux-386:
|
||||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
@ -66,6 +73,12 @@ linux-mips64:
|
|||||||
linux-mips64le:
|
linux-mips64le:
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
|
linux-s390x:
|
||||||
|
GOARCH=s390x GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
|
linux-riscv64:
|
||||||
|
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
freebsd-386:
|
freebsd-386:
|
||||||
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
@ -78,6 +91,9 @@ windows-386:
|
|||||||
windows-amd64:
|
windows-amd64:
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
|
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
|
||||||
|
|
||||||
|
windows-arm64:
|
||||||
|
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe $(GOFILES)
|
||||||
|
|
||||||
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
|
||||||
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
|
||||||
|
|
||||||
|
53
README.md
53
README.md
@ -1,39 +1,40 @@
|
|||||||
gost - GO Simple Tunnel
|
GO Simple Tunnel
|
||||||
======
|
======
|
||||||
|
|
||||||
### GO语言实现的安全隧道
|
### GO语言实现的安全隧道
|
||||||
|
|
||||||
[](https://godoc.org/github.com/ginuerzh/gost)
|
[](https://godoc.org/github.com/ginuerzh/gost)
|
||||||
[](https://travis-ci.org/ginuerzh/gost)
|
|
||||||
[](https://goreportcard.com/report/github.com/ginuerzh/gost)
|
[](https://goreportcard.com/report/github.com/ginuerzh/gost)
|
||||||
[](https://codecov.io/gh/ginuerzh/gost/branch/master)
|
[](https://codecov.io/gh/ginuerzh/gost/branch/master)
|
||||||
[](https://github.com/ginuerzh/gost/releases/latest)
|
[](https://github.com/ginuerzh/gost/releases/latest)
|
||||||
[](https://build.snapcraft.io/user/ginuerzh/gost)
|
[](https://hub.docker.com/r/ginuerzh/gost/)
|
||||||
[](https://hub.docker.com/r/ginuerzh/gost/)
|
[](https://snapcraft.io/gost)
|
||||||
|
|
||||||
[English README](README_en.md)
|
[English README](README_en.md)
|
||||||
|
|
||||||
|
### !!!!!
|
||||||
|
|
||||||
特性
|
特性
|
||||||
------
|
------
|
||||||
|
|
||||||
* 多端口监听
|
* 多端口监听
|
||||||
* 可设置转发代理,支持多级转发(代理链)
|
* 可设置转发代理,支持多级转发(代理链)
|
||||||
* 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议
|
* 支持标准HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5代理协议
|
||||||
* Web代理支持[探测防御](https://docs.ginuerzh.xyz/gost/probe_resist/)
|
* Web代理支持[探测防御](https://v2.gost.run/probe_resist/)
|
||||||
* [支持多种隧道类型](https://docs.ginuerzh.xyz/gost/configuration/)
|
* [支持多种隧道类型](https://v2.gost.run/configuration/)
|
||||||
* [SOCKS5代理支持TLS协商加密](https://docs.ginuerzh.xyz/gost/socks/)
|
* [SOCKS5代理支持TLS协商加密](https://v2.gost.run/socks/)
|
||||||
* [Tunnel UDP over TCP](https://docs.ginuerzh.xyz/gost/socks/)
|
* [Tunnel UDP over TCP](https://v2.gost.run/socks/)
|
||||||
* [TCP透明代理](https://docs.ginuerzh.xyz/gost/redirect/)
|
* [TCP/UDP透明代理](https://v2.gost.run/redirect/)
|
||||||
* [本地/远程TCP/UDP端口转发](https://docs.ginuerzh.xyz/gost/port-forwarding/)
|
* [本地/远程TCP/UDP端口转发](https://v2.gost.run/port-forwarding/)
|
||||||
* [支持Shadowsocks(TCP/UDP)协议](https://docs.ginuerzh.xyz/gost/ss/)
|
* [支持Shadowsocks(TCP/UDP)协议](https://v2.gost.run/ss/)
|
||||||
* [支持SNI代理](https://docs.ginuerzh.xyz/gost/sni/)
|
* [支持SNI代理](https://v2.gost.run/sni/)
|
||||||
* [权限控制](https://docs.ginuerzh.xyz/gost/permission/)
|
* [权限控制](https://v2.gost.run/permission/)
|
||||||
* [负载均衡](https://docs.ginuerzh.xyz/gost/load-balancing/)
|
* [负载均衡](https://v2.gost.run/load-balancing/)
|
||||||
* [路由控制](https://docs.ginuerzh.xyz/gost/bypass/)
|
* [路由控制](https://v2.gost.run/bypass/)
|
||||||
* [DNS控制](https://docs.ginuerzh.xyz/gost/dns/)
|
* DNS[解析](https://v2.gost.run/resolver/)和[代理](https://v2.gost.run/dns/)
|
||||||
* [TUN/TAP设备](https://docs.ginuerzh.xyz/gost/tuntap/)
|
* [TUN/TAP设备](https://v2.gost.run/tuntap/)
|
||||||
|
|
||||||
Wiki站点: <https://docs.ginuerzh.xyz/gost/>
|
Wiki站点: [v2.gost.run](https://v2.gost.run)
|
||||||
|
|
||||||
Telegram讨论群: <https://t.me/gogost>
|
Telegram讨论群: <https://t.me/gogost>
|
||||||
|
|
||||||
@ -49,18 +50,28 @@ Google讨论组: <https://groups.google.com/d/forum/go-gost>
|
|||||||
#### 源码编译
|
#### 源码编译
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u github.com/ginuerzh/gost/cmd/gost
|
git clone https://github.com/ginuerzh/gost.git
|
||||||
|
cd gost/cmd/gost
|
||||||
|
go build
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ginuerzh/gost
|
docker run --rm ginuerzh/gost -V
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Homebrew
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install gost
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Ubuntu商店
|
#### Ubuntu商店
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
sudo snap install core
|
||||||
sudo snap install gost
|
sudo snap install gost
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -184,7 +195,7 @@ gost -L=:8080 -F=h2://server_ip:443
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### QUIC
|
#### QUIC
|
||||||
gost对QUIC的支持是基于[quic-go](https://github.com/lucas-clemente/quic-go)库。
|
gost对QUIC的支持是基于[quic-go](https://github.com/quic-go/quic-go)库。
|
||||||
|
|
||||||
服务端:
|
服务端:
|
||||||
```bash
|
```bash
|
||||||
|
48
README_en.md
48
README_en.md
@ -4,33 +4,32 @@ gost - GO Simple Tunnel
|
|||||||
### A simple security tunnel written in Golang
|
### A simple security tunnel written in Golang
|
||||||
|
|
||||||
[](https://godoc.org/github.com/ginuerzh/gost)
|
[](https://godoc.org/github.com/ginuerzh/gost)
|
||||||
[](https://travis-ci.org/ginuerzh/gost)
|
|
||||||
[](https://goreportcard.com/report/github.com/ginuerzh/gost)
|
[](https://goreportcard.com/report/github.com/ginuerzh/gost)
|
||||||
[](https://codecov.io/gh/ginuerzh/gost/branch/master)
|
[](https://codecov.io/gh/ginuerzh/gost/branch/master)
|
||||||
[](https://github.com/ginuerzh/gost/releases/latest)
|
[](https://github.com/ginuerzh/gost/releases/latest)
|
||||||
[](https://build.snapcraft.io/user/ginuerzh/gost)
|
[](https://hub.docker.com/r/ginuerzh/gost/)
|
||||||
[](https://hub.docker.com/r/ginuerzh/gost/)
|
[](https://snapcraft.io/gost)
|
||||||
|
|
||||||
Features
|
Features
|
||||||
------
|
------
|
||||||
* Listening on multiple ports
|
* Listening on multiple ports
|
||||||
* Multi-level forward proxy - proxy chain
|
* Multi-level forward proxy - proxy chain
|
||||||
* Standard HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5 proxy protocols support
|
* Standard HTTP/HTTPS/HTTP2/SOCKS4(A)/SOCKS5 proxy protocols support
|
||||||
* [Probing resistance](https://docs.ginuerzh.xyz/gost/en/probe_resist/) support for web proxy
|
* [Probing resistance](https://v2.gost.run/en/probe_resist/) support for web proxy
|
||||||
* [Support multiple tunnel types](https://docs.ginuerzh.xyz/gost/en/configuration/)
|
* [Support multiple tunnel types](https://v2.gost.run/en/configuration/)
|
||||||
* [TLS encryption via negotiation support for SOCKS5 proxy](https://docs.ginuerzh.xyz/gost/en/socks/)
|
* [TLS encryption via negotiation support for SOCKS5 proxy](https://v2.gost.run/en/socks/)
|
||||||
* [Tunnel UDP over TCP](https://docs.ginuerzh.xyz/gost/en/socks/)
|
* [Tunnel UDP over TCP](https://v2.gost.run/en/socks/)
|
||||||
* [Transparent TCP proxy](https://docs.ginuerzh.xyz/gost/en/redirect/)
|
* [TCP/UDP Transparent proxy](https://v2.gost.run/en/redirect/)
|
||||||
* [Local/remote TCP/UDP port forwarding](https://docs.ginuerzh.xyz/gost/en/port-forwarding/)
|
* [Local/remote TCP/UDP port forwarding](https://v2.gost.run/en/port-forwarding/)
|
||||||
* [Shadowsocks protocol](https://docs.ginuerzh.xyz/gost/en/ss/)
|
* [Shadowsocks protocol](https://v2.gost.run/en/ss/)
|
||||||
* [SNI proxy](https://docs.ginuerzh.xyz/gost/en/sni/)
|
* [SNI proxy](https://v2.gost.run/en/sni/)
|
||||||
* [Permission control](https://docs.ginuerzh.xyz/gost/en/permission/)
|
* [Permission control](https://v2.gost.run/en/permission/)
|
||||||
* [Load balancing](https://docs.ginuerzh.xyz/gost/en/load-balancing/)
|
* [Load balancing](https://v2.gost.run/en/load-balancing/)
|
||||||
* [Routing control](https://docs.ginuerzh.xyz/gost/en/bypass/)
|
* [Routing control](https://v2.gost.run/en/bypass/)
|
||||||
* [DNS control](https://docs.ginuerzh.xyz/gost/en/dns/)
|
* DNS [resolver](https://v2.gost.run/resolver/) and [proxy](https://v2.gost.run/dns/)
|
||||||
* [TUN/TAP device](https://docs.ginuerzh.xyz/gost/en/tuntap/)
|
* [TUN/TAP device](https://v2.gost.run/en/tuntap/)
|
||||||
|
|
||||||
Wiki: <https://docs.ginuerzh.xyz/gost/en/>
|
Wiki: [v2.gost.run](https://v2.gost.run/en/)
|
||||||
|
|
||||||
Telegram group: <https://t.me/gogost>
|
Telegram group: <https://t.me/gogost>
|
||||||
|
|
||||||
@ -46,18 +45,27 @@ Installation
|
|||||||
#### From source
|
#### From source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u github.com/ginuerzh/gost/cmd/gost
|
git clone https://github.com/ginuerzh/gost.git
|
||||||
|
cd gost/cmd/gost
|
||||||
|
go build
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull ginuerzh/gost
|
docker run --rm ginuerzh/gost -V
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Homebrew
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install gost
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Ubuntu store
|
#### Ubuntu store
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
sudo snap install core
|
||||||
sudo snap install gost
|
sudo snap install gost
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -210,7 +218,7 @@ gost -L=:8080 -F=h2://server_ip:443
|
|||||||
|
|
||||||
#### QUIC
|
#### QUIC
|
||||||
|
|
||||||
Support for QUIC is based on library [quic-go](https://github.com/lucas-clemente/quic-go).
|
Support for QUIC is based on library [quic-go](https://github.com/quic-go/quic-go).
|
||||||
|
|
||||||
Server:
|
Server:
|
||||||
|
|
||||||
|
119
chain.go
119
chain.go
@ -1,8 +1,11 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
@ -17,6 +20,8 @@ var (
|
|||||||
type Chain struct {
|
type Chain struct {
|
||||||
isRoute bool
|
isRoute bool
|
||||||
Retries int
|
Retries int
|
||||||
|
Mark int
|
||||||
|
Interface string
|
||||||
nodeGroups []*NodeGroup
|
nodeGroups []*NodeGroup
|
||||||
route []Node // nodes in the selected route
|
route []Node // nodes in the selected route
|
||||||
}
|
}
|
||||||
@ -33,10 +38,14 @@ func NewChain(nodes ...Node) *Chain {
|
|||||||
|
|
||||||
// newRoute creates a chain route.
|
// newRoute creates a chain route.
|
||||||
// a chain route is the final route after node selection.
|
// a chain route is the final route after node selection.
|
||||||
func newRoute(nodes ...Node) *Chain {
|
func (c *Chain) newRoute(nodes ...Node) *Chain {
|
||||||
chain := NewChain(nodes...)
|
route := NewChain(nodes...)
|
||||||
chain.isRoute = true
|
route.isRoute = true
|
||||||
return chain
|
if !c.IsEmpty() {
|
||||||
|
route.Interface = c.Interface
|
||||||
|
route.Mark = c.Mark
|
||||||
|
}
|
||||||
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes returns the proxy nodes that the chain holds.
|
// Nodes returns the proxy nodes that the chain holds.
|
||||||
@ -100,9 +109,14 @@ func (c *Chain) IsEmpty() bool {
|
|||||||
return c == nil || len(c.nodeGroups) == 0
|
return c == nil || len(c.nodeGroups) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dial connects to the target address addr through the chain.
|
// Dial connects to the target TCP address addr through the chain.
|
||||||
// If the chain is empty, it will use the net.Dial directly.
|
// Deprecated: use DialContext instead.
|
||||||
func (c *Chain) Dial(addr string, opts ...ChainOption) (conn net.Conn, err error) {
|
func (c *Chain) Dial(address string, opts ...ChainOption) (conn net.Conn, err error) {
|
||||||
|
return c.DialContext(context.Background(), "tcp", address, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext connects to the address on the named network using the provided context.
|
||||||
|
func (c *Chain) DialContext(ctx context.Context, network, address string, opts ...ChainOption) (conn net.Conn, err error) {
|
||||||
options := &ChainOptions{}
|
options := &ChainOptions{}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(options)
|
opt(options)
|
||||||
@ -117,7 +131,7 @@ func (c *Chain) Dial(addr string, opts ...ChainOption) (conn net.Conn, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < retries; i++ {
|
for i := 0; i < retries; i++ {
|
||||||
conn, err = c.dialWithOptions(addr, options)
|
conn, err = c.dialWithOptions(ctx, network, address, options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -125,33 +139,80 @@ func (c *Chain) Dial(addr string, opts ...ChainOption) (conn net.Conn, err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chain) dialWithOptions(addr string, options *ChainOptions) (net.Conn, error) {
|
func (c *Chain) dialWithOptions(ctx context.Context, network, address string, options *ChainOptions) (net.Conn, error) {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = &ChainOptions{}
|
options = &ChainOptions{}
|
||||||
}
|
}
|
||||||
route, err := c.selectRouteFor(addr)
|
if c == nil {
|
||||||
|
c = &Chain{}
|
||||||
|
}
|
||||||
|
route, err := c.selectRouteFor(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAddr := c.resolve(addr, options.Resolver, options.Hosts)
|
ipAddr := address
|
||||||
|
if address != "" {
|
||||||
|
ipAddr = c.resolve(address, options.Resolver, options.Hosts)
|
||||||
|
if ipAddr == "" {
|
||||||
|
return nil, fmt.Errorf("resolver: domain %s does not exists", address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
timeout := options.Timeout
|
timeout := options.Timeout
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = DialTimeout
|
timeout = DialTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if route.IsEmpty() {
|
var controlFunction func(_ string, _ string, c syscall.RawConn) error = nil
|
||||||
return net.DialTimeout("tcp", ipAddr, timeout)
|
if c.Mark > 0 {
|
||||||
|
controlFunction = func(_, _ string, cc syscall.RawConn) error {
|
||||||
|
return cc.Control(func(fd uintptr) {
|
||||||
|
ex := setSocketMark(int(fd), c.Mark)
|
||||||
|
if ex != nil {
|
||||||
|
log.Logf("net dialer set mark %d error: %s", c.Mark, ex)
|
||||||
|
} else {
|
||||||
|
// log.Logf("net dialer set mark %d success", options.Mark)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := route.getConn()
|
if c.Interface != "" {
|
||||||
|
controlFunction = func(_, _ string, cc syscall.RawConn) error {
|
||||||
|
return cc.Control(func(fd uintptr) {
|
||||||
|
err := setSocketInterface(int(fd), c.Interface)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("net dialer set interface %s error: %s", c.Interface, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if route.IsEmpty() {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
if address == "" {
|
||||||
|
return net.ListenUDP(network, nil)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
d := &net.Dialer{
|
||||||
|
Timeout: timeout,
|
||||||
|
Control: controlFunction,
|
||||||
|
// LocalAddr: laddr, // TODO: optional local address
|
||||||
|
}
|
||||||
|
return d.DialContext(ctx, network, ipAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := route.getConn(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cOpts := append([]ConnectOption{AddrConnectOption(addr)}, route.LastNode().ConnectOptions...)
|
cOpts := append([]ConnectOption{AddrConnectOption(address)}, route.LastNode().ConnectOptions...)
|
||||||
cc, err := route.LastNode().Client.Connect(conn, ipAddr, cOpts...)
|
cc, err := route.LastNode().Client.ConnectContext(ctx, conn, network, ipAddr, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -173,9 +234,11 @@ func (*Chain) resolve(addr string, resolver Resolver, hosts *Hosts) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[resolver] %s: %v", host, err)
|
log.Logf("[resolver] %s: %v", host, err)
|
||||||
}
|
}
|
||||||
if len(ips) > 0 {
|
if len(ips) == 0 {
|
||||||
return net.JoinHostPort(ips[0].String(), port)
|
log.Logf("[resolver] %s: domain does not exists", host)
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
return net.JoinHostPort(ips[0].String(), port)
|
||||||
}
|
}
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
@ -187,6 +250,8 @@ func (c *Chain) Conn(opts ...ChainOption) (conn net.Conn, err error) {
|
|||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
retries := 1
|
retries := 1
|
||||||
if c != nil && c.Retries > 0 {
|
if c != nil && c.Retries > 0 {
|
||||||
retries = c.Retries
|
retries = c.Retries
|
||||||
@ -201,7 +266,7 @@ func (c *Chain) Conn(opts ...ChainOption) (conn net.Conn, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conn, err = route.getConn()
|
conn, err = route.getConn(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -210,7 +275,7 @@ func (c *Chain) Conn(opts ...ChainOption) (conn net.Conn, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getConn obtains a connection to the last node of the chain.
|
// getConn obtains a connection to the last node of the chain.
|
||||||
func (c *Chain) getConn() (conn net.Conn, err error) {
|
func (c *Chain) getConn(ctx context.Context) (conn net.Conn, err error) {
|
||||||
if c.IsEmpty() {
|
if c.IsEmpty() {
|
||||||
err = ErrEmptyChain
|
err = ErrEmptyChain
|
||||||
return
|
return
|
||||||
@ -218,14 +283,15 @@ func (c *Chain) getConn() (conn net.Conn, err error) {
|
|||||||
nodes := c.Nodes()
|
nodes := c.Nodes()
|
||||||
node := nodes[0]
|
node := nodes[0]
|
||||||
|
|
||||||
cn, err := node.Client.Dial(node.Addr, node.DialOptions...)
|
cc, err := node.Client.Dial(node.Addr, node.DialOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
node.MarkDead()
|
node.MarkDead()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cn, err = node.Client.Handshake(cn, node.HandshakeOptions...)
|
cn, err := node.Client.Handshake(cc, node.HandshakeOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
cc.Close()
|
||||||
node.MarkDead()
|
node.MarkDead()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -234,7 +300,7 @@ func (c *Chain) getConn() (conn net.Conn, err error) {
|
|||||||
preNode := node
|
preNode := node
|
||||||
for _, node := range nodes[1:] {
|
for _, node := range nodes[1:] {
|
||||||
var cc net.Conn
|
var cc net.Conn
|
||||||
cc, err = preNode.Client.Connect(cn, node.Addr, preNode.ConnectOptions...)
|
cc, err = preNode.Client.ConnectContext(ctx, cn, "tcp", node.Addr, preNode.ConnectOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cn.Close()
|
cn.Close()
|
||||||
node.MarkDead()
|
node.MarkDead()
|
||||||
@ -263,13 +329,13 @@ func (c *Chain) selectRoute() (route *Chain, err error) {
|
|||||||
// selectRouteFor selects route with bypass testing.
|
// selectRouteFor selects route with bypass testing.
|
||||||
func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) {
|
func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) {
|
||||||
if c.IsEmpty() {
|
if c.IsEmpty() {
|
||||||
return newRoute(), nil
|
return c.newRoute(), nil
|
||||||
}
|
}
|
||||||
if c.isRoute {
|
if c.isRoute {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
route = newRoute()
|
route = c.newRoute()
|
||||||
var nl []Node
|
var nl []Node
|
||||||
|
|
||||||
for _, group := range c.nodeGroups {
|
for _, group := range c.nodeGroups {
|
||||||
@ -287,7 +353,7 @@ func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) {
|
|||||||
node.DialOptions = append(node.DialOptions,
|
node.DialOptions = append(node.DialOptions,
|
||||||
ChainDialOption(route),
|
ChainDialOption(route),
|
||||||
)
|
)
|
||||||
route = newRoute() // cutoff the chain for multiplex node.
|
route = c.newRoute() // cutoff the chain for multiplex node.
|
||||||
}
|
}
|
||||||
|
|
||||||
route.AddNode(node)
|
route.AddNode(node)
|
||||||
@ -305,6 +371,7 @@ type ChainOptions struct {
|
|||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Hosts *Hosts
|
Hosts *Hosts
|
||||||
Resolver Resolver
|
Resolver Resolver
|
||||||
|
Mark int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainOption allows a common way to set chain options.
|
// ChainOption allows a common way to set chain options.
|
||||||
|
139
client.go
139
client.go
@ -1,12 +1,13 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is a proxy client.
|
// Client is a proxy client.
|
||||||
@ -14,23 +15,8 @@ import (
|
|||||||
// Connector is responsible for connecting to the destination address through this proxy.
|
// Connector is responsible for connecting to the destination address through this proxy.
|
||||||
// Transporter performs a handshake with this proxy.
|
// Transporter performs a handshake with this proxy.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Connector Connector
|
Connector
|
||||||
Transporter Transporter
|
Transporter
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the target address.
|
|
||||||
func (c *Client) Dial(addr string, options ...DialOption) (net.Conn, error) {
|
|
||||||
return c.Transporter.Dial(addr, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake performs a handshake with the proxy over connection conn.
|
|
||||||
func (c *Client) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
|
||||||
return c.Transporter.Handshake(conn, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect connects to the address addr via the proxy over connection conn.
|
|
||||||
func (c *Client) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
|
||||||
return c.Connector.Connect(conn, addr, options...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultClient is a standard HTTP proxy client.
|
// DefaultClient is a standard HTTP proxy client.
|
||||||
@ -53,7 +39,36 @@ func Connect(conn net.Conn, addr string) (net.Conn, error) {
|
|||||||
|
|
||||||
// Connector is responsible for connecting to the destination address.
|
// Connector is responsible for connecting to the destination address.
|
||||||
type Connector interface {
|
type Connector interface {
|
||||||
Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error)
|
// Deprecated: use ConnectContext instead.
|
||||||
|
Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error)
|
||||||
|
ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type autoConnector struct {
|
||||||
|
User *url.Userinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoConnector is a Connector.
|
||||||
|
func AutoConnector(user *url.Userinfo) Connector {
|
||||||
|
return &autoConnector{
|
||||||
|
User: user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *autoConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *autoConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
var cnr Connector
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
cnr = &httpConnector{User: c.User}
|
||||||
|
default:
|
||||||
|
cnr = &socks5UDPTunConnector{User: c.User}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnr.ConnectContext(ctx, conn, network, address, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transporter is responsible for handshaking with the proxy server.
|
// Transporter is responsible for handshaking with the proxy server.
|
||||||
@ -64,72 +79,11 @@ type Transporter interface {
|
|||||||
Multiplex() bool
|
Multiplex() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// tcpTransporter is a raw TCP transporter.
|
|
||||||
type tcpTransporter struct{}
|
|
||||||
|
|
||||||
// TCPTransporter creates a raw TCP client.
|
|
||||||
func TCPTransporter() Transporter {
|
|
||||||
return &tcpTransporter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *tcpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
|
|
||||||
opts := &DialOptions{}
|
|
||||||
for _, option := range options {
|
|
||||||
option(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := opts.Timeout
|
|
||||||
if timeout <= 0 {
|
|
||||||
timeout = DialTimeout
|
|
||||||
}
|
|
||||||
if opts.Chain == nil {
|
|
||||||
return net.DialTimeout("tcp", addr, timeout)
|
|
||||||
}
|
|
||||||
return opts.Chain.Dial(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *tcpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *tcpTransporter) Multiplex() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// udpTransporter is a raw UDP transporter.
|
|
||||||
type udpTransporter struct{}
|
|
||||||
|
|
||||||
// UDPTransporter creates a raw UDP client.
|
|
||||||
func UDPTransporter() Transporter {
|
|
||||||
return &udpTransporter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *udpTransporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
|
|
||||||
opts := &DialOptions{}
|
|
||||||
for _, option := range options {
|
|
||||||
option(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := opts.Timeout
|
|
||||||
if timeout <= 0 {
|
|
||||||
timeout = DialTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.DialTimeout("udp", addr, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *udpTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *udpTransporter) Multiplex() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialOptions describes the options for Transporter.Dial.
|
// DialOptions describes the options for Transporter.Dial.
|
||||||
type DialOptions struct {
|
type DialOptions struct {
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Chain *Chain
|
Chain *Chain
|
||||||
|
Host string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialOption allows a common way to set DialOptions.
|
// DialOption allows a common way to set DialOptions.
|
||||||
@ -149,6 +103,13 @@ func ChainDialOption(chain *Chain) DialOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HostDialOption specifies the host used by Transporter.Dial
|
||||||
|
func HostDialOption(host string) DialOption {
|
||||||
|
return func(opts *DialOptions) {
|
||||||
|
opts.Host = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HandshakeOptions describes the options for handshake.
|
// HandshakeOptions describes the options for handshake.
|
||||||
type HandshakeOptions struct {
|
type HandshakeOptions struct {
|
||||||
Addr string
|
Addr string
|
||||||
@ -161,6 +122,7 @@ type HandshakeOptions struct {
|
|||||||
WSOptions *WSOptions
|
WSOptions *WSOptions
|
||||||
KCPConfig *KCPConfig
|
KCPConfig *KCPConfig
|
||||||
QUICConfig *QUICConfig
|
QUICConfig *QUICConfig
|
||||||
|
SSHConfig *SSHConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandshakeOption allows a common way to set HandshakeOptions.
|
// HandshakeOption allows a common way to set HandshakeOptions.
|
||||||
@ -236,6 +198,13 @@ func QUICConfigHandshakeOption(config *QUICConfig) HandshakeOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSHConfigHandshakeOption specifies the ssh config used by SSH client handshake.
|
||||||
|
func SSHConfigHandshakeOption(config *SSHConfig) HandshakeOption {
|
||||||
|
return func(opts *HandshakeOptions) {
|
||||||
|
opts.SSHConfig = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectOptions describes the options for Connector.Connect.
|
// ConnectOptions describes the options for Connector.Connect.
|
||||||
type ConnectOptions struct {
|
type ConnectOptions struct {
|
||||||
Addr string
|
Addr string
|
||||||
@ -244,6 +213,7 @@ type ConnectOptions struct {
|
|||||||
Selector gosocks5.Selector
|
Selector gosocks5.Selector
|
||||||
UserAgent string
|
UserAgent string
|
||||||
NoTLS bool
|
NoTLS bool
|
||||||
|
NoDelay bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectOption allows a common way to set ConnectOptions.
|
// ConnectOption allows a common way to set ConnectOptions.
|
||||||
@ -290,3 +260,10 @@ func NoTLSConnectOption(b bool) ConnectOption {
|
|||||||
opts.NoTLS = b
|
opts.NoTLS = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoDelayConnectOption specifies the NoDelay option for ss.Connect.
|
||||||
|
func NoDelayConnectOption(b bool) ConnectOption {
|
||||||
|
return func(opts *ConnectOptions) {
|
||||||
|
opts.NoDelay = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
115
cmd/gost/cfg.go
115
cmd/gost/cfg.go
@ -5,8 +5,8 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"io/ioutil"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -43,8 +43,9 @@ var (
|
|||||||
defaultKeyFile = "key.pem"
|
defaultKeyFile = "key.pem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid.
|
// Load the certificate from cert & key files and optional client CA file,
|
||||||
func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
|
// will use the default certificate if the provided info are invalid.
|
||||||
|
func tlsConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
|
||||||
if certFile == "" || keyFile == "" {
|
if certFile == "" || keyFile == "" {
|
||||||
certFile, keyFile = defaultCertFile, defaultKeyFile
|
certFile, keyFile = defaultCertFile, defaultKeyFile
|
||||||
}
|
}
|
||||||
@ -53,7 +54,19 @@ func tlsConfig(certFile, keyFile string) (*tls.Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
|
||||||
|
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
|
||||||
|
pool, err := loadCA(caFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pool != nil {
|
||||||
|
cfg.ClientCAs = pool
|
||||||
|
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCA(caFile string) (cp *x509.CertPool, err error) {
|
func loadCA(caFile string) (cp *x509.CertPool, err error) {
|
||||||
@ -61,12 +74,12 @@ func loadCA(caFile string) (cp *x509.CertPool, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cp = x509.NewCertPool()
|
cp = x509.NewCertPool()
|
||||||
data, err := ioutil.ReadFile(caFile)
|
data, err := os.ReadFile(caFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !cp.AppendCertsFromPEM(data) {
|
if !cp.AppendCertsFromPEM(data) {
|
||||||
return nil, errors.New("AppendCertsFromPEM failed")
|
return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -97,6 +110,7 @@ func parseUsers(authFile string) (users []*url.Userinfo, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
@ -142,33 +156,38 @@ func parseIP(s string, port string) (ips []string) {
|
|||||||
port = "8080" // default port
|
port = "8080" // default port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addrFn := func(s, port string) string {
|
||||||
|
c := strings.Count(s, ":")
|
||||||
|
if c == 0 || //ipv4 or domain
|
||||||
|
s[len(s)-1] == ']' { //[ipv6]
|
||||||
|
return s + ":" + port
|
||||||
|
}
|
||||||
|
if c > 1 && s[0] != '[' { // ipv6
|
||||||
|
return "[" + s + "]:" + port
|
||||||
|
}
|
||||||
|
return s //ipv4:port or [ipv6]:port
|
||||||
|
}
|
||||||
|
|
||||||
file, err := os.Open(s)
|
file, err := os.Open(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ss := strings.Split(s, ",")
|
ss := strings.Split(s, ",")
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
if s != "" {
|
if s != "" {
|
||||||
// TODO: support IPv6
|
ips = append(ips, addrFn(s, port))
|
||||||
if !strings.Contains(s, ":") {
|
|
||||||
s = s + ":" + port
|
|
||||||
}
|
|
||||||
ips = append(ips, s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.Contains(line, ":") {
|
ips = append(ips, addrFn(line, port))
|
||||||
line = line + ":" + port
|
|
||||||
}
|
|
||||||
ips = append(ips, line)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -218,13 +237,19 @@ func parseResolver(cfg string) gost.Resolver {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(s, "https") {
|
if strings.HasPrefix(s, "https") {
|
||||||
|
p := "https"
|
||||||
|
u, _ := url.Parse(s)
|
||||||
|
if u == nil || u.Scheme == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if u.Scheme == "https-chain" {
|
||||||
|
p = u.Scheme
|
||||||
|
}
|
||||||
ns := gost.NameServer{
|
ns := gost.NameServer{
|
||||||
Addr: s,
|
Addr: s,
|
||||||
Protocol: "https",
|
Protocol: p,
|
||||||
}
|
}
|
||||||
if err := ns.Init(); err == nil {
|
|
||||||
nss = append(nss, ns)
|
nss = append(nss, ns)
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,20 +258,16 @@ func parseResolver(cfg string) gost.Resolver {
|
|||||||
ns := gost.NameServer{
|
ns := gost.NameServer{
|
||||||
Addr: ss[0],
|
Addr: ss[0],
|
||||||
}
|
}
|
||||||
if err := ns.Init(); err == nil {
|
|
||||||
nss = append(nss, ns)
|
nss = append(nss, ns)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if len(ss) == 2 {
|
if len(ss) == 2 {
|
||||||
ns := gost.NameServer{
|
ns := gost.NameServer{
|
||||||
Addr: ss[0],
|
Addr: ss[0],
|
||||||
Protocol: ss[1],
|
Protocol: ss[1],
|
||||||
}
|
}
|
||||||
if err := ns.Init(); err == nil {
|
|
||||||
nss = append(nss, ns)
|
nss = append(nss, ns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return gost.NewResolver(0, nss...)
|
return gost.NewResolver(0, nss...)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
@ -273,3 +294,49 @@ func parseHosts(s string) *gost.Hosts {
|
|||||||
|
|
||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseIPRoutes(s string) (routes []gost.IPRoute) {
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(s)
|
||||||
|
if err != nil {
|
||||||
|
ss := strings.Split(s, ",")
|
||||||
|
for _, s := range ss {
|
||||||
|
if _, inet, _ := net.ParseCIDR(strings.TrimSpace(s)); inet != nil {
|
||||||
|
routes = append(routes, gost.IPRoute{Dest: inet})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.Replace(scanner.Text(), "\t", " ", -1)
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var route gost.IPRoute
|
||||||
|
var ss []string
|
||||||
|
for _, s := range strings.Split(line, " ") {
|
||||||
|
if s = strings.TrimSpace(s); s != "" {
|
||||||
|
ss = append(ss, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ss) > 0 && ss[0] != "" {
|
||||||
|
_, route.Dest, _ = net.ParseCIDR(strings.TrimSpace(ss[0]))
|
||||||
|
if route.Dest == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ss) > 1 && ss[1] != "" {
|
||||||
|
route.Gateway = net.ParseIP(ss[1])
|
||||||
|
}
|
||||||
|
routes = append(routes, route)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
@ -31,7 +31,9 @@ func init() {
|
|||||||
|
|
||||||
flag.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain")
|
flag.Var(&baseCfg.route.ChainNodes, "F", "forward address, can make a forward chain")
|
||||||
flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)")
|
flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)")
|
||||||
|
flag.IntVar(&baseCfg.route.Mark, "M", 0, "Specify out connection mark")
|
||||||
flag.StringVar(&configureFile, "C", "", "configure file")
|
flag.StringVar(&configureFile, "C", "", "configure file")
|
||||||
|
flag.StringVar(&baseCfg.route.Interface, "I", "", "Interface to bind")
|
||||||
flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log")
|
flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log")
|
||||||
flag.BoolVar(&printVersion, "V", false, "print version")
|
flag.BoolVar(&printVersion, "V", false, "print version")
|
||||||
if pprofEnabled {
|
if pprofEnabled {
|
||||||
@ -40,7 +42,7 @@ func init() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if printVersion {
|
if printVersion {
|
||||||
fmt.Fprintf(os.Stderr, "gost %s (%s %s/%s)\n",
|
fmt.Fprintf(os.Stdout, "gost %s (%s %s/%s)\n",
|
||||||
gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
gost.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
@ -67,7 +69,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: as of 2.6, you can use custom cert/key files to initialize the default certificate.
|
// NOTE: as of 2.6, you can use custom cert/key files to initialize the default certificate.
|
||||||
tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile)
|
tlsConfig, err := tlsConfig(defaultCertFile, defaultKeyFile, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// generate random self-signed certificate.
|
// generate random self-signed certificate.
|
||||||
cert, err := gost.GenCertificate()
|
cert, err := gost.GenCertificate()
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -16,8 +15,10 @@ import (
|
|||||||
type peerConfig struct {
|
type peerConfig struct {
|
||||||
Strategy string `json:"strategy"`
|
Strategy string `json:"strategy"`
|
||||||
MaxFails int `json:"max_fails"`
|
MaxFails int `json:"max_fails"`
|
||||||
|
FastestCount int `json:"fastest_count"` // topN fastest node count
|
||||||
FailTimeout time.Duration
|
FailTimeout time.Duration
|
||||||
period time.Duration // the period for live reloading
|
period time.Duration // the period for live reloading
|
||||||
|
|
||||||
Nodes []string `json:"nodes"`
|
Nodes []string `json:"nodes"`
|
||||||
group *gost.NodeGroup
|
group *gost.NodeGroup
|
||||||
baseNodes []gost.Node
|
baseNodes []gost.Node
|
||||||
@ -52,6 +53,7 @@ func (cfg *peerConfig) Reload(r io.Reader) error {
|
|||||||
FailTimeout: cfg.FailTimeout,
|
FailTimeout: cfg.FailTimeout,
|
||||||
},
|
},
|
||||||
&gost.InvalidFilter{},
|
&gost.InvalidFilter{},
|
||||||
|
gost.NewFastestFilter(0, cfg.FastestCount),
|
||||||
),
|
),
|
||||||
gost.WithStrategy(gost.NewStrategy(cfg.Strategy)),
|
gost.WithStrategy(gost.NewStrategy(cfg.Strategy)),
|
||||||
)
|
)
|
||||||
@ -83,7 +85,7 @@ func (cfg *peerConfig) Reload(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *peerConfig) parse(r io.Reader) error {
|
func (cfg *peerConfig) parse(r io.Reader) error {
|
||||||
data, err := ioutil.ReadAll(r)
|
data, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -126,6 +128,8 @@ func (cfg *peerConfig) parse(r io.Reader) error {
|
|||||||
cfg.Strategy = ss[1]
|
cfg.Strategy = ss[1]
|
||||||
case "max_fails":
|
case "max_fails":
|
||||||
cfg.MaxFails, _ = strconv.Atoi(ss[1])
|
cfg.MaxFails, _ = strconv.Atoi(ss[1])
|
||||||
|
case "fastest_count":
|
||||||
|
cfg.FastestCount, _ = strconv.Atoi(ss[1])
|
||||||
case "fail_timeout":
|
case "fail_timeout":
|
||||||
cfg.FailTimeout, _ = time.ParseDuration(ss[1])
|
cfg.FailTimeout, _ = time.ParseDuration(ss[1])
|
||||||
case "reload":
|
case "reload":
|
||||||
|
@ -3,8 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -27,11 +30,15 @@ type route struct {
|
|||||||
ServeNodes stringList
|
ServeNodes stringList
|
||||||
ChainNodes stringList
|
ChainNodes stringList
|
||||||
Retries int
|
Retries int
|
||||||
|
Mark int
|
||||||
|
Interface string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) parseChain() (*gost.Chain, error) {
|
func (r *route) parseChain() (*gost.Chain, error) {
|
||||||
chain := gost.NewChain()
|
chain := gost.NewChain()
|
||||||
chain.Retries = r.Retries
|
chain.Retries = r.Retries
|
||||||
|
chain.Mark = r.Mark
|
||||||
|
chain.Interface = r.Interface
|
||||||
gid := 1 // group ID
|
gid := 1 // group ID
|
||||||
|
|
||||||
for _, ns := range r.ChainNodes {
|
for _, ns := range r.ChainNodes {
|
||||||
@ -59,6 +66,7 @@ func (r *route) parseChain() (*gost.Chain, error) {
|
|||||||
FailTimeout: nodes[0].GetDuration("fail_timeout"),
|
FailTimeout: nodes[0].GetDuration("fail_timeout"),
|
||||||
},
|
},
|
||||||
&gost.InvalidFilter{},
|
&gost.InvalidFilter{},
|
||||||
|
gost.NewFastestFilter(0, nodes[0].GetInt("fastest_count")),
|
||||||
),
|
),
|
||||||
gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))),
|
gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))),
|
||||||
)
|
)
|
||||||
@ -90,13 +98,29 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"))
|
users, err := parseUsers(node.Get("secrets"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
if node.User == nil && len(users) > 0 {
|
if len(users) > 0 {
|
||||||
node.User = users[0]
|
node.User = users[0]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serverName, sport, _ := net.SplitHostPort(node.Addr)
|
serverName, sport, _ := net.SplitHostPort(node.Addr)
|
||||||
if serverName == "" {
|
if serverName == "" {
|
||||||
serverName = "localhost" // default server name
|
serverName = "localhost" // default server name
|
||||||
@ -111,6 +135,35 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
InsecureSkipVerify: !node.GetBool("secure"),
|
InsecureSkipVerify: !node.GetBool("secure"),
|
||||||
RootCAs: rootCAs,
|
RootCAs: rootCAs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the argument `ca` is given, but not open `secure`, we verify the
|
||||||
|
// certificate manually.
|
||||||
|
if rootCAs != nil && !node.GetBool("secure") {
|
||||||
|
tlsCfg.VerifyConnection = func(state tls.ConnectionState) error {
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
Roots: rootCAs,
|
||||||
|
CurrentTime: time.Now(),
|
||||||
|
DNSName: "",
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
}
|
||||||
|
|
||||||
|
certs := state.PeerCertificates
|
||||||
|
for i, cert := range certs {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = certs[0].Verify(opts)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert, err := tls.LoadX509KeyPair(node.Get("cert"), node.Get("key")); err == nil {
|
||||||
|
tlsCfg.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
|
||||||
wsOpts := &gost.WSOptions{}
|
wsOpts := &gost.WSOptions{}
|
||||||
wsOpts.EnableCompression = node.GetBool("compression")
|
wsOpts.EnableCompression = node.GetBool("compression")
|
||||||
wsOpts.ReadBufferSize = node.GetInt("rbuf")
|
wsOpts.ReadBufferSize = node.GetInt("rbuf")
|
||||||
@ -118,7 +171,7 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
wsOpts.UserAgent = node.Get("agent")
|
wsOpts.UserAgent = node.Get("agent")
|
||||||
wsOpts.Path = node.Get("path")
|
wsOpts.Path = node.Get("path")
|
||||||
|
|
||||||
var host string
|
timeout := node.GetDuration("timeout")
|
||||||
|
|
||||||
var tr gost.Transporter
|
var tr gost.Transporter
|
||||||
switch node.Transport {
|
switch node.Transport {
|
||||||
@ -157,8 +210,14 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
config := &gost.QUICConfig{
|
config := &gost.QUICConfig{
|
||||||
TLSConfig: tlsCfg,
|
TLSConfig: tlsCfg,
|
||||||
KeepAlive: node.GetBool("keepalive"),
|
KeepAlive: node.GetBool("keepalive"),
|
||||||
Timeout: time.Duration(node.GetInt("timeout")) * time.Second,
|
Timeout: timeout,
|
||||||
IdleTimeout: time.Duration(node.GetInt("idle")) * time.Second,
|
IdleTimeout: node.GetDuration("idle"),
|
||||||
|
}
|
||||||
|
if config.KeepAlive {
|
||||||
|
config.KeepAlivePeriod = node.GetDuration("ttl")
|
||||||
|
if config.KeepAlivePeriod == 0 {
|
||||||
|
config.KeepAlivePeriod = 10 * time.Second
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cipher := node.Get("cipher"); cipher != "" {
|
if cipher := node.Get("cipher"); cipher != "" {
|
||||||
@ -176,10 +235,15 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
case "obfs4":
|
case "obfs4":
|
||||||
tr = gost.Obfs4Transporter()
|
tr = gost.Obfs4Transporter()
|
||||||
case "ohttp":
|
case "ohttp":
|
||||||
host = node.Get("host")
|
|
||||||
tr = gost.ObfsHTTPTransporter()
|
tr = gost.ObfsHTTPTransporter()
|
||||||
|
case "otls":
|
||||||
|
tr = gost.ObfsTLSTransporter()
|
||||||
case "ftcp":
|
case "ftcp":
|
||||||
tr = gost.FakeTCPTransporter()
|
tr = gost.FakeTCPTransporter()
|
||||||
|
case "udp":
|
||||||
|
tr = gost.UDPTransporter()
|
||||||
|
case "vsock":
|
||||||
|
tr = gost.VSOCKTransporter()
|
||||||
default:
|
default:
|
||||||
tr = gost.TCPTransporter()
|
tr = gost.TCPTransporter()
|
||||||
}
|
}
|
||||||
@ -196,8 +260,8 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
connector = gost.SOCKS4AConnector()
|
connector = gost.SOCKS4AConnector()
|
||||||
case "ss":
|
case "ss":
|
||||||
connector = gost.ShadowConnector(node.User)
|
connector = gost.ShadowConnector(node.User)
|
||||||
case "ss2":
|
case "ssu":
|
||||||
connector = gost.Shadow2Connector(node.User)
|
connector = gost.ShadowUDPConnector(node.User)
|
||||||
case "direct":
|
case "direct":
|
||||||
connector = gost.SSHDirectForwardConnector()
|
connector = gost.SSHDirectForwardConnector()
|
||||||
case "remote":
|
case "remote":
|
||||||
@ -207,34 +271,48 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
case "sni":
|
case "sni":
|
||||||
connector = gost.SNIConnector(node.Get("host"))
|
connector = gost.SNIConnector(node.Get("host"))
|
||||||
case "http":
|
case "http":
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
node.Protocol = "http" // default protocol is HTTP
|
|
||||||
connector = gost.HTTPConnector(node.User)
|
connector = gost.HTTPConnector(node.User)
|
||||||
|
case "relay":
|
||||||
|
connector = gost.RelayConnector(node.User)
|
||||||
|
default:
|
||||||
|
connector = gost.AutoConnector(node.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
host := node.Get("host")
|
||||||
|
if host == "" {
|
||||||
|
host = node.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := node.GetInt("timeout")
|
|
||||||
node.DialOptions = append(node.DialOptions,
|
node.DialOptions = append(node.DialOptions,
|
||||||
gost.TimeoutDialOption(time.Duration(timeout)*time.Second),
|
gost.TimeoutDialOption(timeout),
|
||||||
|
gost.HostDialOption(host),
|
||||||
)
|
)
|
||||||
|
|
||||||
node.ConnectOptions = []gost.ConnectOption{
|
node.ConnectOptions = []gost.ConnectOption{
|
||||||
gost.UserAgentConnectOption(node.Get("agent")),
|
gost.UserAgentConnectOption(node.Get("agent")),
|
||||||
gost.NoTLSConnectOption(node.GetBool("notls")),
|
gost.NoTLSConnectOption(node.GetBool("notls")),
|
||||||
|
gost.NoDelayConnectOption(node.GetBool("nodelay")),
|
||||||
}
|
}
|
||||||
|
|
||||||
if host == "" {
|
sshConfig := &gost.SSHConfig{}
|
||||||
host = node.Host
|
if s := node.Get("ssh_key"); s != "" {
|
||||||
|
key, err := gost.ParseSSHKeyFile(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sshConfig.Key = key
|
||||||
}
|
}
|
||||||
handshakeOptions := []gost.HandshakeOption{
|
handshakeOptions := []gost.HandshakeOption{
|
||||||
gost.AddrHandshakeOption(node.Addr),
|
gost.AddrHandshakeOption(node.Addr),
|
||||||
gost.HostHandshakeOption(host),
|
gost.HostHandshakeOption(host),
|
||||||
gost.UserHandshakeOption(node.User),
|
gost.UserHandshakeOption(node.User),
|
||||||
gost.TLSConfigHandshakeOption(tlsCfg),
|
gost.TLSConfigHandshakeOption(tlsCfg),
|
||||||
gost.IntervalHandshakeOption(time.Duration(node.GetInt("ping")) * time.Second),
|
gost.IntervalHandshakeOption(node.GetDuration("ping")),
|
||||||
gost.TimeoutHandshakeOption(time.Duration(timeout) * time.Second),
|
gost.TimeoutHandshakeOption(timeout),
|
||||||
gost.RetryHandshakeOption(node.GetInt("retry")),
|
gost.RetryHandshakeOption(node.GetInt("retry")),
|
||||||
|
gost.SSHConfigHandshakeOption(sshConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Client = &gost.Client{
|
node.Client = &gost.Client{
|
||||||
Connector: connector,
|
Connector: connector,
|
||||||
Transporter: tr,
|
Transporter: tr,
|
||||||
@ -280,6 +358,20 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if auth := node.Get("auth"); auth != "" && node.User == nil {
|
||||||
|
c, err := base64.StdEncoding.DecodeString(auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cs := string(c)
|
||||||
|
s := strings.IndexByte(cs, ':')
|
||||||
|
if s < 0 {
|
||||||
|
node.User = url.User(cs)
|
||||||
|
} else {
|
||||||
|
node.User = url.UserPassword(cs[:s], cs[s+1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
authenticator, err := parseAuthenticator(node.Get("secrets"))
|
authenticator, err := parseAuthenticator(node.Get("secrets"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -295,7 +387,7 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
certFile, keyFile := node.Get("cert"), node.Get("key")
|
certFile, keyFile := node.Get("cert"), node.Get("key")
|
||||||
tlsCfg, err := tlsConfig(certFile, keyFile)
|
tlsCfg, err := tlsConfig(certFile, keyFile, node.Get("ca"))
|
||||||
if err != nil && certFile != "" && keyFile != "" {
|
if err != nil && certFile != "" && keyFile != "" {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -306,9 +398,15 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
wsOpts.WriteBufferSize = node.GetInt("wbuf")
|
wsOpts.WriteBufferSize = node.GetInt("wbuf")
|
||||||
wsOpts.Path = node.Get("path")
|
wsOpts.Path = node.Get("path")
|
||||||
|
|
||||||
ttl, err := time.ParseDuration(node.Get("ttl"))
|
ttl := node.GetDuration("ttl")
|
||||||
if err != nil {
|
timeout := node.GetDuration("timeout")
|
||||||
ttl = time.Duration(node.GetInt("ttl")) * time.Second
|
|
||||||
|
tunRoutes := parseIPRoutes(node.Get("route"))
|
||||||
|
gw := net.ParseIP(node.Get("gw")) // default gateway
|
||||||
|
for i := range tunRoutes {
|
||||||
|
if tunRoutes[i].Gateway == nil {
|
||||||
|
tunRoutes[i].Gateway = gw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ln gost.Listener
|
var ln gost.Listener
|
||||||
@ -343,6 +441,20 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
Authenticator: authenticator,
|
Authenticator: authenticator,
|
||||||
TLSConfig: tlsCfg,
|
TLSConfig: tlsCfg,
|
||||||
}
|
}
|
||||||
|
if s := node.Get("ssh_key"); s != "" {
|
||||||
|
key, err := gost.ParseSSHKeyFile(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Key = key
|
||||||
|
}
|
||||||
|
if s := node.Get("ssh_authorized_keys"); s != "" {
|
||||||
|
keys, err := gost.ParseSSHAuthorizedKeysFile(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.AuthorizedKeys = keys
|
||||||
|
}
|
||||||
if node.Protocol == "forward" {
|
if node.Protocol == "forward" {
|
||||||
ln, err = gost.TCPListener(node.Addr)
|
ln, err = gost.TCPListener(node.Addr)
|
||||||
} else {
|
} else {
|
||||||
@ -352,8 +464,14 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
config := &gost.QUICConfig{
|
config := &gost.QUICConfig{
|
||||||
TLSConfig: tlsCfg,
|
TLSConfig: tlsCfg,
|
||||||
KeepAlive: node.GetBool("keepalive"),
|
KeepAlive: node.GetBool("keepalive"),
|
||||||
Timeout: time.Duration(node.GetInt("timeout")) * time.Second,
|
Timeout: timeout,
|
||||||
IdleTimeout: time.Duration(node.GetInt("idle")) * time.Second,
|
IdleTimeout: node.GetDuration("idle"),
|
||||||
|
}
|
||||||
|
if config.KeepAlive {
|
||||||
|
config.KeepAlivePeriod = node.GetDuration("ttl")
|
||||||
|
if config.KeepAlivePeriod == 0 {
|
||||||
|
config.KeepAlivePeriod = 10 * time.Second
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if cipher := node.Get("cipher"); cipher != "" {
|
if cipher := node.Get("cipher"); cipher != "" {
|
||||||
sum := sha256.Sum256([]byte(cipher))
|
sum := sha256.Sum256([]byte(cipher))
|
||||||
@ -374,6 +492,14 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
|
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
|
||||||
}
|
}
|
||||||
ln, err = gost.TCPListener(node.Addr)
|
ln, err = gost.TCPListener(node.Addr)
|
||||||
|
case "vsock":
|
||||||
|
ln, err = gost.VSOCKListener(node.Addr)
|
||||||
|
case "udp":
|
||||||
|
ln, err = gost.UDPListener(node.Addr, &gost.UDPListenConfig{
|
||||||
|
TTL: ttl,
|
||||||
|
Backlog: node.GetInt("backlog"),
|
||||||
|
QueueSize: node.GetInt("queue"),
|
||||||
|
})
|
||||||
case "rtcp":
|
case "rtcp":
|
||||||
// Directly use SSH port forwarding if the last chain node is forward+ssh
|
// Directly use SSH port forwarding if the last chain node is forward+ssh
|
||||||
if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" {
|
if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" {
|
||||||
@ -381,24 +507,10 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
|
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
|
||||||
}
|
}
|
||||||
ln, err = gost.TCPRemoteForwardListener(node.Addr, chain)
|
ln, err = gost.TCPRemoteForwardListener(node.Addr, chain)
|
||||||
case "udp":
|
|
||||||
ln, err = gost.UDPDirectForwardListener(node.Addr, &gost.UDPForwardListenConfig{
|
|
||||||
TTL: ttl,
|
|
||||||
Backlog: node.GetInt("backlog"),
|
|
||||||
QueueSize: node.GetInt("queue"),
|
|
||||||
})
|
|
||||||
case "rudp":
|
case "rudp":
|
||||||
ln, err = gost.UDPRemoteForwardListener(node.Addr,
|
ln, err = gost.UDPRemoteForwardListener(node.Addr,
|
||||||
chain,
|
chain,
|
||||||
&gost.UDPForwardListenConfig{
|
&gost.UDPListenConfig{
|
||||||
TTL: ttl,
|
|
||||||
Backlog: node.GetInt("backlog"),
|
|
||||||
QueueSize: node.GetInt("queue"),
|
|
||||||
})
|
|
||||||
case "ssu":
|
|
||||||
ln, err = gost.ShadowUDPListener(node.Addr,
|
|
||||||
node.User,
|
|
||||||
&gost.UDPForwardListenConfig{
|
|
||||||
TTL: ttl,
|
TTL: ttl,
|
||||||
Backlog: node.GetInt("backlog"),
|
Backlog: node.GetInt("backlog"),
|
||||||
QueueSize: node.GetInt("queue"),
|
QueueSize: node.GetInt("queue"),
|
||||||
@ -410,12 +522,15 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
ln, err = gost.Obfs4Listener(node.Addr)
|
ln, err = gost.Obfs4Listener(node.Addr)
|
||||||
case "ohttp":
|
case "ohttp":
|
||||||
ln, err = gost.ObfsHTTPListener(node.Addr)
|
ln, err = gost.ObfsHTTPListener(node.Addr)
|
||||||
|
case "otls":
|
||||||
|
ln, err = gost.ObfsTLSListener(node.Addr)
|
||||||
case "tun":
|
case "tun":
|
||||||
cfg := gost.TunConfig{
|
cfg := gost.TunConfig{
|
||||||
Name: node.Get("name"),
|
Name: node.Get("name"),
|
||||||
Addr: node.Get("net"),
|
Addr: node.Get("net"),
|
||||||
|
Peer: node.Get("peer"),
|
||||||
MTU: node.GetInt("mtu"),
|
MTU: node.GetInt("mtu"),
|
||||||
Routes: strings.Split(node.Get("route"), ","),
|
Routes: tunRoutes,
|
||||||
Gateway: node.Get("gw"),
|
Gateway: node.Get("gw"),
|
||||||
}
|
}
|
||||||
ln, err = gost.TunListener(cfg)
|
ln, err = gost.TunListener(cfg)
|
||||||
@ -437,6 +552,20 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
QueueSize: node.GetInt("queue"),
|
QueueSize: node.GetInt("queue"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
case "dns":
|
||||||
|
ln, err = gost.DNSListener(
|
||||||
|
node.Addr,
|
||||||
|
&gost.DNSOptions{
|
||||||
|
Mode: node.Get("mode"),
|
||||||
|
TLSConfig: tlsCfg,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
case "redu", "redirectu":
|
||||||
|
ln, err = gost.UDPRedirectListener(node.Addr, &gost.UDPListenConfig{
|
||||||
|
TTL: ttl,
|
||||||
|
Backlog: node.GetInt("backlog"),
|
||||||
|
QueueSize: node.GetInt("queue"),
|
||||||
|
})
|
||||||
default:
|
default:
|
||||||
ln, err = gost.TCPListener(node.Addr)
|
ln, err = gost.TCPListener(node.Addr)
|
||||||
}
|
}
|
||||||
@ -454,8 +583,6 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
handler = gost.SOCKS4Handler()
|
handler = gost.SOCKS4Handler()
|
||||||
case "ss":
|
case "ss":
|
||||||
handler = gost.ShadowHandler()
|
handler = gost.ShadowHandler()
|
||||||
case "ss2":
|
|
||||||
handler = gost.Shadow2Handler()
|
|
||||||
case "http":
|
case "http":
|
||||||
handler = gost.HTTPHandler()
|
handler = gost.HTTPHandler()
|
||||||
case "tcp":
|
case "tcp":
|
||||||
@ -468,16 +595,22 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
handler = gost.UDPRemoteForwardHandler(node.Remote)
|
handler = gost.UDPRemoteForwardHandler(node.Remote)
|
||||||
case "forward":
|
case "forward":
|
||||||
handler = gost.SSHForwardHandler()
|
handler = gost.SSHForwardHandler()
|
||||||
case "redirect":
|
case "red", "redirect":
|
||||||
handler = gost.TCPRedirectHandler()
|
handler = gost.TCPRedirectHandler()
|
||||||
|
case "redu", "redirectu":
|
||||||
|
handler = gost.UDPRedirectHandler()
|
||||||
case "ssu":
|
case "ssu":
|
||||||
handler = gost.ShadowUDPdHandler()
|
handler = gost.ShadowUDPHandler()
|
||||||
case "sni":
|
case "sni":
|
||||||
handler = gost.SNIHandler()
|
handler = gost.SNIHandler()
|
||||||
case "tun":
|
case "tun":
|
||||||
handler = gost.TunHandler()
|
handler = gost.TunHandler()
|
||||||
case "tap":
|
case "tap":
|
||||||
handler = gost.TapHandler()
|
handler = gost.TapHandler()
|
||||||
|
case "dns":
|
||||||
|
handler = gost.DNSHandler(node.Remote)
|
||||||
|
case "relay":
|
||||||
|
handler = gost.RelayHandler(node.Remote)
|
||||||
default:
|
default:
|
||||||
// start from 2.5, if remote is not empty, then we assume that it is a forward tunnel.
|
// start from 2.5, if remote is not empty, then we assume that it is a forward tunnel.
|
||||||
if node.Remote != "" {
|
if node.Remote != "" {
|
||||||
@ -500,10 +633,20 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.Bypass = parseBypass(node.Get("bypass"))
|
node.Bypass = parseBypass(node.Get("bypass"))
|
||||||
resolver := parseResolver(node.Get("dns"))
|
|
||||||
hosts := parseHosts(node.Get("hosts"))
|
hosts := parseHosts(node.Get("hosts"))
|
||||||
ips := parseIP(node.Get("ip"), "")
|
ips := parseIP(node.Get("ip"), "")
|
||||||
|
|
||||||
|
resolver := parseResolver(node.Get("dns"))
|
||||||
|
if resolver != nil {
|
||||||
|
resolver.Init(
|
||||||
|
gost.ChainResolverOption(chain),
|
||||||
|
gost.TimeoutResolverOption(timeout),
|
||||||
|
gost.TTLResolverOption(ttl),
|
||||||
|
gost.PreferResolverOption(node.Get("prefer")),
|
||||||
|
gost.SrcIPResolverOption(net.ParseIP(node.Get("ip"))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
handler.Init(
|
handler.Init(
|
||||||
gost.AddrHandlerOption(ln.Addr().String()),
|
gost.AddrHandlerOption(ln.Addr().String()),
|
||||||
gost.ChainHandlerOption(chain),
|
gost.ChainHandlerOption(chain),
|
||||||
@ -519,12 +662,15 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
gost.ResolverHandlerOption(resolver),
|
gost.ResolverHandlerOption(resolver),
|
||||||
gost.HostsHandlerOption(hosts),
|
gost.HostsHandlerOption(hosts),
|
||||||
gost.RetryHandlerOption(node.GetInt("retry")), // override the global retry option.
|
gost.RetryHandlerOption(node.GetInt("retry")), // override the global retry option.
|
||||||
gost.TimeoutHandlerOption(time.Duration(node.GetInt("timeout"))*time.Second),
|
gost.TimeoutHandlerOption(timeout),
|
||||||
gost.ProbeResistHandlerOption(node.Get("probe_resist")),
|
gost.ProbeResistHandlerOption(node.Get("probe_resist")),
|
||||||
gost.KnockingHandlerOption(node.Get("knock")),
|
gost.KnockingHandlerOption(node.Get("knock")),
|
||||||
gost.NodeHandlerOption(node),
|
gost.NodeHandlerOption(node),
|
||||||
gost.IPsHandlerOption(ips),
|
gost.IPsHandlerOption(ips),
|
||||||
gost.TCPModeHandlerOption(node.GetBool("tcp")),
|
gost.TCPModeHandlerOption(node.GetBool("tcp")),
|
||||||
|
gost.IPRoutesHandlerOption(tunRoutes...),
|
||||||
|
gost.ProxyAgentHandlerOption(node.Get("proxyAgent")),
|
||||||
|
gost.HTTPTunnelHandlerOption(node.GetBool("httpTunnel")),
|
||||||
)
|
)
|
||||||
|
|
||||||
rt := router{
|
rt := router{
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -36,7 +35,7 @@ func init() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
httpTestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
httpTestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
data, _ := ioutil.ReadAll(r.Body)
|
data, _ := io.ReadAll(r.Body)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
data = []byte("Hello World!")
|
data = []byte("Hello World!")
|
||||||
}
|
}
|
||||||
@ -87,7 +86,7 @@ func httpRoundtrip(conn net.Conn, targetURL string, data []byte) (err error) {
|
|||||||
return errors.New(resp.Status)
|
return errors.New(resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
recv, err := ioutil.ReadAll(resp.Body)
|
recv, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
421
dns.go
Normal file
421
dns.go
Normal 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
|
||||||
|
}
|
@ -6,7 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
417
forward.go
417
forward.go
@ -1,18 +1,18 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
smux "gopkg.in/xtaci/smux.v1"
|
smux "github.com/xtaci/smux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type forwardConnector struct {
|
type forwardConnector struct {
|
||||||
@ -23,7 +23,11 @@ func ForwardConnector() Connector {
|
|||||||
return &forwardConnector{}
|
return &forwardConnector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *forwardConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *forwardConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *forwardConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,13 +74,6 @@ func (h *baseForwardHandler) Init(options ...HandlerOption) {
|
|||||||
|
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
if len(h.group.Nodes()) == 0 {
|
|
||||||
h.group.AddNode(Node{ // dummy address
|
|
||||||
ID: n,
|
|
||||||
Addr: ":0",
|
|
||||||
Host: ":0",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpDirectForwardHandler struct {
|
type tcpDirectForwardHandler struct {
|
||||||
@ -109,6 +106,8 @@ func (h *tcpDirectForwardHandler) Init(options ...HandlerOption) {
|
|||||||
func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
|
func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
|
|
||||||
retries := 1
|
retries := 1
|
||||||
if h.options.Chain != nil && h.options.Chain.Retries > 0 {
|
if h.options.Chain != nil && h.options.Chain.Retries > 0 {
|
||||||
retries = h.options.Chain.Retries
|
retries = h.options.Chain.Retries
|
||||||
@ -121,19 +120,21 @@ func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
|
|||||||
var node Node
|
var node Node
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < retries; i++ {
|
for i := 0; i < retries; i++ {
|
||||||
|
if len(h.group.Nodes()) > 0 {
|
||||||
node, err = h.group.Next()
|
node, err = h.group.Next()
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Logf("[tcp] %s - %s", conn.RemoteAddr(), node.Addr)
|
|
||||||
cc, err = h.options.Chain.Dial(node.Addr,
|
cc, err = h.options.Chain.Dial(node.Addr,
|
||||||
RetryChainOption(h.options.Retries),
|
RetryChainOption(h.options.Retries),
|
||||||
TimeoutChainOption(h.options.Timeout),
|
TimeoutChainOption(h.options.Timeout),
|
||||||
|
ResolverChainOption(h.options.Resolver),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[tcp] %s -> %s : %s", conn.RemoteAddr(), node.Addr, err)
|
log.Logf("[tcp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
node.MarkDead()
|
node.MarkDead()
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@ -146,9 +147,13 @@ func (h *tcpDirectForwardHandler) Handle(conn net.Conn) {
|
|||||||
node.ResetDead()
|
node.ResetDead()
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
log.Logf("[tcp] %s <-> %s", conn.RemoteAddr(), node.Addr)
|
addr := node.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = conn.LocalAddr().String()
|
||||||
|
}
|
||||||
|
log.Logf("[tcp] %s <-> %s", conn.RemoteAddr(), addr)
|
||||||
transport(conn, cc)
|
transport(conn, cc)
|
||||||
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), node.Addr)
|
log.Logf("[tcp] %s >-< %s", conn.RemoteAddr(), addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type udpDirectForwardHandler struct {
|
type udpDirectForwardHandler struct {
|
||||||
@ -181,44 +186,39 @@ func (h *udpDirectForwardHandler) Init(options ...HandlerOption) {
|
|||||||
func (h *udpDirectForwardHandler) Handle(conn net.Conn) {
|
func (h *udpDirectForwardHandler) Handle(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
node, err := h.group.Next()
|
log.Logf("[udp] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
if err != nil {
|
|
||||||
log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp", node.Addr)
|
var node Node
|
||||||
if err != nil {
|
|
||||||
node.MarkDead()
|
|
||||||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var cc net.Conn
|
|
||||||
if h.options.Chain.IsEmpty() {
|
|
||||||
cc, err = net.DialUDP("udp", nil, raddr)
|
|
||||||
if err != nil {
|
|
||||||
node.MarkDead()
|
|
||||||
log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var err error
|
var err error
|
||||||
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
if len(h.group.Nodes()) > 0 {
|
||||||
|
node, err = h.group.Next()
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cc = &udpTunnelConn{Conn: cc, raddr: raddr}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
defer cc.Close()
|
||||||
node.ResetDead()
|
node.ResetDead()
|
||||||
|
|
||||||
log.Logf("[udp] %s <-> %s", conn.RemoteAddr(), node.Addr)
|
addr := node.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = conn.LocalAddr().String()
|
||||||
|
}
|
||||||
|
log.Logf("[udp] %s <-> %s", conn.RemoteAddr(), addr)
|
||||||
transport(conn, cc)
|
transport(conn, cc)
|
||||||
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), node.Addr)
|
log.Logf("[udp] %s >-< %s", conn.RemoteAddr(), addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpRemoteForwardHandler struct {
|
type tcpRemoteForwardHandler struct {
|
||||||
@ -260,11 +260,13 @@ func (h *tcpRemoteForwardHandler) Handle(conn net.Conn) {
|
|||||||
var node Node
|
var node Node
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < retries; i++ {
|
for i := 0; i < retries; i++ {
|
||||||
|
if len(h.group.Nodes()) > 0 {
|
||||||
node, err = h.group.Next()
|
node, err = h.group.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[rtcp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
|
log.Logf("[rtcp] %s - %s : %s", conn.LocalAddr(), h.raddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cc, err = net.DialTimeout("tcp", node.Addr, h.options.Timeout)
|
cc, err = net.DialTimeout("tcp", node.Addr, h.options.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[rtcp] %s -> %s : %s", conn.LocalAddr(), node.Addr, err)
|
log.Logf("[rtcp] %s -> %s : %s", conn.LocalAddr(), node.Addr, err)
|
||||||
@ -315,11 +317,15 @@ func (h *udpRemoteForwardHandler) Init(options ...HandlerOption) {
|
|||||||
func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
|
func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
node, err := h.group.Next()
|
var node Node
|
||||||
|
var err error
|
||||||
|
if len(h.group.Nodes()) > 0 {
|
||||||
|
node, err = h.group.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
|
log.Logf("[rudp] %s - %s : %s", conn.RemoteAddr(), h.raddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp", node.Addr)
|
raddr, err := net.ResolveUDPAddr("udp", node.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -341,271 +347,6 @@ func (h *udpRemoteForwardHandler) Handle(conn net.Conn) {
|
|||||||
log.Logf("[rudp] %s >-< %s", conn.RemoteAddr(), node.Addr)
|
log.Logf("[rudp] %s >-< %s", conn.RemoteAddr(), node.Addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type udpConnMap struct {
|
|
||||||
m sync.Map
|
|
||||||
size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *udpConnMap) Get(key interface{}) (conn *udpServerConn, ok bool) {
|
|
||||||
v, ok := m.m.Load(key)
|
|
||||||
if ok {
|
|
||||||
conn, ok = v.(*udpServerConn)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *udpConnMap) Set(key interface{}, conn *udpServerConn) {
|
|
||||||
m.m.Store(key, conn)
|
|
||||||
atomic.AddInt64(&m.size, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *udpConnMap) Delete(key interface{}) {
|
|
||||||
m.m.Delete(key)
|
|
||||||
atomic.AddInt64(&m.size, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *udpConnMap) Range(f func(key interface{}, value *udpServerConn) bool) {
|
|
||||||
m.m.Range(func(k, v interface{}) bool {
|
|
||||||
return f(k, v.(*udpServerConn))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *udpConnMap) Size() int64 {
|
|
||||||
return atomic.LoadInt64(&m.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UDPForwardListenConfig struct {
|
|
||||||
TTL time.Duration
|
|
||||||
Backlog int
|
|
||||||
QueueSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpDirectForwardListener struct {
|
|
||||||
ln net.PacketConn
|
|
||||||
connChan chan net.Conn
|
|
||||||
errChan chan error
|
|
||||||
connMap udpConnMap
|
|
||||||
config *UDPForwardListenConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDPDirectForwardListener creates a Listener for UDP port forwarding server.
|
|
||||||
func UDPDirectForwardListener(addr string, cfg *UDPForwardListenConfig) (Listener, error) {
|
|
||||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ln, err := net.ListenUDP("udp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg == nil {
|
|
||||||
cfg = &UDPForwardListenConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
backlog := cfg.Backlog
|
|
||||||
if backlog <= 0 {
|
|
||||||
backlog = defaultBacklog
|
|
||||||
}
|
|
||||||
|
|
||||||
l := &udpDirectForwardListener{
|
|
||||||
ln: ln,
|
|
||||||
connChan: make(chan net.Conn, backlog),
|
|
||||||
errChan: make(chan error, 1),
|
|
||||||
config: cfg,
|
|
||||||
}
|
|
||||||
go l.listenLoop()
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *udpDirectForwardListener) listenLoop() {
|
|
||||||
for {
|
|
||||||
b := make([]byte, mediumBufferSize)
|
|
||||||
n, raddr, err := l.ln.ReadFrom(b)
|
|
||||||
if err != nil {
|
|
||||||
log.Logf("[udp] peer -> %s : %s", l.Addr(), err)
|
|
||||||
l.Close()
|
|
||||||
l.errChan <- err
|
|
||||||
close(l.errChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, ok := l.connMap.Get(raddr.String())
|
|
||||||
if !ok {
|
|
||||||
conn = newUDPServerConn(l.ln, raddr, l.config.TTL, l.config.QueueSize)
|
|
||||||
conn.onClose = func() {
|
|
||||||
l.connMap.Delete(raddr.String())
|
|
||||||
log.Logf("[udp] %s closed (%d)", raddr, l.connMap.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case l.connChan <- conn:
|
|
||||||
l.connMap.Set(raddr.String(), conn)
|
|
||||||
log.Logf("[udp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size())
|
|
||||||
default:
|
|
||||||
conn.Close()
|
|
||||||
log.Logf("[udp] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case conn.rChan <- b[:n]:
|
|
||||||
if Debug {
|
|
||||||
log.Logf("[udp] %s >>> %s : length %d", raddr, l.Addr(), n)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Logf("[udp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *udpDirectForwardListener) Accept() (conn net.Conn, err error) {
|
|
||||||
var ok bool
|
|
||||||
select {
|
|
||||||
case conn = <-l.connChan:
|
|
||||||
case err, ok = <-l.errChan:
|
|
||||||
if !ok {
|
|
||||||
err = errors.New("accpet on closed listener")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *udpDirectForwardListener) Addr() net.Addr {
|
|
||||||
return l.ln.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *udpDirectForwardListener) Close() error {
|
|
||||||
err := l.ln.Close()
|
|
||||||
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
|
|
||||||
v.Close()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpServerConn struct {
|
|
||||||
conn net.PacketConn
|
|
||||||
raddr net.Addr
|
|
||||||
rChan chan []byte
|
|
||||||
closed chan struct{}
|
|
||||||
closeMutex sync.Mutex
|
|
||||||
ttl time.Duration
|
|
||||||
nopChan chan int
|
|
||||||
onClose func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUDPServerConn(conn net.PacketConn, raddr net.Addr, ttl time.Duration, qsize int) *udpServerConn {
|
|
||||||
if qsize <= 0 {
|
|
||||||
qsize = defaultQueueSize
|
|
||||||
}
|
|
||||||
c := &udpServerConn{
|
|
||||||
conn: conn,
|
|
||||||
raddr: raddr,
|
|
||||||
rChan: make(chan []byte, qsize),
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
nopChan: make(chan int),
|
|
||||||
ttl: ttl,
|
|
||||||
}
|
|
||||||
go c.ttlWait()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) Read(b []byte) (n int, err error) {
|
|
||||||
select {
|
|
||||||
case bb := <-c.rChan:
|
|
||||||
n = copy(b, bb)
|
|
||||||
case <-c.closed:
|
|
||||||
err = errors.New("read from closed connection")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case c.nopChan <- n:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) Write(b []byte) (n int, err error) {
|
|
||||||
n, err = c.conn.WriteTo(b, c.raddr)
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
if Debug {
|
|
||||||
log.Logf("[udp] %s <<< %s : length %d", c.raddr, c.LocalAddr(), n)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case c.nopChan <- n:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) Close() error {
|
|
||||||
c.closeMutex.Lock()
|
|
||||||
defer c.closeMutex.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-c.closed:
|
|
||||||
return errors.New("connection is closed")
|
|
||||||
default:
|
|
||||||
if c.onClose != nil {
|
|
||||||
c.onClose()
|
|
||||||
}
|
|
||||||
close(c.closed)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) ttlWait() {
|
|
||||||
ttl := c.ttl
|
|
||||||
if ttl == 0 {
|
|
||||||
ttl = defaultTTL
|
|
||||||
}
|
|
||||||
timer := time.NewTimer(ttl)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-c.nopChan:
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
timer.Reset(ttl)
|
|
||||||
case <-timer.C:
|
|
||||||
c.Close()
|
|
||||||
return
|
|
||||||
case <-c.closed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) LocalAddr() net.Addr {
|
|
||||||
return c.conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) RemoteAddr() net.Addr {
|
|
||||||
return c.raddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) SetDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpServerConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return c.conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpRemoteForwardListener struct {
|
type tcpRemoteForwardListener struct {
|
||||||
addr net.Addr
|
addr net.Addr
|
||||||
chain *Chain
|
chain *Chain
|
||||||
@ -615,7 +356,6 @@ type tcpRemoteForwardListener struct {
|
|||||||
sessionMux sync.Mutex
|
sessionMux sync.Mutex
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
closeMux sync.Mutex
|
closeMux sync.Mutex
|
||||||
errChan chan error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server.
|
// TCPRemoteForwardListener creates a Listener for TCP remote port forwarding server.
|
||||||
@ -630,7 +370,6 @@ func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
|
|||||||
chain: chain,
|
chain: chain,
|
||||||
connChan: make(chan net.Conn, 1024),
|
connChan: make(chan net.Conn, 1024),
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
errChan: make(chan error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ln.isChainValid() {
|
if !ln.isChainValid() {
|
||||||
@ -644,9 +383,13 @@ func TCPRemoteForwardListener(addr string, chain *Chain) (Listener, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *tcpRemoteForwardListener) isChainValid() bool {
|
func (l *tcpRemoteForwardListener) isChainValid() bool {
|
||||||
|
if l.chain.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
lastNode := l.chain.LastNode()
|
lastNode := l.chain.LastNode()
|
||||||
if (lastNode.Protocol == "forward" && lastNode.Transport == "ssh") ||
|
if (lastNode.Protocol == "forward" && lastNode.Transport == "ssh") ||
|
||||||
lastNode.Protocol == "socks5" {
|
lastNode.Protocol == "socks5" || lastNode.Protocol == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -712,7 +455,7 @@ func (l *tcpRemoteForwardListener) accept() (conn net.Conn, err error) {
|
|||||||
return l.chain.Dial(l.addr.String())
|
return l.chain.Dial(l.addr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastNode.Protocol == "socks5" {
|
if l.isChainValid() {
|
||||||
if lastNode.GetBool("mbind") {
|
if lastNode.GetBool("mbind") {
|
||||||
return l.muxAccept() // multiplexing support for binding.
|
return l.muxAccept() // multiplexing support for binding.
|
||||||
}
|
}
|
||||||
@ -866,26 +609,26 @@ func (l *tcpRemoteForwardListener) Close() error {
|
|||||||
type udpRemoteForwardListener struct {
|
type udpRemoteForwardListener struct {
|
||||||
addr net.Addr
|
addr net.Addr
|
||||||
chain *Chain
|
chain *Chain
|
||||||
connMap udpConnMap
|
connMap *udpConnMap
|
||||||
connChan chan net.Conn
|
connChan chan net.Conn
|
||||||
ln *net.UDPConn
|
ln *net.UDPConn
|
||||||
errChan chan error
|
|
||||||
ttl time.Duration
|
ttl time.Duration
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
closeMux sync.Mutex
|
ready chan struct{}
|
||||||
once sync.Once
|
once sync.Once
|
||||||
config *UDPForwardListenConfig
|
closeMux sync.Mutex
|
||||||
|
config *UDPListenConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDPRemoteForwardListener creates a Listener for UDP remote port forwarding server.
|
// UDPRemoteForwardListener creates a Listener for UDP remote port forwarding server.
|
||||||
func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPForwardListenConfig) (Listener, error) {
|
func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPListenConfig) (Listener, error) {
|
||||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
laddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &UDPForwardListenConfig{}
|
cfg = &UDPListenConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
backlog := cfg.Backlog
|
backlog := cfg.Backlog
|
||||||
@ -896,22 +639,27 @@ func UDPRemoteForwardListener(addr string, chain *Chain, cfg *UDPForwardListenCo
|
|||||||
ln := &udpRemoteForwardListener{
|
ln := &udpRemoteForwardListener{
|
||||||
addr: laddr,
|
addr: laddr,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
|
connMap: new(udpConnMap),
|
||||||
connChan: make(chan net.Conn, backlog),
|
connChan: make(chan net.Conn, backlog),
|
||||||
errChan: make(chan error, 1),
|
ready: make(chan struct{}),
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
go ln.listenLoop()
|
go ln.listenLoop()
|
||||||
|
|
||||||
err = <-ln.errChan
|
<-ln.ready
|
||||||
|
|
||||||
return ln, err
|
return ln, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *udpRemoteForwardListener) isChainValid() bool {
|
func (l *udpRemoteForwardListener) isChainValid() bool {
|
||||||
|
if l.chain.IsEmpty() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
lastNode := l.chain.LastNode()
|
lastNode := l.chain.LastNode()
|
||||||
return lastNode.Protocol == "socks5"
|
return lastNode.Protocol == "socks5" || lastNode.Protocol == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *udpRemoteForwardListener) listenLoop() {
|
func (l *udpRemoteForwardListener) listenLoop() {
|
||||||
@ -922,6 +670,10 @@ func (l *udpRemoteForwardListener) listenLoop() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.once.Do(func() {
|
||||||
|
close(l.ready)
|
||||||
|
})
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@ -935,11 +687,14 @@ func (l *udpRemoteForwardListener) listenLoop() {
|
|||||||
|
|
||||||
uc, ok := l.connMap.Get(raddr.String())
|
uc, ok := l.connMap.Get(raddr.String())
|
||||||
if !ok {
|
if !ok {
|
||||||
uc = newUDPServerConn(conn, raddr, l.config.TTL, l.config.QueueSize)
|
uc = newUDPServerConn(conn, raddr, &udpServerConnConfig{
|
||||||
uc.onClose = func() {
|
ttl: l.config.TTL,
|
||||||
|
qsize: l.config.QueueSize,
|
||||||
|
onClose: func() {
|
||||||
l.connMap.Delete(raddr.String())
|
l.connMap.Delete(raddr.String())
|
||||||
log.Logf("[rudp] %s closed (%d)", raddr, l.connMap.Size())
|
log.Logf("[rudp] %s closed (%d)", raddr, l.connMap.Size())
|
||||||
}
|
},
|
||||||
|
})
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case l.connChan <- uc:
|
case l.connChan <- uc:
|
||||||
@ -975,14 +730,13 @@ func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
lastNode := l.chain.LastNode()
|
if l.isChainValid() {
|
||||||
if lastNode.Protocol == "socks5" {
|
|
||||||
var cc net.Conn
|
var cc net.Conn
|
||||||
cc, err = getSOCKS5UDPTunnel(l.chain, l.addr)
|
cc, err = getSocks5UDPTunnel(l.chain, l.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[rudp] %s : %s", l.Addr(), err)
|
log.Logf("[rudp] %s : %s", l.Addr(), err)
|
||||||
} else {
|
} else {
|
||||||
conn = &udpTunnelConn{Conn: cc}
|
conn = cc.(net.PacketConn)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var uc *net.UDPConn
|
var uc *net.UDPConn
|
||||||
@ -993,11 +747,6 @@ func (l *udpRemoteForwardListener) connect() (conn net.PacketConn, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l.once.Do(func() {
|
|
||||||
l.errChan <- err
|
|
||||||
close(l.errChan)
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if tempDelay == 0 {
|
if tempDelay == 0 {
|
||||||
tempDelay = 1000 * time.Millisecond
|
tempDelay = 1000 * time.Millisecond
|
||||||
|
@ -128,7 +128,7 @@ func BenchmarkTCPDirectForwardParallel(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func udpDirectForwardRoundtrip(t *testing.T, host string, data []byte) error {
|
func udpDirectForwardRoundtrip(t *testing.T, host string, data []byte) error {
|
||||||
ln, err := UDPDirectForwardListener("localhost:0", nil)
|
ln, err := UDPListener("localhost:0", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ func BenchmarkUDPDirectForward(b *testing.B) {
|
|||||||
sendData := make([]byte, 128)
|
sendData := make([]byte, 128)
|
||||||
rand.Read(sendData)
|
rand.Read(sendData)
|
||||||
|
|
||||||
ln, err := UDPDirectForwardListener("localhost:0", nil)
|
ln, err := UDPListener("localhost:0", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ func BenchmarkUDPDirectForwardParallel(b *testing.B) {
|
|||||||
sendData := make([]byte, 128)
|
sendData := make([]byte, 128)
|
||||||
rand.Read(sendData)
|
rand.Read(sendData)
|
||||||
|
|
||||||
ln, err := UDPDirectForwardListener("localhost:0", nil)
|
ln, err := UDPListener("localhost:0", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
|
10
ftcp.go
10
ftcp.go
@ -45,6 +45,7 @@ func (tr *fakeTCPTransporter) Multiplex() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FakeTCPListenConfig is config for fake TCP Listener.
|
||||||
type FakeTCPListenConfig struct {
|
type FakeTCPListenConfig struct {
|
||||||
TTL time.Duration
|
TTL time.Duration
|
||||||
Backlog int
|
Backlog int
|
||||||
@ -99,11 +100,14 @@ func (l *fakeTCPListener) listenLoop() {
|
|||||||
|
|
||||||
conn, ok := l.connMap.Get(raddr.String())
|
conn, ok := l.connMap.Get(raddr.String())
|
||||||
if !ok {
|
if !ok {
|
||||||
conn = newUDPServerConn(l.ln, raddr, l.config.TTL, l.config.QueueSize)
|
conn = newUDPServerConn(l.ln, raddr, &udpServerConnConfig{
|
||||||
conn.onClose = func() {
|
ttl: l.config.TTL,
|
||||||
|
qsize: l.config.QueueSize,
|
||||||
|
onClose: func() {
|
||||||
l.connMap.Delete(raddr.String())
|
l.connMap.Delete(raddr.String())
|
||||||
log.Logf("[ftcp] %s closed (%d)", raddr, l.connMap.Size())
|
log.Logf("[ftcp] %s closed (%d)", raddr, l.connMap.Size())
|
||||||
}
|
},
|
||||||
|
})
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case l.connChan <- conn:
|
case l.connChan <- conn:
|
||||||
|
95
go.mod
95
go.mod
@ -1,49 +1,60 @@
|
|||||||
module github.com/ginuerzh/gost
|
module github.com/ginuerzh/gost
|
||||||
|
|
||||||
go 1.13
|
go 1.22
|
||||||
|
|
||||||
|
replace github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e
|
git.torproject.org/pluggable-transports/goptlib.git v1.3.0
|
||||||
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e
|
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
|
||||||
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
github.com/go-gost/gosocks4 v0.0.1
|
||||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect
|
github.com/go-gost/gosocks5 v0.3.0
|
||||||
github.com/cheekybits/genny v1.0.0 // indirect
|
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
|
||||||
github.com/coreos/go-iptables v0.4.5 // indirect
|
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451
|
||||||
github.com/dchest/siphash v1.2.1 // indirect
|
github.com/go-log/log v0.2.0
|
||||||
github.com/docker/libcontainer v2.2.1+incompatible
|
|
||||||
github.com/ginuerzh/gosocks4 v0.0.1
|
|
||||||
github.com/ginuerzh/gosocks5 v0.2.0
|
|
||||||
github.com/ginuerzh/tls-dissector v0.0.1
|
|
||||||
github.com/go-log/log v0.1.0
|
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/golang/mock v1.2.0 // indirect
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/google/gopacket v1.1.17 // indirect
|
github.com/klauspost/compress v1.17.6
|
||||||
github.com/gorilla/websocket v1.4.0 // indirect
|
github.com/mdlayher/vsock v1.2.1
|
||||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
github.com/miekg/dns v1.1.58
|
||||||
github.com/klauspost/compress v1.4.1
|
github.com/quic-go/quic-go v0.45.0
|
||||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
github.com/ryanuber/go-glob v1.0.0
|
||||||
github.com/klauspost/reedsolomon v1.7.0 // indirect
|
github.com/shadowsocks/go-shadowsocks2 v0.1.5
|
||||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect
|
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
|
||||||
github.com/lucas-clemente/quic-go v0.10.0
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect
|
github.com/xtaci/kcp-go/v5 v5.6.7
|
||||||
github.com/miekg/dns v1.1.3
|
github.com/xtaci/smux v1.5.24
|
||||||
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8
|
|
||||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
|
||||||
github.com/onsi/gomega v1.4.3 // indirect
|
|
||||||
github.com/pkg/errors v0.8.1 // indirect
|
|
||||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735
|
|
||||||
github.com/shadowsocks/go-shadowsocks2 v0.0.11
|
|
||||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba
|
|
||||||
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b
|
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
|
||||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
|
|
||||||
github.com/tjfoc/gmsm v1.0.1 // indirect
|
|
||||||
github.com/xtaci/tcpraw v1.2.25
|
github.com/xtaci/tcpraw v1.2.25
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
golang.org/x/crypto v0.24.0
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
golang.org/x/net v0.26.0
|
||||||
gopkg.in/gorilla/websocket.v1 v1.4.0
|
)
|
||||||
gopkg.in/xtaci/kcp-go.v4 v4.3.2
|
|
||||||
gopkg.in/xtaci/smux.v1 v1.0.7
|
require (
|
||||||
|
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 // indirect
|
||||||
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||||
|
github.com/coreos/go-iptables v0.6.0 // indirect
|
||||||
|
github.com/dchest/siphash v1.2.2 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
|
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||||
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
|
github.com/templexxx/cpu v0.1.0 // indirect
|
||||||
|
github.com/templexxx/xorsimd v0.4.2 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect
|
||||||
|
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb // indirect
|
||||||
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
|
||||||
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
)
|
)
|
||||||
|
299
go.sum
299
go.sum
@ -1,121 +1,204 @@
|
|||||||
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e h1:PYcONLFUhr00kGrq7Mf14JRtoXHG7BOSKIfIha0Hu5Q=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
|
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 h1:iJoUgXvhagsNMrJrvavw7vu1eG8+hm6jLOxlLFcoODw=
|
||||||
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e h1:c8h60PKrRxEB5debIHBmP7T+s/EUNXTklXqlmJfYiJQ=
|
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||||
git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e/go.mod h1:jRZbfRcLIgFQoCw6tRmsnETVyIj54jOmXhHCYYa0jbs=
|
git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
|
||||||
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0=
|
git.torproject.org/pluggable-transports/goptlib.git v1.3.0 h1:G+iuRUblCCC2xnO+0ag1/4+aaM98D5mjWP1M0v9s8a0=
|
||||||
github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE=
|
git.torproject.org/pluggable-transports/goptlib.git v1.3.0/go.mod h1:4PBMl1dg7/3vMWSoWb46eGWlrxkUyn/CAJmxhDLAlDs=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4=
|
||||||
|
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 h1:x17NvoJaphEzay72TFej4OSSsgu3xRYBLkbIwdofS/4=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||||
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
|
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/dchest/siphash v1.2.0 h1:YWOShuhvg0GqbQpMa60QlCGtEyf7O7HC1Jf0VjdQ60M=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
|
||||||
github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
|
|
||||||
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
||||||
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
|
github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I=
|
||||||
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
|
github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/ginuerzh/gosocks4 v0.0.1 h1:ojDKUyz+uaEeRm2usY1cyQiXTqJqrKxfeE6SVBXq4m0=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/ginuerzh/gosocks4 v0.0.1/go.mod h1:8SdwBMKjfJ9+BfP2vDJM1jcrgWUbWV6qxBPHHVrwptY=
|
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
|
||||||
github.com/ginuerzh/gosocks5 v0.2.0 h1:K0Ua23U9LU3BZrf3XpGDcs0mP8DiEpa6PJE4TA/MU3s=
|
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
|
||||||
github.com/ginuerzh/gosocks5 v0.2.0/go.mod h1:qp22mr6tH/prEoaN0pFukq76LlScIE+F2rP2ZP5ZHno=
|
github.com/go-gost/gosocks5 v0.3.0 h1:Hkmp9YDRBSCJd7xywW6dBPT6B9aQTkuWd+3WCheJiJA=
|
||||||
github.com/ginuerzh/tls-dissector v0.0.1 h1:yF6fIt78TO4CdjiLLn6R8r0XajQJE1Lbnuq6rP8mGW8=
|
github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
||||||
github.com/ginuerzh/tls-dissector v0.0.1/go.mod h1:u/kbBOqIOgJv39gywuUb3VwyzdZG5DKquOqfToKE6lk=
|
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 h1:itaaJhQJ19kUXEB4Igb0EbY8m+1Py2AaNNSBds/9gk4=
|
||||||
github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U=
|
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
|
||||||
github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M=
|
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I=
|
||||||
|
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs=
|
||||||
|
github.com/go-log/log v0.2.0 h1:z8i91GBudxD5L3RmF0KVpetCbcGWAV7q1Tw1eRwQM9Q=
|
||||||
|
github.com/go-log/log v0.2.0/go.mod h1:xzCnwajcues/6w7lne3yK2QU7DBPW7kqbgPGG5AF65U=
|
||||||
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/klauspost/reedsolomon v1.7.0 h1:pLFmRKGko2ZieiTGyo9DahLCIuljyxm+Zzhz/fYEonE=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/klauspost/reedsolomon v1.7.0/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f h1:sSeNEkJrs+0F9TUau0CgWTTNEwF23HST3Eq0A+QIx+A=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/lucas-clemente/quic-go v0.10.0 h1:xEF+pSHYAOcu+U10Meunf+DTtc8vhQDRqlA0BJ6hufc=
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
||||||
github.com/lucas-clemente/quic-go v0.10.0/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY=
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
|
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||||
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8 h1:4WFQEfEJ7zaHYViIVM2Cd6tnQOOhiEHbmQtlcV7aOpc=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/milosgajdos83/tenus v0.0.0-20190415114537-1f3ed00ae7d8/go.mod h1:G95Wwn625/q6JCCytI4VR/a5VtPwrtI0B+Q1Gi38QLA=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
|
||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
|
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||||
github.com/shadowsocks/go-shadowsocks2 v0.0.11 h1:dXloqEhYnZV40jblWTK8kWeC0Eb+dgql4S0tj99e8j0=
|
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||||
github.com/shadowsocks/go-shadowsocks2 v0.0.11/go.mod h1:R+KWaoIwRRhnpw6XV+dZil0XHi64Hc1D7hXUyXTjUzQ=
|
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba h1:tJgNXb3S+RkB4kNPi6N5OmEWe3m+Y3Qs6LUMiNDAONM=
|
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20170121203516-97a5c71f80ba/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
|
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
|
||||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||||
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
|
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
|
||||||
|
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
|
||||||
|
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ=
|
||||||
|
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
|
||||||
|
github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
|
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
|
||||||
|
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.7 h1:7+rnxNFIsjEwTXQk4cSZpXM4pO0hqtpwE1UFFoJBffA=
|
||||||
|
github.com/xtaci/kcp-go/v5 v5.6.7/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||||
|
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
|
||||||
|
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||||
github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk=
|
github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk=
|
||||||
github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
|
github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
|
||||||
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb h1:qRSZHsODmAP5qDvb3YsO7Qnf3TRiVbGxNG/WYnlM4/o=
|
||||||
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8=
|
gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb/go.mod h1:gvdJuZuO/tPZyhEV8K3Hmoxv/DWud5L4qEQxfYjEUTo=
|
||||||
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d h1:tJ8F7ABaQ3p3wjxwXiWSktVDgjZEXkvaRawd2rIq5ws=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d/go.mod h1:9GcM8QNU9/wXtEEH2q8bVOnPI7FtIF6VVLzZ1l6Hgf8=
|
||||||
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
|
||||||
|
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
|
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||||
|
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
gopkg.in/gorilla/websocket.v1 v1.4.0 h1:lREme3ezAGPCpxSHwjGkHhAJX+ed2B6vzAJ+kaqBEIM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
gopkg.in/gorilla/websocket.v1 v1.4.0/go.mod h1:Ons1i8d00TjvJPdla7bJyeXFsdOacUyrTYbg9IetsIE=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
gopkg.in/xtaci/kcp-go.v4 v4.3.2 h1:S9IF+L55Ugzl/hVA6wvuL3SuAtTUzH2cBBC88MXQxnE=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
gopkg.in/xtaci/kcp-go.v4 v4.3.2/go.mod h1:fFYTlSOHNOHDNTKfoqarZMQsu7g7oXKwJ9wq0i9lODc=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
gopkg.in/xtaci/smux.v1 v1.0.7 h1:qootIZs4ZPSx5blhvgaFpx2epdFSWkyw99xT+q0mRXI=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
gopkg.in/xtaci/smux.v1 v1.0.7/go.mod h1:NbrPjLp8lNAYN8KqTplnFr2JjIBbr7CdHBkHtHsXtWA=
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
11
gost.go
11
gost.go
@ -20,7 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Version is the gost version.
|
// Version is the gost version.
|
||||||
const Version = "2.9.1"
|
const Version = "2.12.0"
|
||||||
|
|
||||||
// Debug is a flag that enables the debug log.
|
// Debug is a flag that enables the debug log.
|
||||||
var Debug bool
|
var Debug bool
|
||||||
@ -80,7 +80,10 @@ var (
|
|||||||
// DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket.
|
// DefaultUserAgent is the default HTTP User-Agent header used by HTTP and websocket.
|
||||||
DefaultUserAgent = "Chrome/78.0.3904.106"
|
DefaultUserAgent = "Chrome/78.0.3904.106"
|
||||||
|
|
||||||
DefaultMTU = 1350 // default mtu for tun/tap device
|
DefaultProxyAgent = "gost/" + Version
|
||||||
|
|
||||||
|
// DefaultMTU is the default mtu for tun/tap device
|
||||||
|
DefaultMTU = 1350
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetLogger sets a new logger for internal log system.
|
// SetLogger sets a new logger for internal log system.
|
||||||
@ -146,9 +149,7 @@ func (rw *readWriter) Write(p []byte) (n int, err error) {
|
|||||||
return rw.w.Write(p)
|
return rw.w.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var nopClientConn = &nopConn{}
|
||||||
nopClientConn = &nopConn{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// a nop connection implements net.Conn,
|
// a nop connection implements net.Conn,
|
||||||
// it does nothing.
|
// it does nothing.
|
||||||
|
28
handler.go
28
handler.go
@ -7,8 +7,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks4"
|
"github.com/go-gost/gosocks4"
|
||||||
"github.com/ginuerzh/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,6 +41,9 @@ type HandlerOptions struct {
|
|||||||
Host string
|
Host string
|
||||||
IPs []string
|
IPs []string
|
||||||
TCPMode bool
|
TCPMode bool
|
||||||
|
IPRoutes []IPRoute
|
||||||
|
ProxyAgent string
|
||||||
|
HTTPTunnel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerOption allows a common way to set handler options.
|
// HandlerOption allows a common way to set handler options.
|
||||||
@ -203,6 +206,27 @@ func TCPModeHandlerOption(b bool) HandlerOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPRoutesHandlerOption sets the IP routes for tun tunnel.
|
||||||
|
func IPRoutesHandlerOption(routes ...IPRoute) HandlerOption {
|
||||||
|
return func(opts *HandlerOptions) {
|
||||||
|
opts.IPRoutes = routes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyAgentHandlerOption sets the proxy agent for http handler.
|
||||||
|
func ProxyAgentHandlerOption(agent string) HandlerOption {
|
||||||
|
return func(opts *HandlerOptions) {
|
||||||
|
opts.ProxyAgent = agent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPTunnelHandlerOption sets the Tunnel mode for HTTP client used in HTTP handler.
|
||||||
|
func HTTPTunnelHandlerOption(tunnelMode bool) HandlerOption {
|
||||||
|
return func(opts *HandlerOptions) {
|
||||||
|
opts.HTTPTunnel = tunnelMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type autoHandler struct {
|
type autoHandler struct {
|
||||||
options *HandlerOptions
|
options *HandlerOptions
|
||||||
}
|
}
|
||||||
|
102
http.go
102
http.go
@ -3,8 +3,10 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
@ -27,7 +29,16 @@ func HTTPConnector(user *url.Userinfo) Connector {
|
|||||||
return &httpConnector{User: user}
|
return &httpConnector{User: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *httpConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -47,8 +58,8 @@ func (c *httpConnector) Connect(conn net.Conn, addr string, options ...ConnectOp
|
|||||||
|
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
Method: http.MethodConnect,
|
Method: http.MethodConnect,
|
||||||
URL: &url.URL{Host: addr},
|
URL: &url.URL{Host: address},
|
||||||
Host: addr,
|
Host: address,
|
||||||
ProtoMajor: 1,
|
ProtoMajor: 1,
|
||||||
ProtoMinor: 1,
|
ProtoMinor: 1,
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
@ -163,7 +174,12 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
|
|||||||
ProtoMinor: 1,
|
ProtoMinor: 1,
|
||||||
Header: http.Header{},
|
Header: http.Header{},
|
||||||
}
|
}
|
||||||
resp.Header.Add("Proxy-Agent", "gost/"+Version)
|
|
||||||
|
proxyAgent := DefaultProxyAgent
|
||||||
|
if h.options.ProxyAgent != "" {
|
||||||
|
proxyAgent = h.options.ProxyAgent
|
||||||
|
}
|
||||||
|
resp.Header.Add("Proxy-Agent", proxyAgent)
|
||||||
|
|
||||||
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
||||||
log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s",
|
log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s",
|
||||||
@ -242,7 +258,9 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
|
|||||||
|
|
||||||
// forward http request
|
// forward http request
|
||||||
lastNode := route.LastNode()
|
lastNode := route.LastNode()
|
||||||
if req.Method != http.MethodConnect && lastNode.Protocol == "http" {
|
if req.Method != http.MethodConnect &&
|
||||||
|
lastNode.Protocol == "http" &&
|
||||||
|
!h.options.HTTPTunnel {
|
||||||
err = h.forwardRequest(conn, req, route)
|
err = h.forwardRequest(conn, req, route)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
@ -275,27 +293,65 @@ func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
|
|||||||
}
|
}
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
if req.Method == http.MethodConnect {
|
if req.Method != http.MethodConnect {
|
||||||
|
h.handleProxy(conn, cc, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b := []byte("HTTP/1.1 200 Connection established\r\n" +
|
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 {
|
if Debug {
|
||||||
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(b))
|
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(b))
|
||||||
}
|
}
|
||||||
conn.Write(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)
|
log.Logf("[http] %s <-> %s", conn.RemoteAddr(), host)
|
||||||
transport(conn, cc)
|
transport(conn, cc)
|
||||||
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), host)
|
log.Logf("[http] %s >-< %s", conn.RemoteAddr(), host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpHandler) handleProxy(rw, cc io.ReadWriter, req *http.Request) (err error) {
|
||||||
|
req.Header.Del("Proxy-Connection")
|
||||||
|
|
||||||
|
if err = req.Write(cc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan error, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ch <- copyBuffer(rw, cc)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := func() error {
|
||||||
|
req, err := http.ReadRequest(bufio.NewReader(rw))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpRequest(req, false)
|
||||||
|
log.Log(string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Del("Proxy-Connection")
|
||||||
|
|
||||||
|
if err = req.Write(cc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
ch <- err
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) {
|
func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) {
|
||||||
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
||||||
if Debug && (u != "" || p != "") {
|
if Debug && (u != "" || p != "") {
|
||||||
@ -353,10 +409,16 @@ func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.
|
|||||||
conn.RemoteAddr(), conn.LocalAddr())
|
conn.RemoteAddr(), conn.LocalAddr())
|
||||||
resp.StatusCode = http.StatusProxyAuthRequired
|
resp.StatusCode = http.StatusProxyAuthRequired
|
||||||
resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"")
|
resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"")
|
||||||
|
if strings.ToLower(req.Header.Get("Proxy-Connection")) == "keep-alive" {
|
||||||
|
// XXX libcurl will keep sending auth request in same conn
|
||||||
|
// which we don't supported yet.
|
||||||
|
resp.Header.Add("Connection", "close")
|
||||||
|
resp.Header.Add("Proxy-Connection", "close")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resp.Header = http.Header{}
|
resp.Header = http.Header{}
|
||||||
resp.Header.Set("Server", "nginx/1.14.1")
|
resp.Header.Set("Server", "nginx/1.14.1")
|
||||||
resp.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
resp.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode == http.StatusOK {
|
||||||
resp.Header.Set("Connection", "keep-alive")
|
resp.Header.Set("Connection", "keep-alive")
|
||||||
}
|
}
|
||||||
@ -381,11 +443,9 @@ func (h *httpHandler) forwardRequest(conn net.Conn, req *http.Request, route *Ch
|
|||||||
var userpass string
|
var userpass string
|
||||||
|
|
||||||
if user := route.LastNode().User; user != nil {
|
if user := route.LastNode().User; user != nil {
|
||||||
s := user.String()
|
u := user.Username()
|
||||||
if _, set := user.Password(); !set {
|
p, _ := user.Password()
|
||||||
s += ":"
|
userpass = base64.StdEncoding.EncodeToString([]byte(u + ":" + p))
|
||||||
}
|
|
||||||
userpass = base64.StdEncoding.EncodeToString([]byte(s))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, err := route.Conn()
|
cc, err := route.Conn()
|
||||||
|
33
http2.go
33
http2.go
@ -3,12 +3,12 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
@ -33,7 +33,16 @@ func HTTP2Connector(user *url.Userinfo) Connector {
|
|||||||
return &http2Connector{User: user}
|
return &http2Connector{User: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *http2Connector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *http2Connector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -57,7 +66,7 @@ func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectO
|
|||||||
ProtoMajor: 2,
|
ProtoMajor: 2,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Body: pr,
|
Body: pr,
|
||||||
Host: addr,
|
Host: address,
|
||||||
ContentLength: -1,
|
ContentLength: -1,
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", ua)
|
req.Header.Set("User-Agent", ua)
|
||||||
@ -97,7 +106,7 @@ func (c *http2Connector) Connect(conn net.Conn, addr string, options ...ConnectO
|
|||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr)
|
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", address)
|
||||||
hc.localAddr, _ = net.ResolveTCPAddr("tcp", cc.addr)
|
hc.localAddr, _ = net.ResolveTCPAddr("tcp", cc.addr)
|
||||||
|
|
||||||
return hc, nil
|
return hc, nil
|
||||||
@ -224,7 +233,7 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
|
|||||||
|
|
||||||
transport := http2.Transport{
|
transport := http2.Transport{
|
||||||
TLSClientConfig: tr.tlsConfig,
|
TLSClientConfig: tr.tlsConfig,
|
||||||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
DialTLS: func(network, adr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
conn, err := opts.Chain.Dial(addr)
|
conn, err := opts.Chain.Dial(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -246,13 +255,13 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
|
|||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
Method: http.MethodConnect,
|
Method: http.MethodConnect,
|
||||||
URL: &url.URL{Scheme: "https", Host: addr},
|
URL: &url.URL{Scheme: "https", Host: opts.Host},
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
Proto: "HTTP/2.0",
|
Proto: "HTTP/2.0",
|
||||||
ProtoMajor: 2,
|
ProtoMajor: 2,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Body: pr,
|
Body: pr,
|
||||||
Host: addr,
|
Host: opts.Host,
|
||||||
ContentLength: -1,
|
ContentLength: -1,
|
||||||
}
|
}
|
||||||
if tr.path != "" {
|
if tr.path != "" {
|
||||||
@ -355,7 +364,11 @@ func (h *http2Handler) roundTrip(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Logf("[http2] %s - %s\n%s", r.RemoteAddr, laddr, string(dump))
|
log.Logf("[http2] %s - %s\n%s", r.RemoteAddr, laddr, string(dump))
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Proxy-Agent", "gost/"+Version)
|
proxyAgent := DefaultProxyAgent
|
||||||
|
if h.options.ProxyAgent != "" {
|
||||||
|
proxyAgent = h.options.ProxyAgent
|
||||||
|
}
|
||||||
|
w.Header().Set("Proxy-Agent", proxyAgent)
|
||||||
|
|
||||||
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
||||||
log.Logf("[http2] %s - %s : Unauthorized to tcp connect to %s",
|
log.Logf("[http2] %s - %s : Unauthorized to tcp connect to %s",
|
||||||
@ -375,7 +388,7 @@ func (h *http2Handler) roundTrip(w http.ResponseWriter, r *http.Request) {
|
|||||||
ProtoMajor: 2,
|
ProtoMajor: 2,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Header: http.Header{},
|
Header: http.Header{},
|
||||||
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
Body: io.NopCloser(bytes.NewReader([]byte{})),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.authenticate(w, r, resp) {
|
if !h.authenticate(w, r, resp) {
|
||||||
@ -525,7 +538,7 @@ func (h *http2Handler) authenticate(w http.ResponseWriter, r *http.Request, resp
|
|||||||
} else {
|
} else {
|
||||||
resp.Header = http.Header{}
|
resp.Header = http.Header{}
|
||||||
resp.Header.Set("Server", "nginx/1.14.1")
|
resp.Header.Set("Server", "nginx/1.14.1")
|
||||||
resp.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
resp.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||||
if resp.ContentLength > 0 {
|
if resp.ContentLength > 0 {
|
||||||
resp.Header.Set("Content-Type", "text/html")
|
resp.Header.Set("Content-Type", "text/html")
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -997,7 +997,7 @@ func TestHTTP2ProxyWithWebProbeResist(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
recv, _ := ioutil.ReadAll(conn)
|
recv, _ := io.ReadAll(conn)
|
||||||
if !bytes.Equal(recv, []byte("Hello World!")) {
|
if !bytes.Equal(recv, []byte("Hello World!")) {
|
||||||
t.Error("data not equal")
|
t.Error("data not equal")
|
||||||
}
|
}
|
||||||
@ -1053,7 +1053,7 @@ func TestHTTP2ProxyWithHostProbeResist(t *testing.T) {
|
|||||||
Proto: "HTTP/2.0",
|
Proto: "HTTP/2.0",
|
||||||
ProtoMajor: 2,
|
ProtoMajor: 2,
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(sendData)),
|
Body: io.NopCloser(bytes.NewReader(sendData)),
|
||||||
Host: "github.com:443",
|
Host: "github.com:443",
|
||||||
ContentLength: int64(len(sendData)),
|
ContentLength: int64(len(sendData)),
|
||||||
}
|
}
|
||||||
@ -1068,7 +1068,7 @@ func TestHTTP2ProxyWithHostProbeResist(t *testing.T) {
|
|||||||
t.Error("got non-200 status:", resp.Status)
|
t.Error("got non-200 status:", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
recv, _ := ioutil.ReadAll(resp.Body)
|
recv, _ := io.ReadAll(resp.Body)
|
||||||
if !bytes.Equal(sendData, recv) {
|
if !bytes.Equal(sendData, recv) {
|
||||||
t.Error("data not equal")
|
t.Error("data not equal")
|
||||||
}
|
}
|
||||||
@ -1105,7 +1105,7 @@ func TestHTTP2ProxyWithFileProbeResist(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
recv, _ := ioutil.ReadAll(conn)
|
recv, _ := io.ReadAll(conn)
|
||||||
if !bytes.Equal(recv, []byte("Hello World!")) {
|
if !bytes.Equal(recv, []byte("Hello World!")) {
|
||||||
t.Error("data not equal")
|
t.Error("data not equal")
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -249,7 +249,7 @@ func TestHTTPProxyWithWebProbeResist(t *testing.T) {
|
|||||||
t.Error("got status:", resp.Status)
|
t.Error("got status:", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
recv, _ := ioutil.ReadAll(resp.Body)
|
recv, _ := io.ReadAll(resp.Body)
|
||||||
if !bytes.Equal(recv, []byte("Hello World!")) {
|
if !bytes.Equal(recv, []byte("Hello World!")) {
|
||||||
t.Error("data not equal")
|
t.Error("data not equal")
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ func TestHTTPProxyWithHostProbeResist(t *testing.T) {
|
|||||||
t.Error("got status:", resp.Status)
|
t.Error("got status:", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
recv, _ := ioutil.ReadAll(resp.Body)
|
recv, _ := io.ReadAll(resp.Body)
|
||||||
if !bytes.Equal(sendData, recv) {
|
if !bytes.Equal(sendData, recv) {
|
||||||
t.Error("data not equal")
|
t.Error("data not equal")
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ func TestHTTPProxyWithFileProbeResist(t *testing.T) {
|
|||||||
t.Error("got status:", resp.Status)
|
t.Error("got status:", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
recv, _ := ioutil.ReadAll(resp.Body)
|
recv, _ := io.ReadAll(resp.Body)
|
||||||
if !bytes.Equal(recv, []byte("Hello World!")) {
|
if !bytes.Equal(recv, []byte("Hello World!")) {
|
||||||
t.Error("data not equal, got:", string(recv))
|
t.Error("data not equal, got:", string(recv))
|
||||||
}
|
}
|
||||||
|
42
kcp.go
42
kcp.go
@ -15,9 +15,9 @@ import (
|
|||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
"github.com/klauspost/compress/snappy"
|
"github.com/klauspost/compress/snappy"
|
||||||
|
"github.com/xtaci/kcp-go/v5"
|
||||||
|
"github.com/xtaci/smux"
|
||||||
"github.com/xtaci/tcpraw"
|
"github.com/xtaci/tcpraw"
|
||||||
"gopkg.in/xtaci/kcp-go.v4"
|
|
||||||
"gopkg.in/xtaci/smux.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -43,6 +43,9 @@ type KCPConfig struct {
|
|||||||
Resend int `json:"resend"`
|
Resend int `json:"resend"`
|
||||||
NoCongestion int `json:"nc"`
|
NoCongestion int `json:"nc"`
|
||||||
SockBuf int `json:"sockbuf"`
|
SockBuf int `json:"sockbuf"`
|
||||||
|
SmuxBuf int `json:"smuxbuf"`
|
||||||
|
StreamBuf int `json:"streambuf"`
|
||||||
|
SmuxVer int `json:"smuxver"`
|
||||||
KeepAlive int `json:"keepalive"`
|
KeepAlive int `json:"keepalive"`
|
||||||
SnmpLog string `json:"snmplog"`
|
SnmpLog string `json:"snmplog"`
|
||||||
SnmpPeriod int `json:"snmpperiod"`
|
SnmpPeriod int `json:"snmpperiod"`
|
||||||
@ -62,6 +65,16 @@ func (c *KCPConfig) Init() {
|
|||||||
case "fast3":
|
case "fast3":
|
||||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
|
||||||
}
|
}
|
||||||
|
if c.SmuxVer <= 0 {
|
||||||
|
c.SmuxVer = 1
|
||||||
|
}
|
||||||
|
if c.SmuxBuf <= 0 {
|
||||||
|
c.SmuxBuf = c.SockBuf
|
||||||
|
}
|
||||||
|
if c.StreamBuf <= 0 {
|
||||||
|
c.StreamBuf = c.SockBuf / 2
|
||||||
|
}
|
||||||
|
log.Logf("%#v", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -83,6 +96,9 @@ var (
|
|||||||
Resend: 0,
|
Resend: 0,
|
||||||
NoCongestion: 0,
|
NoCongestion: 0,
|
||||||
SockBuf: 4194304,
|
SockBuf: 4194304,
|
||||||
|
SmuxVer: 1,
|
||||||
|
SmuxBuf: 4194304,
|
||||||
|
StreamBuf: 2097152,
|
||||||
KeepAlive: 10,
|
KeepAlive: 10,
|
||||||
SnmpLog: "",
|
SnmpLog: "",
|
||||||
SnmpPeriod: 60,
|
SnmpPeriod: 60,
|
||||||
@ -231,8 +247,14 @@ func (tr *kcpTransporter) initSession(addr string, conn net.Conn, config *KCPCon
|
|||||||
|
|
||||||
// stream multiplex
|
// stream multiplex
|
||||||
smuxConfig := smux.DefaultConfig()
|
smuxConfig := smux.DefaultConfig()
|
||||||
smuxConfig.MaxReceiveBuffer = config.SockBuf
|
smuxConfig.Version = config.SmuxVer
|
||||||
|
smuxConfig.MaxReceiveBuffer = config.SmuxBuf
|
||||||
|
smuxConfig.MaxStreamBuffer = config.StreamBuf
|
||||||
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
|
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
|
||||||
|
if err := smux.VerifyConfig(smuxConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var cc net.Conn = kcpconn
|
var cc net.Conn = kcpconn
|
||||||
if !config.NoComp {
|
if !config.NoComp {
|
||||||
cc = newCompStreamConn(kcpconn)
|
cc = newCompStreamConn(kcpconn)
|
||||||
@ -332,7 +354,9 @@ func (l *kcpListener) listenLoop() {
|
|||||||
|
|
||||||
func (l *kcpListener) mux(conn net.Conn) {
|
func (l *kcpListener) mux(conn net.Conn) {
|
||||||
smuxConfig := smux.DefaultConfig()
|
smuxConfig := smux.DefaultConfig()
|
||||||
smuxConfig.MaxReceiveBuffer = l.config.SockBuf
|
smuxConfig.Version = l.config.SmuxVer
|
||||||
|
smuxConfig.MaxReceiveBuffer = l.config.SmuxBuf
|
||||||
|
smuxConfig.MaxStreamBuffer = l.config.StreamBuf
|
||||||
smuxConfig.KeepAliveInterval = time.Duration(l.config.KeepAlive) * time.Second
|
smuxConfig.KeepAliveInterval = time.Duration(l.config.KeepAlive) * time.Second
|
||||||
|
|
||||||
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
|
log.Logf("[kcp] %s - %s", conn.RemoteAddr(), l.Addr())
|
||||||
@ -473,9 +497,13 @@ func (c *compStreamConn) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *compStreamConn) Write(b []byte) (n int, err error) {
|
func (c *compStreamConn) Write(b []byte) (n int, err error) {
|
||||||
n, err = c.w.Write(b)
|
if _, err = c.w.Write(b); err != nil {
|
||||||
err = c.w.Flush()
|
return 0, err
|
||||||
return n, err
|
}
|
||||||
|
if err = c.w.Flush(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compStreamConn) Close() error {
|
func (c *compStreamConn) Close() error {
|
||||||
|
2
mux.go
2
mux.go
@ -3,7 +3,7 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
smux "gopkg.in/xtaci/smux.v1"
|
smux "github.com/xtaci/smux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type muxStreamConn struct {
|
type muxStreamConn struct {
|
||||||
|
35
node.go
35
node.go
@ -75,28 +75,44 @@ func ParseNode(s string) (node Node, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch node.Transport {
|
switch node.Transport {
|
||||||
case "tls", "mtls", "ws", "mws", "wss", "mwss", "kcp", "ssh", "quic", "ssu", "http2", "h2", "h2c", "obfs4":
|
|
||||||
case "https":
|
case "https":
|
||||||
node.Protocol = "http"
|
|
||||||
node.Transport = "tls"
|
node.Transport = "tls"
|
||||||
case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding
|
case "tls", "mtls":
|
||||||
|
case "http2", "h2", "h2c":
|
||||||
|
case "ws", "mws", "wss", "mwss":
|
||||||
|
case "kcp", "ssh", "quic":
|
||||||
|
case "ssu":
|
||||||
|
node.Transport = "udp"
|
||||||
|
case "ohttp", "otls", "obfs4": // obfs
|
||||||
|
case "tcp", "udp":
|
||||||
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
|
case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding
|
||||||
case "ohttp": // obfs-http
|
|
||||||
case "tun", "tap": // tun/tap device
|
case "tun", "tap": // tun/tap device
|
||||||
case "ftcp": // fake TCP
|
case "ftcp": // fake TCP
|
||||||
|
case "dns":
|
||||||
|
case "redu", "redirectu": // UDP tproxy
|
||||||
|
case "vsock":
|
||||||
default:
|
default:
|
||||||
node.Transport = "tcp"
|
node.Transport = "tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch node.Protocol {
|
switch node.Protocol {
|
||||||
case "http", "http2", "socks4", "socks4a", "ss", "ss2", "ssu", "sni":
|
case "http", "http2":
|
||||||
|
case "https":
|
||||||
|
node.Protocol = "http"
|
||||||
|
case "socks4", "socks4a":
|
||||||
case "socks", "socks5":
|
case "socks", "socks5":
|
||||||
node.Protocol = "socks5"
|
node.Protocol = "socks5"
|
||||||
|
case "ss", "ssu":
|
||||||
|
case "ss2": // as of 2.10.1, ss2 is same as ss
|
||||||
|
node.Protocol = "ss"
|
||||||
|
case "sni":
|
||||||
case "tcp", "udp", "rtcp", "rudp": // port forwarding
|
case "tcp", "udp", "rtcp", "rudp": // port forwarding
|
||||||
case "direct", "remote", "forward": // forwarding
|
case "direct", "remote", "forward": // forwarding
|
||||||
case "redirect": // TCP transparent proxy
|
case "red", "redirect", "redu", "redirectu": // TCP,UDP transparent proxy
|
||||||
case "tun", "tap": // tun/tap device
|
case "tun", "tap": // tun/tap device
|
||||||
case "ftcp": // fake TCP
|
case "ftcp": // fake TCP
|
||||||
|
case "dns", "dot", "doh":
|
||||||
|
case "relay":
|
||||||
default:
|
default:
|
||||||
node.Protocol = ""
|
node.Protocol = ""
|
||||||
}
|
}
|
||||||
@ -142,13 +158,16 @@ func (node *Node) GetBool(key string) bool {
|
|||||||
|
|
||||||
// GetInt converts node parameter value to int.
|
// GetInt converts node parameter value to int.
|
||||||
func (node *Node) GetInt(key string) int {
|
func (node *Node) GetInt(key string) int {
|
||||||
n, _ := strconv.Atoi(node.Values.Get(key))
|
n, _ := strconv.Atoi(node.Get(key))
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDuration converts node parameter value to time.Duration.
|
// GetDuration converts node parameter value to time.Duration.
|
||||||
func (node *Node) GetDuration(key string) time.Duration {
|
func (node *Node) GetDuration(key string) time.Duration {
|
||||||
d, _ := time.ParseDuration(node.Values.Get(key))
|
d, err := time.ParseDuration(node.Get(key))
|
||||||
|
if err != nil {
|
||||||
|
d = time.Duration(node.GetInt(key)) * time.Second
|
||||||
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
432
obfs.go
432
obfs.go
@ -5,6 +5,8 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -18,8 +20,13 @@ import (
|
|||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
|
|
||||||
pt "git.torproject.org/pluggable-transports/goptlib.git"
|
pt "git.torproject.org/pluggable-transports/goptlib.git"
|
||||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
|
dissector "github.com/go-gost/tls-dissector"
|
||||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4"
|
"gitlab.com/yawning/obfs4.git/transports/base"
|
||||||
|
"gitlab.com/yawning/obfs4.git/transports/obfs4"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxTLSDataLen = 16384
|
||||||
)
|
)
|
||||||
|
|
||||||
type obfsHTTPTransporter struct {
|
type obfsHTTPTransporter struct {
|
||||||
@ -249,6 +256,415 @@ func (c *obfsHTTPConn) Write(b []byte) (n int, err error) {
|
|||||||
return c.Conn.Write(b)
|
return c.Conn.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type obfsTLSTransporter struct {
|
||||||
|
tcpTransporter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObfsTLSTransporter creates a Transporter that is used by TLS obfuscating.
|
||||||
|
func ObfsTLSTransporter() Transporter {
|
||||||
|
return &obfsTLSTransporter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *obfsTLSTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||||
|
opts := &HandshakeOptions{}
|
||||||
|
for _, option := range options {
|
||||||
|
option(opts)
|
||||||
|
}
|
||||||
|
return ClientObfsTLSConn(conn, opts.Host), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type obfsTLSListener struct {
|
||||||
|
net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObfsTLSListener creates a Listener for TLS obfuscating server.
|
||||||
|
func ObfsTLSListener(addr string) (Listener, error) {
|
||||||
|
laddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ln, err := net.ListenTCP("tcp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &obfsTLSListener{Listener: tcpKeepAliveListener{ln}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *obfsTLSListener) Accept() (net.Conn, error) {
|
||||||
|
conn, err := l.Listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerObfsTLSConn(conn, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cipherSuites = []uint16{
|
||||||
|
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8, 0xccaa, 0xc02b, 0xc02f,
|
||||||
|
0x009e, 0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067, 0xc00a,
|
||||||
|
0xc014, 0x0039, 0xc009, 0xc013, 0x0033, 0x009d, 0x009c, 0x003d,
|
||||||
|
0x003c, 0x0035, 0x002f, 0x00ff,
|
||||||
|
}
|
||||||
|
|
||||||
|
compressionMethods = []uint8{0x00}
|
||||||
|
|
||||||
|
algorithms = []uint16{
|
||||||
|
0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402,
|
||||||
|
0x0403, 0x0301, 0x0302, 0x0303, 0x0201, 0x0202, 0x0203,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsRecordTypes = []uint8{0x16, 0x14, 0x16, 0x17}
|
||||||
|
tlsVersionMinors = []uint8{0x01, 0x03, 0x03, 0x03}
|
||||||
|
|
||||||
|
ErrBadType = errors.New("bad type")
|
||||||
|
ErrBadMajorVersion = errors.New("bad major version")
|
||||||
|
ErrBadMinorVersion = errors.New("bad minor version")
|
||||||
|
ErrMaxDataLen = errors.New("bad tls data len")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tlsRecordStateType = iota
|
||||||
|
tlsRecordStateVersion0
|
||||||
|
tlsRecordStateVersion1
|
||||||
|
tlsRecordStateLength0
|
||||||
|
tlsRecordStateLength1
|
||||||
|
tlsRecordStateData
|
||||||
|
)
|
||||||
|
|
||||||
|
type obfsTLSParser struct {
|
||||||
|
step uint8
|
||||||
|
state uint8
|
||||||
|
length uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type obfsTLSConn struct {
|
||||||
|
net.Conn
|
||||||
|
rbuf bytes.Buffer
|
||||||
|
wbuf bytes.Buffer
|
||||||
|
host string
|
||||||
|
isServer bool
|
||||||
|
handshaked chan struct{}
|
||||||
|
parser *obfsTLSParser
|
||||||
|
handshakeMutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *obfsTLSParser) Parse(b []byte) (int, error) {
|
||||||
|
i := 0
|
||||||
|
last := 0
|
||||||
|
length := len(b)
|
||||||
|
|
||||||
|
for i < length {
|
||||||
|
ch := b[i]
|
||||||
|
switch r.state {
|
||||||
|
case tlsRecordStateType:
|
||||||
|
if tlsRecordTypes[r.step] != ch {
|
||||||
|
return 0, ErrBadType
|
||||||
|
}
|
||||||
|
r.state = tlsRecordStateVersion0
|
||||||
|
i++
|
||||||
|
case tlsRecordStateVersion0:
|
||||||
|
if ch != 0x03 {
|
||||||
|
return 0, ErrBadMajorVersion
|
||||||
|
}
|
||||||
|
r.state = tlsRecordStateVersion1
|
||||||
|
i++
|
||||||
|
case tlsRecordStateVersion1:
|
||||||
|
if ch != tlsVersionMinors[r.step] {
|
||||||
|
return 0, ErrBadMinorVersion
|
||||||
|
}
|
||||||
|
r.state = tlsRecordStateLength0
|
||||||
|
i++
|
||||||
|
case tlsRecordStateLength0:
|
||||||
|
r.length = uint16(ch) << 8
|
||||||
|
r.state = tlsRecordStateLength1
|
||||||
|
i++
|
||||||
|
case tlsRecordStateLength1:
|
||||||
|
r.length |= uint16(ch)
|
||||||
|
if r.step == 0 {
|
||||||
|
r.length = 91
|
||||||
|
} else if r.step == 1 {
|
||||||
|
r.length = 1
|
||||||
|
} else if r.length > maxTLSDataLen {
|
||||||
|
return 0, ErrMaxDataLen
|
||||||
|
}
|
||||||
|
if r.length > 0 {
|
||||||
|
r.state = tlsRecordStateData
|
||||||
|
} else {
|
||||||
|
r.state = tlsRecordStateType
|
||||||
|
r.step++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
case tlsRecordStateData:
|
||||||
|
left := uint16(length - i)
|
||||||
|
if left > r.length {
|
||||||
|
left = r.length
|
||||||
|
}
|
||||||
|
if r.step >= 2 {
|
||||||
|
skip := i - last
|
||||||
|
copy(b[last:], b[i:length])
|
||||||
|
length -= int(skip)
|
||||||
|
last += int(left)
|
||||||
|
i = last
|
||||||
|
} else {
|
||||||
|
i += int(left)
|
||||||
|
}
|
||||||
|
r.length -= left
|
||||||
|
if r.length == 0 {
|
||||||
|
if r.step < 3 {
|
||||||
|
r.step++
|
||||||
|
}
|
||||||
|
r.state = tlsRecordStateType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if last == 0 {
|
||||||
|
return 0, nil
|
||||||
|
} else if last < length {
|
||||||
|
length -= last
|
||||||
|
}
|
||||||
|
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientObfsTLSConn creates a connection for obfs-tls client.
|
||||||
|
func ClientObfsTLSConn(conn net.Conn, host string) net.Conn {
|
||||||
|
return &obfsTLSConn{
|
||||||
|
Conn: conn,
|
||||||
|
host: host,
|
||||||
|
handshaked: make(chan struct{}),
|
||||||
|
parser: &obfsTLSParser{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerObfsTLSConn creates a connection for obfs-tls server.
|
||||||
|
func ServerObfsTLSConn(conn net.Conn, host string) net.Conn {
|
||||||
|
return &obfsTLSConn{
|
||||||
|
Conn: conn,
|
||||||
|
host: host,
|
||||||
|
isServer: true,
|
||||||
|
handshaked: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsTLSConn) Handshaked() bool {
|
||||||
|
select {
|
||||||
|
case <-c.handshaked:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsTLSConn) Handshake(payload []byte) (err error) {
|
||||||
|
c.handshakeMutex.Lock()
|
||||||
|
defer c.handshakeMutex.Unlock()
|
||||||
|
|
||||||
|
if c.Handshaked() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.isServer {
|
||||||
|
err = c.serverHandshake()
|
||||||
|
} else {
|
||||||
|
err = c.clientHandshake(payload)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
close(c.handshaked)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsTLSConn) clientHandshake(payload []byte) error {
|
||||||
|
clientMsg := &dissector.ClientHelloMsg{
|
||||||
|
Version: tls.VersionTLS12,
|
||||||
|
SessionID: make([]byte, 32),
|
||||||
|
CipherSuites: cipherSuites,
|
||||||
|
CompressionMethods: compressionMethods,
|
||||||
|
Extensions: []dissector.Extension{
|
||||||
|
&dissector.SessionTicketExtension{
|
||||||
|
Data: payload,
|
||||||
|
},
|
||||||
|
&dissector.ServerNameExtension{
|
||||||
|
Name: c.host,
|
||||||
|
},
|
||||||
|
&dissector.ECPointFormatsExtension{
|
||||||
|
Formats: []uint8{0x01, 0x00, 0x02},
|
||||||
|
},
|
||||||
|
&dissector.SupportedGroupsExtension{
|
||||||
|
Groups: []uint16{0x001d, 0x0017, 0x0019, 0x0018},
|
||||||
|
},
|
||||||
|
&dissector.SignatureAlgorithmsExtension{
|
||||||
|
Algorithms: algorithms,
|
||||||
|
},
|
||||||
|
&dissector.EncryptThenMacExtension{},
|
||||||
|
&dissector.ExtendedMasterSecretExtension{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
clientMsg.Random.Time = uint32(time.Now().Unix())
|
||||||
|
rand.Read(clientMsg.Random.Opaque[:])
|
||||||
|
rand.Read(clientMsg.SessionID)
|
||||||
|
b, err := clientMsg.Encode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := &dissector.Record{
|
||||||
|
Type: dissector.Handshake,
|
||||||
|
Version: tls.VersionTLS10,
|
||||||
|
Opaque: b,
|
||||||
|
}
|
||||||
|
if _, err := record.WriteTo(c.Conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsTLSConn) serverHandshake() error {
|
||||||
|
record := &dissector.Record{}
|
||||||
|
if _, err := record.ReadFrom(c.Conn); err != nil {
|
||||||
|
log.Log(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record.Type != dissector.Handshake {
|
||||||
|
return dissector.ErrBadType
|
||||||
|
}
|
||||||
|
|
||||||
|
clientMsg := &dissector.ClientHelloMsg{}
|
||||||
|
if err := clientMsg.Decode(record.Opaque); err != nil {
|
||||||
|
log.Log(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ext := range clientMsg.Extensions {
|
||||||
|
if ext.Type() == dissector.ExtSessionTicket {
|
||||||
|
b, err := ext.Encode()
|
||||||
|
if err != nil {
|
||||||
|
log.Log(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.rbuf.Write(b)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverMsg := &dissector.ServerHelloMsg{
|
||||||
|
Version: tls.VersionTLS12,
|
||||||
|
SessionID: clientMsg.SessionID,
|
||||||
|
CipherSuite: 0xcca8,
|
||||||
|
CompressionMethod: 0x00,
|
||||||
|
Extensions: []dissector.Extension{
|
||||||
|
&dissector.RenegotiationInfoExtension{},
|
||||||
|
&dissector.ExtendedMasterSecretExtension{},
|
||||||
|
&dissector.ECPointFormatsExtension{
|
||||||
|
Formats: []uint8{0x00},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
serverMsg.Random.Time = uint32(time.Now().Unix())
|
||||||
|
rand.Read(serverMsg.Random.Opaque[:])
|
||||||
|
b, err := serverMsg.Encode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = &dissector.Record{
|
||||||
|
Type: dissector.Handshake,
|
||||||
|
Version: tls.VersionTLS10,
|
||||||
|
Opaque: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := record.WriteTo(&c.wbuf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = &dissector.Record{
|
||||||
|
Type: dissector.ChangeCipherSpec,
|
||||||
|
Version: tls.VersionTLS12,
|
||||||
|
Opaque: []byte{0x01},
|
||||||
|
}
|
||||||
|
if _, err := record.WriteTo(&c.wbuf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsTLSConn) Read(b []byte) (n int, err error) {
|
||||||
|
if c.isServer { // NOTE: only Write performs the handshake operation on client side.
|
||||||
|
if err = c.Handshake(nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-c.handshaked:
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.isServer {
|
||||||
|
if c.rbuf.Len() > 0 {
|
||||||
|
return c.rbuf.Read(b)
|
||||||
|
}
|
||||||
|
record := &dissector.Record{}
|
||||||
|
if _, err = record.ReadFrom(c.Conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n = copy(b, record.Opaque)
|
||||||
|
_, err = c.rbuf.Write(record.Opaque[n:])
|
||||||
|
} else {
|
||||||
|
n, err = c.Conn.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
n, err = c.parser.Parse(b[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfsTLSConn) Write(b []byte) (n int, err error) {
|
||||||
|
n = len(b)
|
||||||
|
if !c.Handshaked() {
|
||||||
|
if err = c.Handshake(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.isServer { // the data b has been sended during handshake phase.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(b) > 0 {
|
||||||
|
data := b
|
||||||
|
if len(b) > maxTLSDataLen {
|
||||||
|
data = b[:maxTLSDataLen]
|
||||||
|
b = b[maxTLSDataLen:]
|
||||||
|
} else {
|
||||||
|
b = b[:0]
|
||||||
|
}
|
||||||
|
record := &dissector.Record{
|
||||||
|
Type: dissector.AppData,
|
||||||
|
Version: tls.VersionTLS12,
|
||||||
|
Opaque: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.wbuf.Len() > 0 {
|
||||||
|
record.Type = dissector.Handshake
|
||||||
|
record.WriteTo(&c.wbuf)
|
||||||
|
_, err = c.wbuf.WriteTo(c.Conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = record.WriteTo(c.Conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type obfs4Context struct {
|
type obfs4Context struct {
|
||||||
cf base.ClientFactory
|
cf base.ClientFactory
|
||||||
cargs interface{} // type obfs4ClientArgs
|
cargs interface{} // type obfs4ClientArgs
|
||||||
@ -388,6 +804,16 @@ func Obfs4Listener(addr string) (Listener, error) {
|
|||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TempError satisfies the net.Error interface and presents itself
|
||||||
|
// as temporary to make sure that it gets retried by the Accept loop
|
||||||
|
// in server.go.
|
||||||
|
type TempError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TempError) Timeout() bool { return false }
|
||||||
|
func (e TempError) Temporary() bool { return true }
|
||||||
|
|
||||||
func (l *obfs4Listener) Accept() (net.Conn, error) {
|
func (l *obfs4Listener) Accept() (net.Conn, error) {
|
||||||
conn, err := l.Listener.Accept()
|
conn, err := l.Listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -396,7 +822,7 @@ func (l *obfs4Listener) Accept() (net.Conn, error) {
|
|||||||
cc, err := obfs4ServerConn(l.addr, conn)
|
cc, err := obfs4ServerConn(l.addr, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, TempError{err}
|
||||||
}
|
}
|
||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
153
quic.go
153
quic.go
@ -1,6 +1,7 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@ -12,16 +13,15 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
quic "github.com/lucas-clemente/quic-go"
|
quic "github.com/quic-go/quic-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type quicSession struct {
|
type quicSession struct {
|
||||||
conn net.Conn
|
session quic.EarlyConnection
|
||||||
session quic.Session
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *quicSession) GetConn() (*quicConn, error) {
|
func (session *quicSession) GetConn() (*quicConn, error) {
|
||||||
stream, err := session.session.OpenStreamSync()
|
stream, err := session.session.OpenStreamSync(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ func (session *quicSession) GetConn() (*quicConn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (session *quicSession) Close() error {
|
func (session *quicSession) Close() error {
|
||||||
return session.session.Close()
|
return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
type quicTransporter struct {
|
type quicTransporter struct {
|
||||||
@ -59,100 +59,71 @@ func (tr *quicTransporter) Dial(addr string, options ...DialOption) (conn net.Co
|
|||||||
option(opts)
|
option(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
tr.sessionMutex.Lock()
|
tr.sessionMutex.Lock()
|
||||||
defer tr.sessionMutex.Unlock()
|
defer tr.sessionMutex.Unlock()
|
||||||
|
|
||||||
session, ok := tr.sessions[addr]
|
session, ok := tr.sessions[addr]
|
||||||
if !ok {
|
if !ok {
|
||||||
var cc *net.UDPConn
|
var pc net.PacketConn
|
||||||
cc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
pc, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn = cc
|
|
||||||
|
|
||||||
if tr.config != nil && tr.config.Key != nil {
|
if tr.config != nil && tr.config.Key != nil {
|
||||||
conn = &quicCipherConn{UDPConn: cc, key: tr.config.Key}
|
pc = &quicCipherConn{PacketConn: pc, key: tr.config.Key}
|
||||||
}
|
}
|
||||||
|
|
||||||
session = &quicSession{conn: conn}
|
session, err = tr.initSession(udpAddr, pc)
|
||||||
|
if err != nil {
|
||||||
|
pc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tr.sessions[addr] = session
|
tr.sessions[addr] = session
|
||||||
}
|
}
|
||||||
return session.conn, nil
|
|
||||||
|
conn, err = session.GetConn()
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
delete(tr.sessions, addr)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
func (tr *quicTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||||
opts := &HandshakeOptions{}
|
return conn, nil
|
||||||
for _, option := range options {
|
}
|
||||||
option(opts)
|
|
||||||
}
|
func (tr *quicTransporter) initSession(addr net.Addr, conn net.PacketConn) (*quicSession, error) {
|
||||||
config := tr.config
|
config := tr.config
|
||||||
if opts.QUICConfig != nil {
|
if config == nil {
|
||||||
config = opts.QUICConfig
|
config = &QUICConfig{}
|
||||||
}
|
}
|
||||||
if config.TLSConfig == nil {
|
if config.TLSConfig == nil {
|
||||||
config.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
config.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.sessionMutex.Lock()
|
|
||||||
defer tr.sessionMutex.Unlock()
|
|
||||||
|
|
||||||
timeout := opts.Timeout
|
|
||||||
if timeout <= 0 {
|
|
||||||
timeout = HandshakeTimeout
|
|
||||||
}
|
|
||||||
conn.SetDeadline(time.Now().Add(timeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
session, ok := tr.sessions[opts.Addr]
|
|
||||||
if session != nil && session.conn != conn {
|
|
||||||
conn.Close()
|
|
||||||
return nil, errors.New("quic: unrecognized connection")
|
|
||||||
}
|
|
||||||
if !ok || session.session == nil {
|
|
||||||
s, err := tr.initSession(opts.Addr, conn, config)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
delete(tr.sessions, opts.Addr)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
session = s
|
|
||||||
tr.sessions[opts.Addr] = session
|
|
||||||
}
|
|
||||||
cc, err := session.GetConn()
|
|
||||||
if err != nil {
|
|
||||||
session.Close()
|
|
||||||
delete(tr.sessions, opts.Addr)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *quicTransporter) initSession(addr string, conn net.Conn, config *QUICConfig) (*quicSession, error) {
|
|
||||||
udpConn, ok := conn.(net.PacketConn)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("quic: wrong connection type")
|
|
||||||
}
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
HandshakeTimeout: config.Timeout,
|
HandshakeIdleTimeout: config.Timeout,
|
||||||
KeepAlive: config.KeepAlive,
|
MaxIdleTimeout: config.IdleTimeout,
|
||||||
IdleTimeout: config.IdleTimeout,
|
KeepAlivePeriod: config.KeepAlivePeriod,
|
||||||
Versions: []quic.VersionNumber{
|
Versions: []quic.VersionNumber{
|
||||||
quic.VersionGQUIC43,
|
quic.Version1,
|
||||||
quic.VersionGQUIC39,
|
quic.Version2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
session, err := quic.Dial(udpConn, udpAddr, addr, config.TLSConfig, quicConfig)
|
session, err := quic.DialEarly(context.Background(), conn, addr, tlsConfigQUICALPN(config.TLSConfig), quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("quic dial %s: %v", addr, err)
|
log.Logf("quic dial %s: %v", addr, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &quicSession{conn: conn, session: session}, nil
|
return &quicSession{session: session}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *quicTransporter) Multiplex() bool {
|
func (tr *quicTransporter) Multiplex() bool {
|
||||||
@ -164,12 +135,13 @@ type QUICConfig struct {
|
|||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
KeepAlive bool
|
KeepAlive bool
|
||||||
|
KeepAlivePeriod time.Duration
|
||||||
IdleTimeout time.Duration
|
IdleTimeout time.Duration
|
||||||
Key []byte
|
Key []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type quicListener struct {
|
type quicListener struct {
|
||||||
ln quic.Listener
|
ln quic.EarlyListener
|
||||||
connChan chan net.Conn
|
connChan chan net.Conn
|
||||||
errChan chan error
|
errChan chan error
|
||||||
}
|
}
|
||||||
@ -180,39 +152,41 @@ func QUICListener(addr string, config *QUICConfig) (Listener, error) {
|
|||||||
config = &QUICConfig{}
|
config = &QUICConfig{}
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
HandshakeTimeout: config.Timeout,
|
HandshakeIdleTimeout: config.Timeout,
|
||||||
KeepAlive: config.KeepAlive,
|
KeepAlivePeriod: config.KeepAlivePeriod,
|
||||||
IdleTimeout: config.IdleTimeout,
|
MaxIdleTimeout: config.IdleTimeout,
|
||||||
|
Versions: []quic.VersionNumber{
|
||||||
|
quic.Version1,
|
||||||
|
quic.Version2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := config.TLSConfig
|
tlsConfig := config.TLSConfig
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
tlsConfig = DefaultTLSConfig
|
tlsConfig = DefaultTLSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var conn net.PacketConn
|
var conn net.PacketConn
|
||||||
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
lconn, err := net.ListenUDP("udp", udpAddr)
|
conn, err = net.ListenUDP("udp", udpAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = lconn
|
|
||||||
|
|
||||||
if config.Key != nil {
|
if config.Key != nil {
|
||||||
conn = &quicCipherConn{UDPConn: lconn, key: config.Key}
|
conn = &quicCipherConn{PacketConn: conn, key: config.Key}
|
||||||
}
|
}
|
||||||
|
|
||||||
ln, err := quic.Listen(conn, tlsConfig, quicConfig)
|
ln, err := quic.ListenEarly(conn, tlsConfigQUICALPN(tlsConfig), quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
l := &quicListener{
|
l := &quicListener{
|
||||||
ln: ln,
|
ln: *ln,
|
||||||
connChan: make(chan net.Conn, 1024),
|
connChan: make(chan net.Conn, 1024),
|
||||||
errChan: make(chan error, 1),
|
errChan: make(chan error, 1),
|
||||||
}
|
}
|
||||||
@ -223,7 +197,7 @@ func QUICListener(addr string, config *QUICConfig) (Listener, error) {
|
|||||||
|
|
||||||
func (l *quicListener) listenLoop() {
|
func (l *quicListener) listenLoop() {
|
||||||
for {
|
for {
|
||||||
session, err := l.ln.Accept()
|
session, err := l.ln.Accept(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log("[quic] accept:", err)
|
log.Log("[quic] accept:", err)
|
||||||
l.errChan <- err
|
l.errChan <- err
|
||||||
@ -234,15 +208,15 @@ func (l *quicListener) listenLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *quicListener) sessionLoop(session quic.Session) {
|
func (l *quicListener) sessionLoop(session quic.Connection) {
|
||||||
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr())
|
log.Logf("[quic] %s <-> %s", session.RemoteAddr(), session.LocalAddr())
|
||||||
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr())
|
defer log.Logf("[quic] %s >-< %s", session.RemoteAddr(), session.LocalAddr())
|
||||||
|
|
||||||
for {
|
for {
|
||||||
stream, err := session.AcceptStream()
|
stream, err := session.AcceptStream(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log("[quic] accept stream:", err)
|
log.Log("[quic] accept stream:", err)
|
||||||
session.Close()
|
session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,12 +265,12 @@ func (c *quicConn) RemoteAddr() net.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type quicCipherConn struct {
|
type quicCipherConn struct {
|
||||||
*net.UDPConn
|
net.PacketConn
|
||||||
key []byte
|
key []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *quicCipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) {
|
func (conn *quicCipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) {
|
||||||
n, addr, err = conn.UDPConn.ReadFrom(data)
|
n, addr, err = conn.PacketConn.ReadFrom(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -316,7 +290,7 @@ func (conn *quicCipherConn) WriteTo(data []byte, addr net.Addr) (n int, err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.UDPConn.WriteTo(b, addr)
|
_, err = conn.PacketConn.WriteTo(b, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -362,3 +336,12 @@ func (conn *quicCipherConn) decrypt(data []byte) ([]byte, error) {
|
|||||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||||
return gcm.Open(nil, nonce, ciphertext, nil)
|
return gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tlsConfigQUICALPN(tlsConfig *tls.Config) *tls.Config {
|
||||||
|
if tlsConfig == nil {
|
||||||
|
panic("quic: tlsconfig is nil")
|
||||||
|
}
|
||||||
|
tlsConfigQUIC := tlsConfig.Clone()
|
||||||
|
tlsConfigQUIC.NextProtos = []string{"http/3", "quic/v1"}
|
||||||
|
return tlsConfigQUIC
|
||||||
|
}
|
||||||
|
149
redirect.go
149
redirect.go
@ -1,13 +1,18 @@
|
|||||||
// +build !windows
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/LiamHaworth/go-tproxy"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +20,7 @@ type tcpRedirectHandler struct {
|
|||||||
options *HandlerOptions
|
options *HandlerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCPRedirectHandler creates a server Handler for TCP redirect server.
|
// TCPRedirectHandler creates a server Handler for TCP transparent server.
|
||||||
func TCPRedirectHandler(opts ...HandlerOption) Handler {
|
func TCPRedirectHandler(opts ...HandlerOption) Handler {
|
||||||
h := &tcpRedirectHandler{}
|
h := &tcpRedirectHandler{}
|
||||||
h.Init(opts...)
|
h.Init(opts...)
|
||||||
@ -49,7 +54,8 @@ func (h *tcpRedirectHandler) Handle(c net.Conn) {
|
|||||||
|
|
||||||
log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr)
|
log.Logf("[red-tcp] %s -> %s", srcAddr, dstAddr)
|
||||||
|
|
||||||
cc, err := h.options.Chain.Dial(dstAddr.String(),
|
cc, err := h.options.Chain.DialContext(context.Background(),
|
||||||
|
"tcp", dstAddr.String(),
|
||||||
RetryChainOption(h.options.Retries),
|
RetryChainOption(h.options.Retries),
|
||||||
TimeoutChainOption(h.options.Timeout),
|
TimeoutChainOption(h.options.Timeout),
|
||||||
)
|
)
|
||||||
@ -97,3 +103,140 @@ func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Add
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type udpRedirectHandler struct {
|
||||||
|
options *HandlerOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPRedirectHandler creates a server Handler for UDP transparent server.
|
||||||
|
func UDPRedirectHandler(opts ...HandlerOption) Handler {
|
||||||
|
h := &udpRedirectHandler{}
|
||||||
|
h.Init(opts...)
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *udpRedirectHandler) Init(options ...HandlerOption) {
|
||||||
|
if h.options == nil {
|
||||||
|
h.options = &HandlerOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(h.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *udpRedirectHandler) Handle(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
raddr, ok := conn.LocalAddr().(*net.UDPAddr)
|
||||||
|
if !ok {
|
||||||
|
log.Log("[red-udp] wrong connection type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, err := h.options.Chain.DialContext(context.Background(),
|
||||||
|
"udp", raddr.String(),
|
||||||
|
RetryChainOption(h.options.Retries),
|
||||||
|
TimeoutChainOption(h.options.Timeout),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[red-udp] %s - %s : %s", conn.RemoteAddr(), raddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
log.Logf("[red-udp] %s <-> %s", conn.RemoteAddr(), raddr)
|
||||||
|
transport(conn, cc)
|
||||||
|
log.Logf("[red-udp] %s >-< %s", conn.RemoteAddr(), raddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpRedirectListener struct {
|
||||||
|
*net.UDPConn
|
||||||
|
config *UDPListenConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPRedirectListener creates a Listener for UDP transparent proxy server.
|
||||||
|
func UDPRedirectListener(addr string, cfg *UDPListenConfig) (Listener, error) {
|
||||||
|
laddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := tproxy.ListenUDP("udp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = &UDPListenConfig{}
|
||||||
|
}
|
||||||
|
return &udpRedirectListener{
|
||||||
|
UDPConn: ln,
|
||||||
|
config: cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *udpRedirectListener) Accept() (conn net.Conn, err error) {
|
||||||
|
b := make([]byte, mediumBufferSize)
|
||||||
|
|
||||||
|
n, raddr, dstAddr, err := tproxy.ReadFromUDP(l.UDPConn, b)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[red-udp] %s : %s", l.Addr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Logf("[red-udp] %s: %s -> %s", l.Addr(), raddr, dstAddr)
|
||||||
|
|
||||||
|
c, err := tproxy.DialUDP("udp", dstAddr, raddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[red-udp] %s -> %s : %s", raddr, dstAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl := l.config.TTL
|
||||||
|
if ttl <= 0 {
|
||||||
|
ttl = defaultTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = &udpRedirectServerConn{
|
||||||
|
Conn: c,
|
||||||
|
buf: b[:n],
|
||||||
|
ttl: ttl,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *udpRedirectListener) Addr() net.Addr {
|
||||||
|
return l.UDPConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
type udpRedirectServerConn struct {
|
||||||
|
net.Conn
|
||||||
|
buf []byte
|
||||||
|
ttl time.Duration
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpRedirectServerConn) Read(b []byte) (n int, err error) {
|
||||||
|
if c.ttl > 0 {
|
||||||
|
c.SetReadDeadline(time.Now().Add(c.ttl))
|
||||||
|
defer c.SetReadDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
c.once.Do(func() {
|
||||||
|
n = copy(b, c.buf)
|
||||||
|
c.buf = nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
n, err = c.Conn.Read(b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *udpRedirectServerConn) Write(b []byte) (n int, err error) {
|
||||||
|
if c.ttl > 0 {
|
||||||
|
c.SetWriteDeadline(time.Now().Add(c.ttl))
|
||||||
|
defer c.SetWriteDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
57
redirect_other.go
Normal file
57
redirect_other.go
Normal 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")
|
||||||
|
}
|
@ -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
369
relay.go
Normal 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)
|
||||||
|
}
|
850
resolver.go
850
resolver.go
File diff suppressed because it is too large
Load Diff
@ -24,10 +24,10 @@ var dnsTests = []struct {
|
|||||||
{NameServer{Addr: "1.1.1.1:853", Protocol: "tls", Hostname: "cloudflare-dns.com"}, "github.com", true},
|
{NameServer{Addr: "1.1.1.1:853", Protocol: "tls", Hostname: "cloudflare-dns.com"}, "github.com", true},
|
||||||
{NameServer{Addr: "https://cloudflare-dns.com/dns-query", Protocol: "https"}, "github.com", true},
|
{NameServer{Addr: "https://cloudflare-dns.com/dns-query", Protocol: "https"}, "github.com", true},
|
||||||
{NameServer{Addr: "https://1.0.0.1/dns-query", Protocol: "https"}, "github.com", true},
|
{NameServer{Addr: "https://1.0.0.1/dns-query", Protocol: "https"}, "github.com", true},
|
||||||
{NameServer{Addr: "1.1.1.1:12345", Timeout: 1 * time.Second}, "github.com", false},
|
{NameServer{Addr: "1.1.1.1:12345"}, "github.com", false},
|
||||||
{NameServer{Addr: "1.1.1.1:12345", Protocol: "tcp", Timeout: 1 * time.Second}, "github.com", false},
|
{NameServer{Addr: "1.1.1.1:12345", Protocol: "tcp"}, "github.com", false},
|
||||||
{NameServer{Addr: "1.1.1.1:12345", Protocol: "tls", Timeout: 1 * time.Second}, "github.com", false},
|
{NameServer{Addr: "1.1.1.1:12345", Protocol: "tls"}, "github.com", false},
|
||||||
{NameServer{Addr: "https://1.0.0.1:12345/dns-query", Protocol: "https", Timeout: 1 * time.Second}, "github.com", false},
|
{NameServer{Addr: "https://1.0.0.1:12345/dns-query", Protocol: "https"}, "github.com", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsResolverRoundtrip(t *testing.T, r Resolver, host string) error {
|
func dnsResolverRoundtrip(t *testing.T, r Resolver, host string) error {
|
||||||
@ -45,13 +45,13 @@ func TestDNSResolver(t *testing.T) {
|
|||||||
tc := tc
|
tc := tc
|
||||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||||
ns := tc.ns
|
ns := tc.ns
|
||||||
if err := ns.Init(); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
t.Log(ns)
|
t.Log(ns)
|
||||||
r := NewResolver(0, ns)
|
r := NewResolver(0, ns)
|
||||||
resolv := r.(*resolver)
|
resolv := r.(*resolver)
|
||||||
resolv.domain = "com"
|
resolv.domain = "com"
|
||||||
|
if err := r.Init(); err != nil {
|
||||||
|
t.Error("got error:", err)
|
||||||
|
}
|
||||||
err := dnsResolverRoundtrip(t, r, tc.host)
|
err := dnsResolverRoundtrip(t, r, tc.host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if tc.pass {
|
if tc.pass {
|
||||||
@ -85,6 +85,7 @@ var resolverCacheTests = []struct {
|
|||||||
[]net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}},
|
[]net.IP{net.IPv4(192, 168, 1, 1), net.IPv4(192, 168, 1, 2)}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func TestResolverCache(t *testing.T) {
|
func TestResolverCache(t *testing.T) {
|
||||||
isEqual := func(a, b []net.IP) bool {
|
isEqual := func(a, b []net.IP) bool {
|
||||||
if a == nil && b == nil {
|
if a == nil && b == nil {
|
||||||
@ -106,8 +107,8 @@ func TestResolverCache(t *testing.T) {
|
|||||||
tc := tc
|
tc := tc
|
||||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||||
r := newResolver(tc.ttl)
|
r := newResolver(tc.ttl)
|
||||||
r.storeCache(tc.name, tc.ips, tc.ttl)
|
r.cache.storeCache(tc.name, tc.ips, tc.ttl)
|
||||||
ips := r.loadCache(tc.name, tc.ttl)
|
ips := r.cache.loadCache(tc.name, tc.ttl)
|
||||||
|
|
||||||
if !isEqual(tc.result, ips) {
|
if !isEqual(tc.result, ips) {
|
||||||
t.Error("unexpected cache value:", tc.name, ips, tc.ttl)
|
t.Error("unexpected cache value:", tc.name, ips, tc.ttl)
|
||||||
@ -115,6 +116,7 @@ func TestResolverCache(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
var resolverReloadTests = []struct {
|
var resolverReloadTests = []struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
@ -167,7 +169,6 @@ var resolverReloadTests = []struct {
|
|||||||
ns: &NameServer{
|
ns: &NameServer{
|
||||||
Protocol: "udp",
|
Protocol: "udp",
|
||||||
Addr: "1.1.1.1",
|
Addr: "1.1.1.1",
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
},
|
},
|
||||||
timeout: 10 * time.Second,
|
timeout: 10 * time.Second,
|
||||||
stopped: true,
|
stopped: true,
|
||||||
@ -219,9 +220,9 @@ func TestResolverReload(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
t.Log(r.String())
|
t.Log(r.String())
|
||||||
if r.TTL != tc.ttl {
|
if r.TTL() != tc.ttl {
|
||||||
t.Errorf("ttl value should be %v, got %v",
|
t.Errorf("ttl value should be %v, got %v",
|
||||||
tc.ttl, r.TTL)
|
tc.ttl, r.TTL())
|
||||||
}
|
}
|
||||||
if r.Period() != tc.period {
|
if r.Period() != tc.period {
|
||||||
t.Errorf("period value should be %v, got %v",
|
t.Errorf("period value should be %v, got %v",
|
||||||
@ -233,13 +234,13 @@ func TestResolverReload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ns *NameServer
|
var ns *NameServer
|
||||||
if len(r.Servers) > 0 {
|
if len(r.servers) > 0 {
|
||||||
ns = &r.Servers[0]
|
ns = &r.servers[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if !compareNameServer(ns, tc.ns) {
|
if !compareNameServer(ns, tc.ns) {
|
||||||
t.Errorf("nameserver not equal, should be %v, got %v",
|
t.Errorf("nameserver not equal, should be %v, got %v",
|
||||||
tc.ns, r.Servers)
|
tc.ns, r.servers)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.stopped {
|
if tc.stopped {
|
||||||
@ -265,6 +266,5 @@ func compareNameServer(n1, n2 *NameServer) bool {
|
|||||||
}
|
}
|
||||||
return n1.Addr == n2.Addr &&
|
return n1.Addr == n2.Addr &&
|
||||||
n1.Hostname == n2.Hostname &&
|
n1.Hostname == n2.Hostname &&
|
||||||
n1.Protocol == n2.Protocol &&
|
n1.Protocol == n2.Protocol
|
||||||
n1.Timeout == n2.Timeout
|
|
||||||
}
|
}
|
||||||
|
93
selector.go
93
selector.go
@ -4,15 +4,18 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-log/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNoneAvailable indicates there is no node available.
|
// ErrNoneAvailable indicates there is no node available.
|
||||||
ErrNoneAvailable = errors.New("none available")
|
ErrNoneAvailable = errors.New("none node available")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeSelector as a mechanism to pick nodes and mark their status.
|
// NodeSelector as a mechanism to pick nodes and mark their status.
|
||||||
@ -205,6 +208,94 @@ func (f *FailFilter) String() string {
|
|||||||
return "fail"
|
return "fail"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FastestFilter filter the fastest node
|
||||||
|
type FastestFilter struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
pinger *net.Dialer
|
||||||
|
pingResult map[int]int
|
||||||
|
pingResultTTL map[int]int64
|
||||||
|
|
||||||
|
topCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFastestFilter(pingTimeOut int, topCount int) *FastestFilter {
|
||||||
|
if pingTimeOut == 0 {
|
||||||
|
pingTimeOut = 3000 // 3s
|
||||||
|
}
|
||||||
|
return &FastestFilter{
|
||||||
|
mu: sync.Mutex{},
|
||||||
|
pinger: &net.Dialer{Timeout: time.Millisecond * time.Duration(pingTimeOut)},
|
||||||
|
pingResult: make(map[int]int, 0),
|
||||||
|
pingResultTTL: make(map[int]int64, 0),
|
||||||
|
topCount: topCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FastestFilter) Filter(nodes []Node) []Node {
|
||||||
|
// disabled
|
||||||
|
if f.topCount == 0 {
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// get latency with ttl cache
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
var getNodeLatency = func(node Node) int {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
|
if f.pingResultTTL[node.ID] < now {
|
||||||
|
f.pingResultTTL[node.ID] = now + 5 // tmp
|
||||||
|
|
||||||
|
// get latency
|
||||||
|
go func(node Node) {
|
||||||
|
latency := f.doTcpPing(node.Addr)
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
ttl := 300 - int64(120*r.Float64())
|
||||||
|
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
|
f.pingResult[node.ID] = latency
|
||||||
|
f.pingResultTTL[node.ID] = now + ttl
|
||||||
|
}(node)
|
||||||
|
}
|
||||||
|
return f.pingResult[node.ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort
|
||||||
|
sort.Slice(nodes, func(i, j int) bool {
|
||||||
|
return getNodeLatency(nodes[i]) < getNodeLatency(nodes[j])
|
||||||
|
})
|
||||||
|
|
||||||
|
// split
|
||||||
|
if len(nodes) <= f.topCount {
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes[0:f.topCount]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FastestFilter) String() string {
|
||||||
|
return "fastest"
|
||||||
|
}
|
||||||
|
|
||||||
|
// doTcpPing
|
||||||
|
func (f *FastestFilter) doTcpPing(address string) int {
|
||||||
|
start := time.Now()
|
||||||
|
conn, err := f.pinger.Dial("tcp", address)
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
latency := int(elapsed.Milliseconds())
|
||||||
|
log.Logf("pingDoTCP: %s, latency: %d", address, latency)
|
||||||
|
return latency
|
||||||
|
}
|
||||||
|
|
||||||
// InvalidFilter filters the invalid node.
|
// InvalidFilter filters the invalid node.
|
||||||
// A node is invalid if its port is invalid (negative or zero value).
|
// A node is invalid if its port is invalid (negative or zero value).
|
||||||
type InvalidFilter struct{}
|
type InvalidFilter struct{}
|
||||||
|
@ -127,6 +127,30 @@ func TestFailFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFastestFilter(t *testing.T) {
|
||||||
|
nodes := []Node{
|
||||||
|
Node{ID: 1, marker: &failMarker{}, Addr: "1.0.0.1:80"},
|
||||||
|
Node{ID: 2, marker: &failMarker{}, Addr: "1.0.0.2:80"},
|
||||||
|
Node{ID: 3, marker: &failMarker{}, Addr: "1.0.0.3:80"},
|
||||||
|
}
|
||||||
|
filter := NewFastestFilter(0, 2)
|
||||||
|
|
||||||
|
var print = func(nodes []Node) []string {
|
||||||
|
var rows []string
|
||||||
|
for _, node := range nodes {
|
||||||
|
rows = append(rows, node.Addr)
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
result1 := filter.Filter(nodes)
|
||||||
|
t.Logf("result 1: %+v", print(result1))
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
result2 := filter.Filter(nodes)
|
||||||
|
t.Logf("result 2: %+v", print(result2))
|
||||||
|
}
|
||||||
|
|
||||||
func TestSelector(t *testing.T) {
|
func TestSelector(t *testing.T) {
|
||||||
nodes := []Node{
|
nodes := []Node{
|
||||||
Node{ID: 1, marker: &failMarker{}},
|
Node{ID: 1, marker: &failMarker{}},
|
||||||
|
39
server.go
39
server.go
@ -102,37 +102,6 @@ type Listener interface {
|
|||||||
net.Listener
|
net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpListener struct {
|
|
||||||
net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPListener creates a Listener for TCP proxy server.
|
|
||||||
func TCPListener(addr string) (Listener, error) {
|
|
||||||
laddr, err := net.ResolveTCPAddr("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ln, err := net.ListenTCP("tcp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &tcpListener{Listener: tcpKeepAliveListener{ln}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpKeepAliveListener struct {
|
|
||||||
*net.TCPListener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
|
||||||
tc, err := ln.AcceptTCP()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tc.SetKeepAlive(true)
|
|
||||||
tc.SetKeepAlivePeriod(KeepAliveTime)
|
|
||||||
return tc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func transport(rw1, rw2 io.ReadWriter) error {
|
func transport(rw1, rw2 io.ReadWriter) error {
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
@ -143,11 +112,11 @@ func transport(rw1, rw2 io.ReadWriter) error {
|
|||||||
errc <- copyBuffer(rw2, rw1)
|
errc <- copyBuffer(rw2, rw1)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := <-errc
|
if err := <-errc; err != nil && err != io.EOF {
|
||||||
if err != nil && err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyBuffer(dst io.Writer, src io.Reader) error {
|
func copyBuffer(dst io.Writer, src io.Reader) error {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package gost
|
package gost
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !windows
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package gost
|
package gost
|
||||||
@ -8,7 +9,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
"gopkg.in/xtaci/kcp-go.v4"
|
"github.com/xtaci/kcp-go/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func kcpSigHandler() {
|
func kcpSigHandler() {
|
||||||
|
34
snap/snapcraft.yaml
Normal file
34
snap/snapcraft.yaml
Normal 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
|
||||||
|
|
@ -1,43 +0,0 @@
|
|||||||
name: gost
|
|
||||||
type: app
|
|
||||||
version: '2.9.1'
|
|
||||||
title: GO Simple Tunnel
|
|
||||||
summary: A simple security tunnel written in golang
|
|
||||||
description: |
|
|
||||||
https://github.com/ginuerzh/gost
|
|
||||||
confinement: strict
|
|
||||||
grade: stable
|
|
||||||
base: core18
|
|
||||||
license: MIT
|
|
||||||
parts:
|
|
||||||
gost:
|
|
||||||
plugin: nil
|
|
||||||
build-snaps: [go/1.13/stable]
|
|
||||||
source: https://github.com/ginuerzh/gost.git
|
|
||||||
source-subdir: cmd/gost
|
|
||||||
source-type: git
|
|
||||||
source-branch: '2.9'
|
|
||||||
build-packages:
|
|
||||||
- build-essential
|
|
||||||
override-build: |
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
echo "Starting override-build:"
|
|
||||||
pwd
|
|
||||||
cd $SNAPCRAFT_PART_BUILD
|
|
||||||
GO111MODULE=on CGO_ENABLED=0 go build --ldflags="-s -w"
|
|
||||||
./gost -V
|
|
||||||
|
|
||||||
echo "Installing to ${SNAPCRAFT_PART_INSTALL}..."
|
|
||||||
install -d $SNAPCRAFT_PART_INSTALL/bin
|
|
||||||
cp -v gost $SNAPCRAFT_PART_INSTALL/bin/
|
|
||||||
|
|
||||||
echo "Override-build done!"
|
|
||||||
apps:
|
|
||||||
gost:
|
|
||||||
command: bin/gost
|
|
||||||
plugs:
|
|
||||||
- home
|
|
||||||
- network
|
|
||||||
- network-bind
|
|
||||||
|
|
30
sni.go
30
sni.go
@ -5,6 +5,7 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
@ -16,7 +17,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
dissector "github.com/ginuerzh/tls-dissector"
|
"github.com/asaskevich/govalidator"
|
||||||
|
dissector "github.com/go-gost/tls-dissector"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,8 +31,17 @@ func SNIConnector(host string) Connector {
|
|||||||
return &sniConnector{host: host}
|
return &sniConnector{host: host}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sniConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *sniConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
return &sniClientConn{addr: addr, host: c.host, Conn: conn}, nil
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sniConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sniClientConn{addr: address, host: c.host, Conn: conn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type sniHandler struct {
|
type sniHandler struct {
|
||||||
@ -76,6 +87,10 @@ func (h *sniHandler) Handle(conn net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
handler := &httpHandler{options: h.options}
|
handler := &httpHandler{options: h.options}
|
||||||
handler.Init()
|
handler.Init()
|
||||||
handler.handleRequest(conn, req)
|
handler.handleRequest(conn, req)
|
||||||
@ -260,7 +275,7 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
clientHello := &dissector.ClientHelloHandshake{}
|
clientHello := &dissector.ClientHelloMsg{}
|
||||||
if err := clientHello.Decode(record.Opaque); err != nil {
|
if err := clientHello.Decode(record.Opaque); err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
@ -270,7 +285,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
|
|||||||
|
|
||||||
for _, ext := range clientHello.Extensions {
|
for _, ext := range clientHello.Extensions {
|
||||||
if ext.Type() == 0xFFFE {
|
if ext.Type() == 0xFFFE {
|
||||||
if host, err = decodeServerName(string(ext.Bytes()[4:])); err == nil {
|
b, _ := ext.Encode()
|
||||||
|
if host, err = decodeServerName(string(b)); err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,8 +302,8 @@ func readClientHelloRecord(r io.Reader, host string, isClient bool) ([]byte, str
|
|||||||
host = snExtension.Name
|
host = snExtension.Name
|
||||||
}
|
}
|
||||||
if isClient {
|
if isClient {
|
||||||
clientHello.Extensions = append(clientHello.Extensions,
|
e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
|
||||||
dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name))))
|
clientHello.Extensions = append(clientHello.Extensions, e)
|
||||||
}
|
}
|
||||||
if host != "" {
|
if host != "" {
|
||||||
snExtension.Name = host
|
snExtension.Name = host
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -69,7 +69,7 @@ func sniRoundtrip(client *Client, server *Server, targetURL string, data []byte)
|
|||||||
return errors.New(resp.Status)
|
return errors.New(resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
recv, err := ioutil.ReadAll(resp.Body)
|
recv, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
11
sockopts_linux.go
Normal file
11
sockopts_linux.go
Normal 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
12
sockopts_other.go
Normal 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
|
||||||
|
}
|
333
socks.go
333
socks.go
@ -2,6 +2,7 @@ package gost
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -9,13 +10,14 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks4"
|
"github.com/go-gost/gosocks4"
|
||||||
"github.com/ginuerzh/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
smux "gopkg.in/xtaci/smux.v1"
|
smux "github.com/xtaci/smux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -35,6 +37,10 @@ const (
|
|||||||
CmdUDPTun uint8 = 0xF3
|
CmdUDPTun uint8 = 0xF3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ net.PacketConn = (*socks5UDPTunnelConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type clientSelector struct {
|
type clientSelector struct {
|
||||||
methods []uint8
|
methods []uint8
|
||||||
User *url.Userinfo
|
User *url.Userinfo
|
||||||
@ -201,7 +207,17 @@ func SOCKS5Connector(user *url.Userinfo) Connector {
|
|||||||
return &socks5Connector{User: user}
|
return &socks5Connector{User: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks5Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks5Connector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks5Connector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
cnr := &socks5UDPTunConnector{User: c.User}
|
||||||
|
return cnr.ConnectContext(ctx, conn, network, address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -229,7 +245,7 @@ func (c *socks5Connector) Connect(conn net.Conn, addr string, options ...Connect
|
|||||||
}
|
}
|
||||||
conn = cc
|
conn = cc
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -273,7 +289,16 @@ func SOCKS5BindConnector(user *url.Userinfo) Connector {
|
|||||||
return &socks5BindConnector{User: user}
|
return &socks5BindConnector{User: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks5BindConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks5BindConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -301,7 +326,7 @@ func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...Con
|
|||||||
}
|
}
|
||||||
conn = cc
|
conn = cc
|
||||||
|
|
||||||
laddr, err := net.ResolveTCPAddr("tcp", addr)
|
laddr, err := net.ResolveTCPAddr("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(err)
|
log.Log(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -331,8 +356,8 @@ func (c *socks5BindConnector) Connect(conn net.Conn, addr string, options ...Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
if reply.Rep != gosocks5.Succeeded {
|
||||||
log.Logf("[socks5] bind on %s failure", addr)
|
log.Logf("[socks5] bind on %s failure", address)
|
||||||
return nil, fmt.Errorf("SOCKS5 bind on %s failure", addr)
|
return nil, fmt.Errorf("SOCKS5 bind on %s failure", address)
|
||||||
}
|
}
|
||||||
baddr, err := net.ResolveTCPAddr("tcp", reply.Addr.String())
|
baddr, err := net.ResolveTCPAddr("tcp", reply.Addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -350,8 +375,17 @@ func Socks5MuxBindConnector() Connector {
|
|||||||
return &socks5MuxBindConnector{}
|
return &socks5MuxBindConnector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *socks5MuxBindConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: the conn must be *muxBindClientConn.
|
// NOTE: the conn must be *muxBindClientConn.
|
||||||
func (c *socks5MuxBindConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks5MuxBindConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
accepter, ok := conn.(Accepter)
|
accepter, ok := conn.(Accepter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("wrong connection type")
|
return nil, errors.New("wrong connection type")
|
||||||
@ -513,7 +547,16 @@ func SOCKS5UDPConnector(user *url.Userinfo) Connector {
|
|||||||
return &socks5UDPConnector{User: user}
|
return &socks5UDPConnector{User: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks5UDPConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks5UDPConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "udp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks5UDPConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -541,7 +584,7 @@ func (c *socks5UDPConnector) Connect(conn net.Conn, addr string, options ...Conn
|
|||||||
}
|
}
|
||||||
conn = cc
|
conn = cc
|
||||||
|
|
||||||
taddr, err := net.ResolveUDPAddr("udp", addr)
|
taddr, err := net.ResolveUDPAddr("udp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -596,71 +639,40 @@ func SOCKS5UDPTunConnector(user *url.Userinfo) Connector {
|
|||||||
return &socks5UDPTunConnector{User: user}
|
return &socks5UDPTunConnector{User: user}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks5UDPTunConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks5UDPTunConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "udp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks5UDPTunConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user := opts.User
|
||||||
|
if user == nil {
|
||||||
|
user = c.User
|
||||||
|
}
|
||||||
|
|
||||||
timeout := opts.Timeout
|
timeout := opts.Timeout
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = ConnectTimeout
|
timeout = ConnectTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.SetDeadline(time.Now().Add(timeout))
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
defer conn.SetDeadline(time.Time{})
|
defer conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
user := opts.User
|
taddr, _ := net.ResolveUDPAddr("udp", address)
|
||||||
if user == nil {
|
return newSocks5UDPTunnelConn(conn,
|
||||||
user = c.User
|
nil, taddr,
|
||||||
}
|
|
||||||
cc, err := socks5Handshake(conn,
|
|
||||||
selectorSocks5HandshakeOption(opts.Selector),
|
selectorSocks5HandshakeOption(opts.Selector),
|
||||||
userSocks5HandshakeOption(user),
|
userSocks5HandshakeOption(user),
|
||||||
noTLSSocks5HandshakeOption(opts.NoTLS),
|
noTLSSocks5HandshakeOption(opts.NoTLS),
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
|
|
||||||
taddr, err := net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := gosocks5.NewRequest(CmdUDPTun, &gosocks5.Addr{
|
|
||||||
Type: gosocks5.AddrIPv4,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
log.Log("[socks5] udp\n", req)
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
log.Log("[socks5] udp\n", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
log.Logf("[socks5] udp relay failure")
|
|
||||||
return nil, fmt.Errorf("SOCKS5 udp relay failure")
|
|
||||||
}
|
|
||||||
baddr, err := net.ResolveUDPAddr("udp", reply.Addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Logf("[socks5] udp-tun associate on %s OK", baddr)
|
|
||||||
|
|
||||||
return &udpTunnelConn{Conn: conn, raddr: taddr}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type socks4Connector struct{}
|
type socks4Connector struct{}
|
||||||
@ -670,7 +682,16 @@ func SOCKS4Connector() Connector {
|
|||||||
return &socks4Connector{}
|
return &socks4Connector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks4Connector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks4Connector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks4Connector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -684,7 +705,7 @@ func (c *socks4Connector) Connect(conn net.Conn, addr string, options ...Connect
|
|||||||
conn.SetDeadline(time.Now().Add(timeout))
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
defer conn.SetDeadline(time.Time{})
|
defer conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
taddr, err := net.ResolveTCPAddr("tcp4", addr)
|
taddr, err := net.ResolveTCPAddr("tcp4", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -730,7 +751,16 @@ func SOCKS4AConnector() Connector {
|
|||||||
return &socks4aConnector{}
|
return &socks4aConnector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks4aConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *socks4aConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks4aConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -744,7 +774,7 @@ func (c *socks4aConnector) Connect(conn net.Conn, addr string, options ...Connec
|
|||||||
conn.SetDeadline(time.Now().Add(timeout))
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
defer conn.SetDeadline(time.Time{})
|
defer conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1095,7 +1125,7 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
relay, err := net.ListenUDP("udp", nil)
|
relay, err := net.ListenUDP("udp", &net.UDPAddr{IP: conn.LocalAddr().(*net.TCPAddr).IP, Port: 0}) // use out-going interface's IP
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
||||||
@ -1108,7 +1138,6 @@ func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) {
|
|||||||
defer relay.Close()
|
defer relay.Close()
|
||||||
|
|
||||||
socksAddr := toSocksAddr(relay.LocalAddr())
|
socksAddr := toSocksAddr(relay.LocalAddr())
|
||||||
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) // replace the IP to the out-going interface's
|
|
||||||
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
|
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
|
||||||
if err := reply.Write(conn); err != nil {
|
if err := reply.Write(conn); err != nil {
|
||||||
log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
@ -1371,14 +1400,14 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
|
|||||||
addr := req.Addr.String()
|
addr := req.Addr.String()
|
||||||
|
|
||||||
if !Can("rudp", addr, h.options.Whitelist, h.options.Blacklist) {
|
if !Can("rudp", addr, h.options.Whitelist, h.options.Blacklist) {
|
||||||
log.Logf("[socks5-udp] Unauthorized to udp bind to %s", addr)
|
log.Logf("[socks5] udp-tun Unauthorized to udp bind to %s", addr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bindAddr, _ := net.ResolveUDPAddr("udp", addr)
|
bindAddr, _ := net.ResolveUDPAddr("udp", addr)
|
||||||
uc, err := net.ListenUDP("udp", bindAddr)
|
uc, err := net.ListenUDP("udp", bindAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
log.Logf("[socks5] udp-tun %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer uc.Close()
|
defer uc.Close()
|
||||||
@ -1387,32 +1416,32 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
|
|||||||
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
|
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
|
||||||
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
|
reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr)
|
||||||
if err := reply.Write(conn); err != nil {
|
if err := reply.Write(conn); err != nil {
|
||||||
log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), socksAddr, err)
|
log.Logf("[socks5] udp-tun %s <- %s : %s", conn.RemoteAddr(), socksAddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), socksAddr, reply)
|
log.Logf("[socks5] udp-tun %s <- %s\n%s", conn.RemoteAddr(), socksAddr, reply)
|
||||||
}
|
}
|
||||||
log.Logf("[socks5-udp] %s <-> %s", conn.RemoteAddr(), socksAddr)
|
log.Logf("[socks5] udp-tun %s <-> %s", conn.RemoteAddr(), socksAddr)
|
||||||
h.tunnelServerUDP(conn, uc)
|
h.tunnelServerUDP(conn, uc)
|
||||||
log.Logf("[socks5-udp] %s >-< %s", conn.RemoteAddr(), socksAddr)
|
log.Logf("[socks5] udp-tun %s >-< %s", conn.RemoteAddr(), socksAddr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, err := h.options.Chain.Conn()
|
cc, err := h.options.Chain.Conn()
|
||||||
// connection error
|
// connection error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
log.Logf("[socks5] udp-tun %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
||||||
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
reply := gosocks5.NewReply(gosocks5.Failure, nil)
|
||||||
reply.Write(conn)
|
reply.Write(conn)
|
||||||
log.Logf("[socks5-udp] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, reply)
|
log.Logf("[socks5] udp-tun %s -> %s\n%s", conn.RemoteAddr(), req.Addr, reply)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
cc, err = socks5Handshake(cc, userSocks5HandshakeOption(h.options.Chain.LastNode().User))
|
cc, err = socks5Handshake(cc, userSocks5HandshakeOption(h.options.Chain.LastNode().User))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
log.Logf("[socks5] udp-tun %s -> %s : %s", conn.RemoteAddr(), req.Addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// tunnel <-> tunnel, direct forwarding
|
// tunnel <-> tunnel, direct forwarding
|
||||||
@ -1420,9 +1449,9 @@ func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) {
|
|||||||
// so we don't need to authenticate it, as it's as explicit as whitelisting
|
// so we don't need to authenticate it, as it's as explicit as whitelisting
|
||||||
req.Write(cc)
|
req.Write(cc)
|
||||||
|
|
||||||
log.Logf("[socks5-udp] %s <-> %s [tun]", conn.RemoteAddr(), cc.RemoteAddr())
|
log.Logf("[socks5] udp-tun %s <-> %s", conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
transport(conn, cc)
|
transport(conn, cc)
|
||||||
log.Logf("[socks5-udp] %s >-< %s [tun]", conn.RemoteAddr(), cc.RemoteAddr())
|
log.Logf("[socks5] udp-tun %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err error) {
|
func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err error) {
|
||||||
@ -1440,7 +1469,7 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if h.options.Bypass.Contains(addr.String()) {
|
if h.options.Bypass.Contains(addr.String()) {
|
||||||
log.Log("[udp-tun] [bypass] read from", addr)
|
log.Log("[socks5] udp-tun bypass read from", addr)
|
||||||
continue // bypass
|
continue // bypass
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1448,12 +1477,12 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
|
|||||||
dgram := gosocks5.NewUDPDatagram(
|
dgram := gosocks5.NewUDPDatagram(
|
||||||
gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n])
|
gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n])
|
||||||
if err := dgram.Write(cc); err != nil {
|
if err := dgram.Write(cc); err != nil {
|
||||||
log.Logf("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), dgram.Header.Addr, err)
|
log.Logf("[socks5] udp-tun %s <- %s : %s", cc.RemoteAddr(), dgram.Header.Addr, err)
|
||||||
errc <- err
|
errc <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[udp-tun] %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data))
|
log.Logf("[socks5] udp-tun %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -1473,16 +1502,16 @@ func (h *socks5Handler) tunnelServerUDP(cc net.Conn, pc net.PacketConn) (err err
|
|||||||
continue // drop silently
|
continue // drop silently
|
||||||
}
|
}
|
||||||
if h.options.Bypass.Contains(addr.String()) {
|
if h.options.Bypass.Contains(addr.String()) {
|
||||||
log.Log("[udp-tun] [bypass] write to", addr)
|
log.Log("[socks5] udp-tun bypass write to", addr)
|
||||||
continue // bypass
|
continue // bypass
|
||||||
}
|
}
|
||||||
if _, err := pc.WriteTo(dgram.Data, addr); err != nil {
|
if _, err := pc.WriteTo(dgram.Data, addr); err != nil {
|
||||||
log.Logf("[udp-tun] %s -> %s : %s", cc.RemoteAddr(), addr, err)
|
log.Logf("[socks5] udp-tun %s -> %s : %s", cc.RemoteAddr(), addr, err)
|
||||||
errc <- err
|
errc <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[udp-tun] %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data))
|
log.Logf("[socks5] udp-tun %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -1601,16 +1630,21 @@ func (h *socks5Handler) muxBindOn(conn net.Conn, addr string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: support domain
|
||||||
func toSocksAddr(addr net.Addr) *gosocks5.Addr {
|
func toSocksAddr(addr net.Addr) *gosocks5.Addr {
|
||||||
host := "0.0.0.0"
|
host := "0.0.0.0"
|
||||||
port := 0
|
port := 0
|
||||||
|
addrType := gosocks5.AddrIPv4
|
||||||
if addr != nil {
|
if addr != nil {
|
||||||
h, p, _ := net.SplitHostPort(addr.String())
|
h, p, _ := net.SplitHostPort(addr.String())
|
||||||
host = h
|
host = h
|
||||||
port, _ = strconv.Atoi(p)
|
port, _ = strconv.Atoi(p)
|
||||||
|
if strings.Count(host, ":") > 0 {
|
||||||
|
addrType = gosocks5.AddrIPv6
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &gosocks5.Addr{
|
return &gosocks5.Addr{
|
||||||
Type: gosocks5.AddrIPv4,
|
Type: addrType,
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: uint16(port),
|
Port: uint16(port),
|
||||||
}
|
}
|
||||||
@ -1795,52 +1829,6 @@ func (h *socks4Handler) handleBind(conn net.Conn, req *gosocks4.Request) {
|
|||||||
log.Logf("[socks4-bind] %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
|
log.Logf("[socks4-bind] %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSOCKS5UDPTunnel(chain *Chain, addr net.Addr) (net.Conn, error) {
|
|
||||||
conn, err := chain.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.SetDeadline(time.Now().Add(HandshakeTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
|
|
||||||
node := chain.LastNode()
|
|
||||||
cc, err := socks5Handshake(conn,
|
|
||||||
userSocks5HandshakeOption(node.User),
|
|
||||||
noTLSSocks5HandshakeOption(node.GetBool("notls")),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
|
|
||||||
req := gosocks5.NewRequest(CmdUDPTun, toSocksAddr(addr))
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if Debug {
|
|
||||||
log.Log("[socks5]", req)
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
log.Log("[socks5]", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
conn.Close()
|
|
||||||
return nil, errors.New("UDP tunnel failure")
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type socks5HandshakeOptions struct {
|
type socks5HandshakeOptions struct {
|
||||||
selector gosocks5.Selector
|
selector gosocks5.Selector
|
||||||
user *url.Userinfo
|
user *url.Userinfo
|
||||||
@ -1896,21 +1884,74 @@ func socks5Handshake(conn net.Conn, opts ...socks5HandshakeOption) (net.Conn, er
|
|||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type udpTunnelConn struct {
|
func getSocks5UDPTunnel(chain *Chain, addr net.Addr) (net.Conn, error) {
|
||||||
raddr net.Addr
|
c, err := chain.Conn()
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpTunnelConn) Read(b []byte) (n int, err error) {
|
|
||||||
dgram, err := gosocks5.ReadUDPDatagram(c.Conn)
|
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
func (c *socks5UDPTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||||
dgram, err := gosocks5.ReadUDPDatagram(c.Conn)
|
dgram, err := gosocks5.ReadUDPDatagram(c.Conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -1920,15 +1961,11 @@ func (c *udpTunnelConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpTunnelConn) Write(b []byte) (n int, err error) {
|
func (c *socks5UDPTunnelConn) Write(b []byte) (n int, err error) {
|
||||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(c.raddr)), b)
|
return c.WriteTo(b, c.taddr)
|
||||||
if err = dgram.Write(c.Conn); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpTunnelConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
func (c *socks5UDPTunnelConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(addr)), b)
|
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(b)), 0, toSocksAddr(addr)), b)
|
||||||
if err = dgram.Write(c.Conn); err != nil {
|
if err = dgram.Write(c.Conn); err != nil {
|
||||||
return
|
return
|
||||||
|
663
ss.go
663
ss.go
@ -2,32 +2,51 @@ package gost
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
|
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxSocksAddrLen = 259
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ net.Conn = (*shadowConn)(nil)
|
||||||
|
_ net.PacketConn = (*shadowUDPPacketConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type shadowConnector struct {
|
type shadowConnector struct {
|
||||||
Cipher *url.Userinfo
|
cipher core.Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShadowConnector creates a Connector for shadowsocks proxy client.
|
// ShadowConnector creates a Connector for shadowsocks proxy client.
|
||||||
// It accepts a cipher info for shadowsocks data encryption/decryption.
|
// It accepts an optional cipher info for shadowsocks data encryption/decryption.
|
||||||
// The cipher must not be nil.
|
func ShadowConnector(info *url.Userinfo) Connector {
|
||||||
func ShadowConnector(cipher *url.Userinfo) Connector {
|
return &shadowConnector{
|
||||||
return &shadowConnector{Cipher: cipher}
|
cipher: initShadowCipher(info),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *shadowConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -38,37 +57,43 @@ func (c *shadowConnector) Connect(conn net.Conn, addr string, options ...Connect
|
|||||||
timeout = ConnectTimeout
|
timeout = ConnectTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socksAddr, err := gosocks5.NewAddr(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawaddr := sPool.Get().([]byte)
|
||||||
|
defer sPool.Put(rawaddr)
|
||||||
|
|
||||||
|
n, err := socksAddr.Encode(rawaddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
conn.SetDeadline(time.Now().Add(timeout))
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
defer conn.SetDeadline(time.Time{})
|
defer conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
rawaddr, err := ss.RawAddr(addr)
|
if c.cipher != nil {
|
||||||
if err != nil {
|
conn = c.cipher.StreamConn(conn)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var method, password string
|
|
||||||
cp := opts.User
|
|
||||||
if cp == nil {
|
|
||||||
cp = c.Cipher
|
|
||||||
}
|
|
||||||
if cp != nil {
|
|
||||||
method = cp.Username()
|
|
||||||
password, _ = cp.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
cipher, err := ss.NewCipher(method, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sc := &shadowConn{
|
sc := &shadowConn{
|
||||||
Conn: ss.NewConn(conn, cipher),
|
Conn: conn,
|
||||||
}
|
}
|
||||||
_, err = sc.Write(rawaddr)
|
|
||||||
return sc, err
|
// write the addr at once.
|
||||||
|
if opts.NoDelay {
|
||||||
|
if _, err := sc.Write(rawaddr[:n]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sc.wbuf.Write(rawaddr[:n]) // cache the header
|
||||||
|
}
|
||||||
|
|
||||||
|
return sc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type shadowHandler struct {
|
type shadowHandler struct {
|
||||||
|
cipher core.Cipher
|
||||||
options *HandlerOptions
|
options *HandlerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,37 +113,34 @@ func (h *shadowHandler) Init(options ...HandlerOption) {
|
|||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(h.options)
|
opt(h.options)
|
||||||
}
|
}
|
||||||
|
if len(h.options.Users) > 0 {
|
||||||
|
h.cipher = initShadowCipher(h.options.Users[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *shadowHandler) Handle(conn net.Conn) {
|
func (h *shadowHandler) Handle(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
var method, password string
|
if h.cipher != nil {
|
||||||
users := h.options.Users
|
conn = &shadowConn{
|
||||||
if len(users) > 0 {
|
Conn: h.cipher.StreamConn(conn),
|
||||||
method = users[0].Username()
|
|
||||||
password, _ = users[0].Password()
|
|
||||||
}
|
}
|
||||||
cipher, err := ss.NewCipher(method, password)
|
|
||||||
if err != nil {
|
|
||||||
log.Logf("[ss] %s -> %s : %s",
|
|
||||||
conn.RemoteAddr(), conn.LocalAddr(), err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
conn = &shadowConn{Conn: ss.NewConn(conn, cipher)}
|
|
||||||
|
|
||||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout))
|
conn.SetReadDeadline(time.Now().Add(ReadTimeout))
|
||||||
host, err := h.getRequest(conn)
|
|
||||||
|
addr, err := readSocksAddr(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[ss] %s -> %s : %s",
|
log.Logf("[ss] %s -> %s : %s",
|
||||||
conn.RemoteAddr(), conn.LocalAddr(), err)
|
conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// clear timer
|
|
||||||
conn.SetReadDeadline(time.Time{})
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
log.Logf("[ss] %s -> %s -> %s",
|
host := addr.String()
|
||||||
conn.RemoteAddr(), h.options.Node.String(), host)
|
log.Logf("[ss] %s -> %s",
|
||||||
|
conn.RemoteAddr(), host)
|
||||||
|
|
||||||
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
||||||
log.Logf("[ss] %s - %s : Unauthorized to tcp connect to %s",
|
log.Logf("[ss] %s - %s : Unauthorized to tcp connect to %s",
|
||||||
@ -181,84 +203,28 @@ func (h *shadowHandler) Handle(conn net.Conn) {
|
|||||||
log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), host)
|
log.Logf("[ss] %s >-< %s", conn.RemoteAddr(), host)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
idType = 0 // address type index
|
|
||||||
idIP0 = 1 // ip address start index
|
|
||||||
idDmLen = 1 // domain address length index
|
|
||||||
idDm0 = 2 // domain address start index
|
|
||||||
|
|
||||||
typeIPv4 = 1 // type is ipv4 address
|
|
||||||
typeDm = 3 // type is domain address
|
|
||||||
typeIPv6 = 4 // type is ipv6 address
|
|
||||||
|
|
||||||
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
|
|
||||||
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
|
|
||||||
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
|
|
||||||
lenHmacSha1 = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// This function is copied from shadowsocks library with some modification.
|
|
||||||
func (h *shadowHandler) getRequest(r io.Reader) (host string, err error) {
|
|
||||||
// buf size should at least have the same size with the largest possible
|
|
||||||
// request size (when addrType is 3, domain name has at most 256 bytes)
|
|
||||||
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
|
|
||||||
buf := make([]byte, smallBufferSize)
|
|
||||||
|
|
||||||
// read till we get possible domain length field
|
|
||||||
if _, err = io.ReadFull(r, buf[:idType+1]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var reqStart, reqEnd int
|
|
||||||
addrType := buf[idType]
|
|
||||||
switch addrType & ss.AddrMask {
|
|
||||||
case typeIPv4:
|
|
||||||
reqStart, reqEnd = idIP0, idIP0+lenIPv4
|
|
||||||
case typeIPv6:
|
|
||||||
reqStart, reqEnd = idIP0, idIP0+lenIPv6
|
|
||||||
case typeDm:
|
|
||||||
if _, err = io.ReadFull(r, buf[idType+1:idDmLen+1]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reqStart, reqEnd = idDm0, idDm0+int(buf[idDmLen])+lenDmBase
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = io.ReadFull(r, buf[reqStart:reqEnd]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return string for typeIP is not most efficient, but browsers (Chrome,
|
|
||||||
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
|
|
||||||
// big problem.
|
|
||||||
switch addrType & ss.AddrMask {
|
|
||||||
case typeIPv4:
|
|
||||||
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
|
|
||||||
case typeIPv6:
|
|
||||||
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
|
|
||||||
case typeDm:
|
|
||||||
host = string(buf[idDm0 : idDm0+int(buf[idDmLen])])
|
|
||||||
}
|
|
||||||
// parse port
|
|
||||||
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
|
|
||||||
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type shadowUDPConnector struct {
|
type shadowUDPConnector struct {
|
||||||
Cipher *url.Userinfo
|
cipher core.Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShadowUDPConnector creates a Connector for shadowsocks UDP client.
|
// ShadowUDPConnector creates a Connector for shadowsocks UDP client.
|
||||||
// It accepts a cipher info for shadowsocks data encryption/decryption.
|
// It accepts an optional cipher info for shadowsocks data encryption/decryption.
|
||||||
// The cipher must not be nil.
|
func ShadowUDPConnector(info *url.Userinfo) Connector {
|
||||||
func ShadowUDPConnector(cipher *url.Userinfo) Connector {
|
return &shadowUDPConnector{
|
||||||
return &shadowUDPConnector{Cipher: cipher}
|
cipher: initShadowCipher(info),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowUDPConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *shadowUDPConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "udp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowUDPConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -272,223 +238,197 @@ func (c *shadowUDPConnector) Connect(conn net.Conn, addr string, options ...Conn
|
|||||||
conn.SetDeadline(time.Now().Add(timeout))
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
defer conn.SetDeadline(time.Time{})
|
defer conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
rawaddr, err := ss.RawAddr(addr)
|
taddr, _ := net.ResolveUDPAddr(network, address)
|
||||||
if err != nil {
|
if taddr == nil {
|
||||||
return nil, err
|
taddr = &net.UDPAddr{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var method, password string
|
pc, ok := conn.(net.PacketConn)
|
||||||
if c.Cipher != nil {
|
if ok {
|
||||||
method = c.Cipher.Username()
|
if c.cipher != nil {
|
||||||
password, _ = c.Cipher.Password()
|
pc = c.cipher.PacketConn(pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher, err := ss.NewCipher(method, password)
|
return &shadowUDPPacketConn{
|
||||||
if err != nil {
|
PacketConn: pc,
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := ss.NewSecurePacketConn(&shadowPacketConn{conn}, cipher, false)
|
|
||||||
return &shadowUDPConn{
|
|
||||||
PacketConn: sc,
|
|
||||||
raddr: conn.RemoteAddr(),
|
raddr: conn.RemoteAddr(),
|
||||||
header: rawaddr,
|
taddr: taddr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.cipher != nil {
|
||||||
|
conn = &shadowConn{
|
||||||
|
Conn: c.cipher.StreamConn(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &socks5UDPTunnelConn{
|
||||||
|
Conn: conn,
|
||||||
|
taddr: taddr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type shadowUDPListener struct {
|
type shadowUDPHandler struct {
|
||||||
ln net.PacketConn
|
cipher core.Cipher
|
||||||
connChan chan net.Conn
|
|
||||||
errChan chan error
|
|
||||||
ttl time.Duration
|
|
||||||
connMap udpConnMap
|
|
||||||
config *UDPForwardListenConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShadowUDPListener creates a Listener for shadowsocks UDP relay server.
|
|
||||||
func ShadowUDPListener(addr string, cipher *url.Userinfo, cfg *UDPForwardListenConfig) (Listener, error) {
|
|
||||||
laddr, err := net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ln, err := net.ListenUDP("udp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var method, password string
|
|
||||||
if cipher != nil {
|
|
||||||
method = cipher.Username()
|
|
||||||
password, _ = cipher.Password()
|
|
||||||
}
|
|
||||||
cp, err := ss.NewCipher(method, password)
|
|
||||||
if err != nil {
|
|
||||||
ln.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg == nil {
|
|
||||||
cfg = &UDPForwardListenConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
backlog := cfg.Backlog
|
|
||||||
if backlog <= 0 {
|
|
||||||
backlog = defaultBacklog
|
|
||||||
}
|
|
||||||
|
|
||||||
l := &shadowUDPListener{
|
|
||||||
ln: ss.NewSecurePacketConn(ln, cp, false),
|
|
||||||
connChan: make(chan net.Conn, backlog),
|
|
||||||
errChan: make(chan error, 1),
|
|
||||||
config: cfg,
|
|
||||||
}
|
|
||||||
go l.listenLoop()
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *shadowUDPListener) listenLoop() {
|
|
||||||
for {
|
|
||||||
b := make([]byte, mediumBufferSize)
|
|
||||||
n, raddr, err := l.ln.ReadFrom(b)
|
|
||||||
if err != nil {
|
|
||||||
log.Logf("[ssu] peer -> %s : %s", l.Addr(), err)
|
|
||||||
l.ln.Close()
|
|
||||||
l.errChan <- err
|
|
||||||
close(l.errChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, ok := l.connMap.Get(raddr.String())
|
|
||||||
if !ok {
|
|
||||||
conn = newUDPServerConn(l.ln, raddr, l.config.TTL, l.config.QueueSize)
|
|
||||||
conn.onClose = func() {
|
|
||||||
l.connMap.Delete(raddr.String())
|
|
||||||
log.Logf("[ssu] %s closed (%d)", raddr, l.connMap.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case l.connChan <- conn:
|
|
||||||
l.connMap.Set(raddr.String(), conn)
|
|
||||||
log.Logf("[ssu] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size())
|
|
||||||
default:
|
|
||||||
conn.Close()
|
|
||||||
log.Logf("[ssu] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case conn.rChan <- b[:n]: // we keep the addr info so that the handler can identify the destination.
|
|
||||||
if Debug {
|
|
||||||
log.Logf("[ssu] %s >>> %s : length %d", raddr, l.Addr(), n)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Logf("[ssu] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *shadowUDPListener) Accept() (conn net.Conn, err error) {
|
|
||||||
var ok bool
|
|
||||||
select {
|
|
||||||
case conn = <-l.connChan:
|
|
||||||
case err, ok = <-l.errChan:
|
|
||||||
if !ok {
|
|
||||||
err = errors.New("accpet on closed listener")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *shadowUDPListener) Addr() net.Addr {
|
|
||||||
return l.ln.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *shadowUDPListener) Close() error {
|
|
||||||
err := l.ln.Close()
|
|
||||||
l.connMap.Range(func(k interface{}, v *udpServerConn) bool {
|
|
||||||
v.Close()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type shadowUDPdHandler struct {
|
|
||||||
ttl time.Duration
|
|
||||||
options *HandlerOptions
|
options *HandlerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShadowUDPdHandler creates a server Handler for shadowsocks UDP relay server.
|
// ShadowUDPHandler creates a server Handler for shadowsocks UDP relay server.
|
||||||
func ShadowUDPdHandler(opts ...HandlerOption) Handler {
|
func ShadowUDPHandler(opts ...HandlerOption) Handler {
|
||||||
h := &shadowUDPdHandler{}
|
h := &shadowUDPHandler{}
|
||||||
h.Init(opts...)
|
h.Init(opts...)
|
||||||
|
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *shadowUDPdHandler) Init(options ...HandlerOption) {
|
func (h *shadowUDPHandler) Init(options ...HandlerOption) {
|
||||||
if h.options == nil {
|
if h.options == nil {
|
||||||
h.options = &HandlerOptions{}
|
h.options = &HandlerOptions{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(h.options)
|
opt(h.options)
|
||||||
}
|
}
|
||||||
|
if len(h.options.Users) > 0 {
|
||||||
|
h.cipher = initShadowCipher(h.options.Users[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *shadowUDPdHandler) Handle(conn net.Conn) {
|
func (h *shadowUDPHandler) Handle(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
var err error
|
|
||||||
var cc net.PacketConn
|
var cc net.PacketConn
|
||||||
if h.options.Chain.IsEmpty() {
|
c, err := h.options.Chain.DialContext(context.Background(), "udp", "")
|
||||||
cc, err = net.ListenUDP("udp", nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
|
log.Logf("[ssu] %s: %s", conn.LocalAddr(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
var ok bool
|
||||||
var c net.Conn
|
cc, ok = c.(net.PacketConn)
|
||||||
c, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
if !ok {
|
||||||
if err != nil {
|
log.Logf("[ssu] %s: not a packet connection", conn.LocalAddr())
|
||||||
log.Logf("[ssu] %s - : %s", conn.LocalAddr(), err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cc = &udpTunnelConn{Conn: c}
|
|
||||||
}
|
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
|
pc, ok := conn.(net.PacketConn)
|
||||||
|
if ok {
|
||||||
|
if h.cipher != nil {
|
||||||
|
pc = h.cipher.PacketConn(pc)
|
||||||
|
}
|
||||||
|
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
|
h.transportPacket(pc, cc)
|
||||||
|
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.cipher != nil {
|
||||||
|
conn = &shadowConn{
|
||||||
|
Conn: h.cipher.StreamConn(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
log.Logf("[ssu] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
h.transportUDP(conn, cc)
|
h.transportUDP(conn, cc)
|
||||||
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
log.Logf("[ssu] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error {
|
func (h *shadowUDPHandler) transportPacket(conn, cc net.PacketConn) (err error) {
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
var clientAddr net.Addr
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
err := func() error {
|
||||||
|
b := mPool.Get().([]byte)
|
||||||
|
defer mPool.Put(b)
|
||||||
|
|
||||||
|
n, addr, err := conn.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if clientAddr == nil {
|
||||||
|
clientAddr = addr
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewBuffer(b[:n])
|
||||||
|
saddr, err := readSocksAddr(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
taddr, err := net.ResolveUDPAddr("udp", saddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[ssu] %s >>> %s length: %d", addr, taddr, r.Len())
|
||||||
|
}
|
||||||
|
_, err = cc.WriteTo(r.Bytes(), taddr)
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
err := func() error {
|
||||||
|
b := mPool.Get().([]byte)
|
||||||
|
defer mPool.Put(b)
|
||||||
|
|
||||||
|
n, addr, err := cc.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if clientAddr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[ssu] %s <<< %s length: %d", clientAddr, addr, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n])
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
if err = dgram.Write(&buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = conn.WriteTo(buf.Bytes()[3:], clientAddr)
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-errc:
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *shadowUDPHandler) transportUDP(conn net.Conn, cc net.PacketConn) error {
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
er := func() (err error) {
|
er := func() (err error) {
|
||||||
b := lPool.Get().([]byte)
|
dgram, err := gosocks5.ReadUDPDatagram(conn)
|
||||||
defer lPool.Put(b)
|
|
||||||
|
|
||||||
b[0] = 0
|
|
||||||
b[1] = 0
|
|
||||||
b[2] = 0
|
|
||||||
|
|
||||||
// add rsv and frag fields to make it the standard SOCKS5 UDP datagram
|
|
||||||
n, err := sc.Read(b[3:])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
// log.Logf("[ssu] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
return
|
|
||||||
}
|
|
||||||
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
|
|
||||||
if err != nil {
|
|
||||||
log.Logf("[ssu] %s - %s : %s", sc.RemoteAddr(), sc.LocalAddr(), err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[ssu] %s >>> %s length: %d", sc.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data))
|
log.Logf("[ssu] %s >>> %s length: %d",
|
||||||
|
conn.RemoteAddr(), dgram.Header.Addr.String(), len(dgram.Data))
|
||||||
}
|
}
|
||||||
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
|
addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -512,28 +452,25 @@ func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
er := func() (err error) {
|
er := func() (err error) {
|
||||||
b := lPool.Get().([]byte)
|
b := mPool.Get().([]byte)
|
||||||
defer lPool.Put(b)
|
defer mPool.Put(b)
|
||||||
|
|
||||||
n, addr, err := cc.ReadFrom(b)
|
n, addr, err := cc.ReadFrom(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[ssu] %s <<< %s length: %d", sc.RemoteAddr(), addr, n)
|
log.Logf("[ssu] %s <<< %s length: %d", conn.RemoteAddr(), addr, n)
|
||||||
}
|
}
|
||||||
if h.options.Bypass.Contains(addr.String()) {
|
if h.options.Bypass.Contains(addr.String()) {
|
||||||
log.Log("[ssu] bypass", addr)
|
log.Log("[ssu] bypass", addr)
|
||||||
return // bypass
|
return // bypass
|
||||||
}
|
}
|
||||||
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(addr)), b[:n])
|
dgram := gosocks5.NewUDPDatagram(
|
||||||
|
gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n])
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
dgram.Write(&buf)
|
dgram.Write(&buf)
|
||||||
if buf.Len() < 10 {
|
_, err = conn.Write(buf.Bytes())
|
||||||
log.Logf("[ssu] %s <- %s : invalid udp datagram", sc.RemoteAddr(), addr)
|
|
||||||
return // ignore invalid datagram
|
|
||||||
}
|
|
||||||
_, err = sc.Write(buf.Bytes()[3:])
|
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -555,34 +492,28 @@ func (h *shadowUDPdHandler) transportUDP(sc net.Conn, cc net.PacketConn) error {
|
|||||||
// we wrap around it to make io.Copy happy.
|
// we wrap around it to make io.Copy happy.
|
||||||
type shadowConn struct {
|
type shadowConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
|
wbuf bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowConn) Write(b []byte) (n int, err error) {
|
func (c *shadowConn) Write(b []byte) (n int, err error) {
|
||||||
n = len(b) // force byte length consistent
|
n = len(b) // force byte length consistent
|
||||||
|
if c.wbuf.Len() > 0 {
|
||||||
|
c.wbuf.Write(b) // append the data to the cached header
|
||||||
|
_, err = c.Conn.Write(c.wbuf.Bytes())
|
||||||
|
c.wbuf.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
_, err = c.Conn.Write(b)
|
_, err = c.Conn.Write(b)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type shadowUDPConn struct {
|
type shadowUDPPacketConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
raddr net.Addr
|
raddr net.Addr
|
||||||
header []byte
|
taddr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowUDPConn) Write(b []byte) (n int, err error) {
|
func (c *shadowUDPPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||||
n = len(b) // force byte length consistent
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
if _, err = buf.Write(c.header); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = buf.Write(b); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *shadowUDPConn) Read(b []byte) (n int, err error) {
|
|
||||||
buf := mPool.Get().([]byte)
|
buf := mPool.Get().([]byte)
|
||||||
defer mPool.Put(buf)
|
defer mPool.Put(buf)
|
||||||
|
|
||||||
@ -600,23 +531,117 @@ func (c *shadowUDPConn) Read(b []byte) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
n = copy(b, dgram.Data)
|
n = copy(b, dgram.Data)
|
||||||
|
addr, err = net.ResolveUDPAddr("udp", dgram.Header.Addr.String())
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowUDPPacketConn) Read(b []byte) (n int, err error) {
|
||||||
|
n, _, err = c.ReadFrom(b)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowUDPConn) RemoteAddr() net.Addr {
|
func (c *shadowUDPPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
sa, err := gosocks5.NewAddr(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rawaddr [maxSocksAddrLen]byte
|
||||||
|
nn, err := sa.Encode(rawaddr[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := mPool.Get().([]byte)
|
||||||
|
defer mPool.Put(buf)
|
||||||
|
|
||||||
|
copy(buf, rawaddr[:nn])
|
||||||
|
n = copy(buf[nn:], b)
|
||||||
|
_, err = c.PacketConn.WriteTo(buf[:n+nn], c.raddr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowUDPPacketConn) Write(b []byte) (n int, err error) {
|
||||||
|
return c.WriteTo(b, c.taddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowUDPPacketConn) RemoteAddr() net.Addr {
|
||||||
return c.raddr
|
return c.raddr
|
||||||
}
|
}
|
||||||
|
|
||||||
type shadowPacketConn struct {
|
type shadowCipher struct {
|
||||||
net.Conn
|
cipher *ss.Cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
|
||||||
n, err = c.Conn.Read(b)
|
return ss.NewConn(conn, c.cipher.Copy())
|
||||||
addr = c.Conn.RemoteAddr()
|
}
|
||||||
|
|
||||||
|
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
|
||||||
|
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
func initShadowCipher(info *url.Userinfo) (cipher core.Cipher) {
|
||||||
|
var method, password string
|
||||||
|
if info != nil {
|
||||||
|
method = info.Username()
|
||||||
|
password, _ = info.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "" || password == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cp, _ := ss.NewCipher(method, password)
|
||||||
|
if cp != nil {
|
||||||
|
cipher = &shadowCipher{cipher: cp}
|
||||||
|
}
|
||||||
|
if cipher == nil {
|
||||||
|
var err error
|
||||||
|
cipher, err = core.PickCipher(method, nil, password)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[ss] %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shadowPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
func readSocksAddr(r io.Reader) (*gosocks5.Addr, error) {
|
||||||
return c.Conn.Write(b)
|
addr := &gosocks5.Addr{}
|
||||||
|
b := sPool.Get().([]byte)
|
||||||
|
defer sPool.Put(b)
|
||||||
|
|
||||||
|
_, err := io.ReadFull(r, b[:1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addr.Type = b[0]
|
||||||
|
|
||||||
|
switch addr.Type {
|
||||||
|
case gosocks5.AddrIPv4:
|
||||||
|
_, err = io.ReadFull(r, b[:net.IPv4len])
|
||||||
|
addr.Host = net.IP(b[0:net.IPv4len]).String()
|
||||||
|
case gosocks5.AddrIPv6:
|
||||||
|
_, err = io.ReadFull(r, b[:net.IPv6len])
|
||||||
|
addr.Host = net.IP(b[0:net.IPv6len]).String()
|
||||||
|
case gosocks5.AddrDomain:
|
||||||
|
if _, err = io.ReadFull(r, b[:1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addrlen := int(b[0])
|
||||||
|
_, err = io.ReadFull(r, b[:addrlen])
|
||||||
|
addr.Host = string(b[:addrlen])
|
||||||
|
default:
|
||||||
|
return nil, gosocks5.ErrBadAddrType
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.ReadFull(r, b[:2])
|
||||||
|
addr.Port = binary.BigEndian.Uint16(b[:2])
|
||||||
|
return addr, err
|
||||||
}
|
}
|
||||||
|
229
ss2.go
229
ss2.go
@ -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
|
|
||||||
}
|
|
425
ss2_test.go
425
ss2_test.go
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
216
ss_test.go
216
ss_test.go
@ -19,88 +19,118 @@ var ssTests = []struct {
|
|||||||
serverCipher *url.Userinfo
|
serverCipher *url.Userinfo
|
||||||
pass bool
|
pass bool
|
||||||
}{
|
}{
|
||||||
{nil, nil, false},
|
{nil, nil, true},
|
||||||
{&url.Userinfo{}, &url.Userinfo{}, false},
|
{&url.Userinfo{}, &url.Userinfo{}, true},
|
||||||
{url.User("abc"), url.User("abc"), false},
|
{url.User("abc"), url.User("abc"), true},
|
||||||
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false},
|
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true},
|
||||||
|
|
||||||
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), false},
|
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
|
||||||
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
|
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
|
||||||
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
|
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
|
||||||
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
|
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
|
||||||
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
|
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
|
||||||
|
|
||||||
{url.User("aes-192-cfb"), url.User("aes-192-cfb"), false},
|
{url.User("aes-192-cfb"), url.User("aes-192-cfb"), true},
|
||||||
{url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false},
|
{url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false},
|
||||||
{url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false},
|
{url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false},
|
||||||
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false},
|
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false},
|
||||||
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true},
|
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true},
|
||||||
|
|
||||||
{url.User("aes-256-cfb"), url.User("aes-256-cfb"), false},
|
{url.User("aes-256-cfb"), url.User("aes-256-cfb"), true},
|
||||||
{url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false},
|
{url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false},
|
||||||
{url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false},
|
{url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false},
|
||||||
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false},
|
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false},
|
||||||
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true},
|
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true},
|
||||||
|
|
||||||
{url.User("aes-128-ctr"), url.User("aes-128-ctr"), false},
|
{url.User("aes-128-ctr"), url.User("aes-128-ctr"), true},
|
||||||
{url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false},
|
{url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false},
|
||||||
{url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false},
|
{url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false},
|
||||||
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false},
|
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false},
|
||||||
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true},
|
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true},
|
||||||
|
|
||||||
{url.User("aes-192-ctr"), url.User("aes-192-ctr"), false},
|
{url.User("aes-192-ctr"), url.User("aes-192-ctr"), true},
|
||||||
{url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false},
|
{url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false},
|
||||||
{url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false},
|
{url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false},
|
||||||
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false},
|
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false},
|
||||||
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true},
|
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true},
|
||||||
|
|
||||||
{url.User("aes-256-ctr"), url.User("aes-256-ctr"), false},
|
{url.User("aes-256-ctr"), url.User("aes-256-ctr"), true},
|
||||||
{url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false},
|
{url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false},
|
||||||
{url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false},
|
{url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false},
|
||||||
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false},
|
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false},
|
||||||
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true},
|
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true},
|
||||||
|
|
||||||
{url.User("des-cfb"), url.User("des-cfb"), false},
|
{url.User("des-cfb"), url.User("des-cfb"), true},
|
||||||
{url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false},
|
{url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false},
|
||||||
{url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false},
|
{url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false},
|
||||||
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false},
|
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false},
|
||||||
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true},
|
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true},
|
||||||
|
|
||||||
{url.User("bf-cfb"), url.User("bf-cfb"), false},
|
{url.User("bf-cfb"), url.User("bf-cfb"), true},
|
||||||
{url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false},
|
{url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false},
|
||||||
{url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false},
|
{url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false},
|
||||||
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false},
|
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false},
|
||||||
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true},
|
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true},
|
||||||
|
|
||||||
{url.User("cast5-cfb"), url.User("cast5-cfb"), false},
|
{url.User("cast5-cfb"), url.User("cast5-cfb"), true},
|
||||||
{url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false},
|
{url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false},
|
||||||
{url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false},
|
{url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false},
|
||||||
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false},
|
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false},
|
||||||
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true},
|
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true},
|
||||||
|
|
||||||
{url.User("rc4-md5"), url.User("rc4-md5"), false},
|
{url.User("rc4-md5"), url.User("rc4-md5"), true},
|
||||||
{url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false},
|
{url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false},
|
||||||
{url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false},
|
{url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false},
|
||||||
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false},
|
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false},
|
||||||
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true},
|
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true},
|
||||||
|
|
||||||
{url.User("chacha20"), url.User("chacha20"), false},
|
{url.User("chacha20"), url.User("chacha20"), true},
|
||||||
{url.User("chacha20"), url.UserPassword("chacha20", "123456"), false},
|
{url.User("chacha20"), url.UserPassword("chacha20", "123456"), false},
|
||||||
{url.UserPassword("chacha20", "123456"), url.User("chacha20"), false},
|
{url.UserPassword("chacha20", "123456"), url.User("chacha20"), false},
|
||||||
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false},
|
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false},
|
||||||
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true},
|
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true},
|
||||||
|
|
||||||
{url.User("chacha20-ietf"), url.User("chacha20-ietf"), false},
|
{url.User("chacha20-ietf"), url.User("chacha20-ietf"), true},
|
||||||
{url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false},
|
{url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false},
|
||||||
{url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false},
|
{url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false},
|
||||||
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false},
|
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false},
|
||||||
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true},
|
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true},
|
||||||
|
|
||||||
{url.User("salsa20"), url.User("salsa20"), false},
|
{url.User("salsa20"), url.User("salsa20"), true},
|
||||||
{url.User("salsa20"), url.UserPassword("salsa20", "123456"), false},
|
{url.User("salsa20"), url.UserPassword("salsa20", "123456"), false},
|
||||||
{url.UserPassword("salsa20", "123456"), url.User("salsa20"), false},
|
{url.UserPassword("salsa20", "123456"), url.User("salsa20"), false},
|
||||||
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false},
|
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false},
|
||||||
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true},
|
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("xchacha20"), url.User("xchacha20"), true},
|
||||||
|
{url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false},
|
||||||
|
{url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false},
|
||||||
|
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false},
|
||||||
|
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true},
|
||||||
|
{url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "abc"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("AES-128-GCM"), url.User("AES-128-GCM"), true},
|
||||||
|
{url.User("AES-128-GCM"), url.UserPassword("AES-128-GCM", "123456"), false},
|
||||||
|
{url.UserPassword("AES-128-GCM", "123456"), url.User("AES-128-GCM"), false},
|
||||||
|
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "abc"), false},
|
||||||
|
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("AES-192-GCM"), url.User("AES-192-GCM"), true},
|
||||||
|
{url.User("AES-192-GCM"), url.UserPassword("AES-192-GCM", "123456"), false},
|
||||||
|
{url.UserPassword("AES-192-GCM", "123456"), url.User("AES-192-GCM"), false},
|
||||||
|
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "abc"), false},
|
||||||
|
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("AES-256-GCM"), url.User("AES-256-GCM"), true},
|
||||||
|
{url.User("AES-256-GCM"), url.UserPassword("AES-256-GCM", "123456"), false},
|
||||||
|
{url.UserPassword("AES-256-GCM", "123456"), url.User("AES-256-GCM"), false},
|
||||||
|
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "abc"), false},
|
||||||
|
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "123456"), true},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssProxyTests = []struct {
|
var ssProxyTests = []struct {
|
||||||
@ -108,16 +138,20 @@ var ssProxyTests = []struct {
|
|||||||
serverCipher *url.Userinfo
|
serverCipher *url.Userinfo
|
||||||
pass bool
|
pass bool
|
||||||
}{
|
}{
|
||||||
{nil, nil, false},
|
{nil, nil, true},
|
||||||
{&url.Userinfo{}, &url.Userinfo{}, false},
|
{&url.Userinfo{}, &url.Userinfo{}, true},
|
||||||
{url.User("abc"), url.User("abc"), false},
|
{url.User("abc"), url.User("abc"), true},
|
||||||
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), false},
|
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true},
|
||||||
|
|
||||||
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), false},
|
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
|
||||||
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
|
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
|
||||||
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
|
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
|
||||||
// {url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
|
|
||||||
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
|
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true},
|
||||||
|
{url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true},
|
||||||
}
|
}
|
||||||
|
|
||||||
func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
|
func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
|
||||||
@ -142,7 +176,7 @@ func ssProxyRoundtrip(targetURL string, data []byte, clientInfo *url.Userinfo, s
|
|||||||
return proxyRoundtrip(client, server, targetURL, data)
|
return proxyRoundtrip(client, server, targetURL, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSSProxy(t *testing.T) {
|
func TestShadowTCP(t *testing.T) {
|
||||||
httpSrv := httptest.NewServer(httpTestHandler)
|
httpSrv := httptest.NewServer(httpTestHandler)
|
||||||
defer httpSrv.Close()
|
defer httpSrv.Close()
|
||||||
|
|
||||||
@ -300,9 +334,128 @@ func BenchmarkSSProxyParallel(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ssuTests = []struct {
|
||||||
|
clientCipher *url.Userinfo
|
||||||
|
serverCipher *url.Userinfo
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{nil, nil, true},
|
||||||
|
{&url.Userinfo{}, &url.Userinfo{}, true},
|
||||||
|
{url.User("abc"), url.User("abc"), true},
|
||||||
|
{url.UserPassword("abc", "def"), url.UserPassword("abc", "def"), true},
|
||||||
|
|
||||||
|
{url.User("aes-128-cfb"), url.User("aes-128-cfb"), true},
|
||||||
|
{url.User("aes-128-cfb"), url.UserPassword("aes-128-cfb", "123456"), false},
|
||||||
|
{url.UserPassword("aes-128-cfb", "123456"), url.User("aes-128-cfb"), false},
|
||||||
|
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "abc"), false},
|
||||||
|
{url.UserPassword("aes-128-cfb", "123456"), url.UserPassword("aes-128-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("aes-192-cfb"), url.User("aes-192-cfb"), true},
|
||||||
|
{url.User("aes-192-cfb"), url.UserPassword("aes-192-cfb", "123456"), false},
|
||||||
|
{url.UserPassword("aes-192-cfb", "123456"), url.User("aes-192-cfb"), false},
|
||||||
|
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "abc"), false},
|
||||||
|
{url.UserPassword("aes-192-cfb", "123456"), url.UserPassword("aes-192-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("aes-256-cfb"), url.User("aes-256-cfb"), true},
|
||||||
|
{url.User("aes-256-cfb"), url.UserPassword("aes-256-cfb", "123456"), false},
|
||||||
|
{url.UserPassword("aes-256-cfb", "123456"), url.User("aes-256-cfb"), false},
|
||||||
|
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "abc"), false},
|
||||||
|
{url.UserPassword("aes-256-cfb", "123456"), url.UserPassword("aes-256-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("aes-128-ctr"), url.User("aes-128-ctr"), true},
|
||||||
|
{url.User("aes-128-ctr"), url.UserPassword("aes-128-ctr", "123456"), false},
|
||||||
|
{url.UserPassword("aes-128-ctr", "123456"), url.User("aes-128-ctr"), false},
|
||||||
|
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "abc"), false},
|
||||||
|
{url.UserPassword("aes-128-ctr", "123456"), url.UserPassword("aes-128-ctr", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("aes-192-ctr"), url.User("aes-192-ctr"), true},
|
||||||
|
{url.User("aes-192-ctr"), url.UserPassword("aes-192-ctr", "123456"), false},
|
||||||
|
{url.UserPassword("aes-192-ctr", "123456"), url.User("aes-192-ctr"), false},
|
||||||
|
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "abc"), false},
|
||||||
|
{url.UserPassword("aes-192-ctr", "123456"), url.UserPassword("aes-192-ctr", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("aes-256-ctr"), url.User("aes-256-ctr"), true},
|
||||||
|
{url.User("aes-256-ctr"), url.UserPassword("aes-256-ctr", "123456"), false},
|
||||||
|
{url.UserPassword("aes-256-ctr", "123456"), url.User("aes-256-ctr"), false},
|
||||||
|
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "abc"), false},
|
||||||
|
{url.UserPassword("aes-256-ctr", "123456"), url.UserPassword("aes-256-ctr", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("des-cfb"), url.User("des-cfb"), true},
|
||||||
|
{url.User("des-cfb"), url.UserPassword("des-cfb", "123456"), false},
|
||||||
|
{url.UserPassword("des-cfb", "123456"), url.User("des-cfb"), false},
|
||||||
|
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "abc"), false},
|
||||||
|
{url.UserPassword("des-cfb", "123456"), url.UserPassword("des-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("bf-cfb"), url.User("bf-cfb"), true},
|
||||||
|
{url.User("bf-cfb"), url.UserPassword("bf-cfb", "123456"), false},
|
||||||
|
{url.UserPassword("bf-cfb", "123456"), url.User("bf-cfb"), false},
|
||||||
|
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "abc"), false},
|
||||||
|
{url.UserPassword("bf-cfb", "123456"), url.UserPassword("bf-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("cast5-cfb"), url.User("cast5-cfb"), true},
|
||||||
|
{url.User("cast5-cfb"), url.UserPassword("cast5-cfb", "123456"), false},
|
||||||
|
{url.UserPassword("cast5-cfb", "123456"), url.User("cast5-cfb"), false},
|
||||||
|
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "abc"), false},
|
||||||
|
{url.UserPassword("cast5-cfb", "123456"), url.UserPassword("cast5-cfb", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("rc4-md5"), url.User("rc4-md5"), true},
|
||||||
|
{url.User("rc4-md5"), url.UserPassword("rc4-md5", "123456"), false},
|
||||||
|
{url.UserPassword("rc4-md5", "123456"), url.User("rc4-md5"), false},
|
||||||
|
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "abc"), false},
|
||||||
|
{url.UserPassword("rc4-md5", "123456"), url.UserPassword("rc4-md5", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("chacha20"), url.User("chacha20"), true},
|
||||||
|
{url.User("chacha20"), url.UserPassword("chacha20", "123456"), false},
|
||||||
|
{url.UserPassword("chacha20", "123456"), url.User("chacha20"), false},
|
||||||
|
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "abc"), false},
|
||||||
|
{url.UserPassword("chacha20", "123456"), url.UserPassword("chacha20", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("chacha20-ietf"), url.User("chacha20-ietf"), true},
|
||||||
|
{url.User("chacha20-ietf"), url.UserPassword("chacha20-ietf", "123456"), false},
|
||||||
|
{url.UserPassword("chacha20-ietf", "123456"), url.User("chacha20-ietf"), false},
|
||||||
|
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "abc"), false},
|
||||||
|
{url.UserPassword("chacha20-ietf", "123456"), url.UserPassword("chacha20-ietf", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("salsa20"), url.User("salsa20"), true},
|
||||||
|
{url.User("salsa20"), url.UserPassword("salsa20", "123456"), false},
|
||||||
|
{url.UserPassword("salsa20", "123456"), url.User("salsa20"), false},
|
||||||
|
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "abc"), false},
|
||||||
|
{url.UserPassword("salsa20", "123456"), url.UserPassword("salsa20", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("xchacha20"), url.User("xchacha20"), true},
|
||||||
|
{url.User("xchacha20"), url.UserPassword("xchacha20", "123456"), false},
|
||||||
|
{url.UserPassword("xchacha20", "123456"), url.User("xchacha20"), false},
|
||||||
|
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "abc"), false},
|
||||||
|
{url.UserPassword("xchacha20", "123456"), url.UserPassword("xchacha20", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("CHACHA20-IETF-POLY1305"), url.User("CHACHA20-IETF-POLY1305"), true},
|
||||||
|
{url.User("CHACHA20-IETF-POLY1305"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.User("CHACHA20-IETF-POLY1305"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "abc"), false},
|
||||||
|
{url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), url.UserPassword("CHACHA20-IETF-POLY1305", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("AES-128-GCM"), url.User("AES-128-GCM"), true},
|
||||||
|
{url.User("AES-128-GCM"), url.UserPassword("AES-128-GCM", "123456"), false},
|
||||||
|
{url.UserPassword("AES-128-GCM", "123456"), url.User("AES-128-GCM"), false},
|
||||||
|
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "abc"), false},
|
||||||
|
{url.UserPassword("AES-128-GCM", "123456"), url.UserPassword("AES-128-GCM", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("AES-192-GCM"), url.User("AES-192-GCM"), true},
|
||||||
|
{url.User("AES-192-GCM"), url.UserPassword("AES-192-GCM", "123456"), false},
|
||||||
|
{url.UserPassword("AES-192-GCM", "123456"), url.User("AES-192-GCM"), false},
|
||||||
|
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "abc"), false},
|
||||||
|
{url.UserPassword("AES-192-GCM", "123456"), url.UserPassword("AES-192-GCM", "123456"), true},
|
||||||
|
|
||||||
|
{url.User("AES-256-GCM"), url.User("AES-256-GCM"), true},
|
||||||
|
{url.User("AES-256-GCM"), url.UserPassword("AES-256-GCM", "123456"), false},
|
||||||
|
{url.UserPassword("AES-256-GCM", "123456"), url.User("AES-256-GCM"), false},
|
||||||
|
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "abc"), false},
|
||||||
|
{url.UserPassword("AES-256-GCM", "123456"), url.UserPassword("AES-256-GCM", "123456"), true},
|
||||||
|
}
|
||||||
|
|
||||||
func shadowUDPRoundtrip(t *testing.T, host string, data []byte,
|
func shadowUDPRoundtrip(t *testing.T, host string, data []byte,
|
||||||
clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
|
clientInfo *url.Userinfo, serverInfo *url.Userinfo) error {
|
||||||
ln, err := ShadowUDPListener("localhost:0", serverInfo, nil)
|
ln, err := UDPListener("localhost:0", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -313,7 +466,9 @@ func shadowUDPRoundtrip(t *testing.T, host string, data []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Handler: ShadowUDPdHandler(),
|
Handler: ShadowUDPHandler(
|
||||||
|
UsersHandlerOption(serverInfo),
|
||||||
|
),
|
||||||
Listener: ln,
|
Listener: ln,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +482,7 @@ func TestShadowUDP(t *testing.T) {
|
|||||||
sendData := make([]byte, 128)
|
sendData := make([]byte, 128)
|
||||||
rand.Read(sendData)
|
rand.Read(sendData)
|
||||||
|
|
||||||
for i, tc := range ssTests {
|
for i, tc := range ssuTests {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||||
udpSrv := newUDPTestServer(udpTestHandler)
|
udpSrv := newUDPTestServer(udpTestHandler)
|
||||||
@ -352,7 +507,6 @@ func TestShadowUDP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix shadowsocks UDP relay benchmark.
|
|
||||||
func BenchmarkShadowUDP(b *testing.B) {
|
func BenchmarkShadowUDP(b *testing.B) {
|
||||||
udpSrv := newUDPTestServer(udpTestHandler)
|
udpSrv := newUDPTestServer(udpTestHandler)
|
||||||
udpSrv.Start()
|
udpSrv.Start()
|
||||||
@ -361,7 +515,7 @@ func BenchmarkShadowUDP(b *testing.B) {
|
|||||||
sendData := make([]byte, 128)
|
sendData := make([]byte, 128)
|
||||||
rand.Read(sendData)
|
rand.Read(sendData)
|
||||||
|
|
||||||
ln, err := ShadowUDPListener("localhost:0", url.UserPassword("chacha20-ietf", "123456"), nil)
|
ln, err := UDPListener("localhost:0", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
@ -372,7 +526,9 @@ func BenchmarkShadowUDP(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Handler: ShadowUDPdHandler(),
|
Handler: ShadowUDPHandler(
|
||||||
|
UsersHandlerOption(url.UserPassword("chacha20-ietf", "123456")),
|
||||||
|
),
|
||||||
Listener: ln,
|
Listener: ln,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
ssh.go
133
ssh.go
@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -26,19 +27,53 @@ const (
|
|||||||
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
|
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var errSessionDead = errors.New("session is dead")
|
||||||
errSessionDead = errors.New("session is dead")
|
|
||||||
)
|
|
||||||
|
|
||||||
type sshDirectForwardConnector struct {
|
// ParseSSHKeyFile parses ssh key file.
|
||||||
|
func ParseSSHKeyFile(fp string) (ssh.Signer, error) {
|
||||||
|
key, err := os.ReadFile(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ssh.ParsePrivateKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseSSHAuthorizedKeysFile parses ssh Authorized Keys file.
|
||||||
|
func ParseSSHAuthorizedKeysFile(fp string) (map[string]bool, error) {
|
||||||
|
authorizedKeysBytes, err := os.ReadFile(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authorizedKeysMap := make(map[string]bool)
|
||||||
|
for len(authorizedKeysBytes) > 0 {
|
||||||
|
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authorizedKeysMap[string(pubKey.Marshal())] = true
|
||||||
|
authorizedKeysBytes = rest
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizedKeysMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshDirectForwardConnector struct{}
|
||||||
|
|
||||||
// SSHDirectForwardConnector creates a Connector for SSH TCP direct port forwarding.
|
// SSHDirectForwardConnector creates a Connector for SSH TCP direct port forwarding.
|
||||||
func SSHDirectForwardConnector() Connector {
|
func SSHDirectForwardConnector() Connector {
|
||||||
return &sshDirectForwardConnector{}
|
return &sshDirectForwardConnector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", raddr, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sshDirectForwardConnector) ConnectContext(ctx context.Context, conn net.Conn, network, raddr string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
opts := &ConnectOptions{}
|
opts := &ConnectOptions{}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(opts)
|
option(opts)
|
||||||
@ -65,15 +100,23 @@ func (c *sshDirectForwardConnector) Connect(conn net.Conn, raddr string, options
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type sshRemoteForwardConnector struct {
|
type sshRemoteForwardConnector struct{}
|
||||||
}
|
|
||||||
|
|
||||||
// SSHRemoteForwardConnector creates a Connector for SSH TCP remote port forwarding.
|
// SSHRemoteForwardConnector creates a Connector for SSH TCP remote port forwarding.
|
||||||
func SSHRemoteForwardConnector() Connector {
|
func SSHRemoteForwardConnector() Connector {
|
||||||
return &sshRemoteForwardConnector{}
|
return &sshRemoteForwardConnector{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options ...ConnectOption) (net.Conn, error) {
|
func (c *sshRemoteForwardConnector) Connect(conn net.Conn, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
return c.ConnectContext(context.Background(), conn, "tcp", address, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sshRemoteForwardConnector) ConnectContext(ctx context.Context, conn net.Conn, network, address string, options ...ConnectOption) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("%s unsupported", network)
|
||||||
|
}
|
||||||
|
|
||||||
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
cc, ok := conn.(*sshNopConn) // TODO: this is an ugly type assertion, need to find a better solution.
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("ssh: wrong connection type")
|
return nil, errors.New("ssh: wrong connection type")
|
||||||
@ -87,10 +130,10 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
|
|||||||
if cc.session == nil || cc.session.client == nil {
|
if cc.session == nil || cc.session.client == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(addr, ":") {
|
if strings.HasPrefix(address, ":") {
|
||||||
addr = "0.0.0.0" + addr
|
address = "0.0.0.0" + address
|
||||||
}
|
}
|
||||||
ln, err := cc.session.client.Listen("tcp", addr)
|
ln, err := cc.session.client.Listen("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -99,7 +142,7 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
|
|||||||
for {
|
for {
|
||||||
rc, err := ln.Accept()
|
rc, err := ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), addr, err)
|
log.Logf("[ssh-rtcp] %s <-> %s accpet : %s", ln.Addr(), address, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// log.Log("[ssh-rtcp] accept", rc.LocalAddr(), rc.RemoteAddr())
|
// log.Log("[ssh-rtcp] accept", rc.LocalAddr(), rc.RemoteAddr())
|
||||||
@ -107,7 +150,7 @@ func (c *sshRemoteForwardConnector) Connect(conn net.Conn, addr string, options
|
|||||||
case cc.session.connChan <- rc:
|
case cc.session.connChan <- rc:
|
||||||
default:
|
default:
|
||||||
rc.Close()
|
rc.Close()
|
||||||
log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), addr)
|
log.Logf("[ssh-rtcp] %s - %s: connection queue is full", ln.Addr(), address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -183,11 +226,15 @@ func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOp
|
|||||||
}
|
}
|
||||||
if opts.User != nil {
|
if opts.User != nil {
|
||||||
config.User = opts.User.Username()
|
config.User = opts.User.Username()
|
||||||
password, _ := opts.User.Password()
|
if password, _ := opts.User.Password(); password != "" {
|
||||||
config.Auth = []ssh.AuthMethod{
|
config.Auth = []ssh.AuthMethod{
|
||||||
ssh.Password(password),
|
ssh.Password(password),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if opts.SSHConfig != nil && opts.SSHConfig.Key != nil {
|
||||||
|
config.Auth = append(config.Auth, ssh.PublicKeys(opts.SSHConfig.Key))
|
||||||
|
}
|
||||||
|
|
||||||
tr.sessionMutex.Lock()
|
tr.sessionMutex.Lock()
|
||||||
defer tr.sessionMutex.Unlock()
|
defer tr.sessionMutex.Unlock()
|
||||||
@ -199,6 +246,7 @@ func (tr *sshForwardTransporter) Handshake(conn net.Conn, options ...HandshakeOp
|
|||||||
if !ok || session.client == nil {
|
if !ok || session.client == nil {
|
||||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
|
sshConn, chans, reqs, err := ssh.NewClientConn(conn, opts.Addr, &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Log("ssh", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
delete(tr.sessions, opts.Addr)
|
delete(tr.sessions, opts.Addr)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -287,16 +335,20 @@ func (tr *sshTunnelTransporter) Handshake(conn net.Conn, options ...HandshakeOpt
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := ssh.ClientConfig{
|
config := ssh.ClientConfig{
|
||||||
|
Timeout: timeout,
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
}
|
}
|
||||||
// TODO: support pubkey auth.
|
|
||||||
if opts.User != nil {
|
if opts.User != nil {
|
||||||
config.User = opts.User.Username()
|
config.User = opts.User.Username()
|
||||||
password, _ := opts.User.Password()
|
if password, _ := opts.User.Password(); password != "" {
|
||||||
config.Auth = []ssh.AuthMethod{
|
config.Auth = []ssh.AuthMethod{
|
||||||
ssh.Password(password),
|
ssh.Password(password),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if opts.SSHConfig != nil && opts.SSHConfig.Key != nil {
|
||||||
|
config.Auth = append(config.Auth, ssh.PublicKeys(opts.SSHConfig.Key))
|
||||||
|
}
|
||||||
|
|
||||||
tr.sessionMutex.Lock()
|
tr.sessionMutex.Lock()
|
||||||
defer tr.sessionMutex.Unlock()
|
defer tr.sessionMutex.Unlock()
|
||||||
@ -594,7 +646,7 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", addr) //tie to the client connection
|
ln, err := net.Listen("tcp", addr) // tie to the client connection
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log("[ssh-rtcp]", err)
|
log.Log("[ssh-rtcp]", err)
|
||||||
req.Reply(false, nil)
|
req.Reply(false, nil)
|
||||||
@ -666,6 +718,8 @@ func (h *sshForwardHandler) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Reque
|
|||||||
type SSHConfig struct {
|
type SSHConfig struct {
|
||||||
Authenticator Authenticator
|
Authenticator Authenticator
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
Key ssh.Signer
|
||||||
|
AuthorizedKeys map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type sshTunnelListener struct {
|
type sshTunnelListener struct {
|
||||||
@ -686,21 +740,22 @@ func SSHTunnelListener(addr string, config *SSHConfig) (Listener, error) {
|
|||||||
config = &SSHConfig{}
|
config = &SSHConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
sshConfig := &ssh.ServerConfig{}
|
sshConfig := &ssh.ServerConfig{
|
||||||
sshConfig.PasswordCallback = defaultSSHPasswordCallback(config.Authenticator)
|
PasswordCallback: defaultSSHPasswordCallback(config.Authenticator),
|
||||||
if config.Authenticator == nil {
|
PublicKeyCallback: defaultSSHPublicKeyCallback(config.AuthorizedKeys),
|
||||||
sshConfig.NoClientAuth = true
|
|
||||||
}
|
|
||||||
tlsConfig := config.TLSConfig
|
|
||||||
if tlsConfig == nil {
|
|
||||||
tlsConfig = DefaultTLSConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
ln.Close()
|
ln.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sshConfig.AddHostKey(signer)
|
sshConfig.AddHostKey(signer)
|
||||||
|
|
||||||
@ -805,9 +860,13 @@ func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PasswordCallbackFunc is a callback function used by SSH server.
|
// PasswordCallbackFunc is a callback function used by SSH server.
|
||||||
|
// It authenticates user using a password.
|
||||||
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
|
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
|
||||||
|
|
||||||
func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc {
|
func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc {
|
||||||
|
if au == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||||
if au.Authenticate(conn.User(), string(password)) {
|
if au.Authenticate(conn.User(), string(password)) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -817,6 +876,28 @@ func defaultSSHPasswordCallback(au Authenticator) PasswordCallbackFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PublicKeyCallbackFunc is a callback function used by SSH server.
|
||||||
|
// It offers a public key for authentication.
|
||||||
|
type PublicKeyCallbackFunc func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error)
|
||||||
|
|
||||||
|
func defaultSSHPublicKeyCallback(keys map[string]bool) PublicKeyCallbackFunc {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
|
if keys[string(pubKey.Marshal())] {
|
||||||
|
return &ssh.Permissions{
|
||||||
|
// Record the public key used for authentication.
|
||||||
|
Extensions: map[string]string{
|
||||||
|
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown public key for %q", c.User())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type sshNopConn struct {
|
type sshNopConn struct {
|
||||||
session *sshSession
|
session *sshSession
|
||||||
}
|
}
|
||||||
|
66
tcp.go
Normal file
66
tcp.go
Normal 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
7
tls.go
@ -2,7 +2,6 @@ package gost
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
@ -10,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
|
|
||||||
smux "gopkg.in/xtaci/smux.v1"
|
smux "github.com/xtaci/smux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tlsTransporter struct {
|
type tlsTransporter struct {
|
||||||
@ -290,6 +289,9 @@ func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration)
|
|||||||
return nil, err
|
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 crypto/tls is doing verification, there's no need to do our own.
|
||||||
if tlsConfig.InsecureSkipVerify == false {
|
if tlsConfig.InsecureSkipVerify == false {
|
||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
@ -320,6 +322,7 @@ func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration)
|
|||||||
tlsConn.Close()
|
tlsConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
return tlsConn, err
|
return tlsConn, err
|
||||||
}
|
}
|
||||||
|
78
tuntap.go
78
tuntap.go
@ -1,6 +1,7 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -12,7 +13,6 @@ import (
|
|||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
|
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
|
||||||
"github.com/shadowsocks/go-shadowsocks2/shadowstream"
|
|
||||||
"github.com/songgao/water"
|
"github.com/songgao/water"
|
||||||
"github.com/songgao/water/waterutil"
|
"github.com/songgao/water/waterutil"
|
||||||
"github.com/xtaci/tcpraw"
|
"github.com/xtaci/tcpraw"
|
||||||
@ -39,11 +39,19 @@ func ipProtocol(p waterutil.IPProtocol) string {
|
|||||||
return fmt.Sprintf("unknown(%d)", p)
|
return fmt.Sprintf("unknown(%d)", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPRoute is an IP routing entry.
|
||||||
|
type IPRoute struct {
|
||||||
|
Dest *net.IPNet
|
||||||
|
Gateway net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// TunConfig is the config for TUN device.
|
||||||
type TunConfig struct {
|
type TunConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Addr string
|
Addr string
|
||||||
|
Peer string // peer addr of point-to-point on MacOS
|
||||||
MTU int
|
MTU int
|
||||||
Routes []string
|
Routes []IPRoute
|
||||||
Gateway string
|
Gateway string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,9 +168,17 @@ func (h *tunHandler) Handle(conn net.Conn) {
|
|||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
// fake tcp mode will be ignored when the client specifies a chain.
|
// fake tcp mode will be ignored when the client specifies a chain.
|
||||||
if raddr != nil && !h.options.Chain.IsEmpty() {
|
if raddr != nil && !h.options.Chain.IsEmpty() {
|
||||||
var cc net.Conn
|
cc, err := h.options.Chain.DialContext(context.Background(), "udp", raddr.String())
|
||||||
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
if err != nil {
|
||||||
pc = &udpTunnelConn{Conn: cc, raddr: raddr}
|
return err
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
pc, ok = cc.(net.PacketConn)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("not a packet connection")
|
||||||
|
log.Logf("[tun] %s - %s: %s", conn.LocalAddr(), raddr, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if h.options.TCPMode {
|
if h.options.TCPMode {
|
||||||
if raddr != nil {
|
if raddr != nil {
|
||||||
@ -224,6 +240,20 @@ func (h *tunHandler) initTunnelConn(pc net.PacketConn) (net.PacketConn, error) {
|
|||||||
return pc, nil
|
return pc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *tunHandler) findRouteFor(dst net.IP) net.Addr {
|
||||||
|
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
|
||||||
|
return v.(net.Addr)
|
||||||
|
}
|
||||||
|
for _, route := range h.options.IPRoutes {
|
||||||
|
if route.Dest.Contains(dst) && route.Gateway != nil {
|
||||||
|
if v, ok := h.routes.Load(ipToTunRouteKey(route.Gateway)); ok {
|
||||||
|
return v.(net.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.Addr) error {
|
func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.Addr) error {
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
|
|
||||||
@ -279,15 +309,15 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addr net.Addr
|
addr := h.findRouteFor(dst)
|
||||||
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
|
|
||||||
addr = v.(net.Addr)
|
|
||||||
}
|
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
log.Logf("[tun] no route for %s -> %s", src, dst)
|
log.Logf("[tun] no route for %s -> %s", src, dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[tun] find route: %s -> %s", dst, addr)
|
||||||
|
}
|
||||||
if _, err := conn.WriteTo(b[:n], addr); err != nil {
|
if _, err := conn.WriteTo(b[:n], addr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -305,11 +335,11 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
|
|||||||
for {
|
for {
|
||||||
err := func() error {
|
err := func() error {
|
||||||
b := sPool.Get().([]byte)
|
b := sPool.Get().([]byte)
|
||||||
defer mPool.Put(b)
|
defer sPool.Put(b)
|
||||||
|
|
||||||
n, addr, err := conn.ReadFrom(b)
|
n, addr, err := conn.ReadFrom(b)
|
||||||
if err != nil &&
|
if err != nil &&
|
||||||
err != shadowaead.ErrShortPacket && err != shadowstream.ErrShortPacket {
|
err != shadowaead.ErrShortPacket {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,11 +391,11 @@ func (h *tunHandler) transportTun(tun net.Conn, conn net.PacketConn, raddr net.A
|
|||||||
log.Logf("[tun] new route: %s -> %s", src, addr)
|
log.Logf("[tun] new route: %s -> %s", src, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
|
if addr := h.findRouteFor(dst); addr != nil {
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Logf("[tun] find route: %s -> %s", dst, v)
|
log.Logf("[tun] find route: %s -> %s", dst, addr)
|
||||||
}
|
}
|
||||||
_, err := conn.WriteTo(b[:n], v.(net.Addr))
|
_, err := conn.WriteTo(b[:n], addr)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,6 +437,7 @@ func etherType(et waterutil.Ethertype) string {
|
|||||||
return fmt.Sprintf("unknown(%v)", et)
|
return fmt.Sprintf("unknown(%v)", et)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TapConfig is the config for TAP device.
|
||||||
type TapConfig struct {
|
type TapConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Addr string
|
Addr string
|
||||||
@ -527,9 +558,17 @@ func (h *tapHandler) Handle(conn net.Conn) {
|
|||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
// fake tcp mode will be ignored when the client specifies a chain.
|
// fake tcp mode will be ignored when the client specifies a chain.
|
||||||
if raddr != nil && !h.options.Chain.IsEmpty() {
|
if raddr != nil && !h.options.Chain.IsEmpty() {
|
||||||
var cc net.Conn
|
cc, err := h.options.Chain.DialContext(context.Background(), "udp", raddr.String())
|
||||||
cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil)
|
if err != nil {
|
||||||
pc = &udpTunnelConn{Conn: cc, raddr: raddr}
|
return err
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
pc, ok = cc.(net.PacketConn)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("not a packet connection")
|
||||||
|
log.Logf("[tap] %s - %s: %s", conn.LocalAddr(), raddr, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if h.options.TCPMode {
|
if h.options.TCPMode {
|
||||||
if raddr != nil {
|
if raddr != nil {
|
||||||
@ -658,11 +697,11 @@ func (h *tapHandler) transportTap(tap net.Conn, conn net.PacketConn, raddr net.A
|
|||||||
for {
|
for {
|
||||||
err := func() error {
|
err := func() error {
|
||||||
b := sPool.Get().([]byte)
|
b := sPool.Get().([]byte)
|
||||||
defer mPool.Put(b)
|
defer sPool.Put(b)
|
||||||
|
|
||||||
n, addr, err := conn.ReadFrom(b)
|
n, addr, err := conn.ReadFrom(b)
|
||||||
if err != nil &&
|
if err != nil &&
|
||||||
err != shadowaead.ErrShortPacket && err != shadowstream.ErrShortPacket {
|
err != shadowaead.ErrShortPacket {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,6 +809,7 @@ func (c *tunTapConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
return &net.OpError{Op: "set", Net: "tuntap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsIPv6Multicast reports whether the address addr is an IPv6 multicast address.
|
||||||
func IsIPv6Multicast(addr net.HardwareAddr) bool {
|
func IsIPv6Multicast(addr net.HardwareAddr) bool {
|
||||||
return addr[0] == 0x33 && addr[1] == 0x33
|
return addr[0] == 0x33 && addr[1] == 0x33
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,12 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
mtu = DefaultMTU
|
mtu = DefaultMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu)
|
peer := cfg.Peer
|
||||||
|
if peer == "" {
|
||||||
|
peer = ip.String()
|
||||||
|
}
|
||||||
|
cmd := fmt.Sprintf("ifconfig %s inet %s %s mtu %d up",
|
||||||
|
ifce.Name(), cfg.Addr, peer, mtu)
|
||||||
log.Log("[tun]", cmd)
|
log.Log("[tun]", cmd)
|
||||||
args := strings.Split(cmd, " ")
|
args := strings.Split(cmd, " ")
|
||||||
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
||||||
@ -37,7 +42,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = addRoutes(ifce.Name(), cfg.Routes...); err != nil {
|
if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,12 +63,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRoutes(ifName string, routes ...string) error {
|
func addTunRoutes(ifName string, routes ...IPRoute) error {
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route == "" {
|
if route.Dest == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := fmt.Sprintf("route add -net %s -interface %s", route, ifName)
|
cmd := fmt.Sprintf("route add -net %s -interface %s", route.Dest.String(), ifName)
|
||||||
log.Log("[tun]", cmd)
|
log.Log("[tun]", cmd)
|
||||||
args := strings.Split(cmd, " ")
|
args := strings.Split(cmd, " ")
|
||||||
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
||||||
|
@ -3,15 +3,15 @@ package gost
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/netlink"
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
"github.com/milosgajdos83/tenus"
|
|
||||||
"github.com/songgao/water"
|
"github.com/songgao/water"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
||||||
ip, ipNet, err := net.ParseCIDR(cfg.Addr)
|
ip, _, err := net.ParseCIDR(cfg.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -26,35 +26,21 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
link, err := tenus.NewLinkFrom(ifce.Name())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mtu := cfg.MTU
|
mtu := cfg.MTU
|
||||||
if mtu <= 0 {
|
if mtu <= 0 {
|
||||||
mtu = DefaultMTU
|
mtu = DefaultMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)
|
if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil {
|
||||||
log.Log("[tun]", cmd)
|
log.Log(err)
|
||||||
if er := link.SetLinkMTU(mtu); er != nil {
|
|
||||||
err = fmt.Errorf("%s: %v", cmd, er)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())
|
if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil {
|
||||||
log.Log("[tun]", cmd)
|
log.Log(err)
|
||||||
if er := link.SetLinkIp(ip, ipNet); er != nil {
|
|
||||||
err = fmt.Errorf("%s: %v", cmd, er)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name())
|
if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil {
|
||||||
log.Log("[tun]", cmd)
|
log.Log(err)
|
||||||
if er := link.SetLinkUp(); er != nil {
|
|
||||||
err = fmt.Errorf("%s: %v", cmd, er)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
|
if err = addTunRoutes(ifce.Name(), cfg.Routes...); err != nil {
|
||||||
@ -74,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) {
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ifce, err := water.New(water.Config{
|
ifce, err := water.New(water.Config{
|
||||||
DeviceType: water.TAP,
|
DeviceType: water.TAP,
|
||||||
@ -89,35 +78,23 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
link, err := tenus.NewLinkFrom(ifce.Name())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mtu := cfg.MTU
|
mtu := cfg.MTU
|
||||||
if mtu <= 0 {
|
if mtu <= 0 {
|
||||||
mtu = DefaultMTU
|
mtu = DefaultMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)
|
if err = exeCmd(fmt.Sprintf("ip link set dev %s mtu %d", ifce.Name(), mtu)); err != nil {
|
||||||
log.Log("[tap]", cmd)
|
log.Log(err)
|
||||||
if er := link.SetLinkMTU(mtu); er != nil {
|
|
||||||
err = fmt.Errorf("%s: %v", cmd, er)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())
|
if cfg.Addr != "" {
|
||||||
log.Log("[tap]", cmd)
|
if err = exeCmd(fmt.Sprintf("ip address add %s dev %s", cfg.Addr, ifce.Name())); err != nil {
|
||||||
if er := link.SetLinkIp(ip, ipNet); er != nil {
|
log.Log(err)
|
||||||
err = fmt.Errorf("%s: %v", cmd, er)
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = fmt.Sprintf("ip link set dev %s up", ifce.Name())
|
if err = exeCmd(fmt.Sprintf("ip link set dev %s up", ifce.Name())); err != nil {
|
||||||
log.Log("[tap]", cmd)
|
log.Log(err)
|
||||||
if er := link.SetLinkUp(); er != nil {
|
|
||||||
err = fmt.Errorf("%s: %v", cmd, er)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
|
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
|
||||||
@ -136,15 +113,17 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTunRoutes(ifName string, routes ...string) error {
|
func addTunRoutes(ifName string, routes ...IPRoute) error {
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route == "" {
|
if route.Dest == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := fmt.Sprintf("ip route add %s dev %s", route, ifName)
|
cmd := fmt.Sprintf("ip route add %s dev %s", route.Dest.String(), ifName)
|
||||||
log.Logf("[tun] %s", cmd)
|
log.Logf("[tun] %s", cmd)
|
||||||
if err := netlink.AddRoute(route, "", "", ifName); err != nil {
|
|
||||||
return fmt.Errorf("%s: %v", cmd, err)
|
args := strings.Split(cmd, " ")
|
||||||
|
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
||||||
|
log.Logf("[tun] %s: %v", cmd, er)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -157,9 +136,22 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
|
|||||||
}
|
}
|
||||||
cmd := fmt.Sprintf("ip route add %s via %s dev %s", route, gw, ifName)
|
cmd := fmt.Sprintf("ip route add %s via %s dev %s", route, gw, ifName)
|
||||||
log.Logf("[tap] %s", cmd)
|
log.Logf("[tap] %s", cmd)
|
||||||
if err := netlink.AddRoute(route, "", gw, ifName); err != nil {
|
|
||||||
return fmt.Errorf("%s: %v", cmd, err)
|
args := strings.Split(cmd, " ")
|
||||||
|
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
||||||
|
log.Logf("[tap] %s: %v", cmd, er)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exeCmd(cmd string) error {
|
||||||
|
log.Log(cmd)
|
||||||
|
|
||||||
|
args := strings.Split(cmd, " ")
|
||||||
|
if err := exec.Command(args[0], args[1:]...).Run(); err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", cmd, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux && !windows && !darwin
|
||||||
// +build !linux,!windows,!darwin
|
// +build !linux,!windows,!darwin
|
||||||
|
|
||||||
package gost
|
package gost
|
||||||
@ -55,10 +56,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
||||||
ip, _, err := net.ParseCIDR(cfg.Addr)
|
ip, _, _ := net.ParseCIDR(cfg.Addr)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ifce, err := water.New(water.Config{
|
ifce, err := water.New(water.Config{
|
||||||
DeviceType: water.TAP,
|
DeviceType: water.TAP,
|
||||||
@ -72,7 +70,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
mtu = DefaultMTU
|
mtu = DefaultMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu)
|
var cmd string
|
||||||
|
if cfg.Addr != "" {
|
||||||
|
cmd = fmt.Sprintf("ifconfig %s inet %s mtu %d up", ifce.Name(), cfg.Addr, mtu)
|
||||||
|
} else {
|
||||||
|
cmd = fmt.Sprintf("ifconfig %s mtu %d up", ifce.Name(), mtu)
|
||||||
|
}
|
||||||
log.Log("[tap]", cmd)
|
log.Log("[tap]", cmd)
|
||||||
args := strings.Split(cmd, " ")
|
args := strings.Split(cmd, " ")
|
||||||
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
||||||
@ -96,12 +99,12 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTunRoutes(ifName string, routes ...string) error {
|
func addTunRoutes(ifName string, routes ...IPRoute) error {
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route == "" {
|
if route.Dest == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd := fmt.Sprintf("route add -net %s -interface %s", route, ifName)
|
cmd := fmt.Sprintf("route add -net %s -interface %s", route.Dest.String(), ifName)
|
||||||
log.Logf("[tun] %s", cmd)
|
log.Logf("[tun] %s", cmd)
|
||||||
args := strings.Split(cmd, " ")
|
args := strings.Split(cmd, " ")
|
||||||
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
if er := exec.Command(args[0], args[1:]...).Run(); er != nil {
|
||||||
|
@ -28,7 +28,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("netsh interface ip set address name=%s "+
|
cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+
|
||||||
"source=static addr=%s mask=%s gateway=none",
|
"source=static addr=%s mask=%s gateway=none",
|
||||||
ifce.Name(), ip.String(), ipMask(ipNet.Mask))
|
ifce.Name(), ip.String(), ipMask(ipNet.Mask))
|
||||||
log.Log("[tun]", cmd)
|
log.Log("[tun]", cmd)
|
||||||
@ -55,10 +55,7 @@ func createTun(cfg TunConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
||||||
ip, ipNet, err := net.ParseCIDR(cfg.Addr)
|
ip, ipNet, _ := net.ParseCIDR(cfg.Addr)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ifce, err := water.New(water.Config{
|
ifce, err := water.New(water.Config{
|
||||||
DeviceType: water.TAP,
|
DeviceType: water.TAP,
|
||||||
@ -72,7 +69,8 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := fmt.Sprintf("netsh interface ip set address name=%s "+
|
if ip != nil && ipNet != nil {
|
||||||
|
cmd := fmt.Sprintf("netsh interface ip set address name=\"%s\" "+
|
||||||
"source=static addr=%s mask=%s gateway=none",
|
"source=static addr=%s mask=%s gateway=none",
|
||||||
ifce.Name(), ip.String(), ipMask(ipNet.Mask))
|
ifce.Name(), ip.String(), ipMask(ipNet.Mask))
|
||||||
log.Log("[tap]", cmd)
|
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)
|
err = fmt.Errorf("%s: %v", cmd, er)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
|
if err = addTapRoutes(ifce.Name(), cfg.Gateway, cfg.Routes...); err != nil {
|
||||||
return
|
return
|
||||||
@ -98,16 +97,16 @@ func createTap(cfg TapConfig) (conn net.Conn, itf *net.Interface, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTunRoutes(ifName string, gw string, routes ...string) error {
|
func addTunRoutes(ifName string, gw string, routes ...IPRoute) error {
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route == "" {
|
if route.Dest == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRoute(ifName, route)
|
deleteRoute(ifName, route.Dest.String())
|
||||||
|
|
||||||
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active",
|
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active",
|
||||||
route, ifName)
|
route.Dest.String(), ifName)
|
||||||
if gw != "" {
|
if gw != "" {
|
||||||
cmd += " nexthop=" + gw
|
cmd += " nexthop=" + gw
|
||||||
}
|
}
|
||||||
@ -128,7 +127,7 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
|
|||||||
|
|
||||||
deleteRoute(ifName, route)
|
deleteRoute(ifName, route)
|
||||||
|
|
||||||
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active",
|
cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=\"%s\" store=active",
|
||||||
route, ifName)
|
route, ifName)
|
||||||
if gw != "" {
|
if gw != "" {
|
||||||
cmd += " nexthop=" + gw
|
cmd += " nexthop=" + gw
|
||||||
@ -143,7 +142,7 @@ func addTapRoutes(ifName string, gw string, routes ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteRoute(ifName string, route string) error {
|
func deleteRoute(ifName string, route string) error {
|
||||||
cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=%s store=active",
|
cmd := fmt.Sprintf("netsh interface ip delete route prefix=%s interface=\"%s\" store=active",
|
||||||
route, ifName)
|
route, ifName)
|
||||||
args := strings.Split(cmd, " ")
|
args := strings.Split(cmd, " ")
|
||||||
return exec.Command(args[0], args[1:]...).Run()
|
return exec.Command(args[0], args[1:]...).Run()
|
||||||
|
356
udp.go
Normal file
356
udp.go
Normal 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
76
vsock.go
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user