mirror of https://github.com/go-gost/gost.git
Compare commits
383 Commits
v3.0.0-alp
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
e2447ce578 | |
|
|
811420e923 | |
|
|
374b46dfe1 | |
|
|
22edb92084 | |
|
|
78a0a8c734 | |
|
|
41e1878ebc | |
|
|
5639c90e98 | |
|
|
36abf9bcd9 | |
|
|
d584b7ac61 | |
|
|
56e2a1c496 | |
|
|
b0bea19275 | |
|
|
cd64f2edd3 | |
|
|
5f04c84e32 | |
|
|
600a64e611 | |
|
|
3d6b16686b | |
|
|
44684d40c3 | |
|
|
2be36abe75 | |
|
|
8db62785fa | |
|
|
8740e6f258 | |
|
|
d4c9ef5056 | |
|
|
c06eb0d331 | |
|
|
c8b48dc248 | |
|
|
45d94cf391 | |
|
|
bc96fe3918 | |
|
|
646d3f906c | |
|
|
4c69940f1e | |
|
|
3d1f6fcbbb | |
|
|
e388426ec6 | |
|
|
340ba32ef0 | |
|
|
96551d5fa5 | |
|
|
8d05a6ed93 | |
|
|
0348a16aa9 | |
|
|
50934e0978 | |
|
|
c2ed9c6f07 | |
|
|
3b9da4e260 | |
|
|
f0a67a1108 | |
|
|
59c9638ce6 | |
|
|
49fa28882f | |
|
|
08c617b54e | |
|
|
d03b0e2360 | |
|
|
245d610baf | |
|
|
f01f0c9215 | |
|
|
9b6e9d9cae | |
|
|
1a0e2b06ac | |
|
|
de8bb498db | |
|
|
1f735d0649 | |
|
|
d173d167da | |
|
|
b593fdb952 | |
|
|
d448628fea | |
|
|
0bfc7f10cd | |
|
|
a24b29c8c2 | |
|
|
87af3da1b6 | |
|
|
dc6fe595e0 | |
|
|
db4a0a9259 | |
|
|
62a097c5e4 | |
|
|
b51cecbbfd | |
|
|
66a260abd1 | |
|
|
1d3603f9ed | |
|
|
952c61c1a2 | |
|
|
6d61ab82db | |
|
|
f61bb2fd72 | |
|
|
4c2835db04 | |
|
|
d662b1fd46 | |
|
|
015923fa5d | |
|
|
f66fce9d49 | |
|
|
982a93eee4 | |
|
|
758251d3f9 | |
|
|
a1e48e164b | |
|
|
581dd74bbb | |
|
|
3c7a984aa3 | |
|
|
2a9b7e7d9b | |
|
|
ab2b9714ee | |
|
|
6d38c24d88 | |
|
|
f376db2000 | |
|
|
161d96f582 | |
|
|
128c0c3117 | |
|
|
66ce980c36 | |
|
|
829148d3f8 | |
|
|
c45f148a88 | |
|
|
fdc88af4ac | |
|
|
c41c5bf79a | |
|
|
b3eeb338fb | |
|
|
72f185bbf6 | |
|
|
a7599990a8 | |
|
|
17ca5600d3 | |
|
|
32ccfe483c | |
|
|
1d0eb06938 | |
|
|
1fa5067d14 | |
|
|
7ddfef85a5 | |
|
|
fa98777ab1 | |
|
|
e53486efe0 | |
|
|
881ebfad5b | |
|
|
d5bb691b38 | |
|
|
24c13b861a | |
|
|
d6daa8bf56 | |
|
|
d18539ef66 | |
|
|
48b0a690c5 | |
|
|
a7037a27bf | |
|
|
ac9960de6d | |
|
|
404e099907 | |
|
|
94cd590142 | |
|
|
d3809ec634 | |
|
|
efb4da6fa6 | |
|
|
2ec8584e96 | |
|
|
f05f5cf862 | |
|
|
bf3018b5b1 | |
|
|
073af6163e | |
|
|
ef07551e01 | |
|
|
8077c039f3 | |
|
|
33c8f3595a | |
|
|
87bff8cac6 | |
|
|
d52fcccf06 | |
|
|
18610d4608 | |
|
|
c1de9e4e72 | |
|
|
1f7c080043 | |
|
|
b091f72900 | |
|
|
8e50e9c085 | |
|
|
7a35d25c5c | |
|
|
8e311e7891 | |
|
|
17fc806c2d | |
|
|
b21fd4a22b | |
|
|
3e8a025c45 | |
|
|
0d7a6f8091 | |
|
|
4a2a2a00de | |
|
|
c71e128fec | |
|
|
1400b5ff5d | |
|
|
cd4fad8c08 | |
|
|
56a68ae06f | |
|
|
5d7100f749 | |
|
|
12871a4ff3 | |
|
|
6130bbc075 | |
|
|
85d4fcaf7d | |
|
|
7904e571c1 | |
|
|
8253c41de6 | |
|
|
2148d10def | |
|
|
8846c69c05 | |
|
|
2161b97064 | |
|
|
e245526b33 | |
|
|
60389894c0 | |
|
|
9ab142ac99 | |
|
|
be1b0627d1 | |
|
|
f4115b867d | |
|
|
a513417d7e | |
|
|
58bd3d062b | |
|
|
d2b79c13a6 | |
|
|
fedd7a7b07 | |
|
|
27f4b6abeb | |
|
|
d4aadbbc14 | |
|
|
4508747635 | |
|
|
50844821f8 | |
|
|
29ec7c94ad | |
|
|
53d6706b5d | |
|
|
594418db53 | |
|
|
72cd708dfe | |
|
|
b576bb5645 | |
|
|
8c529e3595 | |
|
|
b75e133719 | |
|
|
abfbf23842 | |
|
|
42d62550bf | |
|
|
69ba5864ad | |
|
|
fdd3eb61c3 | |
|
|
f68bfdb149 | |
|
|
9e4bca84f1 | |
|
|
925ff89b78 | |
|
|
ca077d418a | |
|
|
7264fca4f8 | |
|
|
bc37fac037 | |
|
|
dc48c4a3b2 | |
|
|
310614663a | |
|
|
8f094d86b0 | |
|
|
638c780acd | |
|
|
1f20f199c0 | |
|
|
8dc6146f79 | |
|
|
ed29b34c68 | |
|
|
b41aea03da | |
|
|
2c17e4417d | |
|
|
ea8ff5f77a | |
|
|
6762957c65 | |
|
|
4c934cd737 | |
|
|
0d494f5ef2 | |
|
|
bf1bab7d84 | |
|
|
b8383b1076 | |
|
|
345da28f01 | |
|
|
907420d18b | |
|
|
c1437794f8 | |
|
|
c2df5650fb | |
|
|
23506579e7 | |
|
|
969db8691b | |
|
|
3b0a2f1bae | |
|
|
b2ed4ae9fd | |
|
|
b2784011d0 | |
|
|
d4e00683c3 | |
|
|
471513896d | |
|
|
6967278600 | |
|
|
2898732c20 | |
|
|
bb0c0450e8 | |
|
|
6bb7dfea8f | |
|
|
e27d2bc593 | |
|
|
3144d61ecd | |
|
|
e4b68d385a | |
|
|
a91300d078 | |
|
|
b8785eb71e | |
|
|
67fcd929be | |
|
|
48c262bf71 | |
|
|
05c35005e9 | |
|
|
f6112773a1 | |
|
|
61002b7c9c | |
|
|
f8a19e0829 | |
|
|
b1ace73943 | |
|
|
3d0e993498 | |
|
|
9cbc875965 | |
|
|
139b934fc6 | |
|
|
c1d9228eee | |
|
|
2ea7dc250a | |
|
|
be33046c16 | |
|
|
d85b8d487b | |
|
|
ab8d77bb5e | |
|
|
8640596064 | |
|
|
8a699e1906 | |
|
|
0d41f5edc8 | |
|
|
87364d2494 | |
|
|
6982b8af63 | |
|
|
0b1540b23a | |
|
|
1bbeeb7e66 | |
|
|
5d4db6e377 | |
|
|
05d06a2718 | |
|
|
420b36b2f8 | |
|
|
3883a4493a | |
|
|
355aaa7690 | |
|
|
c73c98974c | |
|
|
98c9cc2821 | |
|
|
b48d2c0dbc | |
|
|
b7266f4660 | |
|
|
6883c32157 | |
|
|
f1c5f7598c | |
|
|
c31242efbb | |
|
|
6ee632cee2 | |
|
|
8e2060582e | |
|
|
410b4ddf53 | |
|
|
0b6474d836 | |
|
|
4b6d6e7e7c | |
|
|
b2434dd05b | |
|
|
3d15dedab2 | |
|
|
436864ca92 | |
|
|
4847f9ee9f | |
|
|
a88b44d01d | |
|
|
45f1cc2efc | |
|
|
bfa1530ef6 | |
|
|
f02c9810f2 | |
|
|
fda6214c12 | |
|
|
acb8c5bd53 | |
|
|
3f53858c83 | |
|
|
a379565412 | |
|
|
69089b790f | |
|
|
1c2d9b2a5b | |
|
|
8d852b62cf | |
|
|
a80dec0dc1 | |
|
|
62ca8ff2de | |
|
|
b8504f7f28 | |
|
|
95a2f0d649 | |
|
|
d810224e7b | |
|
|
db36804bfa | |
|
|
ad8af1942d | |
|
|
3d01f74f7c | |
|
|
6b05bcb0ab | |
|
|
799c95bfb4 | |
|
|
de0a584175 | |
|
|
381b1c9bf5 | |
|
|
ed7d72bfd8 | |
|
|
98aef6ba90 | |
|
|
dfde6efdeb | |
|
|
8858598cf0 | |
|
|
c765789076 | |
|
|
cf49a3ae46 | |
|
|
fa47726c3d | |
|
|
c351b849e6 | |
|
|
44fca4e04c | |
|
|
a795923980 | |
|
|
d3283d6278 | |
|
|
4b3e711adb | |
|
|
258d9f5b26 | |
|
|
5d9d33f368 | |
|
|
fec33e62ea | |
|
|
2f0da6993b | |
|
|
39cb9c65d9 | |
|
|
4ef4448249 | |
|
|
1151ecd2a4 | |
|
|
df3533b4ef | |
|
|
2400e29fe0 | |
|
|
41bb4cdbb0 | |
|
|
4a192bc1b3 | |
|
|
f17b2d2e96 | |
|
|
e771534920 | |
|
|
68c311a7d1 | |
|
|
1c9974340b | |
|
|
09c12b013c | |
|
|
b86d7ee06d | |
|
|
8274b62672 | |
|
|
7f924fdeb6 | |
|
|
5d797eac43 | |
|
|
2c29028a3b | |
|
|
e8071d9b65 | |
|
|
50cf4edd1f | |
|
|
3d5f0f93fd | |
|
|
aa754f8515 | |
|
|
a7f4d3e63e | |
|
|
180214a124 | |
|
|
baa6f0cea0 | |
|
|
ca0de58757 | |
|
|
5acccebe7b | |
|
|
850bb1379f | |
|
|
9e404471a6 | |
|
|
166317a7d0 | |
|
|
77d9e3798c | |
|
|
5cbba69ae2 | |
|
|
8a8cbdd9e1 | |
|
|
fb5f57fa80 | |
|
|
5603af2fcf | |
|
|
8ae4e43fc3 | |
|
|
96b3b7e962 | |
|
|
85de11ae46 | |
|
|
3a409fea42 | |
|
|
1c65aae3a9 | |
|
|
84255850b6 | |
|
|
54e0091c38 | |
|
|
605c43ca08 | |
|
|
3418dd0f00 | |
|
|
21463c0e9b | |
|
|
11077eaa75 | |
|
|
9cd94cb68b | |
|
|
37ef13655d | |
|
|
961646fb46 | |
|
|
666846d352 | |
|
|
c70521ddab | |
|
|
7c91dfb56f | |
|
|
4b88c240f8 | |
|
|
1b31c80d2c | |
|
|
810f9f4d8f | |
|
|
5fe56cb63f | |
|
|
733655625a | |
|
|
670a118c6d | |
|
|
cb0d81f938 | |
|
|
7b5913123f | |
|
|
bb7fb31e3d | |
|
|
7fd8720f38 | |
|
|
815341dbfa | |
|
|
2864480296 | |
|
|
0a41b70ef4 | |
|
|
20e7528b25 | |
|
|
2e40afe535 | |
|
|
42f72adde0 | |
|
|
a2d62d1753 | |
|
|
749d802a12 | |
|
|
695c720d26 | |
|
|
c282e69ffd | |
|
|
eae153d481 | |
|
|
a7fddd1d7c | |
|
|
5c5af49b0e | |
|
|
bfc1f8472c | |
|
|
4007e80811 | |
|
|
4364190e6d | |
|
|
be374b6488 | |
|
|
ee72cea036 | |
|
|
e587b4df7c | |
|
|
8d8785f534 | |
|
|
b96d37d4cc | |
|
|
ffdf11e89e | |
|
|
07132d8de7 | |
|
|
0aee4f0ebd | |
|
|
9db7137cd3 | |
|
|
307a90c20e | |
|
|
5daefb8e3c | |
|
|
edca3e0a55 | |
|
|
a8804ea02d | |
|
|
f2d806886a | |
|
|
fdd67a6086 | |
|
|
345338d410 | |
|
|
a8a6bbc3a3 | |
|
|
c1bf501734 | |
|
|
f1eaef8d69 | |
|
|
0fba6d2500 | |
|
|
1ec10ff7dd | |
|
|
0983ecc52f |
|
|
@ -1,89 +0,0 @@
|
||||||
# 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
|
|
||||||
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set output parameters.
|
|
||||||
echo ::set-output name=tags::${TAGS}
|
|
||||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
|
||||||
echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
|
||||||
|
|
||||||
# https://github.com/crazy-max/ghaction-docker-buildx
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: crazy-max/ghaction-docker-buildx@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
- name: Environment
|
|
||||||
run: |
|
|
||||||
echo home=$HOME
|
|
||||||
echo git_ref=$GITHUB_REF
|
|
||||||
echo git_sha=$GITHUB_SHA
|
|
||||||
echo version=${{ steps.prepare.outputs.version }}
|
|
||||||
echo image=${{ steps.prepare.outputs.docker_image }}
|
|
||||||
echo platforms=${{ steps.prepare.outputs.docker_platforms }}
|
|
||||||
echo avail_platforms=${{ steps.buildx.outputs.platforms }}
|
|
||||||
|
|
||||||
# https://github.com/actions/checkout
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Docker Buildx (no push)
|
|
||||||
run: |
|
|
||||||
docker buildx bake \
|
|
||||||
--set ${{ github.event.repository.name }}.platform=${{ steps.prepare.outputs.docker_platforms }} \
|
|
||||||
--set ${{ github.event.repository.name }}.output=type=image,push=false \
|
|
||||||
--set ${{ github.event.repository.name }}.tags="${{ steps.prepare.outputs.tags }}" \
|
|
||||||
--file docker-compose.yaml
|
|
||||||
|
|
||||||
- name: Docker Login
|
|
||||||
if: success()
|
|
||||||
env:
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
echo "${DOCKER_PASSWORD}" | docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Docker Buildx (push)
|
|
||||||
if: success()
|
|
||||||
run: |
|
|
||||||
docker buildx bake \
|
|
||||||
--set ${{ github.event.repository.name }}.platform=${{ steps.prepare.outputs.docker_platforms }} \
|
|
||||||
--set ${{ github.event.repository.name }}.output=type=image,push=true \
|
|
||||||
--set ${{ github.event.repository.name }}.tags="${{ steps.prepare.outputs.tags }}" \
|
|
||||||
--file docker-compose.yaml
|
|
||||||
|
|
||||||
- name: Clear
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
rm -f ${HOME}/.docker/config.json
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
# 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 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 }}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
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@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- run: git fetch --force --tags
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.26'
|
||||||
|
cache: true
|
||||||
|
- name: Install UPX
|
||||||
|
uses: crazy-max/ghaction-upx@v3
|
||||||
|
with:
|
||||||
|
install-only: true
|
||||||
|
# More assembly might be required: Docker logins, GPG, etc. It all depends
|
||||||
|
# on your needs.
|
||||||
|
- uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
# either 'goreleaser' (default) or 'goreleaser-pro':
|
||||||
|
distribution: goreleaser
|
||||||
|
version: '~> v2'
|
||||||
|
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 }}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
name: Trigger nightly build
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# * is a special character in YAML, so you have to quote this string
|
||||||
|
- cron: '00 15 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_date:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Check latest commit
|
||||||
|
outputs:
|
||||||
|
should_run: ${{ steps.should_run.outputs.should_run }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: print latest_commit
|
||||||
|
run: echo ${{ github.sha }}
|
||||||
|
- id: should_run
|
||||||
|
continue-on-error: true
|
||||||
|
name: check latest commit is less than a day
|
||||||
|
if: ${{ github.event_name == 'schedule' }}
|
||||||
|
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
trigger-nightly:
|
||||||
|
needs: check_date
|
||||||
|
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||||
|
name: Push tag for nightly build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: 'Checkout'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.NIGHTLY_BUILD_GH_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
-
|
||||||
|
name: 'Push new tag'
|
||||||
|
run: |
|
||||||
|
git config user.name "${GITHUB_ACTOR}"
|
||||||
|
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
||||||
|
|
||||||
|
# A previous release was created using a lightweight tag
|
||||||
|
# git describe by default includes only annotated tags
|
||||||
|
# git describe --tags includes lightweight tags as well
|
||||||
|
DESCRIBE=`git tag -l --sort=-v:refname | grep -v nightly | head -n 1`
|
||||||
|
MAJOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[1]}'`
|
||||||
|
MINOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[2]}'`
|
||||||
|
PATCH_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[3]}'`
|
||||||
|
PATCH_VERSION="$((${PATCH_VERSION} + 1))"
|
||||||
|
TAG="${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-nightly.$(date +'%Y%m%d')"
|
||||||
|
git tag -a $TAG -m "$TAG: nightly build"
|
||||||
|
git push origin $TAG
|
||||||
|
- name: 'Clean up nightly releases'
|
||||||
|
uses: dev-drprasad/delete-older-releases@v0.3.3
|
||||||
|
with:
|
||||||
|
keep_latest: 2
|
||||||
|
delete_tags: true
|
||||||
|
delete_tag_pattern: nightly
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.NIGHTLY_BUILD_GH_TOKEN }}
|
||||||
|
|
@ -32,6 +32,10 @@ _testmain.go
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
cmd/gost/gost
|
cmd/gost/gost
|
||||||
|
gost
|
||||||
snap
|
snap
|
||||||
|
|
||||||
*.pem
|
*.pem
|
||||||
|
/*.yaml
|
||||||
|
*.txt
|
||||||
|
dist/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
# 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
|
||||||
|
- linux_loong64
|
||||||
|
- freebsd_386
|
||||||
|
- freebsd_amd64
|
||||||
|
- windows_386
|
||||||
|
- windows_amd64
|
||||||
|
- windows_amd64_v3
|
||||||
|
- windows_arm64
|
||||||
|
- android_arm64
|
||||||
|
ldflags:
|
||||||
|
- "-s -w -X 'main.version={{ .Tag }}'"
|
||||||
|
|
||||||
|
upx:
|
||||||
|
- # UPX compression. Disabled by default (see #863): upx --best/--lzma/--brute
|
||||||
|
# adds ~3s startup time on low-spec systems (linux/arm). Release builds can
|
||||||
|
# opt in via GORELEASER_UPX=true environment variable.
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
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:'
|
||||||
|
|
||||||
|
release:
|
||||||
|
prerelease: auto
|
||||||
|
mode: keep-existing
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
# CLAUDE.md — gost/
|
||||||
|
|
||||||
|
CLI binary entry point for GOST (GO Simple Tunnel). This module compiles the `gost` command.
|
||||||
|
|
||||||
|
## Build & Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd gost && go build ./cmd/gost/...
|
||||||
|
|
||||||
|
# Cross-compile
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" ./cmd/gost/...
|
||||||
|
|
||||||
|
# Build all platforms
|
||||||
|
make all
|
||||||
|
|
||||||
|
# Run
|
||||||
|
./gost -L "http://:8080" -F "socks5://:1080"
|
||||||
|
./gost -C gost.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `cmd/gost/main.go` | CLI entry point: flag parsing, multi-worker mode (`--` separator), program launch via `svc.Run` |
|
||||||
|
| `cmd/gost/program.go` | Service lifecycle (`Init`/`Start`/`Stop`), config loading, API/metrics/profiling servers, SIGHUP reload |
|
||||||
|
| `cmd/gost/register.go` | Blank imports for all built-in handlers, listeners, dialers, connectors — triggers `init()` registration |
|
||||||
|
| `cmd/gost/version.go` | Version string (`3.3.0`) |
|
||||||
|
|
||||||
|
## CLI Flags
|
||||||
|
|
||||||
|
| Flag | Usage |
|
||||||
|
|------|-------|
|
||||||
|
| `-L` | Inline service definition (repeatable) |
|
||||||
|
| `-F` | Inline chain node definition (repeatable) |
|
||||||
|
| `-C` | Path to config file (YAML/JSON) |
|
||||||
|
| `-D` / `-DD` | Debug / trace logging |
|
||||||
|
| `-api` | API service address (e.g. `:8080`) |
|
||||||
|
| `-metrics` | Prometheus metrics address |
|
||||||
|
| `-O` | Output merged config as yaml or json, then exit |
|
||||||
|
| `-V` | Print version and exit |
|
||||||
|
|
||||||
|
## Multi-Worker Mode
|
||||||
|
|
||||||
|
Arguments separated by ` -- ` spawn multiple worker processes via `exec.CommandContext`. Each worker runs as a child process with `_GOST_ID` set. Any worker's exit cancels all others. This is triggered in `init()` before flag parsing.
|
||||||
|
|
||||||
|
## Service Lifecycle
|
||||||
|
|
||||||
|
- `Init` → calls `parser.Init()` with all CLI/config inputs
|
||||||
|
- `Start` → `parser.Parse()` → `loader.Load()` → `p.run()` (starts services, API, metrics, profiling)
|
||||||
|
- `Stop` → cancels reload context, closes all services
|
||||||
|
- SIGHUP → `reloadConfig()` re-parses and re-runs without restarting the process
|
||||||
|
|
||||||
|
## Key Dependencies
|
||||||
|
|
||||||
|
- `github.com/go-gost/core` — interface definitions
|
||||||
|
- `github.com/go-gost/x` — all implementations, config, registry
|
||||||
|
- `github.com/judwhite/go-svc` — OS service framework (handles daemon/SIGHUP/SIGTERM)
|
||||||
|
|
||||||
|
## Registration Pattern
|
||||||
|
|
||||||
|
All components register via `init()` side-effects in their packages. `cmd/gost/register.go` triggers them with blank imports. When adding a new built-in handler/listener/dialer/connector, add a blank import here.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Tests are in `tests/e2e/` — integration tests using the built binary. No unit tests. Run with `go test ./tests/e2e/...`.
|
||||||
34
Dockerfile
34
Dockerfile
|
|
@ -1,25 +1,37 @@
|
||||||
FROM --platform=$BUILDPLATFORM golang:1-alpine as builder
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1 AS xx
|
||||||
|
|
||||||
# Convert TARGETPLATFORM to GOARCH format
|
FROM --platform=$BUILDPLATFORM golang:1.26-alpine3.23 AS builder
|
||||||
# https://github.com/tonistiigi/xx
|
|
||||||
COPY --from=tonistiigi/xx:golang / /
|
# UPX compression disabled by default (see #863): upx --best adds ~3s startup
|
||||||
|
# time on low-spec systems (linux/arm). Builds can opt in by uncommenting the
|
||||||
|
# upx install line and adding upx --best to the build command below.
|
||||||
|
# RUN apk add --no-cache upx || echo "upx not found"
|
||||||
|
|
||||||
|
COPY --from=xx / /
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
RUN apk add --no-cache musl-dev git gcc
|
RUN xx-info env
|
||||||
|
|
||||||
ADD . /src
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
WORKDIR /src
|
ENV XX_VERIFY_STATIC=1
|
||||||
|
|
||||||
ENV GO111MODULE=on
|
WORKDIR /app
|
||||||
|
|
||||||
RUN cd cmd/gost && go env && go build
|
COPY . .
|
||||||
|
|
||||||
FROM alpine:latest
|
RUN cd cmd/gost && \
|
||||||
|
xx-go build -ldflags "-s -w" && \
|
||||||
|
xx-verify gost
|
||||||
|
|
||||||
|
FROM alpine:3.23
|
||||||
|
|
||||||
|
# add iptables/nftables for tun/tap
|
||||||
|
RUN apk add --no-cache iptables nftables
|
||||||
|
|
||||||
WORKDIR /bin/
|
WORKDIR /bin/
|
||||||
|
|
||||||
COPY --from=builder /src/cmd/gost/gost .
|
COPY --from=builder /app/cmd/gost/gost .
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/gost"]
|
ENTRYPOINT ["/bin/gost"]
|
||||||
26
Makefile
26
Makefile
|
|
@ -2,13 +2,14 @@ NAME=gost
|
||||||
BINDIR=bin
|
BINDIR=bin
|
||||||
VERSION=$(shell cat cmd/gost/version.go | grep 'version =' | sed 's/.*\"\(.*\)\".*/\1/g')
|
VERSION=$(shell cat cmd/gost/version.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 \
|
darwin-arm64 \
|
||||||
linux-386 \
|
linux-386 \
|
||||||
linux-amd64 \
|
linux-amd64 \
|
||||||
|
linux-amd64v3 \
|
||||||
linux-armv5 \
|
linux-armv5 \
|
||||||
linux-armv6 \
|
linux-armv6 \
|
||||||
linux-armv7 \
|
linux-armv7 \
|
||||||
|
|
@ -19,14 +20,18 @@ 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-amd64v3 \
|
||||||
|
windows-arm64
|
||||||
|
|
||||||
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
|
all: linux-amd64 darwin-amd64 darwin-arm64 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)
|
||||||
|
|
@ -40,6 +45,9 @@ linux-386:
|
||||||
linux-amd64:
|
linux-amd64:
|
||||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
|
linux-amd64v3:
|
||||||
|
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
linux-armv5:
|
linux-armv5:
|
||||||
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ $(GOFILES)
|
||||||
|
|
||||||
|
|
@ -70,6 +78,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)
|
||||||
|
|
||||||
|
|
@ -82,6 +96,12 @@ 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-amd64v3:
|
||||||
|
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(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))
|
||||||
|
|
||||||
|
|
|
||||||
108
README.md
108
README.md
|
|
@ -1,4 +1,106 @@
|
||||||
gost 3.0
|
# GO Simple Tunnel
|
||||||
======
|
|
||||||
|
|
||||||
WORK IN PROGRESS...
|
### GO语言实现的安全隧道
|
||||||
|
|
||||||
|
[](README.md) [](README_en.md)
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- [x] [多端口监听](https://gost.run/getting-started/quick-start/)
|
||||||
|
- [x] [多级转发链](https://gost.run/concepts/chain/)
|
||||||
|
- [x] [多协议支持](https://gost.run/tutorials/protocols/overview/)
|
||||||
|
- [x] [TCP/UDP端口转发](https://gost.run/tutorials/port-forwarding/)
|
||||||
|
- [x] [反向代理](https://gost.run/tutorials/reverse-proxy/)和[隧道](https://gost.run/tutorials/reverse-proxy-tunnel/)
|
||||||
|
- [x] [TCP/UDP透明代理](https://gost.run/tutorials/redirect/)
|
||||||
|
- [x] DNS[解析](https://gost.run/concepts/resolver/)和[代理](https://gost.run/tutorials/dns/)
|
||||||
|
- [x] [TUN/TAP设备](https://gost.run/tutorials/tuntap/)与[TUN2SOCKS](https://gost.run/tutorials/tungo/)
|
||||||
|
- [x] [负载均衡](https://gost.run/concepts/selector/)
|
||||||
|
- [x] [路由控制](https://gost.run/concepts/bypass/)
|
||||||
|
- [x] [准入控制](https://gost.run/concepts/admission/)
|
||||||
|
- [x] [限速限流](https://gost.run/concepts/limiter/)
|
||||||
|
- [x] [插件系统](https://gost.run/concepts/plugin/)
|
||||||
|
- [x] [Prometheus监控指标](https://gost.run/tutorials/metrics/)
|
||||||
|
- [x] [动态配置](https://gost.run/tutorials/api/config/)
|
||||||
|
- [x] [Web API](https://gost.run/tutorials/api/overview/)
|
||||||
|
- [x] [GUI](https://github.com/go-gost/gostctl)/[WebUI](https://github.com/go-gost/gost-ui)
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
GOST作为隧道有三种主要使用方式。
|
||||||
|
|
||||||
|
### 正向代理
|
||||||
|
|
||||||
|
作为代理服务访问网络,可以组合使用多种协议组成转发链进行转发。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 端口转发
|
||||||
|
|
||||||
|
将一个服务的端口映射到另外一个服务的端口,同样可以组合使用多种协议组成转发链进行转发。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 反向代理
|
||||||
|
|
||||||
|
利用隧道和内网穿透将内网服务暴露到公网访问。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 下载安装
|
||||||
|
|
||||||
|
### 二进制文件
|
||||||
|
|
||||||
|
[https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
|
||||||
|
|
||||||
|
### 安装脚本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装最新版本 [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
|
||||||
|
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --install
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
# 选择要安装的版本
|
||||||
|
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 源码编译
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/go-gost/gost.git
|
||||||
|
cd gost/cmd/gost
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm gogost/gost -V
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工具
|
||||||
|
|
||||||
|
### GUI
|
||||||
|
|
||||||
|
[go-gost/gostctl](https://github.com/go-gost/gostctl)
|
||||||
|
|
||||||
|
### WebUI
|
||||||
|
|
||||||
|
[go-gost/gost-ui](https://github.com/go-gost/gost-ui)
|
||||||
|
|
||||||
|
### Shadowsocks Android插件
|
||||||
|
|
||||||
|
[hamid-nazari/ShadowsocksGostPlugin](https://github.com/hamid-nazari/ShadowsocksGostPlugin)
|
||||||
|
|
||||||
|
## 帮助与支持
|
||||||
|
|
||||||
|
Wiki站点:[https://gost.run](https://gost.run)
|
||||||
|
|
||||||
|
YouTube: [https://www.youtube.com/@gost-tunnel](https://www.youtube.com/@gost-tunnel)
|
||||||
|
|
||||||
|
Telegram:[https://t.me/gogost](https://t.me/gogost)
|
||||||
|
|
||||||
|
Google讨论组:[https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
|
||||||
|
|
||||||
|
旧版入口:[v2.gost.run](https://v2.gost.run)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
# GO Simple Tunnel
|
||||||
|
|
||||||
|
### A simple security tunnel written in golang
|
||||||
|
|
||||||
|
[](README_en.md) [](README.md)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- [x] [Listening on multiple ports](https://gost.run/en/getting-started/quick-start/)
|
||||||
|
- [x] [Multi-level forwarding chain](https://gost.run/en/concepts/chain/)
|
||||||
|
- [x] Rich protocol
|
||||||
|
- [x] [TCP/UDP port forwarding](https://gost.run/en/tutorials/port-forwarding/)
|
||||||
|
- [x] [Reverse Proxy](https://gost.run/en/tutorials/reverse-proxy/) and [Tunnel](https://gost.run/en/tutorials/reverse-proxy-tunnel/)
|
||||||
|
- [x] [TCP/UDP transparent proxy](https://gost.run/en/tutorials/redirect/)
|
||||||
|
- [x] DNS [resolver](https://gost.run/en/concepts/resolver/) and [proxy](https://gost.run/en/tutorials/dns/)
|
||||||
|
- [x] [TUN/TAP device](https://gost.run/en/tutorials/tuntap/) and [TUN2SOCKS](https://gost.run/en/tutorials/tungo/)
|
||||||
|
- [x] [Load balancing](https://gost.run/en/concepts/selector/)
|
||||||
|
- [x] [Routing control](https://gost.run/en/concepts/bypass/)
|
||||||
|
- [x] [Admission control](https://gost.run/en/concepts/limiter/)
|
||||||
|
- [x] [Bandwidth/Rate Limiter](https://gost.run/en/concepts/limiter/)
|
||||||
|
- [x] [Plugin System](https://gost.run/en/concepts/plugin/)
|
||||||
|
- [x] [Prometheus metrics](https://gost.run/en/tutorials/metrics/)
|
||||||
|
- [x] [Dynamic configuration](https://gost.run/en/tutorials/api/config/)
|
||||||
|
- [x] [Web API](https://gost.run/en/tutorials/api/overview/)
|
||||||
|
- [x] [GUI](https://github.com/go-gost/gostctl)/[WebUI](https://github.com/go-gost/gost-ui)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
There are three main ways to use GOST as a tunnel.
|
||||||
|
|
||||||
|
### Proxy
|
||||||
|
|
||||||
|
As a proxy service to access the network, multiple protocols can be used in combination to form a forwarding chain for traffic forwarding.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Port Forwarding
|
||||||
|
|
||||||
|
Mapping the port of one service to the port of another service, you can also use a combination of multiple protocols to form a forwarding chain for traffic forwarding.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Reverse Proxy
|
||||||
|
|
||||||
|
Use tunnel and intranet penetration to expose local services behind NAT or firewall to public network for access.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Binary files
|
||||||
|
|
||||||
|
[https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
|
||||||
|
|
||||||
|
### install script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# install latest from [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
|
||||||
|
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --install
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
# select version for install
|
||||||
|
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/go-gost/gost.git
|
||||||
|
cd gost/cmd/gost
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm gogost/gost -V
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### GUI
|
||||||
|
|
||||||
|
[go-gost/gostctl](https://github.com/go-gost/gostctl)
|
||||||
|
|
||||||
|
### WebUI
|
||||||
|
|
||||||
|
[go-gost/gost-ui](https://github.com/go-gost/gost-ui)
|
||||||
|
|
||||||
|
### Shadowsocks Android
|
||||||
|
|
||||||
|
[hamid-nazari/ShadowsocksGostPlugin](https://github.com/hamid-nazari/ShadowsocksGostPlugin)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Wiki: [https://gost.run](https://gost.run/en/)
|
||||||
|
|
||||||
|
YouTube: [https://www.youtube.com/@gost-tunnel](https://www.youtube.com/@gost-tunnel)
|
||||||
|
|
||||||
|
Telegram: [https://t.me/gogost](https://t.me/gogost)
|
||||||
|
|
||||||
|
Google group: [https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
|
||||||
|
|
||||||
|
Legacy version: [v2.gost.run](https://v2.gost.run/en/)
|
||||||
494
cmd/gost/cmd.go
494
cmd/gost/cmd.go
|
|
@ -1,494 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/config"
|
|
||||||
"github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidCmd = errors.New("invalid cmd")
|
|
||||||
ErrInvalidNode = errors.New("invalid node")
|
|
||||||
)
|
|
||||||
|
|
||||||
type stringList []string
|
|
||||||
|
|
||||||
func (l *stringList) String() string {
|
|
||||||
return fmt.Sprintf("%s", *l)
|
|
||||||
}
|
|
||||||
func (l *stringList) Set(value string) error {
|
|
||||||
*l = append(*l, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildConfigFromCmd(services, nodes stringList) (*config.Config, error) {
|
|
||||||
cfg := &config.Config{}
|
|
||||||
|
|
||||||
if v := os.Getenv("GOST_PROFILING"); v != "" {
|
|
||||||
cfg.Profiling = &config.ProfilingConfig{
|
|
||||||
Addr: v,
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var chain *config.ChainConfig
|
|
||||||
if len(nodes) > 0 {
|
|
||||||
chain = &config.ChainConfig{
|
|
||||||
Name: "chain-0",
|
|
||||||
}
|
|
||||||
cfg.Chains = append(cfg.Chains, chain)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, node := range nodes {
|
|
||||||
url, err := normCmd(node)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeConfig, err := buildNodeConfig(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
nodeConfig.Name = "node-0"
|
|
||||||
|
|
||||||
var nodes []*config.NodeConfig
|
|
||||||
for _, host := range strings.Split(nodeConfig.Addr, ",") {
|
|
||||||
if host == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nodeCfg := &config.NodeConfig{}
|
|
||||||
*nodeCfg = *nodeConfig
|
|
||||||
nodeCfg.Name = fmt.Sprintf("node-%d", len(nodes))
|
|
||||||
nodeCfg.Addr = host
|
|
||||||
nodes = append(nodes, nodeCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
md := metadata.MapMetadata(nodeConfig.Connector.Metadata)
|
|
||||||
|
|
||||||
hopConfig := &config.HopConfig{
|
|
||||||
Name: fmt.Sprintf("hop-%d", i),
|
|
||||||
Selector: parseSelector(md),
|
|
||||||
Nodes: nodes,
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := metadata.GetString(md, "bypass"); v != "" {
|
|
||||||
bypassCfg := &config.BypassConfig{
|
|
||||||
Name: fmt.Sprintf("bypass-%d", len(cfg.Bypasses)),
|
|
||||||
}
|
|
||||||
if v[0] == '~' {
|
|
||||||
bypassCfg.Reverse = true
|
|
||||||
v = v[1:]
|
|
||||||
}
|
|
||||||
for _, s := range strings.Split(v, ",") {
|
|
||||||
if s == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
|
|
||||||
}
|
|
||||||
hopConfig.Bypass = bypassCfg.Name
|
|
||||||
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
|
|
||||||
md.Del("bypass")
|
|
||||||
}
|
|
||||||
if v := metadata.GetString(md, "resolver"); v != "" {
|
|
||||||
resolverCfg := &config.ResolverConfig{
|
|
||||||
Name: fmt.Sprintf("resolver-%d", len(cfg.Resolvers)),
|
|
||||||
}
|
|
||||||
for _, rs := range strings.Split(v, ",") {
|
|
||||||
if rs == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resolverCfg.Nameservers = append(
|
|
||||||
resolverCfg.Nameservers,
|
|
||||||
config.NameserverConfig{
|
|
||||||
Addr: rs,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
hopConfig.Resolver = resolverCfg.Name
|
|
||||||
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
|
|
||||||
md.Del("resolver")
|
|
||||||
}
|
|
||||||
if v := metadata.GetString(md, "hosts"); v != "" {
|
|
||||||
hostsCfg := &config.HostsConfig{
|
|
||||||
Name: fmt.Sprintf("hosts-%d", len(cfg.Hosts)),
|
|
||||||
}
|
|
||||||
for _, s := range strings.Split(v, ",") {
|
|
||||||
ss := strings.SplitN(s, ":", 2)
|
|
||||||
if len(ss) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hostsCfg.Mappings = append(
|
|
||||||
hostsCfg.Mappings,
|
|
||||||
config.HostMappingConfig{
|
|
||||||
Hostname: ss[0],
|
|
||||||
IP: ss[1],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
hopConfig.Hosts = hostsCfg.Name
|
|
||||||
cfg.Hosts = append(cfg.Hosts, hostsCfg)
|
|
||||||
md.Del("hosts")
|
|
||||||
}
|
|
||||||
|
|
||||||
chain.Hops = append(chain.Hops, hopConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, svc := range services {
|
|
||||||
url, err := normCmd(svc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
service, err := buildServiceConfig(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
service.Name = fmt.Sprintf("service-%d", i)
|
|
||||||
if chain != nil {
|
|
||||||
if service.Listener.Type == "rtcp" || service.Listener.Type == "rudp" {
|
|
||||||
service.Listener.Chain = chain.Name
|
|
||||||
} else {
|
|
||||||
service.Handler.Chain = chain.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg.Services = append(cfg.Services, service)
|
|
||||||
|
|
||||||
md := metadata.MapMetadata(service.Handler.Metadata)
|
|
||||||
if v := metadata.GetInt(md, "retries"); v > 0 {
|
|
||||||
service.Handler.Retries = v
|
|
||||||
md.Del("retries")
|
|
||||||
}
|
|
||||||
if v := metadata.GetString(md, "bypass"); v != "" {
|
|
||||||
bypassCfg := &config.BypassConfig{
|
|
||||||
Name: fmt.Sprintf("bypass-%d", len(cfg.Bypasses)),
|
|
||||||
}
|
|
||||||
if v[0] == '~' {
|
|
||||||
bypassCfg.Reverse = true
|
|
||||||
v = v[1:]
|
|
||||||
}
|
|
||||||
for _, s := range strings.Split(v, ",") {
|
|
||||||
if s == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
|
|
||||||
}
|
|
||||||
service.Bypass = bypassCfg.Name
|
|
||||||
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
|
|
||||||
md.Del("bypass")
|
|
||||||
}
|
|
||||||
if v := metadata.GetString(md, "resolver"); v != "" {
|
|
||||||
resolverCfg := &config.ResolverConfig{
|
|
||||||
Name: fmt.Sprintf("resolver-%d", len(cfg.Resolvers)),
|
|
||||||
}
|
|
||||||
for _, rs := range strings.Split(v, ",") {
|
|
||||||
if rs == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resolverCfg.Nameservers = append(
|
|
||||||
resolverCfg.Nameservers,
|
|
||||||
config.NameserverConfig{
|
|
||||||
Addr: rs,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
service.Resolver = resolverCfg.Name
|
|
||||||
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
|
|
||||||
md.Del("resolver")
|
|
||||||
}
|
|
||||||
if v := metadata.GetString(md, "hosts"); v != "" {
|
|
||||||
hostsCfg := &config.HostsConfig{
|
|
||||||
Name: fmt.Sprintf("hosts-%d", len(cfg.Hosts)),
|
|
||||||
}
|
|
||||||
for _, s := range strings.Split(v, ",") {
|
|
||||||
ss := strings.SplitN(s, ":", 2)
|
|
||||||
if len(ss) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hostsCfg.Mappings = append(
|
|
||||||
hostsCfg.Mappings,
|
|
||||||
config.HostMappingConfig{
|
|
||||||
Hostname: ss[0],
|
|
||||||
IP: ss[1],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
service.Hosts = hostsCfg.Name
|
|
||||||
cfg.Hosts = append(cfg.Hosts, hostsCfg)
|
|
||||||
md.Del("hosts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildServiceConfig(url *url.URL) (*config.ServiceConfig, error) {
|
|
||||||
var handler, listener string
|
|
||||||
schemes := strings.Split(url.Scheme, "+")
|
|
||||||
if len(schemes) == 1 {
|
|
||||||
handler = schemes[0]
|
|
||||||
listener = schemes[0]
|
|
||||||
}
|
|
||||||
if len(schemes) == 2 {
|
|
||||||
handler = schemes[0]
|
|
||||||
listener = schemes[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
svc := &config.ServiceConfig{
|
|
||||||
Addr: url.Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
if h := registry.GetHandler(handler); h == nil {
|
|
||||||
handler = "auto"
|
|
||||||
}
|
|
||||||
if ln := registry.GetListener(listener); ln == nil {
|
|
||||||
listener = "tcp"
|
|
||||||
if handler == "ssu" {
|
|
||||||
listener = "udp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward mode
|
|
||||||
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" {
|
|
||||||
svc.Forwarder = &config.ForwarderConfig{
|
|
||||||
Targets: strings.Split(remotes, ","),
|
|
||||||
}
|
|
||||||
if handler != "relay" {
|
|
||||||
if listener == "tcp" || listener == "udp" ||
|
|
||||||
listener == "rtcp" || listener == "rudp" ||
|
|
||||||
listener == "tun" || listener == "tap" {
|
|
||||||
handler = listener
|
|
||||||
} else {
|
|
||||||
handler = "forward"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var auths []*config.AuthConfig
|
|
||||||
if url.User != nil {
|
|
||||||
auth := &config.AuthConfig{
|
|
||||||
Username: url.User.Username(),
|
|
||||||
}
|
|
||||||
auth.Password, _ = url.User.Password()
|
|
||||||
auths = append(auths, auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
md := metadata.MapMetadata{}
|
|
||||||
for k, v := range url.Query() {
|
|
||||||
if len(v) > 0 {
|
|
||||||
md[k] = v[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sa := metadata.GetString(md, "auth"); sa != "" {
|
|
||||||
au, err := parseAuthFromCmd(sa)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
auths = append(auths, au)
|
|
||||||
}
|
|
||||||
md.Del("auth")
|
|
||||||
|
|
||||||
tlsConfig := &config.TLSConfig{
|
|
||||||
CertFile: metadata.GetString(md, "certFile"),
|
|
||||||
KeyFile: metadata.GetString(md, "keyFile"),
|
|
||||||
CAFile: metadata.GetString(md, "caFile"),
|
|
||||||
}
|
|
||||||
md.Del("certFile")
|
|
||||||
md.Del("keyFile")
|
|
||||||
md.Del("caFile")
|
|
||||||
|
|
||||||
if tlsConfig.CertFile == "" {
|
|
||||||
tlsConfig = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := metadata.GetString(md, "dns"); v != "" {
|
|
||||||
md.Set("dns", strings.Split(v, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc.Forwarder != nil {
|
|
||||||
svc.Forwarder.Selector = parseSelector(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc.Handler = &config.HandlerConfig{
|
|
||||||
Type: handler,
|
|
||||||
Auths: auths,
|
|
||||||
Metadata: md,
|
|
||||||
}
|
|
||||||
svc.Listener = &config.ListenerConfig{
|
|
||||||
Type: listener,
|
|
||||||
TLS: tlsConfig,
|
|
||||||
Metadata: md,
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc.Handler.Type == "sshd" {
|
|
||||||
svc.Handler.Auths = nil
|
|
||||||
}
|
|
||||||
if svc.Listener.Type == "sshd" {
|
|
||||||
svc.Listener.Auths = auths
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
|
|
||||||
var connector, dialer string
|
|
||||||
schemes := strings.Split(url.Scheme, "+")
|
|
||||||
if len(schemes) == 1 {
|
|
||||||
connector = schemes[0]
|
|
||||||
dialer = schemes[0]
|
|
||||||
}
|
|
||||||
if len(schemes) == 2 {
|
|
||||||
connector = schemes[0]
|
|
||||||
dialer = schemes[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &config.NodeConfig{
|
|
||||||
Addr: url.Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := registry.GetConnector(connector); c == nil {
|
|
||||||
connector = "http"
|
|
||||||
}
|
|
||||||
if d := registry.GetDialer(dialer); d == nil {
|
|
||||||
dialer = "tcp"
|
|
||||||
if connector == "ssu" {
|
|
||||||
dialer = "udp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var auth *config.AuthConfig
|
|
||||||
if url.User != nil {
|
|
||||||
auth = &config.AuthConfig{
|
|
||||||
Username: url.User.Username(),
|
|
||||||
}
|
|
||||||
auth.Password, _ = url.User.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
md := metadata.MapMetadata{}
|
|
||||||
for k, v := range url.Query() {
|
|
||||||
if len(v) > 0 {
|
|
||||||
md[k] = v[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sauth := metadata.GetString(md, "auth"); sauth != "" && auth == nil {
|
|
||||||
au, err := parseAuthFromCmd(sauth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
auth = au
|
|
||||||
}
|
|
||||||
md.Del("auth")
|
|
||||||
|
|
||||||
tlsConfig := &config.TLSConfig{
|
|
||||||
CAFile: metadata.GetString(md, "caFile"),
|
|
||||||
Secure: metadata.GetBool(md, "secure"),
|
|
||||||
ServerName: metadata.GetString(md, "serverName"),
|
|
||||||
}
|
|
||||||
if tlsConfig.ServerName == "" {
|
|
||||||
tlsConfig.ServerName = url.Hostname()
|
|
||||||
}
|
|
||||||
md.Del("caFile")
|
|
||||||
md.Del("secure")
|
|
||||||
md.Del("serverName")
|
|
||||||
|
|
||||||
if !tlsConfig.Secure && tlsConfig.CAFile == "" {
|
|
||||||
tlsConfig = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
node.Connector = &config.ConnectorConfig{
|
|
||||||
Type: connector,
|
|
||||||
Auth: auth,
|
|
||||||
Metadata: md,
|
|
||||||
}
|
|
||||||
node.Dialer = &config.DialerConfig{
|
|
||||||
Type: dialer,
|
|
||||||
TLS: tlsConfig,
|
|
||||||
Metadata: md,
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Connector.Type == "sshd" {
|
|
||||||
node.Connector.Auth = nil
|
|
||||||
}
|
|
||||||
if node.Dialer.Type == "sshd" {
|
|
||||||
node.Dialer.Auth = auth
|
|
||||||
}
|
|
||||||
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func normCmd(s string) (*url.URL, error) {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if s == "" {
|
|
||||||
return nil, ErrInvalidCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
if s[0] == ':' || !strings.Contains(s, "://") {
|
|
||||||
s = "auto://" + s
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if url.Scheme == "https" {
|
|
||||||
url.Scheme = "http+tls"
|
|
||||||
}
|
|
||||||
|
|
||||||
return url, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
|
|
||||||
v, err := base64.StdEncoding.DecodeString(sa)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cs := string(v)
|
|
||||||
n := strings.IndexByte(cs, ':')
|
|
||||||
if n < 0 {
|
|
||||||
return &config.AuthConfig{
|
|
||||||
Username: cs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config.AuthConfig{
|
|
||||||
Username: cs[:n],
|
|
||||||
Password: cs[n+1:],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSelector(md metadata.MapMetadata) *config.SelectorConfig {
|
|
||||||
strategy := metadata.GetString(md, "strategy")
|
|
||||||
maxFails := metadata.GetInt(md, "maxFails")
|
|
||||||
failTimeout := metadata.GetDuration(md, "failTimeout")
|
|
||||||
if strategy == "" && maxFails <= 0 && failTimeout <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strategy == "" {
|
|
||||||
strategy = "round"
|
|
||||||
}
|
|
||||||
if maxFails <= 0 {
|
|
||||||
maxFails = 1
|
|
||||||
}
|
|
||||||
if failTimeout <= 0 {
|
|
||||||
failTimeout = time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
md.Del("strategy")
|
|
||||||
md.Del("maxFails")
|
|
||||||
md.Del("failTimeout")
|
|
||||||
|
|
||||||
return &config.SelectorConfig{
|
|
||||||
Strategy: strategy,
|
|
||||||
MaxFails: maxFails,
|
|
||||||
FailTimeout: failTimeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,436 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/bypass"
|
|
||||||
"github.com/go-gost/gost/pkg/chain"
|
|
||||||
"github.com/go-gost/gost/pkg/config"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
"github.com/go-gost/gost/pkg/handler"
|
|
||||||
hostspkg "github.com/go-gost/gost/pkg/hosts"
|
|
||||||
"github.com/go-gost/gost/pkg/listener"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
"github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/go-gost/gost/pkg/resolver"
|
|
||||||
resolver_impl "github.com/go-gost/gost/pkg/resolver/impl"
|
|
||||||
"github.com/go-gost/gost/pkg/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
chains = make(map[string]*chain.Chain)
|
|
||||||
bypasses = make(map[string]bypass.Bypass)
|
|
||||||
resolvers = make(map[string]resolver.Resolver)
|
|
||||||
hosts = make(map[string]hostspkg.HostMapper)
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildService(cfg *config.Config) (services []*service.Service) {
|
|
||||||
if cfg == nil || len(cfg.Services) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bypassCfg := range cfg.Bypasses {
|
|
||||||
bypasses[bypassCfg.Name] = bypassFromConfig(bypassCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, resolverCfg := range cfg.Resolvers {
|
|
||||||
r, err := resolverFromConfig(resolverCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
resolvers[resolverCfg.Name] = r
|
|
||||||
}
|
|
||||||
for _, hostsCfg := range cfg.Hosts {
|
|
||||||
hosts[hostsCfg.Name] = hostsFromConfig(hostsCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chainCfg := range cfg.Chains {
|
|
||||||
chains[chainCfg.Name] = chainFromConfig(chainCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, svc := range cfg.Services {
|
|
||||||
if svc.Listener == nil {
|
|
||||||
svc.Listener = &config.ListenerConfig{
|
|
||||||
Type: "tcp",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if svc.Handler == nil {
|
|
||||||
svc.Handler = &config.HandlerConfig{
|
|
||||||
Type: "auto",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serviceLogger := log.WithFields(map[string]interface{}{
|
|
||||||
"kind": "service",
|
|
||||||
"service": svc.Name,
|
|
||||||
"listener": svc.Listener.Type,
|
|
||||||
"handler": svc.Handler.Type,
|
|
||||||
})
|
|
||||||
|
|
||||||
listenerLogger := serviceLogger.WithFields(map[string]interface{}{
|
|
||||||
"kind": "listener",
|
|
||||||
})
|
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
var err error
|
|
||||||
|
|
||||||
tlsCfg := svc.Listener.TLS
|
|
||||||
if tlsCfg == nil {
|
|
||||||
tlsCfg = &config.TLSConfig{}
|
|
||||||
}
|
|
||||||
tlsConfig, err = loadServerTLSConfig(tlsCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ln := registry.GetListener(svc.Listener.Type)(
|
|
||||||
listener.AddrOption(svc.Addr),
|
|
||||||
listener.ChainOption(chains[svc.Listener.Chain]),
|
|
||||||
listener.AuthsOption(authsFromConfig(svc.Listener.Auths...)...),
|
|
||||||
listener.TLSConfigOption(tlsConfig),
|
|
||||||
listener.LoggerOption(listenerLogger),
|
|
||||||
)
|
|
||||||
|
|
||||||
if svc.Listener.Metadata == nil {
|
|
||||||
svc.Listener.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
if err := ln.Init(metadata.MapMetadata(svc.Listener.Metadata)); err != nil {
|
|
||||||
listenerLogger.Fatal("init: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
handlerLogger := serviceLogger.WithFields(map[string]interface{}{
|
|
||||||
"kind": "handler",
|
|
||||||
})
|
|
||||||
|
|
||||||
tlsConfig = nil
|
|
||||||
tlsCfg = svc.Handler.TLS
|
|
||||||
if tlsCfg == nil {
|
|
||||||
tlsCfg = &config.TLSConfig{}
|
|
||||||
}
|
|
||||||
tlsConfig, err = loadServerTLSConfig(tlsCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := registry.GetHandler(svc.Handler.Type)(
|
|
||||||
handler.AuthsOption(authsFromConfig(svc.Handler.Auths...)...),
|
|
||||||
handler.RetriesOption(svc.Handler.Retries),
|
|
||||||
handler.ChainOption(chains[svc.Handler.Chain]),
|
|
||||||
handler.BypassOption(bypasses[svc.Bypass]),
|
|
||||||
handler.ResolverOption(resolvers[svc.Resolver]),
|
|
||||||
handler.HostsOption(hosts[svc.Hosts]),
|
|
||||||
handler.TLSConfigOption(tlsConfig),
|
|
||||||
handler.LoggerOption(handlerLogger),
|
|
||||||
)
|
|
||||||
|
|
||||||
if forwarder, ok := h.(handler.Forwarder); ok {
|
|
||||||
forwarder.Forward(forwarderFromConfig(svc.Forwarder))
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc.Handler.Metadata == nil {
|
|
||||||
svc.Handler.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
if err := h.Init(metadata.MapMetadata(svc.Handler.Metadata)); err != nil {
|
|
||||||
handlerLogger.Fatal("init: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := (&service.Service{}).
|
|
||||||
WithListener(ln).
|
|
||||||
WithHandler(h).
|
|
||||||
WithLogger(serviceLogger)
|
|
||||||
services = append(services, s)
|
|
||||||
|
|
||||||
serviceLogger.Infof("listening on %s/%s", s.Addr().String(), s.Addr().Network())
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func chainFromConfig(cfg *config.ChainConfig) *chain.Chain {
|
|
||||||
if cfg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
chainLogger := log.WithFields(map[string]interface{}{
|
|
||||||
"kind": "chain",
|
|
||||||
"chain": cfg.Name,
|
|
||||||
})
|
|
||||||
|
|
||||||
c := &chain.Chain{}
|
|
||||||
selector := selectorFromConfig(cfg.Selector)
|
|
||||||
for _, hop := range cfg.Hops {
|
|
||||||
group := &chain.NodeGroup{}
|
|
||||||
for _, v := range hop.Nodes {
|
|
||||||
nodeLogger := chainLogger.WithFields(map[string]interface{}{
|
|
||||||
"kind": "node",
|
|
||||||
"connector": v.Connector.Type,
|
|
||||||
"dialer": v.Dialer.Type,
|
|
||||||
"hop": hop.Name,
|
|
||||||
"node": v.Name,
|
|
||||||
})
|
|
||||||
connectorLogger := nodeLogger.WithFields(map[string]interface{}{
|
|
||||||
"kind": "connector",
|
|
||||||
})
|
|
||||||
|
|
||||||
var user *url.Userinfo
|
|
||||||
if auth := v.Connector.Auth; auth != nil && auth.Username != "" {
|
|
||||||
if auth.Password == "" {
|
|
||||||
user = url.User(auth.Username)
|
|
||||||
} else {
|
|
||||||
user = url.UserPassword(auth.Username, auth.Password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
|
||||||
var err error
|
|
||||||
tlsCfg := v.Connector.TLS
|
|
||||||
if tlsCfg == nil {
|
|
||||||
tlsCfg = &config.TLSConfig{}
|
|
||||||
}
|
|
||||||
tlsConfig, err = loadClientTLSConfig(tlsCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := registry.GetConnector(v.Connector.Type)(
|
|
||||||
connector.UserOption(user),
|
|
||||||
connector.TLSConfigOption(tlsConfig),
|
|
||||||
connector.LoggerOption(connectorLogger),
|
|
||||||
)
|
|
||||||
|
|
||||||
if v.Connector.Metadata == nil {
|
|
||||||
v.Connector.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
if err := cr.Init(metadata.MapMetadata(v.Connector.Metadata)); err != nil {
|
|
||||||
connectorLogger.Fatal("init: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dialerLogger := nodeLogger.WithFields(map[string]interface{}{
|
|
||||||
"kind": "dialer",
|
|
||||||
})
|
|
||||||
|
|
||||||
user = nil
|
|
||||||
if auth := v.Dialer.Auth; auth != nil && auth.Username != "" {
|
|
||||||
if auth.Password == "" {
|
|
||||||
user = url.User(auth.Username)
|
|
||||||
} else {
|
|
||||||
user = url.UserPassword(auth.Username, auth.Password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig = nil
|
|
||||||
tlsCfg = v.Dialer.TLS
|
|
||||||
if tlsCfg == nil {
|
|
||||||
tlsCfg = &config.TLSConfig{}
|
|
||||||
}
|
|
||||||
tlsConfig, err = loadClientTLSConfig(tlsCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d := registry.GetDialer(v.Dialer.Type)(
|
|
||||||
dialer.UserOption(user),
|
|
||||||
dialer.TLSConfigOption(tlsConfig),
|
|
||||||
dialer.LoggerOption(dialerLogger),
|
|
||||||
)
|
|
||||||
|
|
||||||
if v.Dialer.Metadata == nil {
|
|
||||||
v.Dialer.Metadata = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
if err := d.Init(metadata.MapMetadata(v.Dialer.Metadata)); err != nil {
|
|
||||||
dialerLogger.Fatal("init: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := (&chain.Transport{}).
|
|
||||||
WithConnector(cr).
|
|
||||||
WithDialer(d).
|
|
||||||
WithAddr(v.Addr)
|
|
||||||
|
|
||||||
if v.Bypass == "" {
|
|
||||||
v.Bypass = hop.Bypass
|
|
||||||
}
|
|
||||||
if v.Resolver == "" {
|
|
||||||
v.Resolver = hop.Resolver
|
|
||||||
}
|
|
||||||
if v.Hosts == "" {
|
|
||||||
v.Hosts = hop.Hosts
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &chain.Node{
|
|
||||||
Name: v.Name,
|
|
||||||
Addr: v.Addr,
|
|
||||||
Transport: tr,
|
|
||||||
Bypass: bypasses[v.Bypass],
|
|
||||||
Resolver: resolvers[v.Resolver],
|
|
||||||
Hosts: hosts[v.Hosts],
|
|
||||||
Marker: &chain.FailMarker{},
|
|
||||||
}
|
|
||||||
group.AddNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
sel := selector
|
|
||||||
if s := selectorFromConfig(hop.Selector); s != nil {
|
|
||||||
sel = s
|
|
||||||
}
|
|
||||||
group.WithSelector(sel)
|
|
||||||
c.AddNodeGroup(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func forwarderFromConfig(cfg *config.ForwarderConfig) *chain.NodeGroup {
|
|
||||||
if cfg == nil || len(cfg.Targets) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
group := &chain.NodeGroup{}
|
|
||||||
for _, target := range cfg.Targets {
|
|
||||||
if v := strings.TrimSpace(target); v != "" {
|
|
||||||
group.AddNode(&chain.Node{
|
|
||||||
Name: target,
|
|
||||||
Addr: target,
|
|
||||||
Marker: &chain.FailMarker{},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return group.WithSelector(selectorFromConfig(cfg.Selector))
|
|
||||||
}
|
|
||||||
|
|
||||||
func logFromConfig(cfg *config.LogConfig) logger.Logger {
|
|
||||||
if cfg == nil {
|
|
||||||
cfg = &config.LogConfig{}
|
|
||||||
}
|
|
||||||
opts := []logger.LoggerOption{
|
|
||||||
logger.FormatLoggerOption(logger.LogFormat(cfg.Format)),
|
|
||||||
logger.LevelLoggerOption(logger.LogLevel(cfg.Level)),
|
|
||||||
}
|
|
||||||
|
|
||||||
var out io.Writer = os.Stderr
|
|
||||||
switch cfg.Output {
|
|
||||||
case "none":
|
|
||||||
return logger.Nop()
|
|
||||||
case "stdout":
|
|
||||||
out = os.Stdout
|
|
||||||
case "stderr", "":
|
|
||||||
out = os.Stderr
|
|
||||||
default:
|
|
||||||
f, err := os.OpenFile(cfg.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("log", err)
|
|
||||||
} else {
|
|
||||||
out = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opts = append(opts, logger.OutputLoggerOption(out))
|
|
||||||
|
|
||||||
return logger.NewLogger(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectorFromConfig(cfg *config.SelectorConfig) chain.Selector {
|
|
||||||
if cfg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var strategy chain.Strategy
|
|
||||||
switch cfg.Strategy {
|
|
||||||
case "round", "rr":
|
|
||||||
strategy = chain.RoundRobinStrategy()
|
|
||||||
case "random", "rand":
|
|
||||||
strategy = chain.RandomStrategy()
|
|
||||||
case "fifo", "ha":
|
|
||||||
strategy = chain.FIFOStrategy()
|
|
||||||
default:
|
|
||||||
strategy = chain.RoundRobinStrategy()
|
|
||||||
}
|
|
||||||
|
|
||||||
return chain.NewSelector(
|
|
||||||
strategy,
|
|
||||||
chain.InvalidFilter(),
|
|
||||||
chain.FailFilter(cfg.MaxFails, cfg.FailTimeout),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bypassFromConfig(cfg *config.BypassConfig) bypass.Bypass {
|
|
||||||
if cfg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return bypass.NewBypassPatterns(
|
|
||||||
cfg.Reverse,
|
|
||||||
cfg.Matchers,
|
|
||||||
bypass.LoggerBypassOption(log.WithFields(map[string]interface{}{
|
|
||||||
"kind": "bypass",
|
|
||||||
"bypass": cfg.Name,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolverFromConfig(cfg *config.ResolverConfig) (resolver.Resolver, error) {
|
|
||||||
if cfg == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
var nameservers []resolver_impl.NameServer
|
|
||||||
for _, server := range cfg.Nameservers {
|
|
||||||
nameservers = append(nameservers, resolver_impl.NameServer{
|
|
||||||
Addr: server.Addr,
|
|
||||||
Chain: chains[server.Chain],
|
|
||||||
TTL: server.TTL,
|
|
||||||
Timeout: server.Timeout,
|
|
||||||
ClientIP: net.ParseIP(server.ClientIP),
|
|
||||||
Prefer: server.Prefer,
|
|
||||||
Hostname: server.Hostname,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := log.WithFields(map[string]interface{}{
|
|
||||||
"kind": "resolver",
|
|
||||||
"resolver": cfg.Name,
|
|
||||||
})
|
|
||||||
return resolver_impl.NewResolver(
|
|
||||||
nameservers,
|
|
||||||
resolver_impl.LoggerResolverOption(logger),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostsFromConfig(cfg *config.HostsConfig) hostspkg.HostMapper {
|
|
||||||
if cfg == nil || len(cfg.Mappings) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
hosts := hostspkg.NewHosts()
|
|
||||||
hosts.Logger = log.WithFields(map[string]interface{}{
|
|
||||||
"kind": "hosts",
|
|
||||||
"hosts": cfg.Name,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, host := range cfg.Mappings {
|
|
||||||
if host.IP == "" || host.Hostname == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(host.IP)
|
|
||||||
if ip == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hosts.Map(ip, host.Hostname, host.Aliases...)
|
|
||||||
}
|
|
||||||
return hosts
|
|
||||||
}
|
|
||||||
|
|
||||||
func authsFromConfig(cfgs ...*config.AuthConfig) []*url.Userinfo {
|
|
||||||
var auths []*url.Userinfo
|
|
||||||
|
|
||||||
for _, cfg := range cfgs {
|
|
||||||
if cfg == nil || cfg.Username == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
auths = append(auths, url.UserPassword(cfg.Username, cfg.Password))
|
|
||||||
}
|
|
||||||
|
|
||||||
return auths
|
|
||||||
}
|
|
||||||
154
cmd/gost/main.go
154
cmd/gost/main.go
|
|
@ -1,37 +1,99 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"log"
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/config"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
xlogger "github.com/go-gost/x/logger"
|
||||||
|
"github.com/judwhite/go-svc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type stringList []string
|
||||||
|
|
||||||
|
func (l *stringList) String() string {
|
||||||
|
return fmt.Sprintf("%s", *l)
|
||||||
|
}
|
||||||
|
func (l *stringList) Set(value string) error {
|
||||||
|
*l = append(*l, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
log = logger.Default()
|
cfgFile string
|
||||||
|
outputFormat string
|
||||||
cfgFile string
|
services stringList
|
||||||
outputCfgFile string
|
nodes stringList
|
||||||
services stringList
|
debug bool
|
||||||
nodes stringList
|
trace bool
|
||||||
debug bool
|
apiAddr string
|
||||||
|
metricsAddr string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
|
||||||
|
|
||||||
|
args := strings.Join(os.Args[1:], " ")
|
||||||
|
|
||||||
|
if strings.Contains(args, " -- ") {
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
ret int
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for wid, wargs := range strings.Split(" "+args+" ", " -- ") {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(wid int, wargs string) {
|
||||||
|
defer wg.Done()
|
||||||
|
defer cancel()
|
||||||
|
worker(wid, strings.Split(wargs, " "), &ctx, &ret)
|
||||||
|
}(wid, strings.TrimSpace(wargs))
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
os.Exit(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func worker(id int, args []string, ctx *context.Context, ret *int) {
|
||||||
|
cmd := exec.CommandContext(*ctx, os.Args[0], args...)
|
||||||
|
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id))
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if cmd.ProcessState.Exited() {
|
||||||
|
*ret = cmd.ProcessState.ExitCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var printVersion bool
|
var printVersion bool
|
||||||
|
|
||||||
flag.Var(&services, "L", "service list")
|
flag.Var(&services, "L", "service list")
|
||||||
flag.Var(&nodes, "F", "chain node list")
|
flag.Var(&nodes, "F", "chain node list")
|
||||||
flag.StringVar(&cfgFile, "C", "", "configure file")
|
flag.StringVar(&cfgFile, "C", "", "configuration file")
|
||||||
flag.BoolVar(&printVersion, "V", false, "print version")
|
flag.BoolVar(&printVersion, "V", false, "print version")
|
||||||
|
flag.StringVar(&outputFormat, "O", "", "output format, one of yaml|json format")
|
||||||
flag.BoolVar(&debug, "D", false, "debug mode")
|
flag.BoolVar(&debug, "D", false, "debug mode")
|
||||||
flag.StringVar(&outputCfgFile, "O", "", "write config to FILE")
|
flag.BoolVar(&trace, "DD", false, "trace mode")
|
||||||
|
flag.StringVar(&apiAddr, "api", "", "api service address")
|
||||||
|
flag.StringVar(&metricsAddr, "metrics", "", "metrics service address")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if printVersion {
|
if printVersion {
|
||||||
|
|
@ -42,64 +104,12 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg := &config.Config{}
|
log := xlogger.NewLogger()
|
||||||
var err error
|
logger.SetDefault(log)
|
||||||
if len(services) > 0 {
|
|
||||||
cfg, err = buildConfigFromCmd(services, nodes)
|
p := &program{}
|
||||||
if debug && cfg != nil {
|
|
||||||
if cfg.Log == nil {
|
if err := svc.Run(p); err != nil {
|
||||||
cfg.Log = &config.LogConfig{}
|
logger.Default().Fatal(err)
|
||||||
}
|
|
||||||
cfg.Log.Level = string(logger.DebugLevel)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if cfgFile != "" {
|
|
||||||
err = cfg.ReadFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
err = cfg.Load()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log = logFromConfig(cfg.Log)
|
|
||||||
|
|
||||||
if outputCfgFile != "" {
|
|
||||||
var w io.Writer
|
|
||||||
if outputCfgFile == "-" {
|
|
||||||
w = os.Stdout
|
|
||||||
} else {
|
|
||||||
f, err := os.Create(outputCfgFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
w = f
|
|
||||||
}
|
|
||||||
if err := cfg.Write(w); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Profiling != nil && cfg.Profiling.Enabled {
|
|
||||||
go func() {
|
|
||||||
addr := cfg.Profiling.Addr
|
|
||||||
if addr == "" {
|
|
||||||
addr = ":6060"
|
|
||||||
}
|
|
||||||
log.Info("profiling serve on ", addr)
|
|
||||||
log.Fatal(http.ListenAndServe(addr, nil))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
buildDefaultTLSConfig(cfg.TLS)
|
|
||||||
|
|
||||||
services := buildService(cfg)
|
|
||||||
for _, svc := range services {
|
|
||||||
go svc.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
select {}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/auth"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/core/service"
|
||||||
|
api_service "github.com/go-gost/x/api/service"
|
||||||
|
xauth "github.com/go-gost/x/auth"
|
||||||
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/config/loader"
|
||||||
|
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||||
|
"github.com/go-gost/x/config/parsing/parser"
|
||||||
|
xmetrics "github.com/go-gost/x/metrics"
|
||||||
|
metrics "github.com/go-gost/x/metrics/service"
|
||||||
|
"github.com/go-gost/x/registry"
|
||||||
|
"github.com/judwhite/go-svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type program struct {
|
||||||
|
srvApi service.Service
|
||||||
|
srvMetrics service.Service
|
||||||
|
srvProfiling *http.Server
|
||||||
|
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) Init(env svc.Environment) error {
|
||||||
|
parser.Init(parser.Args{
|
||||||
|
CfgFile: cfgFile,
|
||||||
|
Services: services,
|
||||||
|
Nodes: nodes,
|
||||||
|
Debug: debug,
|
||||||
|
Trace: trace,
|
||||||
|
ApiAddr: apiAddr,
|
||||||
|
MetricsAddr: metricsAddr,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) Start() error {
|
||||||
|
cfg, err := parser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputFormat != "" {
|
||||||
|
if err := cfg.Write(os.Stdout, outputFormat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Set(cfg)
|
||||||
|
|
||||||
|
if err := loader.Load(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.run(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
p.cancel = cancel
|
||||||
|
go p.reload(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) run(cfg *config.Config) error {
|
||||||
|
for _, svc := range registry.ServiceRegistry().GetAll() {
|
||||||
|
svc := svc
|
||||||
|
go func() {
|
||||||
|
svc.Serve()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.srvApi != nil {
|
||||||
|
p.srvApi.Close()
|
||||||
|
p.srvApi = nil
|
||||||
|
}
|
||||||
|
if cfg.API != nil {
|
||||||
|
s, err := buildApiService(cfg.API)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.srvApi = s
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@api"})
|
||||||
|
|
||||||
|
log.Info("listening on ", s.Addr())
|
||||||
|
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.srvMetrics != nil {
|
||||||
|
p.srvMetrics.Close()
|
||||||
|
p.srvMetrics = nil
|
||||||
|
}
|
||||||
|
if cfg.Metrics != nil && cfg.Metrics.Addr != "" {
|
||||||
|
s, err := buildMetricsService(cfg.Metrics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.srvMetrics = s
|
||||||
|
|
||||||
|
xmetrics.Enable(true)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@metrics"})
|
||||||
|
|
||||||
|
log.Info("listening on ", s.Addr())
|
||||||
|
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.srvProfiling != nil {
|
||||||
|
p.srvProfiling.Close()
|
||||||
|
p.srvProfiling = nil
|
||||||
|
}
|
||||||
|
if cfg.Profiling != nil {
|
||||||
|
addr := cfg.Profiling.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":6060"
|
||||||
|
}
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
}
|
||||||
|
p.srvProfiling = s
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@profiling"})
|
||||||
|
|
||||||
|
log.Info("listening on ", addr)
|
||||||
|
if err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) Stop() error {
|
||||||
|
if p.cancel != nil {
|
||||||
|
p.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, srv := range registry.ServiceRegistry().GetAll() {
|
||||||
|
srv.Close()
|
||||||
|
logger.Default().Debugf("service %s shutdown", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.srvApi != nil {
|
||||||
|
p.srvApi.Close()
|
||||||
|
logger.Default().Debug("service @api shutdown")
|
||||||
|
}
|
||||||
|
if p.srvMetrics != nil {
|
||||||
|
p.srvMetrics.Close()
|
||||||
|
logger.Default().Debug("service @metrics shutdown")
|
||||||
|
}
|
||||||
|
if p.srvProfiling != nil {
|
||||||
|
p.srvProfiling.Close()
|
||||||
|
logger.Default().Debug("service @profiling shutdown")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) reload(ctx context.Context) {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGHUP)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c:
|
||||||
|
if err := p.reloadConfig(); err != nil {
|
||||||
|
logger.Default().Error(err)
|
||||||
|
} else {
|
||||||
|
logger.Default().Info("config reloaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) reloadConfig() error {
|
||||||
|
cfg, err := parser.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.Set(cfg)
|
||||||
|
|
||||||
|
if err := loader.Load(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.run(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildApiService(cfg *config.APIConfig) (service.Service, error) {
|
||||||
|
var authers []auth.Authenticator
|
||||||
|
if auther := auth_parser.ParseAutherFromAuth(cfg.Auth); auther != nil {
|
||||||
|
authers = append(authers, auther)
|
||||||
|
}
|
||||||
|
if cfg.Auther != "" {
|
||||||
|
authers = append(authers, registry.AutherRegistry().Get(cfg.Auther))
|
||||||
|
}
|
||||||
|
|
||||||
|
var auther auth.Authenticator
|
||||||
|
if len(authers) > 0 {
|
||||||
|
auther = xauth.AuthenticatorGroup(authers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
network := "tcp"
|
||||||
|
addr := cfg.Addr
|
||||||
|
if strings.HasPrefix(addr, "unix://") {
|
||||||
|
network = "unix"
|
||||||
|
addr = strings.TrimPrefix(addr, "unix://")
|
||||||
|
}
|
||||||
|
return api_service.NewService(
|
||||||
|
network, addr,
|
||||||
|
api_service.PathPrefixOption(cfg.PathPrefix),
|
||||||
|
api_service.AccessLogOption(cfg.AccessLog),
|
||||||
|
api_service.AutherOption(auther),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMetricsService(cfg *config.MetricsConfig) (service.Service, error) {
|
||||||
|
auther := auth_parser.ParseAutherFromAuth(cfg.Auth)
|
||||||
|
if cfg.Auther != "" {
|
||||||
|
auther = registry.AutherRegistry().Get(cfg.Auther)
|
||||||
|
}
|
||||||
|
|
||||||
|
network := "tcp"
|
||||||
|
addr := cfg.Addr
|
||||||
|
if strings.HasPrefix(addr, "unix://") {
|
||||||
|
network = "unix"
|
||||||
|
addr = strings.TrimPrefix(addr, "unix://")
|
||||||
|
}
|
||||||
|
return metrics.NewService(
|
||||||
|
network, addr,
|
||||||
|
metrics.PathOption(cfg.Path),
|
||||||
|
metrics.AutherOption(auther),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,78 +2,114 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// Register connectors
|
// Register connectors
|
||||||
_ "github.com/go-gost/gost/pkg/connector/forward"
|
_ "github.com/go-gost/x/connector/direct"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/http"
|
_ "github.com/go-gost/x/connector/forward"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/http2"
|
_ "github.com/go-gost/x/connector/http"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/relay"
|
_ "github.com/go-gost/x/connector/http2"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/sni"
|
_ "github.com/go-gost/x/connector/masque"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/socks/v4"
|
_ "github.com/go-gost/x/connector/relay"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/socks/v5"
|
_ "github.com/go-gost/x/connector/router"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/ss"
|
_ "github.com/go-gost/x/connector/serial"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/ss/udp"
|
_ "github.com/go-gost/x/connector/sni"
|
||||||
_ "github.com/go-gost/gost/pkg/connector/sshd"
|
_ "github.com/go-gost/x/connector/socks/v4"
|
||||||
|
_ "github.com/go-gost/x/connector/socks/v5"
|
||||||
|
_ "github.com/go-gost/x/connector/ss"
|
||||||
|
_ "github.com/go-gost/x/connector/ss/udp"
|
||||||
|
_ "github.com/go-gost/x/connector/sshd"
|
||||||
|
_ "github.com/go-gost/x/connector/tcp"
|
||||||
|
_ "github.com/go-gost/x/connector/tunnel"
|
||||||
|
_ "github.com/go-gost/x/connector/unix"
|
||||||
|
_ "github.com/go-gost/x/connector/masque"
|
||||||
|
|
||||||
// Register dialers
|
// Register dialers
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/ftcp"
|
_ "github.com/go-gost/x/dialer/direct"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/grpc"
|
_ "github.com/go-gost/x/dialer/dtls"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/http2"
|
_ "github.com/go-gost/x/dialer/ftcp"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/http2/h2"
|
_ "github.com/go-gost/x/dialer/grpc"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/http3"
|
_ "github.com/go-gost/x/dialer/http2"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/kcp"
|
_ "github.com/go-gost/x/dialer/http2/h2"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/obfs/http"
|
_ "github.com/go-gost/x/dialer/http3"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/obfs/tls"
|
_ "github.com/go-gost/x/dialer/http3/masque"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/pht"
|
_ "github.com/go-gost/x/dialer/http3/wt"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/quic"
|
_ "github.com/go-gost/x/dialer/icmp"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/ssh"
|
_ "github.com/go-gost/x/dialer/kcp"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/sshd"
|
_ "github.com/go-gost/x/dialer/mtcp"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/tcp"
|
_ "github.com/go-gost/x/dialer/mtls"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/tls"
|
_ "github.com/go-gost/x/dialer/mws"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/tls/mux"
|
_ "github.com/go-gost/x/dialer/obfs/http"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/udp"
|
_ "github.com/go-gost/x/dialer/obfs/tls"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/ws"
|
_ "github.com/go-gost/x/dialer/pht"
|
||||||
_ "github.com/go-gost/gost/pkg/dialer/ws/mux"
|
_ "github.com/go-gost/x/dialer/quic"
|
||||||
|
_ "github.com/go-gost/x/dialer/serial"
|
||||||
|
_ "github.com/go-gost/x/dialer/ssh"
|
||||||
|
_ "github.com/go-gost/x/dialer/sshd"
|
||||||
|
_ "github.com/go-gost/x/dialer/tcp"
|
||||||
|
_ "github.com/go-gost/x/dialer/tls"
|
||||||
|
_ "github.com/go-gost/x/dialer/udp"
|
||||||
|
_ "github.com/go-gost/x/dialer/unix"
|
||||||
|
_ "github.com/go-gost/x/dialer/ws"
|
||||||
|
|
||||||
// Register handlers
|
// Register handlers
|
||||||
_ "github.com/go-gost/gost/pkg/handler/auto"
|
_ "github.com/go-gost/x/handler/api"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/dns"
|
_ "github.com/go-gost/x/handler/auto"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/forward/local"
|
_ "github.com/go-gost/x/handler/dns"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/forward/remote"
|
_ "github.com/go-gost/x/handler/file"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/http"
|
_ "github.com/go-gost/x/handler/forward/local"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/http2"
|
_ "github.com/go-gost/x/handler/forward/remote"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/redirect"
|
_ "github.com/go-gost/x/handler/http"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/relay"
|
_ "github.com/go-gost/x/handler/http2"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/sni"
|
_ "github.com/go-gost/x/handler/http3"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/socks/v4"
|
_ "github.com/go-gost/x/handler/masque"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/socks/v5"
|
_ "github.com/go-gost/x/handler/metrics"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/ss"
|
_ "github.com/go-gost/x/handler/redirect/tcp"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/ss/udp"
|
_ "github.com/go-gost/x/handler/redirect/udp"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/sshd"
|
_ "github.com/go-gost/x/handler/relay"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/tap"
|
_ "github.com/go-gost/x/handler/router"
|
||||||
_ "github.com/go-gost/gost/pkg/handler/tun"
|
_ "github.com/go-gost/x/handler/serial"
|
||||||
|
_ "github.com/go-gost/x/handler/sni"
|
||||||
|
_ "github.com/go-gost/x/handler/socks/v4"
|
||||||
|
_ "github.com/go-gost/x/handler/socks/v5"
|
||||||
|
_ "github.com/go-gost/x/handler/ss"
|
||||||
|
_ "github.com/go-gost/x/handler/ss/udp"
|
||||||
|
_ "github.com/go-gost/x/handler/sshd"
|
||||||
|
_ "github.com/go-gost/x/handler/tap"
|
||||||
|
_ "github.com/go-gost/x/handler/tun"
|
||||||
|
_ "github.com/go-gost/x/handler/tungo"
|
||||||
|
_ "github.com/go-gost/x/handler/tunnel"
|
||||||
|
_ "github.com/go-gost/x/handler/unix"
|
||||||
|
|
||||||
// Register listeners
|
// Register listeners
|
||||||
_ "github.com/go-gost/gost/pkg/listener/dns"
|
_ "github.com/go-gost/x/listener/dns"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/ftcp"
|
_ "github.com/go-gost/x/listener/dtls"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/grpc"
|
_ "github.com/go-gost/x/listener/ftcp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/http2"
|
_ "github.com/go-gost/x/listener/grpc"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/http2/h2"
|
_ "github.com/go-gost/x/listener/http2"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/http3"
|
_ "github.com/go-gost/x/listener/http2/h2"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/kcp"
|
_ "github.com/go-gost/x/listener/http3"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/obfs/http"
|
_ "github.com/go-gost/x/listener/http3/h3"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/obfs/tls"
|
_ "github.com/go-gost/x/listener/http3/wt"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/pht"
|
_ "github.com/go-gost/x/listener/icmp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/quic"
|
_ "github.com/go-gost/x/listener/kcp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/redirect/udp"
|
_ "github.com/go-gost/x/listener/mtcp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/rtcp"
|
_ "github.com/go-gost/x/listener/mtls"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/rudp"
|
_ "github.com/go-gost/x/listener/mws"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/ssh"
|
_ "github.com/go-gost/x/listener/obfs/http"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/sshd"
|
_ "github.com/go-gost/x/listener/obfs/tls"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/tap"
|
_ "github.com/go-gost/x/listener/pht"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/tcp"
|
_ "github.com/go-gost/x/listener/quic"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/tls"
|
_ "github.com/go-gost/x/listener/redirect/tcp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/tls/mux"
|
_ "github.com/go-gost/x/listener/redirect/udp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/tun"
|
_ "github.com/go-gost/x/listener/rtcp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/udp"
|
_ "github.com/go-gost/x/listener/rudp"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/ws"
|
_ "github.com/go-gost/x/listener/serial"
|
||||||
_ "github.com/go-gost/gost/pkg/listener/ws/mux"
|
_ "github.com/go-gost/x/listener/ssh"
|
||||||
|
_ "github.com/go-gost/x/listener/sshd"
|
||||||
|
_ "github.com/go-gost/x/listener/tap"
|
||||||
|
_ "github.com/go-gost/x/listener/tcp"
|
||||||
|
_ "github.com/go-gost/x/listener/tls"
|
||||||
|
_ "github.com/go-gost/x/listener/tun"
|
||||||
|
_ "github.com/go-gost/x/listener/tungo"
|
||||||
|
_ "github.com/go-gost/x/listener/udp"
|
||||||
|
_ "github.com/go-gost/x/listener/unix"
|
||||||
|
_ "github.com/go-gost/x/listener/ws"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
106
cmd/gost/tls.go
106
cmd/gost/tls.go
|
|
@ -1,106 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
tls_util "github.com/go-gost/gost/pkg/common/util/tls"
|
|
||||||
"github.com/go-gost/gost/pkg/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func loadServerTLSConfig(cfg *config.TLSConfig) (*tls.Config, error) {
|
|
||||||
return tls_util.LoadServerConfig(cfg.CertFile, cfg.KeyFile, cfg.CAFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadClientTLSConfig(cfg *config.TLSConfig) (*tls.Config, error) {
|
|
||||||
return tls_util.LoadClientConfig(cfg.CertFile, cfg.KeyFile, cfg.CAFile, cfg.Secure, cfg.ServerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildDefaultTLSConfig(cfg *config.TLSConfig) {
|
|
||||||
if cfg == nil {
|
|
||||||
cfg = &config.TLSConfig{
|
|
||||||
CertFile: "cert.pem",
|
|
||||||
KeyFile: "key.pem",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig, err := loadConfig(cfg.CertFile, cfg.KeyFile)
|
|
||||||
if err != nil {
|
|
||||||
// generate random self-signed certificate.
|
|
||||||
cert, err := genCertificate()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
}
|
|
||||||
log.Warn("load TLS certificate files failed, use random generated certificate")
|
|
||||||
} else {
|
|
||||||
log.Info("load TLS certificate files OK")
|
|
||||||
}
|
|
||||||
tls_util.DefaultConfig = tlsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig(certFile, keyFile string) (*tls.Config, error) {
|
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genCertificate() (cert tls.Certificate, err error) {
|
|
||||||
rawCert, rawKey, err := generateKeyPair()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return tls.X509KeyPair(rawCert, rawKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateKeyPair() (rawCert, rawKey []byte, err error) {
|
|
||||||
// Create private key and self-signed certificate
|
|
||||||
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
|
|
||||||
|
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
validFor := time.Hour * 24 * 365 * 10 // ten years
|
|
||||||
notBefore := time.Now()
|
|
||||||
notAfter := notBefore.Add(validFor)
|
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
||||||
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
|
||||||
template := x509.Certificate{
|
|
||||||
SerialNumber: serialNumber,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
Organization: []string{"gost"},
|
|
||||||
CommonName: "gost.run",
|
|
||||||
},
|
|
||||||
NotBefore: notBefore,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
}
|
|
||||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
|
||||||
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
const (
|
var (
|
||||||
version = "3.0.0-alpha.1"
|
version = "3.3.0"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
version: "3.4"
|
|
||||||
services:
|
|
||||||
gost:
|
|
||||||
build: .
|
|
||||||
221
go.mod
221
go.mod
|
|
@ -1,73 +1,170 @@
|
||||||
module github.com/go-gost/gost
|
module github.com/go-gost/gost
|
||||||
|
|
||||||
go 1.17
|
go 1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed
|
github.com/go-gost/core v0.4.1
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
github.com/go-gost/x v0.10.11-0.20260605152603-e45d9a8cc81a
|
||||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
github.com/judwhite/go-svc v1.2.1
|
||||||
github.com/cheekybits/genny v1.0.0 // indirect
|
github.com/moby/moby/client v0.4.0
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
|
github.com/testcontainers/testcontainers-go v0.42.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||||
|
github.com/alessio/shellescape v1.4.1 // indirect
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/coreos/go-iptables v0.5.0 // indirect
|
github.com/coreos/go-iptables v0.5.0 // indirect
|
||||||
github.com/docker/libcontainer v2.2.1+incompatible
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/danieljoos/wincred v1.2.0 // indirect
|
||||||
github.com/go-gost/gosocks4 v0.0.1
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/go-gost/tls-dissector v0.0.2-0.20211125135007-2b5d5bd9c07e
|
github.com/docker/go-connections v0.7.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/dunglas/httpsfv v1.1.0 // indirect
|
||||||
github.com/golang/snappy v0.0.3
|
github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/cors v1.7.2 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.10.1 // indirect
|
||||||
|
github.com/go-gost/go-shadowsocks2 v0.1.3 // indirect
|
||||||
|
github.com/go-gost/gosocks4 v0.1.0 // indirect
|
||||||
|
github.com/go-gost/gosocks5 v0.5.0 // indirect
|
||||||
|
github.com/go-gost/plugin v0.3.0 // indirect
|
||||||
|
github.com/go-gost/relay v0.6.0 // indirect
|
||||||
|
github.com/go-gost/tls-dissector v0.2.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/gopacket v1.1.19 // indirect
|
github.com/google/gopacket v1.1.19 // indirect
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
|
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid v1.3.1 // indirect
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.9.9 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/lucas-clemente/quic-go v0.24.0
|
github.com/klauspost/compress v1.18.5 // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
|
github.com/klauspost/reedsolomon v1.11.8 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/miekg/dns v1.1.45
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/milosgajdos/tenus v0.0.3
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect
|
github.com/miekg/dns v1.1.61 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/go-archive v0.2.0 // indirect
|
||||||
|
github.com/moby/moby/api v1.54.1 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pion/dtls/v3 v3.1.1 // indirect
|
||||||
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
|
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.0 // indirect
|
||||||
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.59.1 // indirect
|
||||||
|
github.com/quic-go/webtransport-go v0.10.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rs/xid v1.3.0
|
github.com/rs/xid v1.3.0 // indirect
|
||||||
github.com/shadowsocks/go-shadowsocks2 v0.1.4
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 // indirect
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.9.0
|
github.com/spf13/viper v1.19.0 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/templexxx/cpu v0.0.7 // indirect
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
github.com/templexxx/xorsimd v0.4.1 // indirect
|
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||||
github.com/tjfoc/gmsm v1.3.2 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
github.com/xtaci/smux v1.5.16
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
github.com/xtaci/tcpraw v1.2.25
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
github.com/vulcand/predicate v1.2.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
github.com/xjasonlyu/tun2socks/v2 v2.6.0 // indirect
|
||||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
|
github.com/xtaci/kcp-go/v5 v5.6.5 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
github.com/xtaci/smux v1.5.31 // indirect
|
||||||
google.golang.org/grpc v1.44.0
|
github.com/xtaci/tcpraw v1.2.25 // indirect
|
||||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
github.com/yl2chen/cidranger v1.0.2 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
github.com/zalando/go-keyring v0.2.4 // indirect
|
||||||
)
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
require (
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
|
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||||
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
|
golang.org/x/net v0.53.0 // indirect
|
||||||
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
|
golang.org/x/term v0.42.0 // indirect
|
||||||
|
golang.org/x/text v0.36.0 // indirect
|
||||||
|
golang.org/x/time v0.12.0 // indirect
|
||||||
|
golang.org/x/tools v0.43.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
|
google.golang.org/grpc v1.79.3 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
381
gost.yml
381
gost.yml
|
|
@ -1,283 +1,145 @@
|
||||||
log:
|
|
||||||
output: stderr # none, stderr, stdout, /path/to/file
|
|
||||||
level: debug # debug, info, warn, error, fatal
|
|
||||||
format: json # text, json
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: http+tcp
|
- name: service-0
|
||||||
addr: ":28000"
|
addr: ":8080"
|
||||||
# bypass: bypass01
|
interface: eth0
|
||||||
|
admission: admission-0
|
||||||
|
bypass: bypass-0
|
||||||
|
resolver: resolver-0
|
||||||
|
hosts: hosts-0
|
||||||
handler:
|
handler:
|
||||||
type: http
|
type: http
|
||||||
chain: chain01
|
auth:
|
||||||
|
username: user
|
||||||
|
password: pass
|
||||||
|
auther: auther-0
|
||||||
|
chain: chain-0
|
||||||
|
retries: 1
|
||||||
metadata:
|
metadata:
|
||||||
proxyAgent: "gost/3.0"
|
foo: bar
|
||||||
auths:
|
bar: baz
|
||||||
- user1:pass1
|
|
||||||
- user2:pass2
|
|
||||||
# probeResist: code:404 # code, web, host, file
|
|
||||||
# knock: example.com
|
|
||||||
listener:
|
listener:
|
||||||
type: tcp
|
type: tcp
|
||||||
|
auth:
|
||||||
|
username: user
|
||||||
|
password: pass
|
||||||
|
auther: auther-0
|
||||||
|
chain: chain-0
|
||||||
|
tls:
|
||||||
|
certFile: cert.pem
|
||||||
|
keyFile: key.pem
|
||||||
|
caFile: ca.pem
|
||||||
metadata:
|
metadata:
|
||||||
keepAlive: 15s
|
abc: xyz
|
||||||
- name: ss
|
def: 456
|
||||||
addr: ":28338"
|
|
||||||
# bypass: bypass01
|
|
||||||
handler:
|
|
||||||
type: ss
|
|
||||||
# chain: chain01
|
|
||||||
metadata:
|
|
||||||
method: chacha20-ietf
|
|
||||||
password: gost
|
|
||||||
readTimeout: 5s
|
|
||||||
udp: true
|
|
||||||
bufferSize: 4096
|
|
||||||
listener:
|
|
||||||
type: tcp
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
- name: socks5
|
|
||||||
addr: ":21080"
|
|
||||||
# bypass: bypass01
|
|
||||||
handler:
|
|
||||||
type: socks5
|
|
||||||
# chain: chain-ss
|
|
||||||
metadata:
|
|
||||||
auths:
|
|
||||||
- gost:gost
|
|
||||||
readTimeout: 5s
|
|
||||||
notls: true
|
|
||||||
bind: true
|
|
||||||
udp: true
|
|
||||||
# udpBufferSize: 4096 # range [512, 66560]
|
|
||||||
listener:
|
|
||||||
type: tcp
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
- name: socks5+tcp
|
|
||||||
addr: ":21081"
|
|
||||||
handler:
|
|
||||||
type: socks5
|
|
||||||
metadata:
|
|
||||||
auths:
|
|
||||||
- gost:gost
|
|
||||||
readTimeout: 5s
|
|
||||||
notls: true
|
|
||||||
# udpBufferSize: 1024
|
|
||||||
listener:
|
|
||||||
type: tcp
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
- name: forward
|
|
||||||
addr: ":10053"
|
|
||||||
forwarder:
|
forwarder:
|
||||||
targets:
|
nodes:
|
||||||
- 192.168.8.8:53
|
- name: target-0
|
||||||
- 192.168.8.1:53
|
addr: 192.168.1.1:1234
|
||||||
- 1.1.1.1:53
|
- name: target-1
|
||||||
|
addr: 192.168.1.2:2345
|
||||||
selector:
|
selector:
|
||||||
strategy: fifo
|
strategy: rand
|
||||||
maxFails: 1
|
maxFails: 1
|
||||||
failTimeout: 30s
|
failTimeout: 30s
|
||||||
handler:
|
|
||||||
type: forward
|
|
||||||
chain: chain-ss
|
|
||||||
metadata:
|
|
||||||
readTimeout: 5s
|
|
||||||
listener:
|
|
||||||
type: udp
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
|
|
||||||
- name: kcp-forward-tunnel
|
|
||||||
addr: ":8388"
|
|
||||||
forwarder:
|
|
||||||
targets:
|
|
||||||
- 127.0.0.1:28338
|
|
||||||
handler:
|
|
||||||
type: forward
|
|
||||||
metadata:
|
|
||||||
readTimeout: 5s
|
|
||||||
listener:
|
|
||||||
type: kcp
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
|
|
||||||
- name: rtcp
|
|
||||||
addr: ":28100"
|
|
||||||
forwarder:
|
|
||||||
targets:
|
|
||||||
- 192.168.8.8:80
|
|
||||||
handler:
|
|
||||||
type: forward
|
|
||||||
metadata:
|
|
||||||
readTimeout: 5s
|
|
||||||
listener:
|
|
||||||
type: rtcp
|
|
||||||
# chain: chain-socks5
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
mux: true
|
|
||||||
- name: rudp
|
|
||||||
addr: ":1053"
|
|
||||||
forwarder:
|
|
||||||
targets:
|
|
||||||
- 192.168.8.8:53
|
|
||||||
- 192.168.8.1:53
|
|
||||||
selector:
|
|
||||||
strategy: round
|
|
||||||
maxFails: 1
|
|
||||||
failTimeout: 30s
|
|
||||||
handler:
|
|
||||||
type: forward
|
|
||||||
metadata:
|
|
||||||
readTimeout: 5s
|
|
||||||
listener:
|
|
||||||
type: rudp
|
|
||||||
chain: chain-socks5
|
|
||||||
metadata:
|
|
||||||
keepAlive: 15s
|
|
||||||
|
|
||||||
chains:
|
chains:
|
||||||
- name: chain01
|
- name: chain-0
|
||||||
# chain level selector
|
|
||||||
selector:
|
selector:
|
||||||
strategy: round
|
strategy: round
|
||||||
maxFails: 1
|
maxFails: 1
|
||||||
failTimeout: 30s
|
failTimeout: 30s
|
||||||
hops:
|
hops:
|
||||||
- name: hop01
|
- name: hop-0
|
||||||
# hop level selector
|
- name: hop-1
|
||||||
|
interface: 192.168.1.2
|
||||||
selector:
|
selector:
|
||||||
strategy: round
|
strategy: rand
|
||||||
maxFails: 1
|
maxFails: 3
|
||||||
failTimeout: 30s
|
failTimeout: 60s
|
||||||
|
bypass: bypass-0
|
||||||
nodes:
|
nodes:
|
||||||
- name: node01
|
- name: node-0
|
||||||
addr: ":8081"
|
addr: ":1080"
|
||||||
# bypass: bypass01
|
interface: eth1
|
||||||
connector:
|
bypass: bypass-0
|
||||||
type: http
|
|
||||||
metadata:
|
|
||||||
userAgent: "gost/3.0"
|
|
||||||
auth: user1:pass1
|
|
||||||
dialer:
|
|
||||||
type: tcp
|
|
||||||
metadata: {}
|
|
||||||
- name: node02
|
|
||||||
addr: ":8082"
|
|
||||||
# bypass: bypass01
|
|
||||||
connector:
|
|
||||||
type: http
|
|
||||||
metadata:
|
|
||||||
userAgent: "gost/3.0"
|
|
||||||
auth: user2:pass2
|
|
||||||
dialer:
|
|
||||||
type: tcp
|
|
||||||
metadata: {}
|
|
||||||
- name: hop02
|
|
||||||
# hop level selector
|
|
||||||
selector:
|
|
||||||
strategy: round
|
|
||||||
maxFails: 1
|
|
||||||
failTimeout: 30s
|
|
||||||
nodes:
|
|
||||||
- name: node03
|
|
||||||
addr: ":8083"
|
|
||||||
# bypass: bypass01
|
|
||||||
connector:
|
|
||||||
type: http
|
|
||||||
metadata:
|
|
||||||
userAgent: "gost/3.0"
|
|
||||||
auth: user3:pass3
|
|
||||||
dialer:
|
|
||||||
type: tcp
|
|
||||||
metadata: {}
|
|
||||||
- name: chain-socks4
|
|
||||||
hops:
|
|
||||||
- name: hop01
|
|
||||||
nodes:
|
|
||||||
- name: node01
|
|
||||||
addr: ":8081"
|
|
||||||
url: "http://gost:gost@:8081"
|
|
||||||
# bypass: bypass01
|
|
||||||
connector:
|
|
||||||
type: socks4
|
|
||||||
metadata: {}
|
|
||||||
dialer:
|
|
||||||
type: tcp
|
|
||||||
metadata: {}
|
|
||||||
- name: chain-socks5
|
|
||||||
hops:
|
|
||||||
- name: hop01
|
|
||||||
nodes:
|
|
||||||
- name: node01
|
|
||||||
addr: ":21080"
|
|
||||||
# bypass: bypass01
|
|
||||||
connector:
|
connector:
|
||||||
type: socks5
|
type: socks5
|
||||||
|
auth:
|
||||||
|
username: user
|
||||||
|
password: pass
|
||||||
metadata:
|
metadata:
|
||||||
notls: true
|
foo: bar
|
||||||
auth: gost:gost
|
|
||||||
dialer:
|
dialer:
|
||||||
type: tcp
|
type: tcp
|
||||||
metadata: {}
|
auth:
|
||||||
- name: chain-ss
|
username: user
|
||||||
hops:
|
password: pass
|
||||||
- name: hop01
|
tls:
|
||||||
nodes:
|
caFile: "ca.pem"
|
||||||
- name: node01
|
secure: true
|
||||||
addr: ":28338"
|
serverName: "example.com"
|
||||||
url: "http://gost:gost@:8081"
|
|
||||||
# bypass: bypass01
|
|
||||||
connector:
|
|
||||||
type: ss
|
|
||||||
metadata:
|
metadata:
|
||||||
method: chacha20-ietf
|
bar: baz
|
||||||
password: gost
|
|
||||||
readTimeout: 5s
|
hops:
|
||||||
nodelay: true
|
- name: hop-0
|
||||||
udp: true
|
interface: 192.168.1.2
|
||||||
bufferSize: 4096
|
selector:
|
||||||
dialer:
|
strategy: rand
|
||||||
type: tcp
|
maxFails: 3
|
||||||
metadata: {}
|
failTimeout: 60s
|
||||||
|
bypass: bypass-0
|
||||||
|
nodes:
|
||||||
|
- name: node-0
|
||||||
|
addr: ":1080"
|
||||||
|
interface: eth1
|
||||||
|
bypass: bypass-0
|
||||||
|
connector:
|
||||||
|
type: socks5
|
||||||
|
auth:
|
||||||
|
username: user
|
||||||
|
password: pass
|
||||||
|
metadata:
|
||||||
|
foo: bar
|
||||||
|
dialer:
|
||||||
|
type: tcp
|
||||||
|
auth:
|
||||||
|
username: user
|
||||||
|
password: pass
|
||||||
|
tls:
|
||||||
|
caFile: "ca.pem"
|
||||||
|
secure: true
|
||||||
|
serverName: "example.com"
|
||||||
|
metadata:
|
||||||
|
bar: baz
|
||||||
|
|
||||||
|
tls:
|
||||||
|
certFile: "cert.pem"
|
||||||
|
keyFile: "key.pem"
|
||||||
|
caFile: "ca.pem"
|
||||||
|
|
||||||
|
authers:
|
||||||
|
- name: auther-0
|
||||||
|
auths:
|
||||||
|
- username: user1
|
||||||
|
password: pass1
|
||||||
|
- username: user2
|
||||||
|
password: pass2
|
||||||
|
|
||||||
|
admissions:
|
||||||
|
- name: admission-0
|
||||||
|
whitelist: false
|
||||||
|
matchers:
|
||||||
|
- 127.0.0.1
|
||||||
|
- 192.168.0.0/16
|
||||||
|
|
||||||
bypasses:
|
bypasses:
|
||||||
- name: bypass-0
|
- name: bypass-0
|
||||||
reverse: false
|
whitelist: false
|
||||||
matchers:
|
matchers:
|
||||||
- .baidu.com
|
- "*.example.com"
|
||||||
- "*.example.com" # domain wildcard
|
- .example.org
|
||||||
- .example.org # will match example.org and *.example.org
|
- 0.0.0.0/8
|
||||||
|
|
||||||
# From IANA IPv4 Special-Purpose Address Registry
|
|
||||||
# http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
|
||||||
- 0.0.0.0/8 # RFC1122: "This host on this network"
|
|
||||||
- 10.0.0.0/8 # RFC1918: Private-Use
|
|
||||||
- 100.64.0.0/10 # RFC6598: Shared Address Space
|
|
||||||
- 127.0.0.0/8 # RFC1122: Loopback
|
|
||||||
- 169.254.0.0/16 # RFC3927: Link Local
|
|
||||||
- 172.16.0.0/12 # RFC1918: Private-Use
|
|
||||||
- 192.0.0.0/24 # RFC6890: IETF Protocol Assignments
|
|
||||||
- 192.0.2.0/24 # RFC5737: Documentation (TEST-NET-1)
|
|
||||||
- 192.88.99.0/24 # RFC3068: 6to4 Relay Anycast
|
|
||||||
- 192.168.0.0/16 # RFC1918: Private-Use
|
|
||||||
- 198.18.0.0/15 # RFC2544: Benchmarking
|
|
||||||
- 198.51.100.0/24 # RFC5737: Documentation (TEST-NET-2)
|
|
||||||
- 203.0.113.0/24 # RFC5737: Documentation (TEST-NET-3)
|
|
||||||
- 240.0.0.0/4 # RFC1112: Reserved
|
|
||||||
- 255.255.255.255/32 # RFC0919: Limited Broadcast
|
|
||||||
|
|
||||||
# From IANA Multicast Address Space Registry
|
|
||||||
# http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml
|
|
||||||
- 224.0.0.0/4 # RFC5771: Multicast/Reserved
|
|
||||||
|
|
||||||
tls:
|
|
||||||
cert: "cert.pem"
|
|
||||||
key: "key.pem"
|
|
||||||
# ca: "root.ca"
|
|
||||||
|
|
||||||
resolvers:
|
resolvers:
|
||||||
- name: resolver-0
|
- name: resolver-0
|
||||||
|
|
@ -308,6 +170,23 @@ hosts:
|
||||||
- bar
|
- bar
|
||||||
- baz
|
- baz
|
||||||
|
|
||||||
|
log:
|
||||||
|
output: stderr
|
||||||
|
level: debug
|
||||||
|
format: json
|
||||||
|
|
||||||
profiling:
|
profiling:
|
||||||
addr: ":6060"
|
addr: ":6060"
|
||||||
enabled: true
|
|
||||||
|
api:
|
||||||
|
addr: ":18080"
|
||||||
|
pathPrefix: /api
|
||||||
|
accesslog: true
|
||||||
|
auth:
|
||||||
|
username: user
|
||||||
|
password: pass
|
||||||
|
auther: auther-0
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
addr: :9000
|
||||||
|
path: /metrics
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Check Root User
|
||||||
|
|
||||||
|
# If you want to run as another user, please modify $EUID to be owned by this user
|
||||||
|
if [[ "$EUID" -ne '0' ]]; then
|
||||||
|
echo "$(tput setaf 1)Error: You must run this script as root!$(tput sgr0)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set the desired GitHub repository
|
||||||
|
repo="go-gost/gost"
|
||||||
|
base_url="https://api.github.com/repos/$repo/releases"
|
||||||
|
|
||||||
|
# Function to download and install gost
|
||||||
|
install_gost() {
|
||||||
|
version=$1
|
||||||
|
# Detect the operating system
|
||||||
|
if [[ "$(uname)" == "Linux" ]]; then
|
||||||
|
os="linux"
|
||||||
|
elif [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
os="darwin"
|
||||||
|
elif [[ "$(uname)" == "MINGW"* ]]; then
|
||||||
|
os="windows"
|
||||||
|
else
|
||||||
|
echo "Unsupported operating system."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect the CPU architecture
|
||||||
|
arch=$(uname -m)
|
||||||
|
case $arch in
|
||||||
|
x86_64)
|
||||||
|
cpu_arch="amd64"
|
||||||
|
;;
|
||||||
|
armv5*)
|
||||||
|
cpu_arch="armv5"
|
||||||
|
;;
|
||||||
|
armv6*)
|
||||||
|
cpu_arch="armv6"
|
||||||
|
;;
|
||||||
|
armv7*)
|
||||||
|
cpu_arch="armv7"
|
||||||
|
;;
|
||||||
|
aarch64|arm64)
|
||||||
|
cpu_arch="arm64"
|
||||||
|
;;
|
||||||
|
i686)
|
||||||
|
cpu_arch="386"
|
||||||
|
;;
|
||||||
|
mips64*)
|
||||||
|
cpu_arch="mips64"
|
||||||
|
;;
|
||||||
|
mips*)
|
||||||
|
cpu_arch="mips"
|
||||||
|
;;
|
||||||
|
mipsel*)
|
||||||
|
cpu_arch="mipsle"
|
||||||
|
;;
|
||||||
|
riscv64)
|
||||||
|
cpu_arch="riscv64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported CPU architecture."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
get_download_url="$base_url/tags/$version"
|
||||||
|
download_url=$(curl -s "$get_download_url" | awk -F'"' -v re=".*${os}.*${cpu_arch}.*" '/"browser_download_url":/ && $4 ~ re { print $4 }' | head -n 1)
|
||||||
|
|
||||||
|
# Download and install the binary
|
||||||
|
install_path="/usr/local/bin"
|
||||||
|
echo "Downloading and installing gost version $version..."
|
||||||
|
curl -fsSL "$download_url" | tar -xzC "$install_path" gost
|
||||||
|
chmod +x "$install_path/gost"
|
||||||
|
|
||||||
|
# Remove binary from macOS quarantine when installing for first time
|
||||||
|
[[ "$os" == "darwin" ]] && { xattr -d com.apple.quarantine "$install_path/gost" 2>&-; }
|
||||||
|
|
||||||
|
echo "gost installation completed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retrieve available versions from GitHub API
|
||||||
|
versions=$(curl -s "$base_url" | awk -F'"' '/"tag_name":/ {print $4}')
|
||||||
|
|
||||||
|
# Check if --install option provided
|
||||||
|
if [[ "$1" == "--install" ]]; then
|
||||||
|
# Install the latest version automatically
|
||||||
|
latest_version=$(echo "$versions" | head -n 1)
|
||||||
|
install_gost $latest_version
|
||||||
|
else
|
||||||
|
# Display available versions to the user
|
||||||
|
echo "Available gost versions:"
|
||||||
|
select version in $versions; do
|
||||||
|
if [[ -n $version ]]; then
|
||||||
|
install_gost $version
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "Invalid choice! Please select a valid option."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
// Authenticator is an interface for user authentication.
|
|
||||||
type Authenticator interface {
|
|
||||||
Authenticate(user, password string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAuthenticator is an Authenticator that authenticates client by local key-value pairs.
|
|
||||||
type MapAuthenticator struct {
|
|
||||||
kvs map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMapAuthenticator creates an Authenticator that authenticates client by local infos.
|
|
||||||
func NewMapAuthenticator(kvs map[string]string) Authenticator {
|
|
||||||
return &MapAuthenticator{
|
|
||||||
kvs: kvs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate checks the validity of the provided user-password pair.
|
|
||||||
func (au *MapAuthenticator) Authenticate(user, password string) bool {
|
|
||||||
if au == nil || len(au.kvs) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := au.kvs[user]
|
|
||||||
return ok && (v == "" || password == v)
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
package bypass
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
glob "github.com/gobwas/glob"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Matcher is a generic pattern matcher,
|
|
||||||
// it gives the match result of the given pattern for specific v.
|
|
||||||
type Matcher interface {
|
|
||||||
Match(v string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMatcher creates a Matcher for the given pattern.
|
|
||||||
// The acutal Matcher depends on the pattern:
|
|
||||||
// IP Matcher if pattern is a valid IP address.
|
|
||||||
// CIDR Matcher if pattern is a valid CIDR address.
|
|
||||||
// Domain Matcher if both of the above are not.
|
|
||||||
func NewMatcher(pattern string) Matcher {
|
|
||||||
if pattern == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if ip := net.ParseIP(pattern); ip != nil {
|
|
||||||
return IPMatcher(ip)
|
|
||||||
}
|
|
||||||
if _, inet, err := net.ParseCIDR(pattern); err == nil {
|
|
||||||
return CIDRMatcher(inet)
|
|
||||||
}
|
|
||||||
return DomainMatcher(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipMatcher struct {
|
|
||||||
ip net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPMatcher creates a Matcher for a specific IP address.
|
|
||||||
func IPMatcher(ip net.IP) Matcher {
|
|
||||||
return &ipMatcher{
|
|
||||||
ip: ip,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ipMatcher) Match(ip string) bool {
|
|
||||||
if m == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return m.ip.Equal(net.ParseIP(ip))
|
|
||||||
}
|
|
||||||
|
|
||||||
type cidrMatcher struct {
|
|
||||||
ipNet *net.IPNet
|
|
||||||
}
|
|
||||||
|
|
||||||
// CIDRMatcher creates a Matcher for a specific CIDR notation IP address.
|
|
||||||
func CIDRMatcher(inet *net.IPNet) Matcher {
|
|
||||||
return &cidrMatcher{
|
|
||||||
ipNet: inet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *cidrMatcher) Match(ip string) bool {
|
|
||||||
if m == nil || m.ipNet == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return m.ipNet.Contains(net.ParseIP(ip))
|
|
||||||
}
|
|
||||||
|
|
||||||
type domainMatcher struct {
|
|
||||||
pattern string
|
|
||||||
glob glob.Glob
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainMatcher creates a Matcher for a specific domain pattern,
|
|
||||||
// the pattern can be a plain domain such as 'example.com',
|
|
||||||
// a wildcard such as '*.exmaple.com' or a special wildcard '.example.com'.
|
|
||||||
func DomainMatcher(pattern string) Matcher {
|
|
||||||
p := pattern
|
|
||||||
if strings.HasPrefix(pattern, ".") {
|
|
||||||
p = pattern[1:] // trim the prefix '.'
|
|
||||||
pattern = "*" + p
|
|
||||||
}
|
|
||||||
return &domainMatcher{
|
|
||||||
pattern: p,
|
|
||||||
glob: glob.MustCompile(pattern),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *domainMatcher) Match(domain string) bool {
|
|
||||||
if m == nil || m.glob == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if domain == m.pattern {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return m.glob.Match(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bypass is a filter of address (IP or domain).
|
|
||||||
type Bypass interface {
|
|
||||||
// Contains reports whether the bypass includes addr.
|
|
||||||
Contains(addr string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type bypassOptions struct {
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type BypassOption func(opts *bypassOptions)
|
|
||||||
|
|
||||||
func LoggerBypassOption(logger logger.Logger) BypassOption {
|
|
||||||
return func(opts *bypassOptions) {
|
|
||||||
opts.logger = logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type bypass struct {
|
|
||||||
matchers []Matcher
|
|
||||||
reversed bool
|
|
||||||
options bypassOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBypass creates and initializes a new Bypass using matchers as its match rules.
|
|
||||||
// The rules will be reversed if the reversed is true.
|
|
||||||
func NewBypass(reversed bool, matchers []Matcher, opts ...BypassOption) Bypass {
|
|
||||||
options := bypassOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
return &bypass{
|
|
||||||
matchers: matchers,
|
|
||||||
reversed: reversed,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBypassPatterns creates and initializes a new Bypass using matcher patterns as its match rules.
|
|
||||||
// The rules will be reversed if the reverse is true.
|
|
||||||
func NewBypassPatterns(reversed bool, patterns []string, opts ...BypassOption) Bypass {
|
|
||||||
var matchers []Matcher
|
|
||||||
for _, pattern := range patterns {
|
|
||||||
if m := NewMatcher(pattern); m != nil {
|
|
||||||
matchers = append(matchers, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NewBypass(reversed, matchers, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *bypass) Contains(addr string) bool {
|
|
||||||
if addr == "" || bp == nil || len(bp.matchers) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to strip the port
|
|
||||||
if host, port, _ := net.SplitHostPort(addr); host != "" && port != "" {
|
|
||||||
if p, _ := strconv.Atoi(port); p > 0 { // port is valid
|
|
||||||
addr = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var matched bool
|
|
||||||
for _, matcher := range bp.matchers {
|
|
||||||
if matcher == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if matcher.Match(addr) {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b := !bp.reversed && matched ||
|
|
||||||
bp.reversed && !matched
|
|
||||||
if b {
|
|
||||||
bp.options.logger.Debugf("bypass: %s", addr)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
type Chain struct {
|
|
||||||
groups []*NodeGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) AddNodeGroup(group *NodeGroup) {
|
|
||||||
c.groups = append(c.groups, group)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) GetRoute() (r *route) {
|
|
||||||
return c.GetRouteFor("tcp", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) GetRouteFor(network, address string) (r *route) {
|
|
||||||
if c == nil || len(c.groups) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r = &route{}
|
|
||||||
for _, group := range c.groups {
|
|
||||||
node := group.Next()
|
|
||||||
if node == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if node.Bypass != nil && node.Bypass.Contains(address) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Transport.Multiplex() {
|
|
||||||
tr := node.Transport.Copy().
|
|
||||||
WithRoute(r)
|
|
||||||
node = node.Copy()
|
|
||||||
node.Transport = tr
|
|
||||||
r = &route{}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.AddNode(node)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chain) IsEmpty() bool {
|
|
||||||
return c == nil || len(c.groups) == 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/bypass"
|
|
||||||
"github.com/go-gost/gost/pkg/hosts"
|
|
||||||
"github.com/go-gost/gost/pkg/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Name string
|
|
||||||
Addr string
|
|
||||||
Transport *Transport
|
|
||||||
Bypass bypass.Bypass
|
|
||||||
Resolver resolver.Resolver
|
|
||||||
Hosts hosts.HostMapper
|
|
||||||
Marker *FailMarker
|
|
||||||
}
|
|
||||||
|
|
||||||
func (node *Node) Copy() *Node {
|
|
||||||
n := &Node{}
|
|
||||||
*n = *node
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeGroup struct {
|
|
||||||
nodes []*Node
|
|
||||||
selector Selector
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNodeGroup(nodes ...*Node) *NodeGroup {
|
|
||||||
return &NodeGroup{
|
|
||||||
nodes: nodes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *NodeGroup) AddNode(node *Node) {
|
|
||||||
g.nodes = append(g.nodes, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *NodeGroup) WithSelector(selector Selector) *NodeGroup {
|
|
||||||
g.selector = selector
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *NodeGroup) Next() *Node {
|
|
||||||
if g == nil || len(g.nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s := g.selector
|
|
||||||
if s == nil {
|
|
||||||
s = DefaultSelector
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Select(g.nodes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FailMarker struct {
|
|
||||||
failTime int64
|
|
||||||
failCount int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FailMarker) FailTime() int64 {
|
|
||||||
if m == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return atomic.LoadInt64(&m.failTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FailMarker) FailCount() int64 {
|
|
||||||
if m == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return atomic.LoadInt64(&m.failCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FailMarker) Mark() {
|
|
||||||
if m == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddInt64(&m.failCount, 1)
|
|
||||||
atomic.StoreInt64(&m.failTime, time.Now().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FailMarker) Reset() {
|
|
||||||
if m == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StoreInt64(&m.failCount, 0)
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/hosts"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
"github.com/go-gost/gost/pkg/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func resolve(ctx context.Context, addr string, resolver resolver.Resolver, hosts hosts.HostMapper, log logger.Logger) (string, error) {
|
|
||||||
if addr == "" {
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if host == "" {
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if hosts != nil {
|
|
||||||
if ips, _ := hosts.Lookup("ip", host); len(ips) > 0 {
|
|
||||||
log.Debugf("hit host mapper: %s -> %s", host, ips)
|
|
||||||
return net.JoinHostPort(ips[0].String(), port), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resolver != nil {
|
|
||||||
ips, err := resolver.Resolve(ctx, host)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return "", fmt.Errorf("resolver: domain %s does not exists", host)
|
|
||||||
}
|
|
||||||
return net.JoinHostPort(ips[0].String(), port), nil
|
|
||||||
}
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/udp"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrEmptyRoute = errors.New("empty route")
|
|
||||||
)
|
|
||||||
|
|
||||||
type route struct {
|
|
||||||
nodes []*Node
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) AddNode(node *Node) {
|
|
||||||
r.nodes = append(r.nodes, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
if r.IsEmpty() {
|
|
||||||
return r.dialDirect(ctx, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := r.connect(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cc, err := r.Last().Transport.Connect(ctx, conn, network, address)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) dialDirect(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
switch network {
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
if address == "" {
|
|
||||||
return net.ListenUDP(network, nil)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
d := net.Dialer{}
|
|
||||||
return d.DialContext(ctx, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) Bind(ctx context.Context, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
||||||
if r.IsEmpty() {
|
|
||||||
return r.bindLocal(ctx, network, address, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := r.connect(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ln, err := r.Last().Transport.Bind(ctx, conn, network, address, opts...)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ln, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) connect(ctx context.Context) (conn net.Conn, err error) {
|
|
||||||
if r.IsEmpty() {
|
|
||||||
return nil, ErrEmptyRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
node := r.nodes[0]
|
|
||||||
|
|
||||||
addr, err := resolve(ctx, node.Addr, node.Resolver, node.Hosts, r.logger)
|
|
||||||
if err != nil {
|
|
||||||
node.Marker.Mark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cc, err := node.Transport.Dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
node.Marker.Mark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := node.Transport.Handshake(ctx, cc)
|
|
||||||
if err != nil {
|
|
||||||
cc.Close()
|
|
||||||
node.Marker.Mark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node.Marker.Reset()
|
|
||||||
|
|
||||||
preNode := node
|
|
||||||
for _, node := range r.nodes[1:] {
|
|
||||||
addr, err = resolve(ctx, node.Addr, node.Resolver, node.Hosts, r.logger)
|
|
||||||
if err != nil {
|
|
||||||
cn.Close()
|
|
||||||
node.Marker.Mark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cc, err = preNode.Transport.Connect(ctx, cn, "tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
cn.Close()
|
|
||||||
node.Marker.Mark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cc, err = node.Transport.Handshake(ctx, cc)
|
|
||||||
if err != nil {
|
|
||||||
cn.Close()
|
|
||||||
node.Marker.Mark()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node.Marker.Reset()
|
|
||||||
|
|
||||||
cn = cc
|
|
||||||
preNode = node
|
|
||||||
}
|
|
||||||
|
|
||||||
conn = cn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) IsEmpty() bool {
|
|
||||||
return r == nil || len(r.nodes) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) Last() *Node {
|
|
||||||
if r.IsEmpty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return r.nodes[len(r.nodes)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) Path() (path []*Node) {
|
|
||||||
if r == nil || len(r.nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range r.nodes {
|
|
||||||
if node.Transport != nil && node.Transport.route != nil {
|
|
||||||
path = append(path, node.Transport.route.Path()...)
|
|
||||||
}
|
|
||||||
path = append(path, node)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *route) bindLocal(ctx context.Context, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
||||||
options := connector.BindOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
addr, err := net.ResolveTCPAddr(network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.ListenTCP(network, addr)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
addr, err := net.ResolveUDPAddr(network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn, err := net.ListenUDP(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logger := logger.Default().WithFields(map[string]interface{}{
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
ln := udp.NewListener(conn, addr,
|
|
||||||
options.Backlog, options.UDPDataQueueSize, options.UDPDataBufferSize,
|
|
||||||
options.UDPConnTTL, logger)
|
|
||||||
return ln, err
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s unsupported", network)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/hosts"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
"github.com/go-gost/gost/pkg/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Router struct {
|
|
||||||
Retries int
|
|
||||||
Chain *Chain
|
|
||||||
Hosts hosts.HostMapper
|
|
||||||
Resolver resolver.Resolver
|
|
||||||
Logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
|
||||||
conn, err = r.dial(ctx, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if network == "udp" || network == "udp4" || network == "udp6" {
|
|
||||||
if _, ok := conn.(net.PacketConn); !ok {
|
|
||||||
return &packetConn{conn}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
|
||||||
count := r.Retries + 1
|
|
||||||
if count <= 0 {
|
|
||||||
count = 1
|
|
||||||
}
|
|
||||||
r.Logger.Debugf("dial %s/%s", address, network)
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
route := r.Chain.GetRouteFor(network, address)
|
|
||||||
|
|
||||||
if r.Logger.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
for _, node := range route.Path() {
|
|
||||||
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "%s", address)
|
|
||||||
r.Logger.Debugf("route(retry=%d) %s", i, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
address, err = resolve(ctx, address, r.Resolver, r.Hosts, r.Logger)
|
|
||||||
if err != nil {
|
|
||||||
r.Logger.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if route != nil {
|
|
||||||
route.logger = r.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err = route.Dial(ctx, network, address)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.Logger.Errorf("route(retry=%d) %s", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Bind(ctx context.Context, network, address string, opts ...connector.BindOption) (ln net.Listener, err error) {
|
|
||||||
count := r.Retries + 1
|
|
||||||
if count <= 0 {
|
|
||||||
count = 1
|
|
||||||
}
|
|
||||||
r.Logger.Debugf("bind on %s/%s", address, network)
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
route := r.Chain.GetRouteFor(network, address)
|
|
||||||
|
|
||||||
if r.Logger.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
for _, node := range route.Path() {
|
|
||||||
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "%s", address)
|
|
||||||
r.Logger.Debugf("route(retry=%d) %s", i, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
ln, err = route.Bind(ctx, network, address, opts...)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.Logger.Errorf("route(retry=%d) %s", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type packetConn struct {
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *packetConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|
||||||
n, err = c.Read(b)
|
|
||||||
addr = c.Conn.RemoteAddr()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
||||||
return c.Write(b)
|
|
||||||
}
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// default options for FailFilter
|
|
||||||
const (
|
|
||||||
DefaultFailTimeout = 30 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultSelector = NewSelector(RoundRobinStrategy())
|
|
||||||
)
|
|
||||||
|
|
||||||
type Selector interface {
|
|
||||||
Select(nodes ...*Node) *Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type selector struct {
|
|
||||||
strategy Strategy
|
|
||||||
filters []Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSelector(strategy Strategy, filters ...Filter) Selector {
|
|
||||||
return &selector{
|
|
||||||
filters: filters,
|
|
||||||
strategy: strategy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *selector) Select(nodes ...*Node) *Node {
|
|
||||||
for _, filter := range s.filters {
|
|
||||||
nodes = filter.Filter(nodes...)
|
|
||||||
}
|
|
||||||
if len(nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.strategy.Apply(nodes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Strategy interface {
|
|
||||||
Apply(nodes ...*Node) *Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type roundRobinStrategy struct {
|
|
||||||
counter uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundRobinStrategy is a strategy for node selector.
|
|
||||||
// The node will be selected by round-robin algorithm.
|
|
||||||
func RoundRobinStrategy() Strategy {
|
|
||||||
return &roundRobinStrategy{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *roundRobinStrategy) Apply(nodes ...*Node) *Node {
|
|
||||||
if len(nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
n := atomic.AddUint64(&s.counter, 1) - 1
|
|
||||||
return nodes[int(n%uint64(len(nodes)))]
|
|
||||||
}
|
|
||||||
|
|
||||||
type randomStrategy struct {
|
|
||||||
rand *rand.Rand
|
|
||||||
mux sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomStrategy is a strategy for node selector.
|
|
||||||
// The node will be selected randomly.
|
|
||||||
func RandomStrategy() Strategy {
|
|
||||||
return &randomStrategy{
|
|
||||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *randomStrategy) Apply(nodes ...*Node) *Node {
|
|
||||||
if len(nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
r := s.rand.Int()
|
|
||||||
|
|
||||||
return nodes[r%len(nodes)]
|
|
||||||
}
|
|
||||||
|
|
||||||
type fifoStrategy struct{}
|
|
||||||
|
|
||||||
// FIFOStrategy is a strategy for node selector.
|
|
||||||
// The node will be selected from first to last,
|
|
||||||
// and will stick to the selected node until it is failed.
|
|
||||||
func FIFOStrategy() Strategy {
|
|
||||||
return &fifoStrategy{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply applies the fifo strategy for the nodes.
|
|
||||||
func (s *fifoStrategy) Apply(nodes ...*Node) *Node {
|
|
||||||
if len(nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nodes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Filter interface {
|
|
||||||
Filter(nodes ...*Node) []*Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type failFilter struct {
|
|
||||||
maxFails int
|
|
||||||
failTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// FailFilter filters the dead node.
|
|
||||||
// A node is marked as dead if its failed count is greater than MaxFails.
|
|
||||||
func FailFilter(maxFails int, timeout time.Duration) Filter {
|
|
||||||
return &failFilter{
|
|
||||||
maxFails: maxFails,
|
|
||||||
failTimeout: timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter filters dead nodes.
|
|
||||||
func (f *failFilter) Filter(nodes ...*Node) []*Node {
|
|
||||||
maxFails := f.maxFails
|
|
||||||
failTimeout := f.failTimeout
|
|
||||||
if failTimeout == 0 {
|
|
||||||
failTimeout = DefaultFailTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodes) <= 1 || maxFails <= 0 {
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
var nl []*Node
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node.Marker.FailCount() < int64(maxFails) ||
|
|
||||||
time.Since(time.Unix(node.Marker.FailTime(), 0)) >= failTimeout {
|
|
||||||
nl = append(nl, node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nl
|
|
||||||
}
|
|
||||||
|
|
||||||
type invalidFilter struct{}
|
|
||||||
|
|
||||||
// InvalidFilter filters the invalid node.
|
|
||||||
// A node is invalid if its port is invalid (negative or zero value).
|
|
||||||
func InvalidFilter() Filter {
|
|
||||||
return &invalidFilter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter filters invalid nodes.
|
|
||||||
func (f *invalidFilter) Filter(nodes ...*Node) []*Node {
|
|
||||||
var nl []*Node
|
|
||||||
for _, node := range nodes {
|
|
||||||
_, sport, _ := net.SplitHostPort(node.Addr)
|
|
||||||
if port, _ := strconv.Atoi(sport); port > 0 {
|
|
||||||
nl = append(nl, node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nl
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Transport struct {
|
|
||||||
addr string
|
|
||||||
route *route
|
|
||||||
dialer dialer.Dialer
|
|
||||||
connector connector.Connector
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) Copy() *Transport {
|
|
||||||
tr2 := &Transport{}
|
|
||||||
*tr2 = *tr
|
|
||||||
return tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) WithDialer(dialer dialer.Dialer) *Transport {
|
|
||||||
tr.dialer = dialer
|
|
||||||
return tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) WithConnector(connector connector.Connector) *Transport {
|
|
||||||
tr.connector = connector
|
|
||||||
return tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
|
||||||
return tr.dialer.Dial(ctx, addr, tr.dialOptions()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) dialOptions() []dialer.DialOption {
|
|
||||||
opts := []dialer.DialOption{
|
|
||||||
dialer.HostDialOption(tr.addr),
|
|
||||||
}
|
|
||||||
if !tr.route.IsEmpty() {
|
|
||||||
opts = append(opts,
|
|
||||||
dialer.DialFuncDialOption(
|
|
||||||
func(ctx context.Context, addr string) (net.Conn, error) {
|
|
||||||
return tr.route.Dial(ctx, "tcp", addr)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
|
||||||
var err error
|
|
||||||
if hs, ok := tr.dialer.(dialer.Handshaker); ok {
|
|
||||||
conn, err = hs.Handshake(ctx, conn,
|
|
||||||
dialer.AddrHandshakeOption(tr.addr))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hs, ok := tr.connector.(connector.Handshaker); ok {
|
|
||||||
return hs.Handshake(ctx, conn)
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) {
|
|
||||||
return tr.connector.Connect(ctx, conn, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
||||||
if binder, ok := tr.connector.(connector.Binder); ok {
|
|
||||||
return binder.Bind(ctx, conn, network, address, opts...)
|
|
||||||
}
|
|
||||||
return nil, connector.ErrBindUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) Multiplex() bool {
|
|
||||||
if mux, ok := tr.dialer.(dialer.Multiplexer); ok {
|
|
||||||
return mux.Multiplex()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) WithRoute(r *route) *Transport {
|
|
||||||
tr.route = r
|
|
||||||
return tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *Transport) WithAddr(addr string) *Transport {
|
|
||||||
tr.addr = addr
|
|
||||||
return tr
|
|
||||||
}
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
package bufpool
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
var (
|
|
||||||
pools = []struct {
|
|
||||||
size int
|
|
||||||
pool sync.Pool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
size: 128,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 128)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 512,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 512)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 1024,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 1024)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 4096,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 4096)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 8192,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 8192)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 16 * 1024,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 16*1024)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 32 * 1024,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 32*1024)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 64 * 1024,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 64*1024)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
size: 65 * 1024,
|
|
||||||
pool: sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]byte, 65*1024)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get returns a buffer of specified size.
|
|
||||||
func Get(size int) *[]byte {
|
|
||||||
for i := range pools {
|
|
||||||
if size <= pools[i].size {
|
|
||||||
b := pools[i].pool.Get().(*[]byte)
|
|
||||||
*b = (*b)[:size]
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b := make([]byte, size)
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
|
|
||||||
func Put(b *[]byte) {
|
|
||||||
for i := range pools {
|
|
||||||
if cap(*b) == pools[i].size {
|
|
||||||
pools[i].pool.Put(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AuthFromUsers(users ...*url.Userinfo) auth.Authenticator {
|
|
||||||
kvs := make(map[string]string)
|
|
||||||
for _, v := range users {
|
|
||||||
if v == nil || v.Username() == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kvs[v.Username()], _ = v.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
var authenticator auth.Authenticator
|
|
||||||
if len(kvs) > 0 {
|
|
||||||
authenticator = auth.NewMapAuthenticator(kvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return authenticator
|
|
||||||
}
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.26.0
|
|
||||||
// protoc v3.12.4
|
|
||||||
// source: gost.proto
|
|
||||||
|
|
||||||
package proto
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Chunk struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Chunk) Reset() {
|
|
||||||
*x = Chunk{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_gost_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Chunk) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Chunk) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Chunk) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_gost_proto_msgTypes[0]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Chunk.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Chunk) Descriptor() ([]byte, []int) {
|
|
||||||
return file_gost_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Chunk) GetData() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Data
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_gost_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_gost_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x0a, 0x67, 0x6f, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x05,
|
|
||||||
0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
|
|
||||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x29, 0x0a, 0x09, 0x47, 0x6f, 0x73,
|
|
||||||
0x74, 0x54, 0x75, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c,
|
|
||||||
0x12, 0x06, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x06, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b,
|
|
||||||
0x28, 0x01, 0x30, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
|
||||||
0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x67, 0x6f, 0x73, 0x74, 0x2f, 0x67, 0x6f, 0x73, 0x74, 0x2f,
|
|
||||||
0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x75, 0x74, 0x69, 0x6c, 0x2f,
|
|
||||||
0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
|
||||||
0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_gost_proto_rawDescOnce sync.Once
|
|
||||||
file_gost_proto_rawDescData = file_gost_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_gost_proto_rawDescGZIP() []byte {
|
|
||||||
file_gost_proto_rawDescOnce.Do(func() {
|
|
||||||
file_gost_proto_rawDescData = protoimpl.X.CompressGZIP(file_gost_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_gost_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_gost_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
|
||||||
var file_gost_proto_goTypes = []interface{}{
|
|
||||||
(*Chunk)(nil), // 0: Chunk
|
|
||||||
}
|
|
||||||
var file_gost_proto_depIdxs = []int32{
|
|
||||||
0, // 0: GostTunel.Tunnel:input_type -> Chunk
|
|
||||||
0, // 1: GostTunel.Tunnel:output_type -> Chunk
|
|
||||||
1, // [1:2] is the sub-list for method output_type
|
|
||||||
0, // [0:1] is the sub-list for method input_type
|
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
|
||||||
0, // [0:0] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_gost_proto_init() }
|
|
||||||
func file_gost_proto_init() {
|
|
||||||
if File_gost_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_gost_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Chunk); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_gost_proto_rawDesc,
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 1,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 1,
|
|
||||||
},
|
|
||||||
GoTypes: file_gost_proto_goTypes,
|
|
||||||
DependencyIndexes: file_gost_proto_depIdxs,
|
|
||||||
MessageInfos: file_gost_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_gost_proto = out.File
|
|
||||||
file_gost_proto_rawDesc = nil
|
|
||||||
file_gost_proto_goTypes = nil
|
|
||||||
file_gost_proto_depIdxs = nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
option go_package = "github.com/go-gost/gost/pkg/common/util/grpc/proto";
|
|
||||||
|
|
||||||
message Chunk {
|
|
||||||
bytes data = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
service GostTunel {
|
|
||||||
rpc Tunnel (stream Chunk) returns (stream Chunk);
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
|
||||||
|
|
||||||
package proto
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
codes "google.golang.org/grpc/codes"
|
|
||||||
status "google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
// Requires gRPC-Go v1.32.0 or later.
|
|
||||||
const _ = grpc.SupportPackageIsVersion7
|
|
||||||
|
|
||||||
// GostTunelClient is the client API for GostTunel service.
|
|
||||||
//
|
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
|
||||||
type GostTunelClient interface {
|
|
||||||
Tunnel(ctx context.Context, opts ...grpc.CallOption) (GostTunel_TunnelClient, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type gostTunelClient struct {
|
|
||||||
cc grpc.ClientConnInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGostTunelClient(cc grpc.ClientConnInterface) GostTunelClient {
|
|
||||||
return &gostTunelClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *gostTunelClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (GostTunel_TunnelClient, error) {
|
|
||||||
stream, err := c.cc.NewStream(ctx, &GostTunel_ServiceDesc.Streams[0], "/GostTunel/Tunnel", opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &gostTunelTunnelClient{stream}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GostTunel_TunnelClient interface {
|
|
||||||
Send(*Chunk) error
|
|
||||||
Recv() (*Chunk, error)
|
|
||||||
grpc.ClientStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type gostTunelTunnelClient struct {
|
|
||||||
grpc.ClientStream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *gostTunelTunnelClient) Send(m *Chunk) error {
|
|
||||||
return x.ClientStream.SendMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *gostTunelTunnelClient) Recv() (*Chunk, error) {
|
|
||||||
m := new(Chunk)
|
|
||||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GostTunelServer is the server API for GostTunel service.
|
|
||||||
// All implementations must embed UnimplementedGostTunelServer
|
|
||||||
// for forward compatibility
|
|
||||||
type GostTunelServer interface {
|
|
||||||
Tunnel(GostTunel_TunnelServer) error
|
|
||||||
mustEmbedUnimplementedGostTunelServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnimplementedGostTunelServer must be embedded to have forward compatible implementations.
|
|
||||||
type UnimplementedGostTunelServer struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedGostTunelServer) Tunnel(GostTunel_TunnelServer) error {
|
|
||||||
return status.Errorf(codes.Unimplemented, "method Tunnel not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedGostTunelServer) mustEmbedUnimplementedGostTunelServer() {}
|
|
||||||
|
|
||||||
// UnsafeGostTunelServer may be embedded to opt out of forward compatibility for this service.
|
|
||||||
// Use of this interface is not recommended, as added methods to GostTunelServer will
|
|
||||||
// result in compilation errors.
|
|
||||||
type UnsafeGostTunelServer interface {
|
|
||||||
mustEmbedUnimplementedGostTunelServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterGostTunelServer(s grpc.ServiceRegistrar, srv GostTunelServer) {
|
|
||||||
s.RegisterService(&GostTunel_ServiceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _GostTunel_Tunnel_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
return srv.(GostTunelServer).Tunnel(&gostTunelTunnelServer{stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
type GostTunel_TunnelServer interface {
|
|
||||||
Send(*Chunk) error
|
|
||||||
Recv() (*Chunk, error)
|
|
||||||
grpc.ServerStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type gostTunelTunnelServer struct {
|
|
||||||
grpc.ServerStream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *gostTunelTunnelServer) Send(m *Chunk) error {
|
|
||||||
return x.ServerStream.SendMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *gostTunelTunnelServer) Recv() (*Chunk, error) {
|
|
||||||
m := new(Chunk)
|
|
||||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GostTunel_ServiceDesc is the grpc.ServiceDesc for GostTunel service.
|
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
|
||||||
// and not to be introspected or modified (even as a copy)
|
|
||||||
var GostTunel_ServiceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "GostTunel",
|
|
||||||
HandlerType: (*GostTunelServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{},
|
|
||||||
Streams: []grpc.StreamDesc{
|
|
||||||
{
|
|
||||||
StreamName: "Tunnel",
|
|
||||||
Handler: _GostTunel_Tunnel_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
ClientStreams: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Metadata: "gost.proto",
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
protoc --go_out=. --go_opt=paths=source_relative \
|
|
||||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
|
||||||
gost.proto
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
|
|
||||||
"github.com/xtaci/kcp-go/v5"
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultSalt is the default salt for KCP cipher.
|
|
||||||
DefaultSalt = "kcp-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultKCPConfig is the default KCP config.
|
|
||||||
DefaultConfig = &Config{
|
|
||||||
Key: "it's a secrect",
|
|
||||||
Crypt: "aes",
|
|
||||||
Mode: "fast",
|
|
||||||
MTU: 1350,
|
|
||||||
SndWnd: 1024,
|
|
||||||
RcvWnd: 1024,
|
|
||||||
DataShard: 10,
|
|
||||||
ParityShard: 3,
|
|
||||||
DSCP: 0,
|
|
||||||
NoComp: false,
|
|
||||||
AckNodelay: false,
|
|
||||||
NoDelay: 0,
|
|
||||||
Interval: 50,
|
|
||||||
Resend: 0,
|
|
||||||
NoCongestion: 0,
|
|
||||||
SockBuf: 4194304,
|
|
||||||
KeepAlive: 10,
|
|
||||||
SnmpLog: "",
|
|
||||||
SnmpPeriod: 60,
|
|
||||||
Signal: false,
|
|
||||||
TCP: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// KCPConfig describes the config for KCP.
|
|
||||||
type Config struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Crypt string `json:"crypt"`
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
SndWnd int `json:"sndwnd"`
|
|
||||||
RcvWnd int `json:"rcvwnd"`
|
|
||||||
DataShard int `json:"datashard"`
|
|
||||||
ParityShard int `json:"parityshard"`
|
|
||||||
DSCP int `json:"dscp"`
|
|
||||||
NoComp bool `json:"nocomp"`
|
|
||||||
AckNodelay bool `json:"acknodelay"`
|
|
||||||
NoDelay int `json:"nodelay"`
|
|
||||||
Interval int `json:"interval"`
|
|
||||||
Resend int `json:"resend"`
|
|
||||||
NoCongestion int `json:"nc"`
|
|
||||||
SockBuf int `json:"sockbuf"`
|
|
||||||
KeepAlive int `json:"keepalive"`
|
|
||||||
SnmpLog string `json:"snmplog"`
|
|
||||||
SnmpPeriod int `json:"snmpperiod"`
|
|
||||||
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
|
|
||||||
TCP bool `json:"tcp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the KCP config.
|
|
||||||
func (c *Config) Init() {
|
|
||||||
switch c.Mode {
|
|
||||||
case "normal":
|
|
||||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1
|
|
||||||
case "fast":
|
|
||||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 30, 2, 1
|
|
||||||
case "fast2":
|
|
||||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
|
|
||||||
case "fast3":
|
|
||||||
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BlockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
|
|
||||||
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
|
|
||||||
|
|
||||||
switch crypt {
|
|
||||||
case "sm4":
|
|
||||||
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
|
|
||||||
case "tea":
|
|
||||||
block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
|
||||||
case "xor":
|
|
||||||
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
|
||||||
case "none":
|
|
||||||
block, _ = kcp.NewNoneBlockCrypt(pass)
|
|
||||||
case "aes-128":
|
|
||||||
block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
|
||||||
case "aes-192":
|
|
||||||
block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
|
||||||
case "blowfish":
|
|
||||||
block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
|
||||||
case "twofish":
|
|
||||||
block, _ = kcp.NewTwofishBlockCrypt(pass)
|
|
||||||
case "cast5":
|
|
||||||
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
|
||||||
case "3des":
|
|
||||||
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
|
||||||
case "xtea":
|
|
||||||
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
|
||||||
case "salsa20":
|
|
||||||
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
|
||||||
case "aes":
|
|
||||||
fallthrough
|
|
||||||
default: // aes
|
|
||||||
block, _ = kcp.NewAESBlockCrypt(pass)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/golang/snappy"
|
|
||||||
)
|
|
||||||
|
|
||||||
type kcpCompStreamConn struct {
|
|
||||||
net.Conn
|
|
||||||
w *snappy.Writer
|
|
||||||
r *snappy.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func CompStreamConn(conn net.Conn) net.Conn {
|
|
||||||
return &kcpCompStreamConn{
|
|
||||||
Conn: conn,
|
|
||||||
w: snappy.NewBufferedWriter(conn),
|
|
||||||
r: snappy.NewReader(conn),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *kcpCompStreamConn) Read(b []byte) (n int, err error) {
|
|
||||||
return c.r.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *kcpCompStreamConn) Write(b []byte) (n int, err error) {
|
|
||||||
n, err = c.w.Write(b)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.w.Flush()
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
smux "github.com/xtaci/smux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
conn net.Conn
|
|
||||||
session *smux.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientSession(conn net.Conn) (*Session, error) {
|
|
||||||
s, err := smux.Client(conn, smux.DefaultConfig())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Session{
|
|
||||||
conn: conn,
|
|
||||||
session: s,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServerSession(conn net.Conn) (*Session, error) {
|
|
||||||
s, err := smux.Server(conn, smux.DefaultConfig())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Session{
|
|
||||||
conn: conn,
|
|
||||||
session: s,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *Session) GetConn() (net.Conn, error) {
|
|
||||||
stream, err := session.session.OpenStream()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &StreamConn{Conn: session.conn, stream: stream}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *Session) Accept() (net.Conn, error) {
|
|
||||||
stream, err := session.session.AcceptStream()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &StreamConn{Conn: session.conn, stream: stream}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *Session) Close() error {
|
|
||||||
if session.session == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return session.session.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *Session) IsClosed() bool {
|
|
||||||
if session.session == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return session.session.IsClosed()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *Session) NumStreams() int {
|
|
||||||
return session.session.NumStreams()
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamConn struct {
|
|
||||||
net.Conn
|
|
||||||
stream *smux.Stream
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamConn) Read(b []byte) (n int, err error) {
|
|
||||||
return c.stream.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamConn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.stream.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StreamConn) Close() error {
|
|
||||||
return c.stream.Close()
|
|
||||||
}
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
package socks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/bufpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type udpTunConn struct {
|
|
||||||
net.Conn
|
|
||||||
taddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPTunClientConn(c net.Conn, targetAddr net.Addr) net.Conn {
|
|
||||||
return &udpTunConn{
|
|
||||||
Conn: c,
|
|
||||||
taddr: targetAddr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPTunClientPacketConn(c net.Conn) net.PacketConn {
|
|
||||||
return &udpTunConn{
|
|
||||||
Conn: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPTunServerConn(c net.Conn) net.PacketConn {
|
|
||||||
return &udpTunConn{
|
|
||||||
Conn: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpTunConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|
||||||
socksAddr := gosocks5.Addr{}
|
|
||||||
header := gosocks5.UDPHeader{
|
|
||||||
Addr: &socksAddr,
|
|
||||||
}
|
|
||||||
dgram := gosocks5.UDPDatagram{
|
|
||||||
Header: &header,
|
|
||||||
Data: b,
|
|
||||||
}
|
|
||||||
_, err = dgram.ReadFrom(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n = len(dgram.Data)
|
|
||||||
if n > len(b) {
|
|
||||||
n = copy(b, dgram.Data)
|
|
||||||
}
|
|
||||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpTunConn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpTunConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
||||||
socksAddr := gosocks5.Addr{}
|
|
||||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
header := gosocks5.UDPHeader{
|
|
||||||
Addr: &socksAddr,
|
|
||||||
}
|
|
||||||
dgram := gosocks5.UDPDatagram{
|
|
||||||
Header: &header,
|
|
||||||
Data: b,
|
|
||||||
}
|
|
||||||
dgram.Header.Rsv = uint16(len(dgram.Data))
|
|
||||||
dgram.Header.Frag = 0xff // UDP tun relay flag, used by shadowsocks
|
|
||||||
_, err = dgram.WriteTo(c.Conn)
|
|
||||||
n = len(b)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpTunConn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.WriteTo(b, c.taddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultBufferSize = 4096
|
|
||||||
)
|
|
||||||
|
|
||||||
type udpConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
raddr net.Addr
|
|
||||||
taddr net.Addr
|
|
||||||
bufferSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPConn(c net.PacketConn, bufferSize int) net.PacketConn {
|
|
||||||
return &udpConn{
|
|
||||||
PacketConn: c,
|
|
||||||
bufferSize: bufferSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom reads an UDP datagram.
|
|
||||||
// NOTE: for server side,
|
|
||||||
// the returned addr is the target address the client want to relay to.
|
|
||||||
func (c *udpConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|
||||||
rbuf := bufpool.Get(c.bufferSize)
|
|
||||||
defer bufpool.Put(rbuf)
|
|
||||||
|
|
||||||
n, c.raddr, err = c.PacketConn.ReadFrom(*rbuf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
socksAddr := gosocks5.Addr{}
|
|
||||||
header := gosocks5.UDPHeader{
|
|
||||||
Addr: &socksAddr,
|
|
||||||
}
|
|
||||||
hlen, err := header.ReadFrom(bytes.NewReader((*rbuf)[:n]))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n = copy(b, (*rbuf)[hlen:n])
|
|
||||||
|
|
||||||
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
||||||
wbuf := bufpool.Get(c.bufferSize)
|
|
||||||
defer bufpool.Put(wbuf)
|
|
||||||
|
|
||||||
socksAddr := gosocks5.Addr{}
|
|
||||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
header := gosocks5.UDPHeader{
|
|
||||||
Addr: &socksAddr,
|
|
||||||
}
|
|
||||||
dgram := gosocks5.UDPDatagram{
|
|
||||||
Header: &header,
|
|
||||||
Data: b,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer((*wbuf)[:0])
|
|
||||||
_, err = dgram.WriteTo(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
|
|
||||||
n = len(b)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.WriteTo(b, c.taddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) RemoteAddr() net.Addr {
|
|
||||||
return c.raddr
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package socks
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MethodTLS is an extended SOCKS5 method with tls encryption support.
|
|
||||||
MethodTLS uint8 = 0x80
|
|
||||||
// MethodTLSAuth is an extended SOCKS5 method with tls encryption and authentication support.
|
|
||||||
MethodTLSAuth uint8 = 0x82
|
|
||||||
// MethodMux is an extended SOCKS5 method for stream multiplexing.
|
|
||||||
MethodMux = 0x88
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// CmdMuxBind is an extended SOCKS5 request CMD for
|
|
||||||
// multiplexing transport with the binding server.
|
|
||||||
CmdMuxBind uint8 = 0xF2
|
|
||||||
// CmdUDPTun is an extended SOCKS5 request CMD for UDP over TCP.
|
|
||||||
CmdUDPTun uint8 = 0xF3
|
|
||||||
)
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/bufpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultBufferSize = 4096
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ net.PacketConn = (*UDPConn)(nil)
|
|
||||||
_ net.Conn = (*UDPConn)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type UDPConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
raddr net.Addr
|
|
||||||
taddr net.Addr
|
|
||||||
bufferSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr, bufferSize int) *UDPConn {
|
|
||||||
return &UDPConn{
|
|
||||||
PacketConn: c,
|
|
||||||
raddr: remoteAddr,
|
|
||||||
taddr: targetAddr,
|
|
||||||
bufferSize: bufferSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPServerConn(c net.PacketConn, remoteAddr net.Addr, bufferSize int) *UDPConn {
|
|
||||||
return &UDPConn{
|
|
||||||
PacketConn: c,
|
|
||||||
raddr: remoteAddr,
|
|
||||||
bufferSize: bufferSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|
||||||
rbuf := bufpool.Get(c.bufferSize)
|
|
||||||
defer bufpool.Put(rbuf)
|
|
||||||
|
|
||||||
n, _, err = c.PacketConn.ReadFrom(*rbuf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
saddr := gosocks5.Addr{}
|
|
||||||
addrLen, err := saddr.ReadFrom(bytes.NewReader((*rbuf)[:n]))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(b, (*rbuf)[addrLen:n])
|
|
||||||
addr, err = net.ResolveUDPAddr("udp", saddr.String())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPConn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
||||||
wbuf := bufpool.Get(c.bufferSize)
|
|
||||||
defer bufpool.Put(wbuf)
|
|
||||||
|
|
||||||
socksAddr := gosocks5.Addr{}
|
|
||||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addrLen, err := socksAddr.Encode(*wbuf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy((*wbuf)[addrLen:], b)
|
|
||||||
_, err = c.PacketConn.WriteTo((*wbuf)[:addrLen+n], c.raddr)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPConn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.WriteTo(b, c.taddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UDPConn) RemoteAddr() net.Addr {
|
|
||||||
return c.raddr
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
|
||||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
type shadowCipher struct {
|
|
||||||
cipher *ss.Cipher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
|
|
||||||
return ss.NewConn(conn, c.cipher.Copy())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
|
|
||||||
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShadowCipher(method, password string, key string) (core.Cipher, error) {
|
|
||||||
if method == "" && password == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c, _ := ss.NewCipher(method, password)
|
|
||||||
if c != nil {
|
|
||||||
return &shadowCipher{cipher: c}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return core.PickCipher(method, []byte(key), password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
|
||||||
// we wrap around it to make io.Copy happy.
|
|
||||||
type shadowConn struct {
|
|
||||||
net.Conn
|
|
||||||
wbuf *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShadowConn(conn net.Conn, header []byte) net.Conn {
|
|
||||||
return &shadowConn{
|
|
||||||
Conn: conn,
|
|
||||||
wbuf: bytes.NewBuffer(header),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *shadowConn) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b) // force byte length consistent
|
|
||||||
if c.wbuf.Len() > 0 {
|
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
|
||||||
_, err = c.Conn.Write(c.wbuf.Bytes())
|
|
||||||
c.wbuf.Reset()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultKeepAlivePeriod = 180 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// TCPKeepAliveListener is a TCP listener with keep alive enabled.
|
|
||||||
type TCPKeepAliveListener struct {
|
|
||||||
KeepAlivePeriod time.Duration
|
|
||||||
*net.TCPListener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *TCPKeepAliveListener) Accept() (c net.Conn, err error) {
|
|
||||||
tc, err := l.AcceptTCP()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tc.SetKeepAlive(true)
|
|
||||||
period := l.KeepAlivePeriod
|
|
||||||
if period <= 0 {
|
|
||||||
period = defaultKeepAlivePeriod
|
|
||||||
}
|
|
||||||
tc.SetKeepAlivePeriod(period)
|
|
||||||
|
|
||||||
return tc, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultConfig is a default TLS config for global use.
|
|
||||||
DefaultConfig *tls.Config
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadServerConfig loads the certificate from cert & key files and optional client CA file.
|
|
||||||
func LoadServerConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
|
|
||||||
if certFile == "" && keyFile == "" {
|
|
||||||
return DefaultConfig.Clone(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadClientConfig loads the certificate from cert & key files and optional CA file.
|
|
||||||
func LoadClientConfig(certFile, keyFile, caFile string, verify bool, serverName string) (*tls.Config, error) {
|
|
||||||
var cfg *tls.Config
|
|
||||||
|
|
||||||
if certFile == "" && keyFile == "" {
|
|
||||||
cfg = &tls.Config{}
|
|
||||||
} else {
|
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootCAs, err := loadCA(caFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.RootCAs = rootCAs
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.InsecureSkipVerify = !verify
|
|
||||||
|
|
||||||
// If the root ca is given, but skip verify, we verify the certificate manually.
|
|
||||||
if cfg.RootCAs != nil && !verify {
|
|
||||||
cfg.VerifyConnection = func(state tls.ConnectionState) error {
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
Roots: cfg.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCA(caFile string) (cp *x509.CertPool, err error) {
|
|
||||||
if caFile == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cp = x509.NewCertPool()
|
|
||||||
data, err := ioutil.ReadFile(caFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !cp.AppendCertsFromPEM(data) {
|
|
||||||
return nil, errors.New("AppendCertsFromPEM failed")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap a net.Conn into a client tls connection, performing any
|
|
||||||
// additional verification as needed.
|
|
||||||
//
|
|
||||||
// As of go 1.3, crypto/tls only supports either doing no certificate
|
|
||||||
// verification, or doing full verification including of the peer's
|
|
||||||
// DNS name. For consul, we want to validate that the certificate is
|
|
||||||
// signed by a known CA, but because consul doesn't use DNS names for
|
|
||||||
// node names, we don't verify the certificate DNS names. Since go 1.3
|
|
||||||
// no longer supports this mode of operation, we have to do it
|
|
||||||
// manually.
|
|
||||||
//
|
|
||||||
// This code is taken from consul:
|
|
||||||
// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go
|
|
||||||
func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration) (net.Conn, error) {
|
|
||||||
var err error
|
|
||||||
var tlsConn *tls.Conn
|
|
||||||
|
|
||||||
if timeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(timeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConn = tls.Client(conn, tlsConfig)
|
|
||||||
|
|
||||||
// Otherwise perform handshake, but don't verify the domain
|
|
||||||
//
|
|
||||||
// The following is lightly-modified from the doFullHandshake
|
|
||||||
// method in https://golang.org/src/crypto/tls/handshake_client.go
|
|
||||||
if err = tlsConn.Handshake(); err != nil {
|
|
||||||
tlsConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can do this in `tls.Config.VerifyConnection`, which effective for
|
|
||||||
// other TLS protocols such as WebSocket. See `route.go:parseChainNode`
|
|
||||||
/*
|
|
||||||
// If crypto/tls is doing verification, there's no need to do our own.
|
|
||||||
if tlsConfig.InsecureSkipVerify == false {
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similarly if we use host's CA, we can do full handshake
|
|
||||||
if tlsConfig.RootCAs == nil {
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
CurrentTime: time.Now(),
|
|
||||||
DNSName: "",
|
|
||||||
Intermediates: x509.NewCertPool(),
|
|
||||||
}
|
|
||||||
|
|
||||||
certs := tlsConn.ConnectionState().PeerCertificates
|
|
||||||
for i, cert := range certs {
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
opts.Intermediates.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = certs[0].Verify(opts)
|
|
||||||
if err != nil {
|
|
||||||
tlsConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return tlsConn, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
package udp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/bufpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Conn is a server side connection for UDP client peer, it implements net.Conn and net.PacketConn.
|
|
||||||
type Conn struct {
|
|
||||||
net.PacketConn
|
|
||||||
localAddr net.Addr
|
|
||||||
remoteAddr net.Addr
|
|
||||||
rc chan []byte // data receive queue
|
|
||||||
idle int32 // indicate the connection is idle
|
|
||||||
closed chan struct{}
|
|
||||||
closeMutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(c net.PacketConn, localAddr, remoteAddr net.Addr, queueSize int) *Conn {
|
|
||||||
return &Conn{
|
|
||||||
PacketConn: c,
|
|
||||||
localAddr: localAddr,
|
|
||||||
remoteAddr: remoteAddr,
|
|
||||||
rc: make(chan []byte, queueSize),
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|
||||||
select {
|
|
||||||
case bb := <-c.rc:
|
|
||||||
n = copy(b, bb)
|
|
||||||
c.SetIdle(false)
|
|
||||||
bufpool.Put(&bb)
|
|
||||||
|
|
||||||
case <-c.closed:
|
|
||||||
err = net.ErrClosed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addr = c.remoteAddr
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.WriteTo(b, c.remoteAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
c.closeMutex.Lock()
|
|
||||||
defer c.closeMutex.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-c.closed:
|
|
||||||
default:
|
|
||||||
close(c.closed)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) LocalAddr() net.Addr {
|
|
||||||
return c.localAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) RemoteAddr() net.Addr {
|
|
||||||
return c.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) IsIdle() bool {
|
|
||||||
return atomic.LoadInt32(&c.idle) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) SetIdle(idle bool) {
|
|
||||||
v := int32(0)
|
|
||||||
if idle {
|
|
||||||
v = 1
|
|
||||||
}
|
|
||||||
atomic.StoreInt32(&c.idle, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriteQueue(b []byte) error {
|
|
||||||
select {
|
|
||||||
case c.rc <- b:
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case <-c.closed:
|
|
||||||
return net.ErrClosed
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New("recv queue is full")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package udp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/bufpool"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type listener struct {
|
|
||||||
addr net.Addr
|
|
||||||
conn net.PacketConn
|
|
||||||
cqueue chan net.Conn
|
|
||||||
readQueueSize int
|
|
||||||
readBufferSize int
|
|
||||||
connPool *ConnPool
|
|
||||||
mux sync.Mutex
|
|
||||||
closed chan struct{}
|
|
||||||
errChan chan error
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewListener(conn net.PacketConn, addr net.Addr, backlog, dataQueueSize, dataBufferSize int, ttl time.Duration, logger logger.Logger) net.Listener {
|
|
||||||
ln := &listener{
|
|
||||||
conn: conn,
|
|
||||||
addr: addr,
|
|
||||||
cqueue: make(chan net.Conn, backlog),
|
|
||||||
connPool: NewConnPool(ttl).WithLogger(logger),
|
|
||||||
readQueueSize: dataQueueSize,
|
|
||||||
readBufferSize: dataBufferSize,
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
errChan: make(chan error, 1),
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
go ln.listenLoop()
|
|
||||||
|
|
||||||
return ln
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Accept() (conn net.Conn, err error) {
|
|
||||||
select {
|
|
||||||
case conn = <-ln.cqueue:
|
|
||||||
return
|
|
||||||
case <-ln.closed:
|
|
||||||
return nil, net.ErrClosed
|
|
||||||
case err = <-ln.errChan:
|
|
||||||
if err == nil {
|
|
||||||
err = net.ErrClosed
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) listenLoop() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ln.closed:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bufpool.Get(ln.readBufferSize)
|
|
||||||
|
|
||||||
n, raddr, err := ln.conn.ReadFrom(*b)
|
|
||||||
if err != nil {
|
|
||||||
ln.errChan <- err
|
|
||||||
close(ln.errChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := ln.getConn(raddr)
|
|
||||||
if c == nil {
|
|
||||||
bufpool.Put(b)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.WriteQueue((*b)[:n]); err != nil {
|
|
||||||
ln.logger.Warn("data discarded: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Addr() net.Addr {
|
|
||||||
return ln.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) Close() error {
|
|
||||||
select {
|
|
||||||
case <-ln.closed:
|
|
||||||
default:
|
|
||||||
close(ln.closed)
|
|
||||||
ln.conn.Close()
|
|
||||||
ln.connPool.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ln *listener) getConn(raddr net.Addr) *Conn {
|
|
||||||
ln.mux.Lock()
|
|
||||||
defer ln.mux.Unlock()
|
|
||||||
|
|
||||||
c, ok := ln.connPool.Get(raddr.String())
|
|
||||||
if ok {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
c = NewConn(ln.conn, ln.addr, raddr, ln.readQueueSize)
|
|
||||||
select {
|
|
||||||
case ln.cqueue <- c:
|
|
||||||
ln.connPool.Set(raddr.String(), c)
|
|
||||||
return c
|
|
||||||
default:
|
|
||||||
c.Close()
|
|
||||||
ln.logger.Warnf("connection queue is full, client %s discarded", raddr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
package udp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnPool struct {
|
|
||||||
m sync.Map
|
|
||||||
ttl time.Duration
|
|
||||||
closed chan struct{}
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnPool(ttl time.Duration) *ConnPool {
|
|
||||||
p := &ConnPool{
|
|
||||||
ttl: ttl,
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}
|
|
||||||
go p.idleCheck()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) WithLogger(logger logger.Logger) *ConnPool {
|
|
||||||
p.logger = logger
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Get(key interface{}) (c *Conn, ok bool) {
|
|
||||||
v, ok := p.m.Load(key)
|
|
||||||
if ok {
|
|
||||||
c, ok = v.(*Conn)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Set(key interface{}, c *Conn) {
|
|
||||||
p.m.Store(key, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Delete(key interface{}) {
|
|
||||||
p.m.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Close() {
|
|
||||||
select {
|
|
||||||
case <-p.closed:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
close(p.closed)
|
|
||||||
|
|
||||||
p.m.Range(func(k, v interface{}) bool {
|
|
||||||
if c, ok := v.(*Conn); ok && c != nil {
|
|
||||||
c.Close()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) idleCheck() {
|
|
||||||
ticker := time.NewTicker(p.ttl)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
size := 0
|
|
||||||
idles := 0
|
|
||||||
p.m.Range(func(key, value interface{}) bool {
|
|
||||||
c, ok := value.(*Conn)
|
|
||||||
if !ok || c == nil {
|
|
||||||
p.Delete(key)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
size++
|
|
||||||
|
|
||||||
if c.IsIdle() {
|
|
||||||
idles++
|
|
||||||
p.Delete(key)
|
|
||||||
c.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SetIdle(true)
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if idles > 0 {
|
|
||||||
p.logger.Debugf("connection pool: size=%d, idle=%d", size, idles)
|
|
||||||
}
|
|
||||||
case <-p.closed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
v = viper.GetViper()
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
v.SetConfigName("gost")
|
|
||||||
v.AddConfigPath("/etc/gost/")
|
|
||||||
v.AddConfigPath("$HOME/.gost/")
|
|
||||||
v.AddConfigPath(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogConfig struct {
|
|
||||||
Output string `yaml:",omitempty"`
|
|
||||||
Level string `yaml:",omitempty"`
|
|
||||||
Format string `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProfilingConfig struct {
|
|
||||||
Addr string
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type TLSConfig struct {
|
|
||||||
CertFile string `yaml:"certFile,omitempty"`
|
|
||||||
KeyFile string `yaml:"keyFile,omitempty"`
|
|
||||||
CAFile string `yaml:"caFile,omitempty"`
|
|
||||||
Secure bool `yaml:",omitempty"`
|
|
||||||
ServerName string `yaml:"serverName,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthConfig struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SelectorConfig struct {
|
|
||||||
Strategy string
|
|
||||||
MaxFails int `yaml:"maxFails"`
|
|
||||||
FailTimeout time.Duration `yaml:"failTimeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BypassConfig struct {
|
|
||||||
Name string
|
|
||||||
Reverse bool `yaml:",omitempty"`
|
|
||||||
Matchers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type NameserverConfig struct {
|
|
||||||
Addr string
|
|
||||||
Chain string `yaml:",omitempty"`
|
|
||||||
Prefer string `yaml:",omitempty"`
|
|
||||||
ClientIP string `yaml:"clientIP,omitempty"`
|
|
||||||
Hostname string `yaml:",omitempty"`
|
|
||||||
TTL time.Duration `yaml:",omitempty"`
|
|
||||||
Timeout time.Duration `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResolverConfig struct {
|
|
||||||
Name string
|
|
||||||
Nameservers []NameserverConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type HostMappingConfig struct {
|
|
||||||
IP string
|
|
||||||
Hostname string
|
|
||||||
Aliases []string `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HostsConfig struct {
|
|
||||||
Name string
|
|
||||||
Mappings []HostMappingConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListenerConfig struct {
|
|
||||||
Type string
|
|
||||||
Chain string `yaml:",omitempty"`
|
|
||||||
Auths []*AuthConfig `yaml:",omitempty"`
|
|
||||||
TLS *TLSConfig `yaml:",omitempty"`
|
|
||||||
Metadata map[string]interface{} `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandlerConfig struct {
|
|
||||||
Type string
|
|
||||||
Retries int `yaml:",omitempty"`
|
|
||||||
Chain string `yaml:",omitempty"`
|
|
||||||
Auths []*AuthConfig `yaml:",omitempty"`
|
|
||||||
TLS *TLSConfig `yaml:",omitempty"`
|
|
||||||
Metadata map[string]interface{} `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ForwarderConfig struct {
|
|
||||||
Targets []string
|
|
||||||
Selector *SelectorConfig `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DialerConfig struct {
|
|
||||||
Type string
|
|
||||||
Auth *AuthConfig `yaml:",omitempty"`
|
|
||||||
TLS *TLSConfig `yaml:",omitempty"`
|
|
||||||
Metadata map[string]interface{} `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectorConfig struct {
|
|
||||||
Type string
|
|
||||||
Auth *AuthConfig `yaml:",omitempty"`
|
|
||||||
TLS *TLSConfig `yaml:",omitempty"`
|
|
||||||
Metadata map[string]interface{} `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceConfig struct {
|
|
||||||
Name string
|
|
||||||
Addr string `yaml:",omitempty"`
|
|
||||||
Bypass string `yaml:",omitempty"`
|
|
||||||
Resolver string `yaml:",omitempty"`
|
|
||||||
Hosts string `yaml:",omitempty"`
|
|
||||||
Handler *HandlerConfig `yaml:",omitempty"`
|
|
||||||
Listener *ListenerConfig `yaml:",omitempty"`
|
|
||||||
Forwarder *ForwarderConfig `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChainConfig struct {
|
|
||||||
Name string
|
|
||||||
Selector *SelectorConfig `yaml:",omitempty"`
|
|
||||||
Hops []*HopConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type HopConfig struct {
|
|
||||||
Name string
|
|
||||||
Selector *SelectorConfig `yaml:",omitempty"`
|
|
||||||
Bypass string `yaml:",omitempty"`
|
|
||||||
Resolver string `yaml:",omitempty"`
|
|
||||||
Hosts string `yaml:",omitempty"`
|
|
||||||
Nodes []*NodeConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeConfig struct {
|
|
||||||
Name string
|
|
||||||
Addr string `yaml:",omitempty"`
|
|
||||||
Bypass string `yaml:",omitempty"`
|
|
||||||
Resolver string `yaml:",omitempty"`
|
|
||||||
Hosts string `yaml:",omitempty"`
|
|
||||||
Connector *ConnectorConfig `yaml:",omitempty"`
|
|
||||||
Dialer *DialerConfig `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Services []*ServiceConfig
|
|
||||||
Chains []*ChainConfig `yaml:",omitempty"`
|
|
||||||
Bypasses []*BypassConfig `yaml:",omitempty"`
|
|
||||||
Resolvers []*ResolverConfig `yaml:",omitempty"`
|
|
||||||
Hosts []*HostsConfig `yaml:",omitempty"`
|
|
||||||
TLS *TLSConfig `yaml:",omitempty"`
|
|
||||||
Log *LogConfig `yaml:",omitempty"`
|
|
||||||
Profiling *ProfilingConfig `yaml:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Load() error {
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.Unmarshal(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Read(r io.Reader) error {
|
|
||||||
if err := v.ReadConfig(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.Unmarshal(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) ReadFile(file string) error {
|
|
||||||
v.SetConfigFile(file)
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return v.Unmarshal(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Write(w io.Writer) error {
|
|
||||||
enc := yaml.NewEncoder(w)
|
|
||||||
defer enc.Close()
|
|
||||||
|
|
||||||
return enc.Encode(c)
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package connector
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrBindUnsupported = errors.New("bind unsupported")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Accepter interface {
|
|
||||||
Accept() (net.Conn, error)
|
|
||||||
Addr() net.Addr
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Binder interface {
|
|
||||||
Bind(ctx context.Context, conn net.Conn, network, address string, opts ...BindOption) (net.Listener, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AcceptError struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAcceptError(err error) error {
|
|
||||||
return &AcceptError{err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AcceptError) Error() string {
|
|
||||||
return e.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AcceptError) Timeout() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AcceptError) Temporary() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AcceptError) Unwrap() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package connector
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Connector is responsible for connecting to the destination address.
|
|
||||||
type Connector interface {
|
|
||||||
Init(metadata.Metadata) error
|
|
||||||
Connect(ctx context.Context, conn net.Conn, network, address string, opts ...ConnectOption) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handshaker interface {
|
|
||||||
Handshake(ctx context.Context, conn net.Conn) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package forward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("forward", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type forwardConnector struct {
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &forwardConnector{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *forwardConnector) Init(md md.Metadata) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *forwardConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("http", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpConnector struct {
|
|
||||||
user *url.Userinfo
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &httpConnector{
|
|
||||||
user: options.User,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpConnector) Init(md md.Metadata) (err error) {
|
|
||||||
return c.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodConnect,
|
|
||||||
URL: &url.URL{Host: address},
|
|
||||||
Host: address,
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
Header: c.md.header,
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Header == nil {
|
|
||||||
req.Header = http.Header{}
|
|
||||||
}
|
|
||||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
|
||||||
|
|
||||||
if user := c.user; user != nil {
|
|
||||||
u := user.Username()
|
|
||||||
p, _ := user.Password()
|
|
||||||
req.Header.Set("Proxy-Authorization",
|
|
||||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if _, ok := conn.(net.PacketConn); ok {
|
|
||||||
err := fmt.Errorf("tcp over udp is unsupported")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
req.Header.Set("X-Gost-Protocol", "udp")
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if log.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpRequest(req, false)
|
|
||||||
log.Debug(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
req = req.WithContext(ctx)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// NOTE: the server may return `Transfer-Encoding: chunked` header,
|
|
||||||
// then the Content-Length of response will be unknown (-1),
|
|
||||||
// in this case, close body will be blocked, so we leave it untouched.
|
|
||||||
// defer resp.Body.Close()
|
|
||||||
|
|
||||||
if log.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpResponse(resp, false)
|
|
||||||
log.Debug(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("%s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == "udp" {
|
|
||||||
addr, _ := net.ResolveUDPAddr(network, address)
|
|
||||||
return socks.UDPTunClientConn(conn, addr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
connectTimeout time.Duration
|
|
||||||
header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
header = "header"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
|
|
||||||
if mm := mdata.GetStringMapString(md, header); len(mm) > 0 {
|
|
||||||
hd := http.Header{}
|
|
||||||
for k, v := range mm {
|
|
||||||
hd.Add(k, v)
|
|
||||||
}
|
|
||||||
c.md.header = hd
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package http2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTP2 connection, wrapped up just like a net.Conn.
|
|
||||||
type http2Conn struct {
|
|
||||||
r io.Reader
|
|
||||||
w io.Writer
|
|
||||||
remoteAddr net.Addr
|
|
||||||
localAddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) Read(b []byte) (n int, err error) {
|
|
||||||
return c.r.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) Close() (err error) {
|
|
||||||
if r, ok := c.r.(io.Closer); ok {
|
|
||||||
err = r.Close()
|
|
||||||
}
|
|
||||||
if w, ok := c.w.(io.Closer); ok {
|
|
||||||
err = w.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) LocalAddr() net.Addr {
|
|
||||||
return c.localAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) RemoteAddr() net.Addr {
|
|
||||||
return c.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) SetDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) SetReadDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
package http2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
http2_util "github.com/go-gost/gost/pkg/internal/util/http2"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("http2", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type http2Connector struct {
|
|
||||||
user *url.Userinfo
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http2Connector{
|
|
||||||
user: options.User,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Connector) Init(md md.Metadata) (err error) {
|
|
||||||
return c.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
cc, ok := conn.(*http2_util.ClientConn)
|
|
||||||
if !ok {
|
|
||||||
err := errors.New("wrong connection type")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodConnect,
|
|
||||||
URL: &url.URL{Scheme: "https", Host: conn.RemoteAddr().String()},
|
|
||||||
Host: address,
|
|
||||||
ProtoMajor: 2,
|
|
||||||
ProtoMinor: 0,
|
|
||||||
Header: make(http.Header),
|
|
||||||
Body: pr,
|
|
||||||
// ContentLength: -1,
|
|
||||||
}
|
|
||||||
if c.md.UserAgent != "" {
|
|
||||||
req.Header.Set("User-Agent", c.md.UserAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if user := c.user; user != nil {
|
|
||||||
u := user.Username()
|
|
||||||
p, _ := user.Password()
|
|
||||||
req.Header.Set("Proxy-Authorization",
|
|
||||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if log.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpRequest(req, false)
|
|
||||||
log.Debug(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cc.Client().Do(req.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
cc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if log.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpResponse(resp, false)
|
|
||||||
log.Debug(string(dump))
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
resp.Body.Close()
|
|
||||||
err = fmt.Errorf("%s", resp.Status)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hc := &http2Conn{
|
|
||||||
r: resp.Body,
|
|
||||||
w: pw,
|
|
||||||
localAddr: conn.RemoteAddr(),
|
|
||||||
}
|
|
||||||
|
|
||||||
hc.remoteAddr, _ = net.ResolveTCPAddr(network, address)
|
|
||||||
|
|
||||||
return hc, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package http2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultUserAgent = "Chrome/78.0.3904.106"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
connectTimeout time.Duration
|
|
||||||
UserAgent string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Connector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
userAgent = "userAgent"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
c.md.UserAgent = mdata.GetString(md, userAgent)
|
|
||||||
if c.md.UserAgent == "" {
|
|
||||||
c.md.UserAgent = defaultUserAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
package connector
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
User *url.Userinfo
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
Logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(opts *Options)
|
|
||||||
|
|
||||||
func UserOption(user *url.Userinfo) Option {
|
|
||||||
return func(opts *Options) {
|
|
||||||
opts.User = user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TLSConfigOption(tlsConfig *tls.Config) Option {
|
|
||||||
return func(opts *Options) {
|
|
||||||
opts.TLSConfig = tlsConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoggerOption(logger logger.Logger) Option {
|
|
||||||
return func(opts *Options) {
|
|
||||||
opts.Logger = logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectOptions struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectOption func(opts *ConnectOptions)
|
|
||||||
|
|
||||||
type BindOptions struct {
|
|
||||||
Mux bool
|
|
||||||
Backlog int
|
|
||||||
UDPDataQueueSize int
|
|
||||||
UDPDataBufferSize int
|
|
||||||
UDPConnTTL time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type BindOption func(opts *BindOptions)
|
|
||||||
|
|
||||||
func MuxBindOption(mux bool) BindOption {
|
|
||||||
return func(opts *BindOptions) {
|
|
||||||
opts.Mux = mux
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BacklogBindOption(backlog int) BindOption {
|
|
||||||
return func(opts *BindOptions) {
|
|
||||||
opts.Backlog = backlog
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPDataQueueSizeBindOption(size int) BindOption {
|
|
||||||
return func(opts *BindOptions) {
|
|
||||||
opts.UDPDataQueueSize = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPDataBufferSizeBindOption(size int) BindOption {
|
|
||||||
return func(opts *BindOptions) {
|
|
||||||
opts.UDPDataBufferSize = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UDPConnTTLBindOption(ttl time.Duration) BindOption {
|
|
||||||
return func(opts *BindOptions) {
|
|
||||||
opts.UDPConnTTL = ttl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
package relay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/mux"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/udp"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
"github.com/go-gost/relay"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bind implements connector.Binder.
|
|
||||||
func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("bind on %s/%s", address, network)
|
|
||||||
|
|
||||||
options := connector.BindOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
return c.bindTCP(ctx, conn, network, address, log)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
return c.bindUDP(ctx, conn, network, address, &options, log)
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
|
||||||
laddr, err := c.bind(conn, relay.BIND, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
|
|
||||||
|
|
||||||
session, err := mux.ServerSession(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tcpListener{
|
|
||||||
addr: laddr,
|
|
||||||
session: session,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
|
|
||||||
laddr, err := c.bind(conn, relay.FUDP|relay.BIND, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
|
|
||||||
|
|
||||||
ln := udp.NewListener(
|
|
||||||
socks.UDPTunClientPacketConn(conn),
|
|
||||||
laddr,
|
|
||||||
opts.Backlog,
|
|
||||||
opts.UDPDataQueueSize, opts.UDPDataBufferSize,
|
|
||||||
opts.UDPConnTTL,
|
|
||||||
log)
|
|
||||||
|
|
||||||
return ln, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) bind(conn net.Conn, cmd uint8, network, address string) (net.Addr, error) {
|
|
||||||
req := relay.Request{
|
|
||||||
Version: relay.Version1,
|
|
||||||
Flags: cmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.user != nil {
|
|
||||||
pwd, _ := c.user.Password()
|
|
||||||
req.Features = append(req.Features, &relay.UserAuthFeature{
|
|
||||||
Username: c.user.Username(),
|
|
||||||
Password: pwd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fa := &relay.AddrFeature{}
|
|
||||||
fa.ParseFrom(address)
|
|
||||||
req.Features = append(req.Features, fa)
|
|
||||||
if _, err := req.WriteTo(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// first reply, bind status
|
|
||||||
resp := relay.Response{}
|
|
||||||
if _, err := resp.ReadFrom(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Status != relay.StatusOK {
|
|
||||||
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addr string
|
|
||||||
for _, f := range resp.Features {
|
|
||||||
if f.Type() == relay.FeatureAddr {
|
|
||||||
if fa, ok := f.(*relay.AddrFeature); ok {
|
|
||||||
addr = net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var baddr net.Addr
|
|
||||||
var err error
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
baddr, err = net.ResolveTCPAddr(network, addr)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
baddr, err = net.ResolveUDPAddr(network, addr)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unknown network %s", network)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return baddr, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
package relay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-gost/relay"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpConn struct {
|
|
||||||
net.Conn
|
|
||||||
wbuf bytes.Buffer
|
|
||||||
once sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
|
||||||
c.once.Do(func() {
|
|
||||||
err = readResponse(c.Conn)
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b) // force byte length consistent
|
|
||||||
if c.wbuf.Len() > 0 {
|
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
|
||||||
_, err = c.Conn.Write(c.wbuf.Bytes())
|
|
||||||
c.wbuf.Reset()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpConn struct {
|
|
||||||
net.Conn
|
|
||||||
wbuf bytes.Buffer
|
|
||||||
once sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
|
||||||
c.once.Do(func() {
|
|
||||||
err = readResponse(c.Conn)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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 *udpConn) Write(b []byte) (n int, err error) {
|
|
||||||
if len(b) > math.MaxUint16 {
|
|
||||||
err = errors.New("write: data maximum exceeded")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n = len(b)
|
|
||||||
if c.wbuf.Len() > 0 {
|
|
||||||
var bb [2]byte
|
|
||||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
|
||||||
c.wbuf.Write(bb[:])
|
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
|
||||||
_, err = c.wbuf.WriteTo(c.Conn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var bb [2]byte
|
|
||||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
|
||||||
_, err = c.Conn.Write(bb[:])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readResponse(r io.Reader) (err error) {
|
|
||||||
resp := relay.Response{}
|
|
||||||
_, err = resp.ReadFrom(r)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type bindConn struct {
|
|
||||||
net.Conn
|
|
||||||
localAddr net.Addr
|
|
||||||
remoteAddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bindConn) LocalAddr() net.Addr {
|
|
||||||
return c.localAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bindConn) RemoteAddr() net.Addr {
|
|
||||||
return c.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package relay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/go-gost/relay"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("relay", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type relayConnector struct {
|
|
||||||
user *url.Userinfo
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &relayConnector{
|
|
||||||
user: options.User,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) Init(md md.Metadata) (err error) {
|
|
||||||
return c.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
req := relay.Request{
|
|
||||||
Version: relay.Version1,
|
|
||||||
Flags: relay.CONNECT,
|
|
||||||
}
|
|
||||||
if network == "udp" || network == "udp4" || network == "udp6" {
|
|
||||||
req.Flags |= relay.FUDP
|
|
||||||
|
|
||||||
// UDP association
|
|
||||||
if address == "" {
|
|
||||||
baddr, err := c.bind(conn, relay.FUDP|relay.BIND, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("associate on %s OK", baddr)
|
|
||||||
|
|
||||||
return socks.UDPTunClientConn(conn, nil), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.user != nil {
|
|
||||||
pwd, _ := c.user.Password()
|
|
||||||
req.Features = append(req.Features, &relay.UserAuthFeature{
|
|
||||||
Username: c.user.Username(),
|
|
||||||
Password: pwd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if address != "" {
|
|
||||||
af := &relay.AddrFeature{}
|
|
||||||
if err := af.ParseFrom(address); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward mode if port is 0.
|
|
||||||
if af.Port > 0 {
|
|
||||||
req.Features = append(req.Features, af)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.noDelay {
|
|
||||||
if _, err := req.WriteTo(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
cc := &tcpConn{
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
if !c.md.noDelay {
|
|
||||||
if _, err := req.WriteTo(&cc.wbuf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
cc := &udpConn{
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
if !c.md.noDelay {
|
|
||||||
if _, err := req.WriteTo(&cc.wbuf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
package relay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/mux"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
"github.com/go-gost/relay"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpListener struct {
|
|
||||||
addr net.Addr
|
|
||||||
session *mux.Session
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) Accept() (net.Conn, error) {
|
|
||||||
cc, err := p.session.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := p.getPeerConn(cc)
|
|
||||||
if err != nil {
|
|
||||||
cc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) getPeerConn(conn net.Conn) (net.Conn, error) {
|
|
||||||
// second reply, peer connected
|
|
||||||
resp := relay.Response{}
|
|
||||||
if _, err := resp.ReadFrom(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Status != relay.StatusOK {
|
|
||||||
err := fmt.Errorf("peer connect failed")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var address string
|
|
||||||
for _, f := range resp.Features {
|
|
||||||
if f.Type() == relay.FeatureAddr {
|
|
||||||
if fa, ok := f.(*relay.AddrFeature); ok {
|
|
||||||
address = net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
raddr, err := net.ResolveTCPAddr("tcp", address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bindConn{
|
|
||||||
Conn: conn,
|
|
||||||
localAddr: p.addr,
|
|
||||||
remoteAddr: raddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) Addr() net.Addr {
|
|
||||||
return p.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) Close() error {
|
|
||||||
return p.session.Close()
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package relay
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
connectTimeout time.Duration
|
|
||||||
noDelay bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
connectTimeout = "connectTimeout"
|
|
||||||
noDelay = "nodelay"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
c.md.noDelay = mdata.GetBool(md, noDelay)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
package sni
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"hash/crc32"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
dissector "github.com/go-gost/tls-dissector"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sniClientConn struct {
|
|
||||||
host string
|
|
||||||
obfuscated bool
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sniClientConn) Write(p []byte) (int, error) {
|
|
||||||
b, err := c.obfuscate(p)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if _, err = c.Conn.Write(b); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sniClientConn) obfuscate(p []byte) ([]byte, error) {
|
|
||||||
if c.host == "" {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.obfuscated {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if p[0] == dissector.Handshake {
|
|
||||||
b, err := readClientHelloRecord(bytes.NewReader(p), c.host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.obfuscated = true
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
br := bufio.NewReader(bytes.NewReader(p))
|
|
||||||
for {
|
|
||||||
s, err := br.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if s != "" {
|
|
||||||
buf.Write([]byte(s))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// end of HTTP header
|
|
||||||
if s == "\r\n" {
|
|
||||||
buf.Write([]byte(s))
|
|
||||||
// drain the remain bytes.
|
|
||||||
io.Copy(buf, br)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(s, "Host") {
|
|
||||||
s = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(s, "Host:"), "\r\n"))
|
|
||||||
host := encodeServerName(s)
|
|
||||||
buf.WriteString("Host: " + c.host + "\r\n")
|
|
||||||
buf.WriteString("Gost-Target: " + host + "\r\n")
|
|
||||||
// drain the remain bytes.
|
|
||||||
io.Copy(buf, br)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buf.Write([]byte(s))
|
|
||||||
}
|
|
||||||
c.obfuscated = true
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readClientHelloRecord(r io.Reader, host string) ([]byte, error) {
|
|
||||||
record, err := dissector.ReadRecord(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
clientHello := dissector.ClientHelloMsg{}
|
|
||||||
if err := clientHello.Decode(record.Opaque); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ext := range clientHello.Extensions {
|
|
||||||
if ext.Type() == dissector.ExtServerName {
|
|
||||||
snExtension := ext.(*dissector.ServerNameExtension)
|
|
||||||
if host != "" {
|
|
||||||
e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
|
|
||||||
clientHello.Extensions = append(clientHello.Extensions, e)
|
|
||||||
snExtension.Name = host
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
record.Opaque, err = clientHello.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
if _, err := record.WriteTo(buf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeServerName(name string) string {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
binary.Write(buf, binary.BigEndian, crc32.ChecksumIEEE([]byte(name)))
|
|
||||||
buf.WriteString(base64.RawURLEncoding.EncodeToString([]byte(name)))
|
|
||||||
return base64.RawURLEncoding.EncodeToString(buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package sni
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("sni", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type sniConnector struct {
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sniConnector{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sniConnector) Init(md md.Metadata) (err error) {
|
|
||||||
return c.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sniConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
return &sniClientConn{Conn: conn, host: c.md.host}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package sni
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
host string
|
|
||||||
connectTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sniConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
host = "host"
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.host = mdata.GetString(md, host)
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks4"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("socks4", NewConnector)
|
|
||||||
registry.RegiserConnector("socks4a", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type socks4Connector struct {
|
|
||||||
user *url.Userinfo
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &socks4Connector{
|
|
||||||
user: options.User,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks4Connector) Init(md md.Metadata) (err error) {
|
|
||||||
return c.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if _, ok := conn.(net.PacketConn); ok {
|
|
||||||
err := fmt.Errorf("tcp over udp is unsupported")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var addr *gosocks4.Addr
|
|
||||||
|
|
||||||
if c.md.disable4a {
|
|
||||||
taddr, err := net.ResolveTCPAddr("tcp4", address)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("resolve: ", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(taddr.IP) == 0 {
|
|
||||||
taddr.IP = net.IPv4zero
|
|
||||||
}
|
|
||||||
addr = &gosocks4.Addr{
|
|
||||||
Type: gosocks4.AddrIPv4,
|
|
||||||
Host: taddr.IP.String(),
|
|
||||||
Port: uint16(taddr.Port),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
addr = &gosocks4.Addr{
|
|
||||||
Type: gosocks4.AddrDomain,
|
|
||||||
Host: host,
|
|
||||||
Port: uint16(p),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var userid []byte
|
|
||||||
if c.user != nil && c.user.Username() != "" {
|
|
||||||
userid = []byte(c.user.Username())
|
|
||||||
}
|
|
||||||
req := gosocks4.NewRequest(gosocks4.CmdConnect, addr, userid)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(req)
|
|
||||||
|
|
||||||
reply, err := gosocks4.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(reply)
|
|
||||||
|
|
||||||
if reply.Code != gosocks4.Granted {
|
|
||||||
err = errors.New("host unreachable")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package v4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
connectTimeout time.Duration
|
|
||||||
disable4a bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks4Connector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
disable4a = "disable4a"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
c.md.disable4a = mdata.GetBool(md, disable4a)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/mux"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/udp"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bind implements connector.Binder.
|
|
||||||
func (c *socks5Connector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("bind on %s/%s", address, network)
|
|
||||||
|
|
||||||
options := connector.BindOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if options.Mux {
|
|
||||||
return c.muxBindTCP(ctx, conn, network, address, log)
|
|
||||||
}
|
|
||||||
return c.bindTCP(ctx, conn, network, address, log)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
return c.bindUDP(ctx, conn, network, address, &options, log)
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
|
||||||
laddr, err := c.bind(conn, gosocks5.CmdBind, network, address, log)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tcpListener{
|
|
||||||
addr: laddr,
|
|
||||||
conn: conn,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) muxBindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
|
||||||
laddr, err := c.bind(conn, socks.CmdMuxBind, network, address, log)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := mux.ServerSession(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tcpMuxListener{
|
|
||||||
addr: laddr,
|
|
||||||
session: session,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
|
|
||||||
laddr, err := c.bind(conn, socks.CmdUDPTun, network, address, log)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ln := udp.NewListener(
|
|
||||||
socks.UDPTunClientPacketConn(conn),
|
|
||||||
laddr,
|
|
||||||
opts.Backlog,
|
|
||||||
opts.UDPDataQueueSize, opts.UDPDataBufferSize,
|
|
||||||
opts.UDPConnTTL,
|
|
||||||
log)
|
|
||||||
|
|
||||||
return ln, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *socks5Connector) bind(conn net.Conn, cmd uint8, network, address string, log logger.Logger) (net.Addr, error) {
|
|
||||||
addr := gosocks5.Addr{}
|
|
||||||
addr.ParseFrom(address)
|
|
||||||
req := gosocks5.NewRequest(cmd, &addr)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(req)
|
|
||||||
|
|
||||||
// first reply, bind status
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(reply)
|
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
|
|
||||||
}
|
|
||||||
|
|
||||||
var baddr net.Addr
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
baddr, err = net.ResolveTCPAddr(network, reply.Addr.String())
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
baddr, err = net.ResolveUDPAddr(network, reply.Addr.String())
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unknown network %s", network)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("bind on %s/%s OK", baddr, baddr.Network())
|
|
||||||
|
|
||||||
return baddr, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package v5
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
type bindConn struct {
|
|
||||||
net.Conn
|
|
||||||
localAddr net.Addr
|
|
||||||
remoteAddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bindConn) LocalAddr() net.Addr {
|
|
||||||
return c.localAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *bindConn) RemoteAddr() net.Addr {
|
|
||||||
return c.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("socks5", NewConnector)
|
|
||||||
registry.RegiserConnector("socks", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type socks5Connector struct {
|
|
||||||
selector gosocks5.Selector
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &socks5Connector{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) Init(md md.Metadata) (err error) {
|
|
||||||
if err = c.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
selector := &clientSelector{
|
|
||||||
methods: []uint8{
|
|
||||||
gosocks5.MethodNoAuth,
|
|
||||||
gosocks5.MethodUserPass,
|
|
||||||
},
|
|
||||||
User: c.options.User,
|
|
||||||
TLSConfig: c.options.TLSConfig,
|
|
||||||
logger: c.options.Logger,
|
|
||||||
}
|
|
||||||
if !c.md.noTLS {
|
|
||||||
selector.methods = append(selector.methods, socks.MethodTLS)
|
|
||||||
if selector.TLSConfig == nil {
|
|
||||||
selector.TLSConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.selector = selector
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake implements connector.Handshaker.
|
|
||||||
func (c *socks5Connector) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
cc := gosocks5.ClientConn(conn, c.selector)
|
|
||||||
if err := cc.Handleshake(); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
return c.connectUDP(ctx, conn, network, address, log)
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if _, ok := conn.(net.PacketConn); ok {
|
|
||||||
err := fmt.Errorf("tcp over udp is unsupported")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := gosocks5.Addr{}
|
|
||||||
if err := addr.ParseFrom(address); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(req)
|
|
||||||
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(reply)
|
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
err = errors.New("host unreachable")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) connectUDP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Conn, error) {
|
|
||||||
addr, err := net.ResolveUDPAddr(network, address)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := gosocks5.NewRequest(socks.CmdUDPTun, nil)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(req)
|
|
||||||
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(reply)
|
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
return nil, errors.New("get socks5 UDP tunnel failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
return socks.UDPTunClientConn(conn, addr), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/mux"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpListener struct {
|
|
||||||
addr net.Addr
|
|
||||||
conn net.Conn
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) Accept() (net.Conn, error) {
|
|
||||||
// second reply, peer connected
|
|
||||||
rep, err := gosocks5.ReadReply(p.conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.logger.Debug(rep)
|
|
||||||
|
|
||||||
if rep.Rep != gosocks5.Succeeded {
|
|
||||||
return nil, fmt.Errorf("peer connect failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bindConn{
|
|
||||||
Conn: p.conn,
|
|
||||||
localAddr: p.addr,
|
|
||||||
remoteAddr: raddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) Addr() net.Addr {
|
|
||||||
return p.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpListener) Close() error {
|
|
||||||
return p.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpMuxListener struct {
|
|
||||||
addr net.Addr
|
|
||||||
session *mux.Session
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpMuxListener) Accept() (net.Conn, error) {
|
|
||||||
cc, err := p.session.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := p.getPeerConn(cc)
|
|
||||||
if err != nil {
|
|
||||||
cc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpMuxListener) getPeerConn(conn net.Conn) (net.Conn, error) {
|
|
||||||
// second reply, peer connected
|
|
||||||
rep, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.logger.Debug(rep)
|
|
||||||
|
|
||||||
if rep.Rep != gosocks5.Succeeded {
|
|
||||||
err = fmt.Errorf("peer connect failed")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bindConn{
|
|
||||||
Conn: conn,
|
|
||||||
localAddr: p.addr,
|
|
||||||
remoteAddr: raddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpMuxListener) Addr() net.Addr {
|
|
||||||
return p.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *tcpMuxListener) Close() error {
|
|
||||||
return p.session.Close()
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
connectTimeout time.Duration
|
|
||||||
noTLS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
noTLS = "notls"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
c.md.noTLS = mdata.GetBool(md, noTLS)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type clientSelector struct {
|
|
||||||
methods []uint8
|
|
||||||
User *url.Userinfo
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *clientSelector) Methods() []uint8 {
|
|
||||||
s.logger.Debug("methods: ", s.methods)
|
|
||||||
return s.methods
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *clientSelector) AddMethod(methods ...uint8) {
|
|
||||||
s.methods = append(s.methods, methods...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *clientSelector) Select(methods ...uint8) (method uint8) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
|
|
||||||
s.logger.Debug("method selected: ", method)
|
|
||||||
|
|
||||||
switch method {
|
|
||||||
case socks.MethodTLS:
|
|
||||||
conn = tls.Client(conn, s.TLSConfig)
|
|
||||||
|
|
||||||
case gosocks5.MethodUserPass, socks.MethodTLSAuth:
|
|
||||||
if method == socks.MethodTLSAuth {
|
|
||||||
conn = tls.Client(conn, s.TLSConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
var username, password string
|
|
||||||
if s.User != nil {
|
|
||||||
username = s.User.Username()
|
|
||||||
password, _ = s.User.Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
s.logger.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.logger.Debug(req)
|
|
||||||
|
|
||||||
resp, err := gosocks5.ReadUserPassResponse(conn)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.logger.Debug(resp)
|
|
||||||
|
|
||||||
if resp.Status != gosocks5.Succeeded {
|
|
||||||
return nil, gosocks5.ErrAuthFailure
|
|
||||||
}
|
|
||||||
case gosocks5.MethodNoAcceptable:
|
|
||||||
return nil, gosocks5.ErrBadMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gosocks5"
|
|
||||||
"github.com/go-gost/gost/pkg/common/bufpool"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/ss"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("ss", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ssConnector struct {
|
|
||||||
cipher core.Cipher
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ssConnector{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ssConnector) Init(md md.Metadata) (err error) {
|
|
||||||
if err = c.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.options.User != nil {
|
|
||||||
method := c.options.User.Username()
|
|
||||||
password, _ := c.options.User.Password()
|
|
||||||
c.cipher, err = ss.ShadowCipher(method, password, c.md.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ssConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
if _, ok := conn.(net.PacketConn); ok {
|
|
||||||
err := fmt.Errorf("tcp over udp is unsupported")
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := gosocks5.Addr{}
|
|
||||||
if err := addr.ParseFrom(address); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawaddr := bufpool.Get(512)
|
|
||||||
defer bufpool.Put(rawaddr)
|
|
||||||
|
|
||||||
n, err := addr.Encode(*rawaddr)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("encoding addr: ", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.cipher != nil {
|
|
||||||
conn = c.cipher.StreamConn(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sc net.Conn
|
|
||||||
if c.md.noDelay {
|
|
||||||
sc = ss.ShadowConn(conn, nil)
|
|
||||||
// write the addr at once.
|
|
||||||
if _, err := sc.Write((*rawaddr)[:n]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// cache the header
|
|
||||||
sc = ss.ShadowConn(conn, (*rawaddr)[:n])
|
|
||||||
}
|
|
||||||
|
|
||||||
return sc, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
key string
|
|
||||||
connectTimeout time.Duration
|
|
||||||
noDelay bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ssConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
key = "key"
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
noDelay = "nodelay"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.key = mdata.GetString(md, key)
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
c.md.noDelay = mdata.GetBool(md, noDelay)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/socks"
|
|
||||||
"github.com/go-gost/gost/pkg/common/util/ss"
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/shadowsocks/go-shadowsocks2/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("ssu", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ssuConnector struct {
|
|
||||||
cipher core.Cipher
|
|
||||||
md metadata
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ssuConnector{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ssuConnector) Init(md md.Metadata) (err error) {
|
|
||||||
if err = c.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.options.User != nil {
|
|
||||||
method := c.options.User.Username()
|
|
||||||
password, _ := c.options.User.Password()
|
|
||||||
c.cipher, err = ss.ShadowCipher(method, password, c.md.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ssuConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.connectTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
taddr, _ := net.ResolveUDPAddr(network, address)
|
|
||||||
if taddr == nil {
|
|
||||||
taddr = &net.UDPAddr{}
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, ok := conn.(net.PacketConn)
|
|
||||||
if ok {
|
|
||||||
if c.cipher != nil {
|
|
||||||
pc = c.cipher.PacketConn(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard UDP relay
|
|
||||||
return ss.UDPClientConn(pc, conn.RemoteAddr(), taddr, c.md.bufferSize), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.cipher != nil {
|
|
||||||
conn = ss.ShadowConn(c.cipher.StreamConn(conn), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP over TCP
|
|
||||||
return socks.UDPTunClientConn(conn, taddr), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package ss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
key string
|
|
||||||
connectTimeout time.Duration
|
|
||||||
bufferSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ssuConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
key = "key"
|
|
||||||
connectTimeout = "timeout"
|
|
||||||
bufferSize = "bufferSize" // udp buffer size
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.key = mdata.GetString(md, key)
|
|
||||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
|
||||||
|
|
||||||
if bs := mdata.GetInt(md, bufferSize); bs > 0 {
|
|
||||||
c.md.bufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024))
|
|
||||||
} else {
|
|
||||||
c.md.bufferSize = 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
package sshd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/connector"
|
|
||||||
ssh_util "github.com/go-gost/gost/pkg/internal/util/ssh"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegiserConnector("sshd", NewConnector)
|
|
||||||
}
|
|
||||||
|
|
||||||
type sshdConnector struct {
|
|
||||||
options connector.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
|
||||||
options := connector.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sshdConnector{
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshdConnector) Init(md md.Metadata) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshdConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("connect %s/%s", address, network)
|
|
||||||
|
|
||||||
cc, ok := conn.(*ssh_util.ClientConn)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("ssh: invalid connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := cc.Client().Dial(network, address)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind implements connector.Binder.
|
|
||||||
func (c *sshdConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
|
||||||
log := c.options.Logger.WithFields(map[string]interface{}{
|
|
||||||
"remote": conn.RemoteAddr().String(),
|
|
||||||
"local": conn.LocalAddr().String(),
|
|
||||||
"network": network,
|
|
||||||
"address": address,
|
|
||||||
})
|
|
||||||
log.Infof("bind on %s/%s", address, network)
|
|
||||||
|
|
||||||
cc, ok := conn.(*ssh_util.ClientConn)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("ssh: invalid connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
if host, port, _ := net.SplitHostPort(address); host == "" {
|
|
||||||
address = net.JoinHostPort("0.0.0.0", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cc.Client().Listen(network, address)
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Transporter is responsible for dialing to the proxy server.
|
|
||||||
type Dialer interface {
|
|
||||||
Init(metadata.Metadata) error
|
|
||||||
Dial(ctx context.Context, addr string, opts ...DialOption) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handshaker interface {
|
|
||||||
Handshake(ctx context.Context, conn net.Conn, opts ...HandshakeOption) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Multiplexer interface {
|
|
||||||
Multiplex() bool
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package ftcp
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
type fakeTCPConn struct {
|
|
||||||
raddr net.Addr
|
|
||||||
net.PacketConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeTCPConn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeTCPConn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.WriteTo(b, c.raddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeTCPConn) RemoteAddr() net.Addr {
|
|
||||||
return c.raddr
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package ftcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/xtaci/tcpraw"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("ftcp", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ftcpDialer struct {
|
|
||||||
md metadata
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := &dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ftcpDialer{
|
|
||||||
logger: options.Logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ftcpDialer) Init(md md.Metadata) (err error) {
|
|
||||||
return d.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ftcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
|
|
||||||
raddr, er := net.ResolveTCPAddr("tcp", addr)
|
|
||||||
if er != nil {
|
|
||||||
return nil, er
|
|
||||||
}
|
|
||||||
c, err := tcpraw.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return &fakeTCPConn{
|
|
||||||
raddr: raddr,
|
|
||||||
PacketConn: c,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package ftcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dialTimeout = "dialTimeout"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultDialTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
dialTimeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ftcpDialer) parseMetadata(md md.Metadata) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
pb "github.com/go-gost/gost/pkg/common/util/grpc/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type conn struct {
|
|
||||||
c pb.GostTunel_TunnelClient
|
|
||||||
rb []byte
|
|
||||||
localAddr net.Addr
|
|
||||||
remoteAddr net.Addr
|
|
||||||
closed chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Read(b []byte) (n int, err error) {
|
|
||||||
select {
|
|
||||||
case <-c.c.Context().Done():
|
|
||||||
err = c.c.Context().Err()
|
|
||||||
return
|
|
||||||
case <-c.closed:
|
|
||||||
err = io.ErrClosedPipe
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.rb) == 0 {
|
|
||||||
chunk, err := c.c.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
c.rb = chunk.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(b, c.rb)
|
|
||||||
c.rb = c.rb[n:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Write(b []byte) (n int, err error) {
|
|
||||||
select {
|
|
||||||
case <-c.c.Context().Done():
|
|
||||||
err = c.c.Context().Err()
|
|
||||||
return
|
|
||||||
case <-c.closed:
|
|
||||||
err = io.ErrClosedPipe
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.c.Send(&pb.Chunk{
|
|
||||||
Data: b,
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n = len(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Close() error {
|
|
||||||
select {
|
|
||||||
case <-c.closed:
|
|
||||||
default:
|
|
||||||
close(c.closed)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) LocalAddr() net.Addr {
|
|
||||||
return c.localAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) RemoteAddr() net.Addr {
|
|
||||||
return c.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) SetDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) SetReadDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
pb "github.com/go-gost/gost/pkg/common/util/grpc/proto"
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/backoff"
|
|
||||||
"google.golang.org/grpc/credentials"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("grpc", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type grpcDialer struct {
|
|
||||||
clients map[string]pb.GostTunelClient
|
|
||||||
clientMutex sync.Mutex
|
|
||||||
logger logger.Logger
|
|
||||||
md metadata
|
|
||||||
options dialer.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &grpcDialer{
|
|
||||||
clients: make(map[string]pb.GostTunelClient),
|
|
||||||
logger: options.Logger,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *grpcDialer) Init(md md.Metadata) (err error) {
|
|
||||||
return d.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplex implements dialer.Multiplexer interface.
|
|
||||||
func (d *grpcDialer) Multiplex() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *grpcDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
|
|
||||||
remoteAddr, err := net.ResolveTCPAddr("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.clientMutex.Lock()
|
|
||||||
defer d.clientMutex.Unlock()
|
|
||||||
|
|
||||||
client, ok := d.clients[addr]
|
|
||||||
if !ok {
|
|
||||||
var options dialer.DialOptions
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
host := d.md.host
|
|
||||||
if host == "" {
|
|
||||||
host = options.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcOpts := []grpc.DialOption{
|
|
||||||
grpc.WithBlock(),
|
|
||||||
grpc.WithContextDialer(func(c context.Context, s string) (net.Conn, error) {
|
|
||||||
return d.dial(ctx, "tcp", s, &options)
|
|
||||||
}),
|
|
||||||
grpc.WithAuthority(host),
|
|
||||||
grpc.WithConnectParams(grpc.ConnectParams{
|
|
||||||
Backoff: backoff.DefaultConfig,
|
|
||||||
MinConnectTimeout: 10 * time.Second,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
if !d.md.insecure {
|
|
||||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(d.options.TLSConfig)))
|
|
||||||
} else {
|
|
||||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
}
|
|
||||||
|
|
||||||
cc, err := grpc.DialContext(ctx, addr, grpcOpts...)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client = pb.NewGostTunelClient(cc)
|
|
||||||
d.clients[addr] = client
|
|
||||||
}
|
|
||||||
|
|
||||||
cli, err := client.Tunnel(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &conn{
|
|
||||||
c: cli,
|
|
||||||
localAddr: &net.TCPAddr{},
|
|
||||||
remoteAddr: remoteAddr,
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *grpcDialer) dial(ctx context.Context, network, addr string, opts *dialer.DialOptions) (net.Conn, error) {
|
|
||||||
dial := opts.DialFunc
|
|
||||||
if dial != nil {
|
|
||||||
conn, err := dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
} else {
|
|
||||||
d.logger.WithFields(map[string]interface{}{
|
|
||||||
"src": conn.LocalAddr().String(),
|
|
||||||
"dst": addr,
|
|
||||||
}).Debug("dial with dial func")
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var netd net.Dialer
|
|
||||||
conn, err := netd.DialContext(ctx, network, addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
} else {
|
|
||||||
d.logger.WithFields(map[string]interface{}{
|
|
||||||
"src": conn.LocalAddr().String(),
|
|
||||||
"dst": addr,
|
|
||||||
}).Debugf("dial direct %s/%s", addr, network)
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package grpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
insecure bool
|
|
||||||
host string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *grpcDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
insecure = "grpcInsecure"
|
|
||||||
host = "host"
|
|
||||||
)
|
|
||||||
|
|
||||||
d.md.insecure = mdata.GetBool(md, insecure)
|
|
||||||
d.md.host = mdata.GetString(md, host)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
package http2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
http2_util "github.com/go-gost/gost/pkg/internal/util/http2"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("http2", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type http2Dialer struct {
|
|
||||||
clients map[string]*http.Client
|
|
||||||
clientMutex sync.Mutex
|
|
||||||
logger logger.Logger
|
|
||||||
md metadata
|
|
||||||
options dialer.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http2Dialer{
|
|
||||||
clients: make(map[string]*http.Client),
|
|
||||||
logger: options.Logger,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http2Dialer) Init(md md.Metadata) (err error) {
|
|
||||||
if err = d.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplex implements dialer.Multiplexer interface.
|
|
||||||
func (d *http2Dialer) Multiplex() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.DialOption) (net.Conn, error) {
|
|
||||||
raddr, err := net.ResolveTCPAddr("tcp", address)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.clientMutex.Lock()
|
|
||||||
defer d.clientMutex.Unlock()
|
|
||||||
|
|
||||||
client, ok := d.clients[address]
|
|
||||||
if !ok {
|
|
||||||
options := dialer.DialOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
client = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: d.options.TLSConfig,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return d.dial(ctx, network, addr, &options)
|
|
||||||
},
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
d.clients[address] = client
|
|
||||||
}
|
|
||||||
|
|
||||||
return http2_util.NewClientConn(
|
|
||||||
&net.TCPAddr{}, raddr,
|
|
||||||
client,
|
|
||||||
func() {
|
|
||||||
d.clientMutex.Lock()
|
|
||||||
defer d.clientMutex.Unlock()
|
|
||||||
delete(d.clients, address)
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http2Dialer) dial(ctx context.Context, network, addr string, opts *dialer.DialOptions) (net.Conn, error) {
|
|
||||||
dial := opts.DialFunc
|
|
||||||
if dial != nil {
|
|
||||||
conn, err := dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
} else {
|
|
||||||
d.logger.WithFields(map[string]interface{}{
|
|
||||||
"src": conn.LocalAddr().String(),
|
|
||||||
"dst": addr,
|
|
||||||
}).Debug("dial with dial func")
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var netd net.Dialer
|
|
||||||
conn, err := netd.DialContext(ctx, network, addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
} else {
|
|
||||||
d.logger.WithFields(map[string]interface{}{
|
|
||||||
"src": conn.LocalAddr().String(),
|
|
||||||
"dst": addr,
|
|
||||||
}).Debugf("dial direct %s/%s", addr, network)
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package h2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTP2 connection, wrapped up just like a net.Conn.
|
|
||||||
type http2Conn struct {
|
|
||||||
r io.Reader
|
|
||||||
w io.Writer
|
|
||||||
remoteAddr net.Addr
|
|
||||||
localAddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) Read(b []byte) (n int, err error) {
|
|
||||||
return c.r.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) Close() (err error) {
|
|
||||||
if r, ok := c.r.(io.Closer); ok {
|
|
||||||
err = r.Close()
|
|
||||||
}
|
|
||||||
if w, ok := c.w.(io.Closer); ok {
|
|
||||||
err = w.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) LocalAddr() net.Addr {
|
|
||||||
return c.localAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) RemoteAddr() net.Addr {
|
|
||||||
return c.remoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) SetDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "h2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) SetReadDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "h2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *http2Conn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return &net.OpError{Op: "set", Net: "h2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
|
||||||
}
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
package h2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("h2", NewTLSDialer)
|
|
||||||
registry.RegisterDialer("h2c", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type h2Dialer struct {
|
|
||||||
clients map[string]*http.Client
|
|
||||||
clientMutex sync.Mutex
|
|
||||||
h2c bool
|
|
||||||
logger logger.Logger
|
|
||||||
md metadata
|
|
||||||
options dialer.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &h2Dialer{
|
|
||||||
h2c: true,
|
|
||||||
clients: make(map[string]*http.Client),
|
|
||||||
logger: options.Logger,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &h2Dialer{
|
|
||||||
clients: make(map[string]*http.Client),
|
|
||||||
logger: options.Logger,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *h2Dialer) Init(md md.Metadata) (err error) {
|
|
||||||
if err = d.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplex implements dialer.Multiplexer interface.
|
|
||||||
func (d *h2Dialer) Multiplex() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.DialOption) (net.Conn, error) {
|
|
||||||
raddr, err := net.ResolveTCPAddr("tcp", address)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.clientMutex.Lock()
|
|
||||||
|
|
||||||
client, ok := d.clients[address]
|
|
||||||
if !ok {
|
|
||||||
options := &dialer.DialOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
client = &http.Client{}
|
|
||||||
if d.h2c {
|
|
||||||
client.Transport = &http2.Transport{
|
|
||||||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
|
||||||
return d.dial(ctx, network, addr, options)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
client.Transport = &http.Transport{
|
|
||||||
TLSClientConfig: d.options.TLSConfig,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return d.dial(ctx, network, addr, options)
|
|
||||||
},
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.clients[address] = client
|
|
||||||
}
|
|
||||||
d.clientMutex.Unlock()
|
|
||||||
|
|
||||||
host := d.md.host
|
|
||||||
if host == "" {
|
|
||||||
host = address
|
|
||||||
}
|
|
||||||
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodConnect,
|
|
||||||
URL: &url.URL{Scheme: "https", Host: host},
|
|
||||||
Header: make(http.Header),
|
|
||||||
ProtoMajor: 2,
|
|
||||||
ProtoMinor: 0,
|
|
||||||
Body: pr,
|
|
||||||
Host: host,
|
|
||||||
// ContentLength: -1,
|
|
||||||
}
|
|
||||||
if d.md.path != "" {
|
|
||||||
req.Method = http.MethodGet
|
|
||||||
req.URL.Path = d.md.path
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.logger.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpRequest(req, false)
|
|
||||||
d.logger.Debug(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.logger.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpResponse(resp, false)
|
|
||||||
d.logger.Debug(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, errors.New(resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &http2Conn{
|
|
||||||
r: resp.Body,
|
|
||||||
w: pw,
|
|
||||||
remoteAddr: raddr,
|
|
||||||
localAddr: &net.TCPAddr{IP: net.IPv4zero, Port: 0},
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *h2Dialer) dial(ctx context.Context, network, addr string, opts *dialer.DialOptions) (net.Conn, error) {
|
|
||||||
dial := opts.DialFunc
|
|
||||||
if dial != nil {
|
|
||||||
conn, err := dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
} else {
|
|
||||||
d.logger.WithFields(map[string]interface{}{
|
|
||||||
"src": conn.LocalAddr().String(),
|
|
||||||
"dst": addr,
|
|
||||||
}).Debug("dial with dial func")
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var netd net.Dialer
|
|
||||||
conn, err := netd.DialContext(ctx, network, addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
} else {
|
|
||||||
d.logger.WithFields(map[string]interface{}{
|
|
||||||
"src": conn.LocalAddr().String(),
|
|
||||||
"dst": addr,
|
|
||||||
}).Debugf("dial direct %s/%s", addr, network)
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package h2
|
|
||||||
|
|
||||||
import (
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
host string
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *h2Dialer) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
host = "host"
|
|
||||||
path = "path"
|
|
||||||
)
|
|
||||||
|
|
||||||
d.md.host = mdata.GetString(md, host)
|
|
||||||
d.md.path = mdata.GetString(md, path)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package http2
|
|
||||||
|
|
||||||
import (
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http2Dialer) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package http3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
pht_util "github.com/go-gost/gost/pkg/internal/util/pht"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/lucas-clemente/quic-go/http3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("http3", NewDialer)
|
|
||||||
registry.RegisterDialer("h3", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type http3Dialer struct {
|
|
||||||
client *pht_util.Client
|
|
||||||
md metadata
|
|
||||||
logger logger.Logger
|
|
||||||
options dialer.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http3Dialer{
|
|
||||||
logger: options.Logger,
|
|
||||||
options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http3Dialer) Init(md md.Metadata) (err error) {
|
|
||||||
if err = d.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := &http3.RoundTripper{
|
|
||||||
TLSClientConfig: d.options.TLSConfig,
|
|
||||||
}
|
|
||||||
d.client = &pht_util.Client{
|
|
||||||
Client: &http.Client{
|
|
||||||
// Timeout: 60 * time.Second,
|
|
||||||
Transport: tr,
|
|
||||||
},
|
|
||||||
AuthorizePath: d.md.authorizePath,
|
|
||||||
PushPath: d.md.pushPath,
|
|
||||||
PullPath: d.md.pullPath,
|
|
||||||
TLSEnabled: true,
|
|
||||||
Logger: d.options.Logger,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
|
|
||||||
return d.client.Dial(ctx, addr)
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package http3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dialTimeout = "dialTimeout"
|
|
||||||
defaultAuthorizePath = "/authorize"
|
|
||||||
defaultPushPath = "/push"
|
|
||||||
defaultPullPath = "/pull"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultDialTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
dialTimeout time.Duration
|
|
||||||
authorizePath string
|
|
||||||
pushPath string
|
|
||||||
pullPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *http3Dialer) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
authorizePath = "authorizePath"
|
|
||||||
pushPath = "pushPath"
|
|
||||||
pullPath = "pullPath"
|
|
||||||
)
|
|
||||||
|
|
||||||
d.md.authorizePath = mdata.GetString(md, authorizePath)
|
|
||||||
if !strings.HasPrefix(d.md.authorizePath, "/") {
|
|
||||||
d.md.authorizePath = defaultAuthorizePath
|
|
||||||
}
|
|
||||||
d.md.pushPath = mdata.GetString(md, pushPath)
|
|
||||||
if !strings.HasPrefix(d.md.pushPath, "/") {
|
|
||||||
d.md.pushPath = defaultPushPath
|
|
||||||
}
|
|
||||||
d.md.pullPath = mdata.GetString(md, pullPath)
|
|
||||||
if !strings.HasPrefix(d.md.pullPath, "/") {
|
|
||||||
d.md.pullPath = defaultPullPath
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/xtaci/smux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type muxSession struct {
|
|
||||||
conn net.Conn
|
|
||||||
session *smux.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *muxSession) GetConn() (net.Conn, error) {
|
|
||||||
return session.session.OpenStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *muxSession) Accept() (net.Conn, error) {
|
|
||||||
return session.session.AcceptStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *muxSession) Close() error {
|
|
||||||
if session.session == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return session.session.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *muxSession) IsClosed() bool {
|
|
||||||
if session.session == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return session.session.IsClosed()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (session *muxSession) NumStreams() int {
|
|
||||||
return session.session.NumStreams()
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeTCPConn struct {
|
|
||||||
raddr net.Addr
|
|
||||||
net.PacketConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeTCPConn) Read(b []byte) (n int, err error) {
|
|
||||||
n, _, err = c.ReadFrom(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeTCPConn) Write(b []byte) (n int, err error) {
|
|
||||||
return c.WriteTo(b, c.raddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *fakeTCPConn) RemoteAddr() net.Addr {
|
|
||||||
return c.raddr
|
|
||||||
}
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
kcp_util "github.com/go-gost/gost/pkg/common/util/kcp"
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
"github.com/xtaci/kcp-go/v5"
|
|
||||||
"github.com/xtaci/smux"
|
|
||||||
"github.com/xtaci/tcpraw"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("kcp", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type kcpDialer struct {
|
|
||||||
sessions map[string]*muxSession
|
|
||||||
sessionMutex sync.Mutex
|
|
||||||
logger logger.Logger
|
|
||||||
md metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := &dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &kcpDialer{
|
|
||||||
sessions: make(map[string]*muxSession),
|
|
||||||
logger: options.Logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *kcpDialer) Init(md md.Metadata) (err error) {
|
|
||||||
if err = d.parseMetadata(md); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d.md.config.Init()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiplex implements dialer.Multiplexer interface.
|
|
||||||
func (d *kcpDialer) Multiplex() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *kcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
|
|
||||||
var options dialer.DialOptions
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.sessionMutex.Lock()
|
|
||||||
defer d.sessionMutex.Unlock()
|
|
||||||
|
|
||||||
session, ok := d.sessions[addr]
|
|
||||||
if session != nil && session.IsClosed() {
|
|
||||||
delete(d.sessions, addr) // session is dead
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.md.config.TCP {
|
|
||||||
pc, err := tcpraw.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = &fakeTCPConn{
|
|
||||||
raddr: raddr,
|
|
||||||
PacketConn: pc,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conn, err = net.ListenUDP("udp", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
session = &muxSession{conn: conn}
|
|
||||||
d.sessions[addr] = session
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake implements dialer.Handshaker
|
|
||||||
func (d *kcpDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
|
|
||||||
opts := &dialer.HandshakeOptions{}
|
|
||||||
for _, option := range options {
|
|
||||||
option(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.sessionMutex.Lock()
|
|
||||||
defer d.sessionMutex.Unlock()
|
|
||||||
|
|
||||||
if d.md.handshakeTimeout > 0 {
|
|
||||||
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
|
|
||||||
defer conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
session, ok := d.sessions[opts.Addr]
|
|
||||||
if session != nil && session.conn != conn {
|
|
||||||
conn.Close()
|
|
||||||
return nil, errors.New("kcp: unrecognized connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok || session.session == nil {
|
|
||||||
s, err := d.initSession(ctx, opts.Addr, conn)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
conn.Close()
|
|
||||||
delete(d.sessions, opts.Addr)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
session = s
|
|
||||||
d.sessions[opts.Addr] = session
|
|
||||||
}
|
|
||||||
cc, err := session.GetConn()
|
|
||||||
if err != nil {
|
|
||||||
session.Close()
|
|
||||||
delete(d.sessions, opts.Addr)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *kcpDialer) initSession(ctx context.Context, addr string, conn net.Conn) (*muxSession, error) {
|
|
||||||
pc, ok := conn.(net.PacketConn)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("kcp: wrong connection type")
|
|
||||||
}
|
|
||||||
|
|
||||||
config := d.md.config
|
|
||||||
|
|
||||||
kcpconn, err := kcp.NewConn(addr,
|
|
||||||
kcp_util.BlockCrypt(config.Key, config.Crypt, kcp_util.DefaultSalt),
|
|
||||||
config.DataShard, config.ParityShard, pc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kcpconn.SetStreamMode(true)
|
|
||||||
kcpconn.SetWriteDelay(false)
|
|
||||||
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
|
|
||||||
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
|
|
||||||
kcpconn.SetMtu(config.MTU)
|
|
||||||
kcpconn.SetACKNoDelay(config.AckNodelay)
|
|
||||||
|
|
||||||
if config.DSCP > 0 {
|
|
||||||
if err := kcpconn.SetDSCP(config.DSCP); err != nil {
|
|
||||||
d.logger.Warn("SetDSCP: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil {
|
|
||||||
d.logger.Warn("SetReadBuffer: ", err)
|
|
||||||
}
|
|
||||||
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil {
|
|
||||||
d.logger.Warn("SetWriteBuffer: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stream multiplex
|
|
||||||
smuxConfig := smux.DefaultConfig()
|
|
||||||
smuxConfig.MaxReceiveBuffer = config.SockBuf
|
|
||||||
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
|
|
||||||
var cc net.Conn = kcpconn
|
|
||||||
if !config.NoComp {
|
|
||||||
cc = kcp_util.CompStreamConn(kcpconn)
|
|
||||||
}
|
|
||||||
session, err := smux.Client(cc, smuxConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &muxSession{conn: conn, session: session}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
kcp_util "github.com/go-gost/gost/pkg/common/util/kcp"
|
|
||||||
mdata "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadata struct {
|
|
||||||
handshakeTimeout time.Duration
|
|
||||||
config *kcp_util.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|
||||||
const (
|
|
||||||
config = "config"
|
|
||||||
handshakeTimeout = "handshakeTimeout"
|
|
||||||
)
|
|
||||||
|
|
||||||
if m := mdata.GetStringMap(md, config); len(m) > 0 {
|
|
||||||
b, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cfg := &kcp_util.Config{}
|
|
||||||
if err := json.Unmarshal(b, cfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.md.config = cfg
|
|
||||||
}
|
|
||||||
if d.md.config == nil {
|
|
||||||
d.md.config = kcp_util.DefaultConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type obfsHTTPConn struct {
|
|
||||||
net.Conn
|
|
||||||
host string
|
|
||||||
rbuf bytes.Buffer
|
|
||||||
wbuf bytes.Buffer
|
|
||||||
headerDrained bool
|
|
||||||
handshaked bool
|
|
||||||
handshakeMutex sync.Mutex
|
|
||||||
header http.Header
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *obfsHTTPConn) Handshake() (err error) {
|
|
||||||
c.handshakeMutex.Lock()
|
|
||||||
defer c.handshakeMutex.Unlock()
|
|
||||||
|
|
||||||
if c.handshaked {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.handshake()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.handshaked = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *obfsHTTPConn) handshake() (err error) {
|
|
||||||
r := &http.Request{
|
|
||||||
Method: http.MethodGet,
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
URL: &url.URL{Scheme: "http", Host: c.host},
|
|
||||||
Header: c.header,
|
|
||||||
}
|
|
||||||
if r.Header == nil {
|
|
||||||
r.Header = http.Header{}
|
|
||||||
}
|
|
||||||
r.Header.Set("Connection", "Upgrade")
|
|
||||||
r.Header.Set("Upgrade", "websocket")
|
|
||||||
key, _ := c.generateChallengeKey()
|
|
||||||
r.Header.Set("Sec-WebSocket-Key", key)
|
|
||||||
|
|
||||||
// cache the request header
|
|
||||||
if err = r.Write(&c.wbuf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
dump, _ := httputil.DumpRequest(r, false)
|
|
||||||
c.logger.Debug(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *obfsHTTPConn) Read(b []byte) (n int, err error) {
|
|
||||||
if err = c.Handshake(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.drainHeader(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.rbuf.Len() > 0 {
|
|
||||||
return c.rbuf.Read(b)
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *obfsHTTPConn) drainHeader() (err error) {
|
|
||||||
if c.headerDrained {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.headerDrained = true
|
|
||||||
|
|
||||||
br := bufio.NewReader(c.Conn)
|
|
||||||
// drain and discard the response header
|
|
||||||
var line string
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for {
|
|
||||||
line, err = br.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf.WriteString(line)
|
|
||||||
if line == "\r\n" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.logger.IsLevelEnabled(logger.DebugLevel) {
|
|
||||||
c.logger.Debug(buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// cache the extra data for next read.
|
|
||||||
var b []byte
|
|
||||||
b, err = br.Peek(br.Buffered())
|
|
||||||
if len(b) > 0 {
|
|
||||||
_, err = c.rbuf.Write(b)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *obfsHTTPConn) Write(b []byte) (n int, err error) {
|
|
||||||
if err = c.Handshake(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.wbuf.Len() > 0 {
|
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
|
||||||
_, err = c.wbuf.WriteTo(c.Conn)
|
|
||||||
n = len(b) // exclude the header length
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *obfsHTTPConn) generateChallengeKey() (string, error) {
|
|
||||||
p := make([]byte, 16)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, p); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return base64.StdEncoding.EncodeToString(p), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/go-gost/gost/pkg/dialer"
|
|
||||||
"github.com/go-gost/gost/pkg/logger"
|
|
||||||
md "github.com/go-gost/gost/pkg/metadata"
|
|
||||||
"github.com/go-gost/gost/pkg/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registry.RegisterDialer("ohttp", NewDialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type obfsHTTPDialer struct {
|
|
||||||
md metadata
|
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|
||||||
options := &dialer.Options{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &obfsHTTPDialer{
|
|
||||||
logger: options.Logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *obfsHTTPDialer) Init(md md.Metadata) (err error) {
|
|
||||||
return d.parseMetadata(md)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *obfsHTTPDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
|
|
||||||
var netd net.Dialer
|
|
||||||
conn, err := netd.DialContext(ctx, "tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handshake implements dialer.Handshaker
|
|
||||||
func (d *obfsHTTPDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
|
|
||||||
opts := &dialer.HandshakeOptions{}
|
|
||||||
for _, option := range options {
|
|
||||||
option(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
host := d.md.host
|
|
||||||
if host == "" {
|
|
||||||
host = opts.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
return &obfsHTTPConn{
|
|
||||||
Conn: conn,
|
|
||||||
host: host,
|
|
||||||
header: d.md.header,
|
|
||||||
logger: d.logger,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue