mirror of
https://github.com/nadoo/glider.git
synced 2025-04-29 23:49:58 +08:00
Compare commits
161 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bb439c9345 | ||
![]() |
6aee7b35c0 | ||
![]() |
8e81e09a8f | ||
![]() |
bd40b07388 | ||
![]() |
40809b56a9 | ||
![]() |
2f154678a9 | ||
![]() |
b598c03dab | ||
![]() |
1108ef29a0 | ||
![]() |
708db591e9 | ||
![]() |
0d75bbda7e | ||
![]() |
62f2a85677 | ||
![]() |
7d4075282d | ||
![]() |
c71c95482a | ||
![]() |
4b8b05aa3e | ||
![]() |
6d2b1e95cc | ||
![]() |
80a7d3b7fd | ||
![]() |
7016a3d340 | ||
![]() |
c7d072372b | ||
![]() |
d0e2d9be42 | ||
![]() |
4f12a4f308 | ||
![]() |
6815f866cb | ||
![]() |
7e800555d7 | ||
![]() |
8d0d8881b1 | ||
![]() |
03157367ca | ||
![]() |
d57d35c062 | ||
![]() |
0ef0208615 | ||
![]() |
badb17e921 | ||
![]() |
d1eacebd25 | ||
![]() |
1c0106ce6b | ||
![]() |
1e01d8692d | ||
![]() |
846ca0b699 | ||
![]() |
401efd621a | ||
![]() |
6c88d8bde4 | ||
![]() |
decabcca6a | ||
![]() |
341a309198 | ||
![]() |
e862ba79f1 | ||
![]() |
a153c34e37 | ||
![]() |
730e9c765e | ||
![]() |
ed7ee6bcd4 | ||
![]() |
b0584be6cc | ||
![]() |
7c18fd7d42 | ||
![]() |
3740375569 | ||
![]() |
420ec26368 | ||
![]() |
a98995e2cb | ||
![]() |
d68f361c35 | ||
![]() |
1b972af52c | ||
![]() |
c9c2ce995f | ||
![]() |
fc3a21617e | ||
![]() |
2ad3498abd | ||
![]() |
4b313a3fe1 | ||
![]() |
9f6e5ebb98 | ||
![]() |
c261e5989c | ||
![]() |
1f196c9cf5 | ||
![]() |
f96ad73c7d | ||
![]() |
fa97a44e8d | ||
![]() |
cb698713ee | ||
![]() |
813c5fef94 | ||
![]() |
5cfb20562a | ||
![]() |
cc63a59f1e | ||
![]() |
d05d71e591 | ||
![]() |
f358a1e877 | ||
![]() |
7f925fb711 | ||
![]() |
fdb32370e9 | ||
![]() |
6cfbfff75f | ||
![]() |
f2eb638b91 | ||
![]() |
7e7c7553cc | ||
![]() |
e12642b47a | ||
![]() |
a814f8c545 | ||
![]() |
9b2f00f4c8 | ||
![]() |
fac4b86f60 | ||
![]() |
755a8ca565 | ||
![]() |
b0b043a280 | ||
![]() |
792b244c59 | ||
![]() |
d3fbef02bb | ||
![]() |
773d5d3b9d | ||
![]() |
b21ce3394d | ||
![]() |
faae2a9e22 | ||
![]() |
a919ac3469 | ||
![]() |
2754fdeb60 | ||
![]() |
27de61a59d | ||
![]() |
720f12aa0a | ||
![]() |
cf1a4e3817 | ||
![]() |
253e5008c4 | ||
![]() |
0238b3fcce | ||
![]() |
a8d4e8d7ca | ||
![]() |
1fd59a1677 | ||
![]() |
a529261893 | ||
![]() |
39ae201afe | ||
![]() |
5b1a127d04 | ||
![]() |
40f75ef38b | ||
![]() |
e3f7555032 | ||
![]() |
55ab44fc90 | ||
![]() |
71c7cd2823 | ||
![]() |
826695df9a | ||
![]() |
b502b129b7 | ||
![]() |
3c431ee6e1 | ||
![]() |
2c8a5065fa | ||
![]() |
5cbfcf815f | ||
![]() |
ff09c45fb6 | ||
![]() |
638adc63d6 | ||
![]() |
346db3338e | ||
![]() |
63caac133d | ||
![]() |
ca320d3c7f | ||
![]() |
6006ec13c7 | ||
![]() |
807aebc678 | ||
![]() |
e5031ac8d5 | ||
![]() |
ce85f15c4b | ||
![]() |
83a43b7e0c | ||
![]() |
6bd77a0b2a | ||
![]() |
c86cadb4be | ||
![]() |
5578b19f61 | ||
![]() |
c055c33143 | ||
![]() |
8b29af3c0c | ||
![]() |
7c50915f20 | ||
![]() |
aae2fc8256 | ||
![]() |
926b364400 | ||
![]() |
8729908660 | ||
![]() |
7486373821 | ||
![]() |
32e1c37cfe | ||
![]() |
f3dc252967 | ||
![]() |
a08c939dac | ||
![]() |
f66303b38d | ||
![]() |
b38f8a8761 | ||
![]() |
d615dc087e | ||
![]() |
41861ff48e | ||
![]() |
650f1eed4e | ||
![]() |
affa00a871 | ||
![]() |
9af5ca9baf | ||
![]() |
56277acb7d | ||
![]() |
15f9e74e39 | ||
![]() |
a62674838e | ||
![]() |
879e736f01 | ||
![]() |
53fe94251f | ||
![]() |
7f85f664e3 | ||
![]() |
72e57ab6e3 | ||
![]() |
d92e7f6191 | ||
![]() |
41fee381d0 | ||
![]() |
3092d857ff | ||
![]() |
ceb8b82df8 | ||
![]() |
70cf30b5e6 | ||
![]() |
e9e4515e67 | ||
![]() |
074ca54053 | ||
![]() |
c5fd785cde | ||
![]() |
bcf17ade29 | ||
![]() |
dbd2e04521 | ||
![]() |
34a053b875 | ||
![]() |
ddfcaae49c | ||
![]() |
2a7eed7667 | ||
![]() |
2d16870803 | ||
![]() |
93728c6378 | ||
![]() |
88f09b30bc | ||
![]() |
8116b5994b | ||
![]() |
4fab9d7b4b | ||
![]() |
8bca9cb3e4 | ||
![]() |
fbf694f5cd | ||
![]() |
d2268b623f | ||
![]() |
a310635a9f | ||
![]() |
bf8e37c6df | ||
![]() |
40aadf3e24 | ||
![]() |
068281cafa | ||
![]() |
226dd97d7d |
38
.Dockerfile
Normal file
38
.Dockerfile
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Build Stage
|
||||||
|
FROM --platform=$BUILDPLATFORM alpine AS build-env
|
||||||
|
|
||||||
|
COPY ./dist /dist
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
RUN case $TARGETPLATFORM in \
|
||||||
|
'linux/386') \
|
||||||
|
export FOLDER='default_linux_386_sse2'; \
|
||||||
|
;; \
|
||||||
|
'linux/amd64') \
|
||||||
|
export FOLDER='default_linux_amd64_v1'; \
|
||||||
|
;; \
|
||||||
|
'linux/arm/v6') \
|
||||||
|
export FOLDER='default_linux_arm_6'; \
|
||||||
|
;; \
|
||||||
|
'linux/arm/v7') \
|
||||||
|
export FOLDER='default_linux_arm_7'; \
|
||||||
|
;; \
|
||||||
|
'linux/arm64') \
|
||||||
|
export FOLDER='default_linux_arm64_v8.0'; \
|
||||||
|
;; \
|
||||||
|
'linux/riscv64') \
|
||||||
|
export FOLDER='default_linux_riscv64_rva20u64'; \
|
||||||
|
;; \
|
||||||
|
*) echo >&2 "error: unsupported architecture '$TARGETPLATFORM'"; exit 1 ;; \
|
||||||
|
esac \
|
||||||
|
&& mv /dist/$FOLDER /app
|
||||||
|
|
||||||
|
|
||||||
|
# Final Stage
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build-env /app /app
|
||||||
|
WORKDIR /app
|
||||||
|
USER 1000
|
||||||
|
ENTRYPOINT ["./glider"]
|
118
.github/workflows/build.yml
vendored
118
.github/workflows/build.yml
vendored
@ -1,35 +1,103 @@
|
|||||||
name: Build
|
name: Build
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "dev"
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
APP_NAME: glider
|
||||||
|
DOCKERHUB_REPO: nadoo/glider
|
||||||
|
GHCR_REPO: ghcr.io/nadoo/glider
|
||||||
|
PLATFORMS: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.15.x
|
|
||||||
- name: Go Env
|
|
||||||
run: go env
|
|
||||||
- name: Test
|
|
||||||
run: go test -v .
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [test]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
with:
|
||||||
go-version: 1.15.x
|
fetch-depth: 0
|
||||||
- name: Go Env
|
|
||||||
run: go env
|
- name: Set Vars
|
||||||
|
run: |
|
||||||
|
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
check-latest: true
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v ./...
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v .
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||||
|
with:
|
||||||
|
args: build --snapshot --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
||||||
|
with:
|
||||||
|
name: ${{ env.APP_NAME }}-dev-${{ env.SHA_SHORT }}
|
||||||
|
path: |
|
||||||
|
./dist/default_linux_amd64_v1/${{ env.APP_NAME }}
|
||||||
|
./dist/default_linux_arm64/${{ env.APP_NAME }}
|
||||||
|
./dist/default_darwin_arm64/${{ env.APP_NAME }}
|
||||||
|
./dist/default_windows_amd64_v1/${{ env.APP_NAME }}.exe
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker - Set up Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker - Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker - Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker - Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.DOCKERHUB_REPO }}
|
||||||
|
${{ env.GHCR_REPO }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
|
||||||
|
- name: Docker - Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: .Dockerfile
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
86
.github/workflows/docker.yml
vendored
86
.github/workflows/docker.yml
vendored
@ -1,86 +0,0 @@
|
|||||||
# https://github.com/docker/build-push-action#usage
|
|
||||||
|
|
||||||
name: Docker
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: Publish
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
buildkitd-flags: "--debug"
|
|
||||||
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build dev branch and push
|
|
||||||
if: github.ref == 'refs/heads/dev'
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: 'nadoo/glider:dev,ghcr.io/nadoo/glider:dev'
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,mode=max,dest=/tmp/.buildx-cache
|
|
||||||
|
|
||||||
- name: Get all docker tags
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
id: tags
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const ref = `${context.payload.ref.replace(/\/?refs\/tags\//, '')}`
|
|
||||||
const tags = [
|
|
||||||
'nadoo/glider:latest',
|
|
||||||
`nadoo/glider:${ref}`,
|
|
||||||
'ghcr.io/nadoo/glider:latest',
|
|
||||||
`ghcr.io/nadoo/glider:${ref}`
|
|
||||||
]
|
|
||||||
return tags.join(',')
|
|
||||||
result-encoding: string
|
|
||||||
|
|
||||||
- name: Build release and push
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: ${{steps.tags.outputs.result}}
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,mode=max,dest=/tmp/.buildx-cache
|
|
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release on GitHub
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v1
|
|
||||||
with:
|
|
||||||
go-version: 1.15.x
|
|
||||||
- name: Go Env
|
|
||||||
run: go env
|
|
||||||
- name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: release --rm-dist
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
17
.github/workflows/stale.yml
vendored
Normal file
17
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: 'Close stale issues'
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||||
|
days-before-stale: 90
|
||||||
|
days-before-close: 5
|
||||||
|
exempt-issue-labels: "bug,enhancement"
|
||||||
|
exempt-pr-labels: "bug,enhancement"
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,11 +17,14 @@
|
|||||||
# custom
|
# custom
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.zed
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# dev test only
|
# dev test only
|
||||||
/dev/
|
/dev/
|
||||||
dev*.go
|
dev*.go
|
||||||
|
|
||||||
|
*_test.go
|
||||||
|
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
@ -1,23 +1,9 @@
|
|||||||
# Make sure to check the documentation at http://goreleaser.com
|
version: 2
|
||||||
|
|
||||||
# release:
|
|
||||||
# git tag -a v0.1.0 -m "v0.1.0"
|
|
||||||
# git push origin v0.1.0
|
|
||||||
# goreleaser release --skip-publish --rm-dist
|
|
||||||
|
|
||||||
# #git tag -d v0.1.0
|
|
||||||
# #git push origin --delete tag v0.1.0
|
|
||||||
|
|
||||||
# snapshot:
|
|
||||||
# goreleaser --snapshot --rm-dist
|
|
||||||
|
|
||||||
# https://goreleaser.com/customization/
|
|
||||||
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
|
|
||||||
# https://goreleaser.com/customization/build/
|
|
||||||
builds:
|
builds:
|
||||||
- id: default
|
- id: default
|
||||||
env:
|
env:
|
||||||
@ -26,6 +12,7 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
|
- freebsd
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- 386
|
||||||
- amd64
|
- amd64
|
||||||
@ -35,6 +22,10 @@ builds:
|
|||||||
- mipsle
|
- mipsle
|
||||||
- mips64
|
- mips64
|
||||||
- mips64le
|
- mips64le
|
||||||
|
- riscv64
|
||||||
|
goamd64:
|
||||||
|
- v1
|
||||||
|
- v3
|
||||||
goarm:
|
goarm:
|
||||||
- 6
|
- 6
|
||||||
- 7
|
- 7
|
||||||
@ -42,37 +33,64 @@ builds:
|
|||||||
- hardfloat
|
- hardfloat
|
||||||
- softfloat
|
- softfloat
|
||||||
|
|
||||||
ignore:
|
|
||||||
- goos: darwin
|
|
||||||
goarch: 386
|
|
||||||
|
|
||||||
# https://goreleaser.com/customization/archive/
|
|
||||||
archives:
|
archives:
|
||||||
- id: default
|
- id: default
|
||||||
builds:
|
builds:
|
||||||
- default
|
- default
|
||||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
|
||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
format: tar.gz
|
formats: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats: zip
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
- README.md
|
- README.md
|
||||||
- config/**/*
|
- config/**/*
|
||||||
- systemd/*
|
- systemd/*
|
||||||
|
|
||||||
# https://goreleaser.com/customization/snapshots/
|
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "dev@{{.ShortCommit}}"
|
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
||||||
|
|
||||||
# https://goreleaser.com/customization/checksum/
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||||
|
|
||||||
# https://goreleaser.com/customization/release/
|
|
||||||
release:
|
release:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
- id: glider
|
||||||
|
package_name: glider
|
||||||
|
vendor: nadoo
|
||||||
|
homepage: https://github.com/nadoo/glider
|
||||||
|
maintainer: nadoo
|
||||||
|
description: Glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
||||||
|
license: GPL-3.0 License
|
||||||
|
formats:
|
||||||
|
# - apk
|
||||||
|
- deb
|
||||||
|
# - rpm
|
||||||
|
dependencies:
|
||||||
|
- libsystemd0
|
||||||
|
bindir: /usr/bin
|
||||||
|
release: 1
|
||||||
|
epoch: 1
|
||||||
|
version_metadata: git
|
||||||
|
section: default
|
||||||
|
priority: extra
|
||||||
|
contents:
|
||||||
|
- src: systemd/glider@.service
|
||||||
|
dst: /etc/systemd/system/glider@.service
|
||||||
|
|
||||||
|
- src: config/glider.conf.example
|
||||||
|
dst: /etc/glider/glider.conf.example
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
postinstall: "systemd/postinstall.sh"
|
||||||
|
preremove: "systemd/preremove.sh"
|
||||||
|
postremove: "systemd/postremove.sh"
|
||||||
|
|
||||||
|
deb:
|
||||||
|
triggers:
|
||||||
|
interest_noawait:
|
||||||
|
- /lib/systemd/systemd
|
||||||
|
15
Dockerfile
15
Dockerfile
@ -1,11 +1,14 @@
|
|||||||
# build stage
|
# Build Stage
|
||||||
FROM golang:alpine AS build-env
|
FROM golang:1.24-alpine AS build-env
|
||||||
RUN apk --no-cache add build-base git gcc
|
|
||||||
ADD . /src
|
ADD . /src
|
||||||
RUN cd /src && go build -v -i -ldflags "-s -w"
|
RUN apk --no-cache add git \
|
||||||
|
&& cd /src && go build -v -ldflags "-s -w"
|
||||||
|
|
||||||
# final stage
|
# Final Stage
|
||||||
FROM alpine
|
FROM alpine
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=build-env /src/glider /app/
|
COPY --from=build-env /src/glider /app/
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk -U upgrade --no-cache \
|
||||||
|
&& apk --no-cache add ca-certificates
|
||||||
|
USER 1000
|
||||||
ENTRYPOINT ["./glider"]
|
ENTRYPOINT ["./glider"]
|
||||||
|
538
README.md
538
README.md
@ -1,8 +1,10 @@
|
|||||||
# [glider](https://github.com/nadoo/glider)
|
# [glider](https://github.com/nadoo/glider)
|
||||||
|
|
||||||
|
[](https://go.dev/dl/)
|
||||||
[](https://goreportcard.com/report/github.com/nadoo/glider)
|
[](https://goreportcard.com/report/github.com/nadoo/glider)
|
||||||
[](https://github.com/nadoo/glider/releases)
|
[](https://github.com/nadoo/glider/releases)
|
||||||
[](https://github.com/nadoo/glider/actions)
|
[](https://github.com/nadoo/glider/actions)
|
||||||
|
[](https://hub.docker.com/r/nadoo/glider)
|
||||||
|
|
||||||
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features(like dnsmasq).
|
||||||
|
|
||||||
@ -37,266 +39,342 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
|||||||
- Periodical availability checking for forwarders
|
- Periodical availability checking for forwarders
|
||||||
- Send requests from specific local ip/interface
|
- Send requests from specific local ip/interface
|
||||||
- Services:
|
- Services:
|
||||||
- dhcpd: a simple dhcp server that can detect existing dhcp server and avoid conflicts
|
- dhcpd: a simple dhcp server that can run in failover mode
|
||||||
|
|
||||||
## Protocols
|
## Protocols
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>click to see details</summary>
|
<summary>click to see details</summary>
|
||||||
|
|
||||||
|Protocol | Listen/TCP | Listen/UDP | Forward/TCP | Forward/UDP | Description
|
|Protocol | Listen/TCP | Listen/UDP | Forward/TCP | Forward/UDP | Description
|
||||||
|:-: |:-:|:-:|:-:|:-:|:-
|
|:-: |:-:|:-:|:-:|:-:|:-
|
||||||
|Mixed |√|√| | |http+socks5 server
|
|Mixed |√|√| | |http+socks5 server
|
||||||
|HTTP |√| |√| |client & server
|
|HTTP |√| |√| |client & server
|
||||||
|SOCKS5 |√|√|√|√|client & server
|
|SOCKS5 |√|√|√|√|client & server
|
||||||
|SS |√|√|√|√|client & server
|
|SS |√|√|√|√|client & server
|
||||||
|Trojan |√|√|√|√|client & server
|
|Trojan |√|√|√|√|client & server
|
||||||
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
||||||
|VLESS |√|√|√|√|client & server
|
|VLESS |√|√|√|√|client & server
|
||||||
|VMess | | |√|√|client only
|
|VMess | | |√|√|client only
|
||||||
|SSR | | |√| |client only
|
|SSR | | |√| |client only
|
||||||
|SSH | | |√| |client only
|
|SSH | | |√| |client only
|
||||||
|SOCKS4 | | |√| |client only
|
|SOCKS4 | | |√| |client only
|
||||||
|TCP |√| |√| |tcp tunnel client & server
|
|SOCKS4A | | |√| |client only
|
||||||
|UDP | |√| |√|udp tunnel client & server
|
|TCP |√| |√| |tcp tunnel client & server
|
||||||
|TLS |√| |√| |transport client & server
|
|UDP | |√| |√|udp tunnel client & server
|
||||||
|KCP | |√|√| |transport client & server
|
|TLS |√| |√| |transport client & server
|
||||||
|Unix |√|√|√|√|transport client & server
|
|KCP | |√|√| |transport client & server
|
||||||
|Websocket |√| |√| |transport client & server
|
|Unix |√|√|√|√|transport client & server
|
||||||
|Simple-Obfs | | |√| |transport client only
|
|VSOCK |√| |√| |transport client & server
|
||||||
|Redir |√| | | |linux only
|
|Smux |√| |√| |transport client & server
|
||||||
|Redir6 |√| | | |linux only(ipv6)
|
|Websocket(WS) |√| |√| |transport client & server
|
||||||
|Reject | | |√|√|reject all requests
|
|WS Secure |√| |√| |websocket secure (wss)
|
||||||
|
|Proxy Protocol |√| | | |version 1 server only
|
||||||
|
|Simple-Obfs | | |√| |transport client only
|
||||||
|
|Redir |√| | | |linux redirect proxy
|
||||||
|
|Redir6 |√| | | |linux redirect proxy(ipv6)
|
||||||
|
|TProxy | |√| | |linux tproxy(udp only)
|
||||||
|
|Reject | | |√|√|reject all requests
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Download:
|
- Binary: [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
||||||
- [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
- Docker: `docker pull nadoo/glider`
|
||||||
|
- Manjaro: `pamac install glider`
|
||||||
Docker:
|
- ArchLinux: `sudo pacman -S glider`
|
||||||
```bash
|
- Homebrew: `brew install glider`
|
||||||
docker pull nadoo/glider
|
- MacPorts: `sudo port install glider`
|
||||||
#docker pull ghcr.io/nadoo/glider
|
- Source: `go install github.com/nadoo/glider@latest`
|
||||||
```
|
|
||||||
|
|
||||||
ArchLinux:
|
|
||||||
```bash
|
|
||||||
sudo pacman -S glider
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
#### Run
|
||||||
glider -h
|
|
||||||
```
|
|
||||||
<details>
|
|
||||||
<summary>click to see details</summary>
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
glider 0.13.0 usage:
|
glider -verbose -listen :8443
|
||||||
|
# docker run --rm -it nadoo/glider -verbose -listen :8443
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Help
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><code>glider -help</code></summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Usage: glider [-listen URL]... [-forward URL]... [OPTION]...
|
||||||
|
|
||||||
|
e.g. glider -config /etc/glider/glider.conf
|
||||||
|
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
|
||||||
|
|
||||||
|
OPTION:
|
||||||
-check string
|
-check string
|
||||||
check=tcp[://HOST:PORT]: tcp port connect check
|
check=tcp[://HOST:PORT]: tcp port connect check
|
||||||
check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||||
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||||
check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
|
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
|
||||||
|
check=disable: disable health check (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
|
||||||
-checkdisabledonly
|
-checkdisabledonly
|
||||||
check disabled fowarders only
|
check disabled fowarders only
|
||||||
-checkinterval int
|
-checkinterval int
|
||||||
fowarder check interval(seconds) (default 30)
|
fowarder check interval(seconds) (default 30)
|
||||||
|
-checklatencysamples int
|
||||||
|
use the average latency of the latest N checks (default 10)
|
||||||
-checktimeout int
|
-checktimeout int
|
||||||
fowarder check timeout(seconds) (default 10)
|
fowarder check timeout(seconds) (default 10)
|
||||||
-checktolerance int
|
-checktolerance int
|
||||||
fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode
|
fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode
|
||||||
-config string
|
-config string
|
||||||
config file path
|
config file path
|
||||||
-dialtimeout int
|
-dialtimeout int
|
||||||
dial timeout(seconds) (default 3)
|
dial timeout(seconds) (default 3)
|
||||||
-dns string
|
-dns string
|
||||||
local dns server listen address
|
local dns server listen address
|
||||||
-dnsalwaystcp
|
-dnsalwaystcp
|
||||||
always use tcp to query upstream dns servers no matter there is a forwarder or not
|
always use tcp to query upstream dns servers no matter there is a forwarder or not
|
||||||
|
-dnscachelog
|
||||||
|
show query log of dns cache
|
||||||
-dnscachesize int
|
-dnscachesize int
|
||||||
size of CACHE (default 4096)
|
max number of dns response in CACHE (default 4096)
|
||||||
-dnsmaxttl int
|
-dnsmaxttl int
|
||||||
maximum TTL value for entries in the CACHE(seconds) (default 1800)
|
maximum TTL value for entries in the CACHE(seconds) (default 1800)
|
||||||
-dnsminttl int
|
-dnsminttl int
|
||||||
minimum TTL value for entries in the CACHE(seconds)
|
minimum TTL value for entries in the CACHE(seconds)
|
||||||
|
-dnsnoaaaa
|
||||||
|
disable AAAA query
|
||||||
-dnsrecord value
|
-dnsrecord value
|
||||||
custom dns record, format: domain/ip
|
custom dns record, format: domain/ip
|
||||||
-dnsserver value
|
-dnsserver value
|
||||||
remote dns server address
|
remote dns server address
|
||||||
-dnstimeout int
|
-dnstimeout int
|
||||||
timeout value used in multiple dnsservers switch(seconds) (default 3)
|
timeout value used in multiple dnsservers switch(seconds) (default 3)
|
||||||
|
-example
|
||||||
|
show usage examples
|
||||||
-forward value
|
-forward value
|
||||||
forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]
|
forward url, see the URL section below
|
||||||
-include value
|
-include value
|
||||||
include file
|
include file
|
||||||
-interface string
|
-interface string
|
||||||
source ip or source interface
|
source ip or source interface
|
||||||
-listen value
|
-listen value
|
||||||
listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS
|
listen url, see the URL section below
|
||||||
|
-logflags int
|
||||||
|
do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants (default 19)
|
||||||
-maxfailures int
|
-maxfailures int
|
||||||
max failures to change forwarder status to disabled (default 3)
|
max failures to change forwarder status to disabled (default 3)
|
||||||
-relaytimeout int
|
-relaytimeout int
|
||||||
relay timeout(seconds)
|
relay timeout(seconds)
|
||||||
-rulefile value
|
-rulefile value
|
||||||
rule file path
|
rule file path
|
||||||
-rules-dir string
|
-rules-dir string
|
||||||
rule file folder
|
rule file folder
|
||||||
|
-scheme string
|
||||||
|
show help message of proxy scheme, use 'all' to see all schemes
|
||||||
-service value
|
-service value
|
||||||
run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]
|
run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]
|
||||||
-strategy string
|
-strategy string
|
||||||
forward strategy, default: rr (default "rr")
|
rr: Round Robin mode
|
||||||
|
ha: High Availability mode
|
||||||
|
lha: Latency based High Availability mode
|
||||||
|
dh: Destination Hashing mode (default "rr")
|
||||||
|
-tcpbufsize int
|
||||||
|
tcp buffer size in Bytes (default 32768)
|
||||||
|
-udpbufsize int
|
||||||
|
udp buffer size in Bytes (default 2048)
|
||||||
-verbose
|
-verbose
|
||||||
verbose mode
|
verbose mode
|
||||||
|
|
||||||
|
URL:
|
||||||
|
proxy: SCHEME://[USER:PASS@][HOST]:PORT
|
||||||
|
chain: proxy,proxy[,proxy]...
|
||||||
|
|
||||||
|
e.g. -listen socks5://:1080
|
||||||
|
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
|
||||||
|
|
||||||
|
e.g. -forward socks5://server:1080
|
||||||
|
-forward tls://server.com:443,http:// (protocol chain)
|
||||||
|
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
|
||||||
|
|
||||||
|
SCHEME:
|
||||||
|
listen : http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
||||||
|
forward: direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss
|
||||||
|
|
||||||
|
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
||||||
|
|
||||||
|
--
|
||||||
|
Forwarder Options: FORWARD_URL#OPTIONS
|
||||||
|
priority : the priority of that forwarder, the larger the higher, default: 0
|
||||||
|
interface: the local interface or ip address used to connect remote server.
|
||||||
|
|
||||||
|
e.g. -forward socks5://server:1080#priority=100
|
||||||
|
-forward socks5://server:1080#interface=eth0
|
||||||
|
-forward socks5://server:1080#priority=100&interface=192.168.1.99
|
||||||
|
|
||||||
|
Services:
|
||||||
|
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
|
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
|
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
|
||||||
|
|
||||||
|
--
|
||||||
|
Help:
|
||||||
|
glider -help
|
||||||
|
glider -scheme all
|
||||||
|
glider -example
|
||||||
|
|
||||||
|
see README.md and glider.conf.example for more details.
|
||||||
|
--
|
||||||
|
glider 0.16.4, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
run:
|
|
||||||
```bash
|
|
||||||
glider -config CONFIGPATH
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
glider -verbose -listen :8443 -forward SCHEME://HOST:PORT
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Schemes
|
#### Schemes
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>click to see details</summary>
|
<summary><code>glider -scheme all</code></summary>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Available schemes:
|
Direct scheme:
|
||||||
listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix kcp
|
direct://
|
||||||
forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix kcp simple-obfs
|
|
||||||
|
|
||||||
|
Only needed when you want to specify the outgoing interface:
|
||||||
|
glider -verbose -listen :8443 -forward direct://#interface=eth0
|
||||||
|
|
||||||
|
Or load balance multiple interfaces directly:
|
||||||
|
glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
|
||||||
|
|
||||||
|
Or you can use the high availability mode:
|
||||||
|
glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha
|
||||||
|
|
||||||
|
--
|
||||||
|
Http scheme:
|
||||||
|
http://[user:pass@]host:port
|
||||||
|
|
||||||
|
--
|
||||||
|
KCP scheme:
|
||||||
|
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
|
||||||
|
|
||||||
|
Available crypt types for KCP:
|
||||||
|
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
||||||
|
|
||||||
|
Available modes for KCP:
|
||||||
|
fast, fast2, fast3, normal, default: fast
|
||||||
|
|
||||||
|
--
|
||||||
|
Simple-Obfs scheme:
|
||||||
|
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
||||||
|
|
||||||
|
Available types for simple-obfs:
|
||||||
|
http, tls
|
||||||
|
|
||||||
|
--
|
||||||
|
Reject scheme:
|
||||||
|
reject://
|
||||||
|
|
||||||
|
--
|
||||||
|
Smux scheme:
|
||||||
|
smux://host:port
|
||||||
|
|
||||||
|
--
|
||||||
|
Socks4 scheme:
|
||||||
|
socks4://host:port
|
||||||
|
|
||||||
|
--
|
||||||
Socks5 scheme:
|
Socks5 scheme:
|
||||||
socks://[user:pass@]host:port
|
socks5://[user:pass@]host:port
|
||||||
|
|
||||||
|
--
|
||||||
SS scheme:
|
SS scheme:
|
||||||
ss://method:pass@host:port
|
ss://method:pass@host:port
|
||||||
|
|
||||||
|
Available methods for ss:
|
||||||
|
AEAD Ciphers:
|
||||||
|
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
||||||
|
Stream Ciphers:
|
||||||
|
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
|
||||||
|
Alias:
|
||||||
|
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
||||||
|
Plain: NONE
|
||||||
|
|
||||||
Available methods for ss:
|
--
|
||||||
AEAD Ciphers:
|
SSH scheme:
|
||||||
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
||||||
Stream Ciphers:
|
timeout: timeout of ssh handshake and channel operation, default: 5
|
||||||
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
|
|
||||||
Alias:
|
|
||||||
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
|
||||||
Plain: NONE
|
|
||||||
|
|
||||||
|
--
|
||||||
SSR scheme:
|
SSR scheme:
|
||||||
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||||
|
|
||||||
SSH scheme:
|
--
|
||||||
ssh://user[:pass]@host:port[?key=keypath]
|
|
||||||
|
|
||||||
VMess scheme:
|
|
||||||
vmess://[security:]uuid@host:port?alterID=num
|
|
||||||
|
|
||||||
VLESS scheme:
|
|
||||||
vless://uuid@host:port[?fallback=127.0.0.1:80]
|
|
||||||
|
|
||||||
Trojan client scheme:
|
|
||||||
trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true]
|
|
||||||
trojanc://pass@host:port (cleartext, without TLS)
|
|
||||||
|
|
||||||
Trojan server scheme:
|
|
||||||
trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]
|
|
||||||
trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)
|
|
||||||
|
|
||||||
Available securities for vmess:
|
|
||||||
none, aes-128-gcm, chacha20-poly1305
|
|
||||||
|
|
||||||
TLS client scheme:
|
TLS client scheme:
|
||||||
tls://host:port[?serverName=SERVERNAME][&skipVerify=true]
|
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]
|
||||||
|
|
||||||
Proxy over tls client:
|
Proxy over tls client:
|
||||||
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://
|
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://
|
||||||
tls://host:port[?skipVerify=true],http://[user:pass@]
|
tls://host:port[?skipVerify=true],http://[user:pass@]
|
||||||
tls://host:port[?skipVerify=true],socks5://[user:pass@]
|
tls://host:port[?skipVerify=true],socks5://[user:pass@]
|
||||||
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
|
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
|
||||||
|
|
||||||
TLS server scheme:
|
TLS server scheme:
|
||||||
tls://host:port?cert=PATH&key=PATH
|
tls://host:port?cert=PATH&key=PATH[&alpn=proto1][&alpn=proto2]
|
||||||
|
|
||||||
Proxy over tls server:
|
Proxy over tls server:
|
||||||
tls://host:port?cert=PATH&key=PATH,scheme://
|
tls://host:port?cert=PATH&key=PATH,scheme://
|
||||||
tls://host:port?cert=PATH&key=PATH,http://
|
tls://host:port?cert=PATH&key=PATH,http://
|
||||||
tls://host:port?cert=PATH&key=PATH,socks5://
|
tls://host:port?cert=PATH&key=PATH,socks5://
|
||||||
tls://host:port?cert=PATH&key=PATH,ss://method:pass@
|
tls://host:port?cert=PATH&key=PATH,ss://method:pass@
|
||||||
|
|
||||||
|
--
|
||||||
|
Trojan client scheme:
|
||||||
|
trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH]
|
||||||
|
trojanc://pass@host:port (cleartext, without TLS)
|
||||||
|
|
||||||
|
Trojan server scheme:
|
||||||
|
trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]
|
||||||
|
trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)
|
||||||
|
|
||||||
|
--
|
||||||
|
Unix domain socket scheme:
|
||||||
|
unix://path
|
||||||
|
|
||||||
|
--
|
||||||
|
VLESS scheme:
|
||||||
|
vless://uuid@host:port[?fallback=127.0.0.1:80]
|
||||||
|
|
||||||
|
--
|
||||||
|
VMess scheme:
|
||||||
|
vmess://[security:]uuid@host:port[?alterID=num]
|
||||||
|
if alterID=0 or not set, VMessAEAD will be enabled
|
||||||
|
|
||||||
|
Available security for vmess:
|
||||||
|
zero, none, aes-128-gcm, chacha20-poly1305
|
||||||
|
|
||||||
|
--
|
||||||
Websocket client scheme:
|
Websocket client scheme:
|
||||||
ws://host:port[/path][?host=HOST][&origin=ORIGIN]
|
ws://host:port[/path][?host=HOST][&origin=ORIGIN]
|
||||||
|
wss://host:port[/path][?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&host=HOST][&origin=ORIGIN]
|
||||||
|
|
||||||
Websocket server scheme:
|
Websocket server scheme:
|
||||||
ws://:port[/path][?host=HOST]
|
ws://:port[/path][?host=HOST]
|
||||||
|
wss://:port[/path]?cert=PATH&key=PATH[?host=HOST]
|
||||||
|
|
||||||
Websocket with a specified proxy protocol:
|
Websocket with a specified proxy protocol:
|
||||||
ws://host:port[/path][?host=HOST],scheme://
|
ws://host:port[/path][?host=HOST],scheme://
|
||||||
ws://host:port[/path][?host=HOST],http://[user:pass@]
|
ws://host:port[/path][?host=HOST],http://[user:pass@]
|
||||||
ws://host:port[/path][?host=HOST],socks5://[user:pass@]
|
ws://host:port[/path][?host=HOST],socks5://[user:pass@]
|
||||||
ws://host:port[/path][?host=HOST],vmess://[security:]uuid@?alterID=num
|
|
||||||
|
|
||||||
TLS and Websocket with a specified proxy protocol:
|
TLS and Websocket with a specified proxy protocol:
|
||||||
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme://
|
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme://
|
||||||
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@]
|
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@]
|
||||||
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@]
|
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@]
|
||||||
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num
|
tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num
|
||||||
|
|
||||||
Unix domain socket scheme:
|
--
|
||||||
unix://path
|
VM socket scheme(linux only):
|
||||||
|
vsock://[CID]:port
|
||||||
|
|
||||||
KCP scheme:
|
if you want to listen on any address, just set CID to 4294967295.
|
||||||
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
|
|
||||||
|
|
||||||
Available crypt types for KCP:
|
|
||||||
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
|
||||||
|
|
||||||
Available modes for KCP:
|
|
||||||
fast, fast2, fast3, normal, default: fast
|
|
||||||
|
|
||||||
Simple-Obfs scheme:
|
|
||||||
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
|
||||||
|
|
||||||
Available types for simple-obfs:
|
|
||||||
http, tls
|
|
||||||
|
|
||||||
DNS forwarding server:
|
|
||||||
dns=:53
|
|
||||||
dnsserver=8.8.8.8:53
|
|
||||||
dnsserver=1.1.1.1:53
|
|
||||||
dnsrecord=www.example.com/1.2.3.4
|
|
||||||
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
|
||||||
|
|
||||||
Available forward strategies:
|
|
||||||
rr: Round Robin mode
|
|
||||||
ha: High Availability mode
|
|
||||||
lha: Latency based High Availability mode
|
|
||||||
dh: Destination Hashing mode
|
|
||||||
|
|
||||||
Forwarder option scheme: FORWARD_URL#OPTIONS
|
|
||||||
priority: set the priority of that forwarder, default:0
|
|
||||||
interface: set local interface or ip address used to connect remote server
|
|
||||||
-
|
|
||||||
Examples:
|
|
||||||
socks5://1.1.1.1:1080#priority=100
|
|
||||||
vmess://[security:]uuid@host:port?alterID=num#priority=200
|
|
||||||
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99
|
|
||||||
vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0
|
|
||||||
|
|
||||||
Services:
|
|
||||||
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP
|
|
||||||
e.g.,service=dhcpd,eth1,192.168.50.100,192.168.50.199
|
|
||||||
|
|
||||||
Config file format(see `./glider.conf.example` as an example):
|
|
||||||
# COMMENT LINE
|
|
||||||
KEY=VALUE
|
|
||||||
KEY=VALUE
|
|
||||||
# KEY equals to command line flag name: listen forward strategy...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -304,47 +382,50 @@ Config file format(see `./glider.conf.example` as an example):
|
|||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>click to see details</summary>
|
<summary><code>glider -example</code></summary>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./glider -config glider.conf
|
Examples:
|
||||||
|
glider -config glider.conf
|
||||||
-run glider with specified config file.
|
-run glider with specified config file.
|
||||||
|
|
||||||
./glider -listen :8443 -verbose
|
glider -listen :8443 -verbose
|
||||||
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
||||||
|
|
||||||
./glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose
|
glider -listen socks5://:1080 -listen http://:8080 -verbose
|
||||||
-listen on 0.0.0.0:8443 as a ss server.
|
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server.
|
||||||
|
|
||||||
./glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1
|
||||||
-listen on :443 as a https(http over tls) proxy server.
|
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode.
|
||||||
|
|
||||||
./glider -listen http://:8080 -forward socks5://127.0.0.1:1080
|
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
||||||
-listen on :8080 as a http proxy server, forward all requests via socks5 server.
|
-protocol chain: listen on :443 as a https(http over tls) proxy server.
|
||||||
|
|
||||||
./glider -listen socks5://:1080 -forward "tls://abc.com:443,vmess://security:uuid@?alterID=10"
|
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080
|
||||||
-listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server.
|
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain.
|
||||||
|
|
||||||
./glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr
|
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10
|
||||||
-listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.
|
-forwarder priority: serverA will only be used when serverB and serverC are not available.
|
||||||
|
|
||||||
./glider -listen tcp://:80 -forward tcp://2.2.2.2:80
|
glider -listen tcp://:80 -forward tcp://serverA:80
|
||||||
-tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80.
|
-tcp tunnel: listen on :80 and forward all requests to serverA:80.
|
||||||
|
|
||||||
./glider -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53
|
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53
|
||||||
-listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.
|
-udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
|
||||||
|
|
||||||
./glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443
|
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
||||||
-listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.
|
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
|
||||||
|
|
||||||
./glider -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4
|
|
||||||
-listen on :53 as dns server, forward to 8.8.8.8:53 via ss server.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
glider -config CONFIG_PATH
|
||||||
|
```
|
||||||
|
|
||||||
- [ConfigFile](config)
|
- [ConfigFile](config)
|
||||||
- [glider.conf.example](config/glider.conf.example)
|
- [glider.conf.example](config/glider.conf.example)
|
||||||
- [office.rule.example](config/rules.d/office.rule.example)
|
- [office.rule.example](config/rules.d/office.rule.example)
|
||||||
@ -354,13 +435,40 @@ Config file format(see `./glider.conf.example` as an example):
|
|||||||
|
|
||||||
## Service
|
## Service
|
||||||
|
|
||||||
- dhcpd:
|
- dhcpd / dhcpd-failover:
|
||||||
- service=dhcpd,INTERFACE,START_IP,END_IP
|
- service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
- e.g., service=dhcpd,eth1,192.168.50.100,192.168.50.199
|
- service=dhcpd,eth1,192.168.1.100,192.168.1.199,720,fc:23:34:9e:25:01=192.168.1.101
|
||||||
|
- service=dhcpd-failover,eth2,192.168.2.100,192.168.2.199,720
|
||||||
|
- note: `dhcpd-failover` only serves requests when there's no other dhcp server exists in lan
|
||||||
|
- detect interval: 1min
|
||||||
|
|
||||||
## Linux Service
|
## Linux Daemon
|
||||||
|
|
||||||
|
- systemd: [https://github.com/nadoo/glider/tree/main/systemd](https://github.com/nadoo/glider/tree/main/systemd)
|
||||||
|
|
||||||
|
- <details> <summary>docker: click to see details</summary>
|
||||||
|
|
||||||
|
- run glider (config file path: /etc/glider/glider.conf)
|
||||||
|
```
|
||||||
|
docker run -d --name glider --net host --restart=always \
|
||||||
|
-v /etc/glider:/etc/glider \
|
||||||
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
|
nadoo/glider -config=/etc/glider/glider.conf
|
||||||
|
```
|
||||||
|
- run watchtower if you need auto update
|
||||||
|
```
|
||||||
|
docker run -d --name watchtower --restart=always \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
containrrr/watchtower --interval 21600 --cleanup \
|
||||||
|
glider
|
||||||
|
```
|
||||||
|
- open udp ports if you need udp nat fullcone
|
||||||
|
```
|
||||||
|
iptables -I INPUT -p udp -m udp --dport 1024:65535 -j ACCEPT
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
- systemd: [https://github.com/nadoo/glider/blob/master/systemd/](https://github.com/nadoo/glider/blob/master/systemd/)
|
|
||||||
|
|
||||||
## Customize Build
|
## Customize Build
|
||||||
|
|
||||||
@ -369,7 +477,7 @@ Config file format(see `./glider.conf.example` as an example):
|
|||||||
|
|
||||||
1. Clone the source code:
|
1. Clone the source code:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/nadoo/glider
|
git clone https://github.com/nadoo/glider && cd glider
|
||||||
```
|
```
|
||||||
2. Customize features:
|
2. Customize features:
|
||||||
|
|
||||||
@ -378,12 +486,12 @@ Config file format(see `./glider.conf.example` as an example):
|
|||||||
// _ "github.com/nadoo/glider/proxy/kcp"
|
// _ "github.com/nadoo/glider/proxy/kcp"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Build it(requires **Go 1.15+** )
|
3. Build it:
|
||||||
```bash
|
```bash
|
||||||
cd glider && go build -v -i -ldflags "-s -w"
|
go build -v -ldflags "-s -w"
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Proxy & Protocol Chains
|
## Proxy & Protocol Chains
|
||||||
<details><summary>In glider, you can easily chain several proxy servers or protocols together (click to see details)</summary>
|
<details><summary>In glider, you can easily chain several proxy servers or protocols together (click to see details)</summary>
|
||||||
@ -418,11 +526,17 @@ Config file format(see `./glider.conf.example` as an example):
|
|||||||
listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Chain protocols in listener: http over smux over websocket proxy server
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
listen=ws://:10000,smux://,http://
|
||||||
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go.
|
- [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go.
|
||||||
- [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support.
|
- [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support.
|
||||||
- [ArchLinux](https://www.archlinux.org/packages/community/x86_64/glider): a great linux distribution with glider pre-built package.
|
- [ArchLinux](https://archlinux.org/packages/extra/x86_64/glider): a great linux distribution with glider pre-built package.
|
||||||
- [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40`
|
- [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40`
|
||||||
|
358
config.go
358
config.go
@ -8,7 +8,8 @@ import (
|
|||||||
"github.com/nadoo/conflag"
|
"github.com/nadoo/conflag"
|
||||||
|
|
||||||
"github.com/nadoo/glider/dns"
|
"github.com/nadoo/glider/dns"
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/rule"
|
"github.com/nadoo/glider/rule"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +17,10 @@ var flag = conflag.New()
|
|||||||
|
|
||||||
// Config is global config struct.
|
// Config is global config struct.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Verbose bool
|
Verbose bool
|
||||||
|
LogFlags int
|
||||||
|
TCPBufSize int
|
||||||
|
UDPBufSize int
|
||||||
|
|
||||||
Listens []string
|
Listens []string
|
||||||
|
|
||||||
@ -39,15 +43,30 @@ func parseConfig() *Config {
|
|||||||
|
|
||||||
flag.SetOutput(os.Stdout)
|
flag.SetOutput(os.Stdout)
|
||||||
|
|
||||||
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
scheme := flag.String("scheme", "", "show help message of proxy scheme, use 'all' to see all schemes")
|
||||||
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
|
example := flag.Bool("example", false, "show usage examples")
|
||||||
|
|
||||||
flag.StringSliceUniqVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||||
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
flag.IntVar(&conf.LogFlags, "logflags", 19, "do not change it if you do not know what it is, ref: https://pkg.go.dev/log#pkg-constants")
|
||||||
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]\ncheck=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR\ncheck=disable: disable health check")
|
flag.IntVar(&conf.TCPBufSize, "tcpbufsize", 32768, "tcp buffer size in Bytes")
|
||||||
|
flag.IntVar(&conf.UDPBufSize, "udpbufsize", 2048, "udp buffer size in Bytes")
|
||||||
|
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, see the URL section below")
|
||||||
|
|
||||||
|
flag.StringSliceVar(&conf.Forwards, "forward", nil, "forward url, see the URL section below")
|
||||||
|
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", `rr: Round Robin mode
|
||||||
|
ha: High Availability mode
|
||||||
|
lha: Latency based High Availability mode
|
||||||
|
dh: Destination Hashing mode`)
|
||||||
|
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200",
|
||||||
|
`check=tcp[://HOST:PORT]: tcp port connect check
|
||||||
|
check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||||
|
check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||||
|
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, env vars: FORWARDER_ADDR,FORWARDER_URL
|
||||||
|
check=disable: disable health check`)
|
||||||
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
||||||
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
||||||
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
||||||
|
flag.IntVar(&conf.Strategy.CheckLatencySamples, "checklatencysamples", 10, "use the average latency of the latest N checks")
|
||||||
flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
||||||
flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
||||||
flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
||||||
@ -64,31 +83,55 @@ func parseConfig() *Config {
|
|||||||
flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)")
|
flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)")
|
||||||
flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)")
|
flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)")
|
||||||
flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)")
|
flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)")
|
||||||
flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 4096, "size of CACHE")
|
flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 4096, "max number of dns response in CACHE")
|
||||||
|
flag.BoolVar(&conf.DNSConfig.CacheLog, "dnscachelog", false, "show query log of dns cache")
|
||||||
|
flag.BoolVar(&conf.DNSConfig.NoAAAA, "dnsnoaaaa", false, "disable AAAA query")
|
||||||
flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip")
|
flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip")
|
||||||
|
|
||||||
// service configs
|
// service configs
|
||||||
flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]")
|
flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]")
|
||||||
|
|
||||||
flag.Usage = usage
|
flag.Usage = usage
|
||||||
err := flag.Parse()
|
if err := flag.Parse(); err != nil {
|
||||||
if err != nil {
|
|
||||||
// flag.Usage()
|
// flag.Usage()
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup a log func
|
if *scheme != "" {
|
||||||
if conf.Verbose {
|
fmt.Fprint(flag.Output(), proxy.Usage(*scheme))
|
||||||
log.F = log.Debugf
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *example {
|
||||||
|
fmt.Fprint(flag.Output(), examples)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup logger
|
||||||
|
log.Set(conf.Verbose, conf.LogFlags)
|
||||||
|
|
||||||
if len(conf.Listens) == 0 && conf.DNS == "" && len(conf.Services) == 0 {
|
if len(conf.Listens) == 0 && conf.DNS == "" && len(conf.Services) == 0 {
|
||||||
// flag.Usage()
|
// flag.Usage()
|
||||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tcpbufsize
|
||||||
|
if conf.TCPBufSize > 0 {
|
||||||
|
proxy.TCPBufSize = conf.TCPBufSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// udpbufsize
|
||||||
|
if conf.UDPBufSize > 0 {
|
||||||
|
proxy.UDPBufSize = conf.UDPBufSize
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRules(conf)
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadRules(conf *Config) {
|
||||||
// rulefiles
|
// rulefiles
|
||||||
for _, ruleFile := range conf.RuleFiles {
|
for _, ruleFile := range conf.RuleFiles {
|
||||||
if !path.IsAbs(ruleFile) {
|
if !path.IsAbs(ruleFile) {
|
||||||
@ -117,210 +160,95 @@ func parseConfig() *Config {
|
|||||||
conf.rules = append(conf.rules, rule)
|
conf.rules = append(conf.rules, rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return conf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
app := os.Args[0]
|
fmt.Fprint(flag.Output(), usage1)
|
||||||
w := flag.Output()
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, "%s %s usage:\n", app, version)
|
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
fmt.Fprintf(w, "\n")
|
fmt.Fprintf(flag.Output(), usage2, proxy.ServerSchemes(), proxy.DialerSchemes(), version)
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available schemes:\n")
|
|
||||||
fmt.Fprintf(w, " listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix kcp\n")
|
|
||||||
fmt.Fprintf(w, " forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix kcp simple-obfs\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Socks5 scheme:\n")
|
|
||||||
fmt.Fprintf(w, " socks://[user:pass@]host:port\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "SS scheme:\n")
|
|
||||||
fmt.Fprintf(w, " ss://method:pass@host:port\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available methods for ss:\n")
|
|
||||||
fmt.Fprintf(w, " AEAD Ciphers:\n")
|
|
||||||
fmt.Fprintf(w, " AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305\n")
|
|
||||||
fmt.Fprintf(w, " Stream Ciphers:\n")
|
|
||||||
fmt.Fprintf(w, " AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5\n")
|
|
||||||
fmt.Fprintf(w, " Alias:\n")
|
|
||||||
fmt.Fprintf(w, " chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305\n")
|
|
||||||
fmt.Fprintf(w, " Plain: NONE\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "SSR scheme:\n")
|
|
||||||
fmt.Fprintf(w, " ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "SSH scheme:\n")
|
|
||||||
fmt.Fprintf(w, " ssh://user[:pass]@host:port[?key=keypath]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "VMess scheme:\n")
|
|
||||||
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "VLESS scheme:\n")
|
|
||||||
fmt.Fprintf(w, " vless://uuid@host:port[?fallback=127.0.0.1:80]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Trojan client scheme:\n")
|
|
||||||
fmt.Fprintf(w, " trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true]\n")
|
|
||||||
fmt.Fprintf(w, " trojanc://pass@host:port (cleartext, without TLS)\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Trojan server scheme:\n")
|
|
||||||
fmt.Fprintf(w, " trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]\n")
|
|
||||||
fmt.Fprintf(w, " trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available securities for vmess:\n")
|
|
||||||
fmt.Fprintf(w, " none, aes-128-gcm, chacha20-poly1305\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "TLS client scheme:\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?serverName=SERVERNAME][&skipVerify=true]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Proxy over tls client:\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],http://[user:pass@]\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],socks5://[user:pass@]\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "TLS server scheme:\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Proxy over tls server:\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,scheme://\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,http://\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,socks5://\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port?cert=PATH&key=PATH,ss://method:pass@\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Websocket client scheme:\n")
|
|
||||||
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST][&origin=ORIGIN]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Websocket server scheme:\n")
|
|
||||||
fmt.Fprintf(w, " ws://:port[/path][?host=HOST]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Websocket with a specified proxy protocol:\n")
|
|
||||||
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],scheme://\n")
|
|
||||||
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],http://[user:pass@]\n")
|
|
||||||
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],socks5://[user:pass@]\n")
|
|
||||||
fmt.Fprintf(w, " ws://host:port[/path][?host=HOST],vmess://[security:]uuid@?alterID=num\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "TLS and Websocket with a specified proxy protocol:\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true][&serverName=SERVERNAME],ws://[@/path[?host=HOST]],scheme://\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@]\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@]\n")
|
|
||||||
fmt.Fprintf(w, " tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Unix domain socket scheme:\n")
|
|
||||||
fmt.Fprintf(w, " unix://path\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "KCP scheme:\n")
|
|
||||||
fmt.Fprintf(w, " kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available crypt types for KCP:\n")
|
|
||||||
fmt.Fprintf(w, " none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available modes for KCP:\n")
|
|
||||||
fmt.Fprintf(w, " fast, fast2, fast3, normal, default: fast\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Simple-Obfs scheme:\n")
|
|
||||||
fmt.Fprintf(w, " simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available types for simple-obfs:\n")
|
|
||||||
fmt.Fprintf(w, " http, tls\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "DNS forwarding server:\n")
|
|
||||||
fmt.Fprintf(w, " dns=:53\n")
|
|
||||||
fmt.Fprintf(w, " dnsserver=8.8.8.8:53\n")
|
|
||||||
fmt.Fprintf(w, " dnsserver=1.1.1.1:53\n")
|
|
||||||
fmt.Fprintf(w, " dnsrecord=www.example.com/1.2.3.4\n")
|
|
||||||
fmt.Fprintf(w, " dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Available forward strategies:\n")
|
|
||||||
fmt.Fprintf(w, " rr: Round Robin mode\n")
|
|
||||||
fmt.Fprintf(w, " ha: High Availability mode\n")
|
|
||||||
fmt.Fprintf(w, " lha: Latency based High Availability mode\n")
|
|
||||||
fmt.Fprintf(w, " dh: Destination Hashing mode\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Forwarder option scheme: FORWARD_URL#OPTIONS\n")
|
|
||||||
fmt.Fprintf(w, " priority: set the priority of that forwarder, default:0\n")
|
|
||||||
fmt.Fprintf(w, " interface: set local interface or ip address used to connect remote server\n")
|
|
||||||
fmt.Fprintf(w, " -\n")
|
|
||||||
fmt.Fprintf(w, " Examples:\n")
|
|
||||||
fmt.Fprintf(w, " socks5://1.1.1.1:1080#priority=100\n")
|
|
||||||
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num#priority=200\n")
|
|
||||||
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=192.168.1.99\n")
|
|
||||||
fmt.Fprintf(w, " vmess://[security:]uuid@host:port?alterID=num#priority=200&interface=eth0\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Services:\n")
|
|
||||||
fmt.Fprintf(w, " dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP\n")
|
|
||||||
fmt.Fprintf(w, " e.g.,service=dhcpd,eth1,192.168.50.100,192.168.50.199\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Config file format(see `"+app+".conf.example` as an example):\n")
|
|
||||||
fmt.Fprintf(w, " # COMMENT LINE\n")
|
|
||||||
fmt.Fprintf(w, " KEY=VALUE\n")
|
|
||||||
fmt.Fprintf(w, " KEY=VALUE\n")
|
|
||||||
fmt.Fprintf(w, " # KEY equals to command line flag name: listen forward strategy...\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Examples:\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -config glider.conf\n")
|
|
||||||
fmt.Fprintf(w, " -run glider with specified config file.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen :8443 -verbose\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose\n")
|
|
||||||
fmt.Fprintf(w, " -listen on 0.0.0.0:8443 as a ss server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :443 as a https(http over tls) proxy server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -forward \"tls://abc.com:443,vmess://security:uuid@?alterID=10\"\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen tcp://:80 -forward tcp://2.2.2.2:80\n")
|
|
||||||
fmt.Fprintf(w, " -tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, " "+app+" -verbose -listen -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server:port -dnsrecord=www.example.com/1.2.3.4\n")
|
|
||||||
fmt.Fprintf(w, " -listen on :53 as dns server, forward to 8.8.8.8:53 via ss server.\n")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var usage1 = `
|
||||||
|
Usage: glider [-listen URL]... [-forward URL]... [OPTION]...
|
||||||
|
|
||||||
|
e.g. glider -config /etc/glider/glider.conf
|
||||||
|
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080 -verbose
|
||||||
|
|
||||||
|
OPTION:
|
||||||
|
`
|
||||||
|
|
||||||
|
var usage2 = `
|
||||||
|
URL:
|
||||||
|
proxy: SCHEME://[USER:PASS@][HOST]:PORT
|
||||||
|
chain: proxy,proxy[,proxy]...
|
||||||
|
|
||||||
|
e.g. -listen socks5://:1080
|
||||||
|
-listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// (protocol chain)
|
||||||
|
|
||||||
|
e.g. -forward socks5://server:1080
|
||||||
|
-forward tls://server.com:443,http:// (protocol chain)
|
||||||
|
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
|
||||||
|
|
||||||
|
SCHEME:
|
||||||
|
listen : %s
|
||||||
|
forward: %s
|
||||||
|
|
||||||
|
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
||||||
|
|
||||||
|
--
|
||||||
|
Forwarder Options: FORWARD_URL#OPTIONS
|
||||||
|
priority : the priority of that forwarder, the larger the higher, default: 0
|
||||||
|
interface: the local interface or ip address used to connect remote server.
|
||||||
|
|
||||||
|
e.g. -forward socks5://server:1080#priority=100
|
||||||
|
-forward socks5://server:1080#interface=eth0
|
||||||
|
-forward socks5://server:1080#priority=100&interface=192.168.1.99
|
||||||
|
|
||||||
|
Services:
|
||||||
|
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
|
service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
|
e.g. service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
|
||||||
|
|
||||||
|
--
|
||||||
|
Help:
|
||||||
|
glider -help
|
||||||
|
glider -scheme all
|
||||||
|
glider -example
|
||||||
|
|
||||||
|
see README.md and glider.conf.example for more details.
|
||||||
|
--
|
||||||
|
glider %s, https://github.com/nadoo/glider (glider.proxy@gmail.com)
|
||||||
|
`
|
||||||
|
|
||||||
|
var examples = `
|
||||||
|
Examples:
|
||||||
|
glider -config glider.conf
|
||||||
|
-run glider with specified config file.
|
||||||
|
|
||||||
|
glider -listen :8443 -verbose
|
||||||
|
-listen on :8443, serve as http/socks5 proxy on the same port, in verbose mode.
|
||||||
|
|
||||||
|
glider -listen socks5://:1080 -listen http://:8080 -verbose
|
||||||
|
-multiple listeners: listen on :1080 as socks5 proxy server, and on :8080 as http proxy server.
|
||||||
|
|
||||||
|
glider -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1
|
||||||
|
-multiple forwarders: listen on 8443 and forward requests via interface eth0 and eth1 in round robin mode.
|
||||||
|
|
||||||
|
glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
||||||
|
-protocol chain: listen on :443 as a https(http over tls) proxy server.
|
||||||
|
|
||||||
|
glider -listen http://:8080 -forward socks5://serverA:1080,socks5://serverB:1080
|
||||||
|
-proxy chain: listen on :8080 as a http proxy server, forward all requests via forward chain.
|
||||||
|
|
||||||
|
glider -listen :8443 -forward socks5://serverA:1080 -forward socks5://serverB:1080#priority=10 -forward socks5://serverC:1080#priority=10
|
||||||
|
-forwarder priority: serverA will only be used when serverB and serverC are not available.
|
||||||
|
|
||||||
|
glider -listen tcp://:80 -forward tcp://serverA:80
|
||||||
|
-tcp tunnel: listen on :80 and forward all requests to serverA:80.
|
||||||
|
|
||||||
|
glider -listen udp://:53 -forward socks5://serverA:1080,udp://8.8.8.8:53
|
||||||
|
-udp tunnel: listen on :53 and forward all udp requests to 8.8.8.8:53 via remote socks5 server.
|
||||||
|
|
||||||
|
glider -verbose -dns=:53 -dnsserver=8.8.8.8:53 -forward socks5://serverA:1080 -dnsrecord=abc.com/1.2.3.4
|
||||||
|
-dns over proxy: listen on :53 as dns server, forward to 8.8.8.8:53 via socks5 server.
|
||||||
|
`
|
||||||
|
@ -31,7 +31,7 @@ forward=ss://method:pass@1.1.1.1:8443
|
|||||||
# upstream forward proxy (forward chain)
|
# upstream forward proxy (forward chain)
|
||||||
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
forward=http://1.1.1.1:8080,socks5://2.2.2.2:1080
|
||||||
|
|
||||||
# multiple upstream proxies forwad strategy
|
# multiple upstream proxies forward strategy
|
||||||
strategy=rr
|
strategy=rr
|
||||||
|
|
||||||
# forwarder health check
|
# forwarder health check
|
||||||
@ -56,8 +56,8 @@ rules-dir=rules.d
|
|||||||
#include=more.inc.conf
|
#include=more.inc.conf
|
||||||
```
|
```
|
||||||
See:
|
See:
|
||||||
- [glider.conf.example](config/glider.conf.example)
|
- [glider.conf.example](glider.conf.example)
|
||||||
- [examples](config/examples)
|
- [examples](examples)
|
||||||
|
|
||||||
## Rule File
|
## Rule File
|
||||||
Rule file, **same as the config file but specify forwarders based on destinations**:
|
Rule file, **same as the config file but specify forwarders based on destinations**:
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
forward=http://forwarder4:8080
|
forward=http://forwarder1:8080
|
||||||
|
|
||||||
# first connect forwarder1 then forwarder2 then internet
|
# first connect forwarder1 then forwarder2 then internet
|
||||||
forward=http://forwarder5:8080,socks6://forwarder3:1080
|
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||||
|
|
||||||
|
|
||||||
# Round Robin mode: rr
|
# Round Robin mode: rr
|
||||||
|
@ -82,7 +82,8 @@ Set server's nameserver to glider:
|
|||||||
echo nameserver 127.0.0.1 > /etc/resolv.conf
|
echo nameserver 127.0.0.1 > /etc/resolv.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Client DNS settings
|
#### Client settings
|
||||||
|
Use the linux server's ip as your gateway.
|
||||||
Use the linux server's ip as your dns server.
|
Use the linux server's ip as your dns server.
|
||||||
|
|
||||||
#### When client requesting to access http://example1.com (in office.rule), the whole process:
|
#### When client requesting to access http://example1.com (in office.rule), the whole process:
|
||||||
@ -91,10 +92,10 @@ DNS Resolving:
|
|||||||
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
|
2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen)
|
||||||
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy).
|
3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy).
|
||||||
4. glider updates it's office rule config, adds the resolved ip address to it.
|
4. glider updates it's office rule config, adds the resolved ip address to it.
|
||||||
5. glider adds the resolved ip into ipset "glider", and return the dns answer to client.
|
5. glider adds the resolved ip into ipset "glider", and returns the dns answer to client.
|
||||||
|
|
||||||
Destination Accessing:
|
Destination Accessing:
|
||||||
1. client sends http request to the resolved ip of example1.com.
|
1. client sends http request to the resolved ip of example1.com.
|
||||||
2. linux gateway server will get the request.
|
2. linux gateway server will get the request.
|
||||||
3. iptabes matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
3. iptables matches the ip in ipset "glider" and redirect this request to :1081(glider)
|
||||||
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.
|
4. glider finds the ip in office rule, and then choose a forwarder in office.rule to complete the request.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
forward=http://forwarder4:8080
|
forward=http://forwarder1:8080
|
||||||
|
|
||||||
# first connect forwarder1 then forwarder2 then internet
|
# first connect forwarder1 then forwarder2 then internet
|
||||||
forward=http://forwarder5:8080,socks5://forwarder3:1080
|
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||||
|
|
||||||
|
|
||||||
# Round Robin mode: rr
|
# Round Robin mode: rr
|
||||||
|
@ -32,16 +32,17 @@ verbose=True
|
|||||||
# different protocols.
|
# different protocols.
|
||||||
|
|
||||||
# listen on 8443, serve as http/socks5 proxy on the same port.
|
# listen on 8443, serve as http/socks5 proxy on the same port.
|
||||||
listen=:8443
|
# listen=:8443
|
||||||
|
listen=127.0.0.1:8443
|
||||||
|
|
||||||
# listen on 8448 as a ss server.
|
# listen on 8448 as a ss server.
|
||||||
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
|
# listen=ss://AEAD_CHACHA20_POLY1305:pass@:8448
|
||||||
|
|
||||||
# listen on 8080 as a http proxy server.
|
# listen on 8080 as a http proxy server.
|
||||||
listen=http://:8080
|
# listen=http://:8080
|
||||||
|
|
||||||
# listen on 1080 as a socks5 proxy server.
|
# listen on 1080 as a socks5 proxy server.
|
||||||
listen=socks5://:1080
|
# listen=socks5://:1080
|
||||||
|
|
||||||
# listen on 1234 as vless proxy server.
|
# listen on 1234 as vless proxy server.
|
||||||
# listen=vless://uuid@:1234
|
# listen=vless://uuid@:1234
|
||||||
@ -51,6 +52,9 @@ listen=socks5://:1080
|
|||||||
# listen on 1081 as a linux transparent proxy server.
|
# listen on 1081 as a linux transparent proxy server.
|
||||||
# listen=redir://:1081
|
# listen=redir://:1081
|
||||||
|
|
||||||
|
# listen on 1082 as a linux transparent proxy server(tproxy).
|
||||||
|
# listen=tproxy://:1082
|
||||||
|
|
||||||
# http over tls (HTTPS proxy)
|
# http over tls (HTTPS proxy)
|
||||||
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,http://
|
||||||
|
|
||||||
@ -58,7 +62,10 @@ listen=socks5://:1080
|
|||||||
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
|
# listen=tls://:443?cert=crtFilePath&key=keyFilePath,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
|
|
||||||
# socks5 over unix domain socket
|
# socks5 over unix domain socket
|
||||||
# listen=unix:///tmp/glider.socket,socks5://
|
# listen=unix:///dev/shm/socket,socks5://
|
||||||
|
|
||||||
|
# socks5 over vm socket
|
||||||
|
# listen=vsock://:1234,socks5://
|
||||||
|
|
||||||
# socks5 over kcp
|
# socks5 over kcp
|
||||||
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5://
|
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5://
|
||||||
@ -73,10 +80,10 @@ listen=socks5://:1080
|
|||||||
# listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80
|
# listen=ws://:1234/path?host=domain.com,vless://707f20ea-d4b8-4d1d-8e2e-2c86cb2ed97a@?fallback=127.0.0.1:80
|
||||||
|
|
||||||
# trojan server
|
# trojan server
|
||||||
# listen=trojan://PASSWORD:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1
|
# listen=trojan://PASSWORD@:1234?cert=/path/to/cert&key=/path/to/key&fallback=127.0.0.1
|
||||||
|
|
||||||
# trojanc server (trojan without tls)
|
# trojanc server (trojan without tls)
|
||||||
# listen=trojanc://PASSWORD:1234?fallback=127.0.0.1
|
# listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1
|
||||||
|
|
||||||
# FORWARDERS
|
# FORWARDERS
|
||||||
# ----------
|
# ----------
|
||||||
@ -106,9 +113,10 @@ listen=socks5://:1080
|
|||||||
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
|
# forward=ssr://method:pass@1.1.1.1:8443?protocol=auth_aes128_md5&protocol_param=xxx&obfs=tls1.2_ticket_auth&obfs_param=yyy
|
||||||
|
|
||||||
# ssh forwarder
|
# ssh forwarder
|
||||||
# forward=ssh://user[:pass]@host:port[?key=keypath]
|
# forward=ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
||||||
# forward=ssh://root:pass@host:port
|
# forward=ssh://root:pass@host:port
|
||||||
# forward=ssh://root@host:port?key=/path/to/keyfile
|
# forward=ssh://root@host:port?key=/path/to/keyfile
|
||||||
|
# forward=ssh://root@host:port?key=/path/to/keyfile&timeout=5
|
||||||
|
|
||||||
# http proxy as forwarder
|
# http proxy as forwarder
|
||||||
# forward=http://1.1.1.1:8080
|
# forward=http://1.1.1.1:8080
|
||||||
@ -122,21 +130,21 @@ listen=socks5://:1080
|
|||||||
# vless forwarder
|
# vless forwarder
|
||||||
# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
||||||
|
|
||||||
# vmess with none security
|
# vmess with aead auth
|
||||||
|
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
||||||
|
|
||||||
|
# vmess with md5 auth (by setting alterID)
|
||||||
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
# forward=vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
||||||
|
|
||||||
# vmess with aes-128-gcm security
|
|
||||||
# forward=vmess://aes-128-gcm:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443?alterID=2
|
|
||||||
|
|
||||||
# vmess over tls
|
# vmess over tls
|
||||||
# forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
# forward=tls://server.com:443,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
|
||||||
|
|
||||||
# vmess over websocket
|
# vmess over websocket
|
||||||
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
# forward=ws://1.1.1.1:80/path?host=server.com,vmess://chacha20-poly1305:5a146038-0b56-4e95-b1dc-5c6f5a32cd98@
|
||||||
|
|
||||||
# vmess over ws over tls
|
# vmess over ws over tls
|
||||||
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
|
||||||
# forward=tls://server.com:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
# forward=tls://server.com:443,ws://@/path,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98
|
||||||
|
|
||||||
# ss over tls
|
# ss over tls
|
||||||
# forward=tls://server.com:443,ss://AEAD_CHACHA20_POLY1305:pass@
|
# forward=tls://server.com:443,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
@ -148,7 +156,7 @@ listen=socks5://:1080
|
|||||||
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
|
# forward=simple-obfs://1.1.1.1:443?type=tls&host=apple.com,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||||
|
|
||||||
# socks5 over unix domain socket
|
# socks5 over unix domain socket
|
||||||
# forward=unix:///tmp/glider.socket,socks5://
|
# forward=unix:///dev/shm/socket,socks5://
|
||||||
|
|
||||||
# FORWARDER CHAIN
|
# FORWARDER CHAIN
|
||||||
# ---------------
|
# ---------------
|
||||||
@ -188,8 +196,10 @@ maxfailures=3
|
|||||||
|
|
||||||
# Forwarder health check:
|
# Forwarder health check:
|
||||||
# check=tcp[://HOST:PORT]: tcp port connect check
|
# check=tcp[://HOST:PORT]: tcp port connect check
|
||||||
# check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
# check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||||
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
# check=https://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||||
|
# e.g. check=https://www.netflix.com/title/81215567#expect=301|404
|
||||||
|
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR,FORWARDER_URL
|
||||||
# check=disable: disable health check
|
# check=disable: disable health check
|
||||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||||
|
|
||||||
@ -202,6 +212,9 @@ checktimeout=10
|
|||||||
# switch forwarder only when new_latency < old_latency - tolerance, used in lha mode
|
# switch forwarder only when new_latency < old_latency - tolerance, used in lha mode
|
||||||
checktolerance=100
|
checktolerance=100
|
||||||
|
|
||||||
|
# use the average latency of the latest N checks
|
||||||
|
checklatencysamples=10
|
||||||
|
|
||||||
# check disabled fowarders only
|
# check disabled fowarders only
|
||||||
checkdisabledonly=false
|
checkdisabledonly=false
|
||||||
|
|
||||||
@ -210,7 +223,7 @@ checkdisabledonly=false
|
|||||||
# we can specify different upstream dns server in rule file for different destinations.
|
# we can specify different upstream dns server in rule file for different destinations.
|
||||||
|
|
||||||
# Setup a dns forwarding server
|
# Setup a dns forwarding server
|
||||||
dns=:53
|
# dns=:53
|
||||||
|
|
||||||
# global remote dns server (you can specify different dns server in rule file)
|
# global remote dns server (you can specify different dns server in rule file)
|
||||||
dnsserver=8.8.8.8:53
|
dnsserver=8.8.8.8:53
|
||||||
@ -233,35 +246,52 @@ dnsminttl=0
|
|||||||
# size of CACHE
|
# size of CACHE
|
||||||
dnscachesize=4096
|
dnscachesize=4096
|
||||||
|
|
||||||
|
# show query log of dns cache
|
||||||
|
dnscachelog=True
|
||||||
|
|
||||||
|
# disable AAAA queries
|
||||||
|
# dnsnoaaaa=True
|
||||||
|
|
||||||
# custom records
|
# custom records
|
||||||
dnsrecord=www.example.com/1.2.3.4
|
dnsrecord=www.example.com/1.2.3.4
|
||||||
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||||
|
|
||||||
# SERVICES
|
# SERVICES
|
||||||
# service=dhcpd,INTERFACE,START_IP,END_IP
|
# service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
|
# service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||||
# e.g.:
|
# e.g.:
|
||||||
# service=dhcpd,eth1,192.168.50.100,192.168.50.199
|
# service=dhcpd,eth1,192.168.1.100,192.168.1.199,720
|
||||||
|
# service=dhcpd,eth2,192.168.2.100,192.168.2.199,720,fc:23:34:9e:25:01=192.168.2.101,fc:23:34:9e:25:02=192.168.2.102
|
||||||
|
|
||||||
# INTERFACE SPECIFIC
|
# INTERFACE SPECIFIC
|
||||||
# ------------------
|
# ------------------
|
||||||
# Specify the outbound ip/interface.
|
# Specify global outbound ip/interface.
|
||||||
#
|
#
|
||||||
# interface=""
|
# interface=""
|
||||||
# interface="192.168.1.100"
|
# interface="192.168.1.100"
|
||||||
# interface="eth0"
|
# interface="eth0"
|
||||||
|
#
|
||||||
|
# Specify interface for a forwarder:
|
||||||
|
# forward=socks5://192.168.1.10:1080#priority=100&interface=eth0
|
||||||
|
# forward=socks5://192.168.1.10:1080#priority=100&interface=192.168.1.100
|
||||||
|
|
||||||
# RULE FILES
|
# RULE FILES
|
||||||
# ----------
|
# ----------
|
||||||
# Specify additional forward rules.
|
# Specify additional forward rules.
|
||||||
|
#
|
||||||
# specify rules folder, so all *.rule files under this folder will be parsed as rule file
|
# specify rules folder, so all *.rule files under this folder will be parsed as rule file
|
||||||
rules-dir=rules.d
|
# rules-dir=rules.d
|
||||||
|
#
|
||||||
# specify a rule file
|
# specify a rule file
|
||||||
#rulefile=office.rule
|
#rulefile=office.rule
|
||||||
#rulefile=home.rule
|
#rulefile=home.rule
|
||||||
|
|
||||||
|
# INCLUDE CONFIG FILES
|
||||||
# INCLUDE MORE CONFIG FILES
|
# ----------
|
||||||
#include=dnsrecord.inc.conf
|
#include=dnsrecord.inc.conf
|
||||||
#include=more.conf
|
#include=more.conf
|
||||||
|
|
||||||
|
# ENVIRONMENT VARIABLES
|
||||||
|
# ----------
|
||||||
|
# use {$ENV_VAR_NAME} in VALUE to get the Environment Variable value.
|
||||||
|
# forward=socks5://{$USER_NAME}:{$USER_PASS}@:1080
|
||||||
|
@ -26,6 +26,7 @@ dnsserver=208.67.222.222:53
|
|||||||
# - add ip/cidrs in rule files on startup
|
# - add ip/cidrs in rule files on startup
|
||||||
# - add resolved ips for domains in rule files by dns forwarding server
|
# - add resolved ips for domains in rule files by dns forwarding server
|
||||||
# Usually used in transparent proxy mode on linux
|
# Usually used in transparent proxy mode on linux
|
||||||
|
# Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6
|
||||||
ipset=glider
|
ipset=glider
|
||||||
|
|
||||||
# DESTINATIONS
|
# DESTINATIONS
|
||||||
|
101
dns/client.go
101
dns/client.go
@ -5,17 +5,18 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnswerHandler function handles the dns TypeA or TypeAAAA answer.
|
// AnswerHandler function handles the dns TypeA or TypeAAAA answer.
|
||||||
type AnswerHandler func(domain, ip string) error
|
type AnswerHandler func(domain string, ip netip.Addr) error
|
||||||
|
|
||||||
// Config for dns.
|
// Config for dns.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -26,6 +27,8 @@ type Config struct {
|
|||||||
Records []string
|
Records []string
|
||||||
AlwaysTCP bool
|
AlwaysTCP bool
|
||||||
CacheSize int
|
CacheSize int
|
||||||
|
CacheLog bool
|
||||||
|
NoAAAA bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a dns client struct.
|
// Client is a dns client struct.
|
||||||
@ -50,7 +53,9 @@ func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
|
|||||||
|
|
||||||
// custom records
|
// custom records
|
||||||
for _, record := range config.Records {
|
for _, record := range config.Records {
|
||||||
c.AddRecord(record)
|
if err := c.AddRecord(record); err != nil {
|
||||||
|
log.F("[dns] add record '%s' error: %s", record, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
@ -64,13 +69,21 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.config.NoAAAA && req.Question.QTYPE == QTypeAAAA {
|
||||||
|
respBytes := valCopy(reqBytes)
|
||||||
|
respBytes[2] |= uint8(ResponseMsg) << 7
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
|
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
|
||||||
if v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 {
|
if v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 {
|
||||||
v = valCopy(v)
|
v = valCopy(v)
|
||||||
binary.BigEndian.PutUint16(v[:2], req.ID)
|
binary.BigEndian.PutUint16(v[:2], req.ID)
|
||||||
|
|
||||||
log.F("[dns] %s <-> cache, type: %d, %s",
|
if c.config.CacheLog {
|
||||||
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
log.F("[dns] %s <-> cache, type: %d, %s",
|
||||||
|
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
||||||
|
}
|
||||||
|
|
||||||
if expired { // update cache
|
if expired { // update cache
|
||||||
go func(qname string, reqBytes []byte, preferTCP bool) {
|
go func(qname string, reqBytes []byte, preferTCP bool) {
|
||||||
@ -106,12 +119,19 @@ func (c *Client) handleAnswer(respBytes []byte, clientAddr, dnsServer, network,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ips, ttl := c.extractAnswer(resp)
|
ips, ttl := c.extractAnswer(resp)
|
||||||
if len(ips) != 0 && ttl > 0 {
|
if ttl > c.config.MaxTTL {
|
||||||
c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl)
|
ttl = c.config.MaxTTL
|
||||||
|
} else if ttl < c.config.MinTTL {
|
||||||
|
ttl = c.config.MinTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s: %s",
|
if ttl <= 0 { // we got a null result
|
||||||
clientAddr, dnsServer, network, dialerAddr, resp.Question.QTYPE, resp.Question.QNAME, strings.Join(ips, ","))
|
ttl = 1800
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl)
|
||||||
|
log.F("[dns] %s <-> %s(%s) via %s, %s/%d: %s, ttl: %ds",
|
||||||
|
clientAddr, dnsServer, network, dialerAddr, resp.Question.QNAME, resp.Question.QTYPE, strings.Join(ips, ","), ttl)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -121,11 +141,11 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
|
|||||||
ttl := c.config.MinTTL
|
ttl := c.config.MinTTL
|
||||||
for _, answer := range resp.Answers {
|
for _, answer := range resp.Answers {
|
||||||
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
||||||
for _, h := range c.handlers {
|
if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
|
||||||
h(resp.Question.QNAME, answer.IP)
|
for _, h := range c.handlers {
|
||||||
}
|
h(resp.Question.QNAME, answer.IP)
|
||||||
if answer.IP != "" {
|
}
|
||||||
ips = append(ips, answer.IP)
|
ips = append(ips, answer.IP.String())
|
||||||
}
|
}
|
||||||
if answer.TTL != 0 {
|
if answer.TTL != 0 {
|
||||||
ttl = int(answer.TTL)
|
ttl = int(answer.TTL)
|
||||||
@ -133,12 +153,6 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ttl > c.config.MaxTTL {
|
|
||||||
ttl = c.config.MaxTTL
|
|
||||||
} else if ttl < c.config.MinTTL {
|
|
||||||
ttl = c.config.MinTTL
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, ttl
|
return ips, ttl
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,12 +162,13 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
|
|
||||||
// use tcp to connect upstream server default
|
// use tcp to connect upstream server default
|
||||||
network = "tcp"
|
network = "tcp"
|
||||||
dialer := c.proxy.NextDialer(qname + ":53")
|
dialer := c.proxy.NextDialer(qname + ":0")
|
||||||
|
|
||||||
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop
|
// if we are resolving a domain which uses a forwarder `REJECT`, then use `DIRECT` instead
|
||||||
|
// so we can resolve it correctly.
|
||||||
// TODO: dialer.Addr() == "REJECT", tricky
|
// TODO: dialer.Addr() == "REJECT", tricky
|
||||||
if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
|
if dialer.Addr() == "REJECT" {
|
||||||
dialer = proxy.Default
|
dialer = c.proxy.NextDialer("direct:0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If client uses udp and no forwarders specified, use udp
|
// If client uses udp and no forwarders specified, use udp
|
||||||
@ -164,7 +179,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
|
|
||||||
ups := c.UpStream(qname)
|
ups := c.UpStream(qname)
|
||||||
server = ups.Server()
|
server = ups.Server()
|
||||||
for i := 0; i < ups.Len(); i++ {
|
for range ups.Len() {
|
||||||
var rc net.Conn
|
var rc net.Conn
|
||||||
rc, err = dialer.Dial(network, server)
|
rc, err = dialer.Dial(network, server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -271,10 +286,13 @@ func (c *Client) AddHandler(h AnswerHandler) {
|
|||||||
// AddRecord adds custom record to dns cache, format:
|
// AddRecord adds custom record to dns cache, format:
|
||||||
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||||
func (c *Client) AddRecord(record string) error {
|
func (c *Client) AddRecord(record string) error {
|
||||||
r := strings.Split(record, "/")
|
domain, ip, found := strings.Cut(record, "/")
|
||||||
domain, ip := r[0], r[1]
|
if !found {
|
||||||
m, err := c.MakeResponse(domain, ip)
|
return errors.New("wrong record format, must contain '/'")
|
||||||
|
}
|
||||||
|
m, err := MakeResponse(domain, ip, uint32(c.config.MaxTTL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.F("[dns] add custom record error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,27 +310,22 @@ func (c *Client) AddRecord(record string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MakeResponse makes a dns response message for the given domain and ip address.
|
// MakeResponse makes a dns response message for the given domain and ip address.
|
||||||
func (c *Client) MakeResponse(domain string, ip string) (*Message, error) {
|
// Note: you should make sure ttl > 0.
|
||||||
ipb := net.ParseIP(ip)
|
func MakeResponse(domain, ip string, ttl uint32) (*Message, error) {
|
||||||
if ipb == nil {
|
addr, err := netip.ParseAddr(ip)
|
||||||
return nil, errors.New("GenResponse: invalid ip format")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rdata []byte
|
var qtype, rdlen uint16 = QTypeA, net.IPv4len
|
||||||
var qtype, rdlen uint16
|
if addr.Is6() {
|
||||||
if rdata = ipb.To4(); rdata != nil {
|
qtype, rdlen = QTypeAAAA, net.IPv6len
|
||||||
qtype = QTypeA
|
|
||||||
rdlen = net.IPv4len
|
|
||||||
} else {
|
|
||||||
qtype = QTypeAAAA
|
|
||||||
rdlen = net.IPv6len
|
|
||||||
rdata = ipb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NewMessage(0, Response)
|
m := NewMessage(0, ResponseMsg)
|
||||||
m.SetQuestion(NewQuestion(qtype, domain))
|
m.SetQuestion(NewQuestion(qtype, domain))
|
||||||
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
|
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
|
||||||
TTL: uint32(c.config.MinTTL), RDLENGTH: rdlen, RDATA: rdata}
|
TTL: ttl, RDLENGTH: rdlen, RDATA: addr.AsSlice()}
|
||||||
m.AddAnswer(rr)
|
m.AddAnswer(rr)
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
|
191
dns/message.go
191
dns/message.go
@ -5,25 +5,25 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"net"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UDPMaxLen is the max size of udp dns request.
|
// UDPMaxLen is the max size of udp dns request.
|
||||||
// https://tools.ietf.org/html/rfc1035#section-4.2.1
|
// https://www.dnsflagday.net/2020/
|
||||||
// Messages carried by UDP are restricted to 512 bytes (not counting the IP
|
const UDPMaxLen = 1232
|
||||||
// or UDP headers). Longer messages are truncated and the TC bit is set in
|
|
||||||
// the header.
|
|
||||||
const UDPMaxLen = 512
|
|
||||||
|
|
||||||
// HeaderLen is the length of dns msg header.
|
// HeaderLen is the length of dns msg header.
|
||||||
const HeaderLen = 12
|
const HeaderLen = 12
|
||||||
|
|
||||||
|
// MsgType is the dns Message type.
|
||||||
|
type MsgType byte
|
||||||
|
|
||||||
// Message types.
|
// Message types.
|
||||||
const (
|
const (
|
||||||
Query = 0
|
QueryMsg MsgType = 0
|
||||||
Response = 1
|
ResponseMsg MsgType = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Query types.
|
// Query types.
|
||||||
@ -36,21 +36,21 @@ const (
|
|||||||
const ClassINET uint16 = 1
|
const ClassINET uint16 = 1
|
||||||
|
|
||||||
// Message format:
|
// Message format:
|
||||||
// https://tools.ietf.org/html/rfc1035#section-4.1
|
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1
|
||||||
// All communications inside of the domain protocol are carried in a single
|
// All communications inside of the domain protocol are carried in a single
|
||||||
// format called a message. The top level format of message is divided
|
// format called a message. The top level format of message is divided
|
||||||
// into 5 sections (some of which are empty in certain cases) shown below:
|
// into 5 sections (some of which are empty in certain cases) shown below:
|
||||||
//
|
//
|
||||||
// +---------------------+
|
// +---------------------+
|
||||||
// | Header |
|
// | Header |
|
||||||
// +---------------------+
|
// +---------------------+
|
||||||
// | Question | the question for the name server
|
// | Question | the question for the name server
|
||||||
// +---------------------+
|
// +---------------------+
|
||||||
// | Answer | RRs answering the question
|
// | Answer | RRs answering the question
|
||||||
// +---------------------+
|
// +---------------------+
|
||||||
// | Authority | RRs pointing toward an authority
|
// | Authority | RRs pointing toward an authority
|
||||||
// +---------------------+
|
// +---------------------+
|
||||||
// | Additional | RRs holding additional information
|
// | Additional | RRs holding additional information
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Header
|
Header
|
||||||
// most dns implementation only support 1 question
|
// most dns implementation only support 1 question
|
||||||
@ -64,7 +64,7 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMessage returns a new message.
|
// NewMessage returns a new message.
|
||||||
func NewMessage(id uint16, msgType int) *Message {
|
func NewMessage(id uint16, msgType MsgType) *Message {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
id = uint16(rand.Uint32())
|
id = uint16(rand.Uint32())
|
||||||
}
|
}
|
||||||
@ -91,8 +91,7 @@ func (m *Message) AddAnswer(rr *RR) error {
|
|||||||
// Marshal marshals message struct to []byte.
|
// Marshal marshals message struct to []byte.
|
||||||
func (m *Message) Marshal() ([]byte, error) {
|
func (m *Message) Marshal() ([]byte, error) {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
_, err := m.MarshalTo(buf)
|
if _, err := m.MarshalTo(buf); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
@ -134,8 +133,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := &Message{unMarshaled: b}
|
m := &Message{unMarshaled: b}
|
||||||
err := UnmarshalHeader(b[:HeaderLen], &m.Header)
|
if err := UnmarshalHeader(b[:HeaderLen], &m.Header); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +146,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
|
|
||||||
// resp answers
|
// resp answers
|
||||||
rrIdx := HeaderLen + qLen
|
rrIdx := HeaderLen + qLen
|
||||||
for i := 0; i < int(m.Header.ANCOUNT); i++ {
|
for range int(m.Header.ANCOUNT) {
|
||||||
rr := &RR{}
|
rr := &RR{}
|
||||||
rrLen, err := m.UnmarshalRR(rrIdx, rr)
|
rrLen, err := m.UnmarshalRR(rrIdx, rr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -165,25 +163,24 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Header format:
|
// Header format:
|
||||||
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.1
|
||||||
// The header contains the following fields:
|
// The header contains the following fields:
|
||||||
//
|
//
|
||||||
// 1 1 1 1 1 1
|
// 1 1 1 1 1 1
|
||||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | ID |
|
// | ID |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | QDCOUNT |
|
// | QDCOUNT |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | ANCOUNT |
|
// | ANCOUNT |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | NSCOUNT |
|
// | NSCOUNT |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | ARCOUNT |
|
// | ARCOUNT |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
//
|
|
||||||
type Header struct {
|
type Header struct {
|
||||||
ID uint16
|
ID uint16
|
||||||
Bits uint16
|
Bits uint16
|
||||||
@ -194,7 +191,7 @@ type Header struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetMsgType sets the message type.
|
// SetMsgType sets the message type.
|
||||||
func (h *Header) SetMsgType(qr int) {
|
func (h *Header) SetMsgType(qr MsgType) {
|
||||||
h.Bits |= uint16(qr) << 15
|
h.Bits |= uint16(qr) << 15
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,10 +210,11 @@ func (h *Header) SetAncount(ancount int) {
|
|||||||
h.ANCOUNT = uint16(ancount)
|
h.ANCOUNT = uint16(ancount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
// Not used now, but keep it for future use.
|
||||||
TC uint16, RD uint16, RA uint16, RCODE uint16) {
|
// func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
||||||
h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
|
// TC uint16, RD uint16, RA uint16, RCODE uint16) {
|
||||||
}
|
// h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
|
||||||
|
// }
|
||||||
|
|
||||||
// MarshalTo marshals header struct to []byte and write to w.
|
// MarshalTo marshals header struct to []byte and write to w.
|
||||||
func (h *Header) MarshalTo(w io.Writer) (int, error) {
|
func (h *Header) MarshalTo(w io.Writer) (int, error) {
|
||||||
@ -244,22 +242,22 @@ func UnmarshalHeader(b []byte, h *Header) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Question format:
|
// Question format:
|
||||||
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.2
|
||||||
// The question section is used to carry the "question" in most queries,
|
// The question section is used to carry the "question" in most queries,
|
||||||
// i.e., the parameters that define what is being asked. The section
|
// i.e., the parameters that define what is being asked. The section
|
||||||
// contains QDCOUNT (usually 1) entries, each of the following format:
|
// contains QDCOUNT (usually 1) entries, each of the following format:
|
||||||
//
|
//
|
||||||
// 1 1 1 1 1 1
|
// 1 1 1 1 1 1
|
||||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | |
|
// | |
|
||||||
// / QNAME /
|
// / QNAME /
|
||||||
// / /
|
// / /
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | QTYPE |
|
// | QTYPE |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | QCLASS |
|
// | QCLASS |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
type Question struct {
|
type Question struct {
|
||||||
QNAME string
|
QNAME string
|
||||||
QTYPE uint16
|
QTYPE uint16
|
||||||
@ -282,14 +280,12 @@ func (q *Question) MarshalTo(w io.Writer) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = binary.Write(w, binary.BigEndian, q.QTYPE)
|
if err = binary.Write(w, binary.BigEndian, q.QTYPE); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n += 2
|
n += 2
|
||||||
|
|
||||||
err = binary.Write(w, binary.BigEndian, q.QCLASS)
|
if err = binary.Write(w, binary.BigEndian, q.QCLASS); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n += 2
|
n += 2
|
||||||
@ -322,33 +318,33 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RR format:
|
// RR format:
|
||||||
// https://tools.ietf.org/html/rfc1035#section-3.2.1
|
// https://www.rfc-editor.org/rfc/rfc1035#section-3.2.1
|
||||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.3
|
||||||
// The answer, authority, and additional sections all share the same
|
// The answer, authority, and additional sections all share the same
|
||||||
// format: a variable number of resource records, where the number of
|
// format: a variable number of resource records, where the number of
|
||||||
// records is specified in the corresponding count field in the header.
|
// records is specified in the corresponding count field in the header.
|
||||||
// Each resource record has the following format:
|
// Each resource record has the following format:
|
||||||
//
|
//
|
||||||
// 1 1 1 1 1 1
|
// 1 1 1 1 1 1
|
||||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | |
|
// | |
|
||||||
// / /
|
// / /
|
||||||
// / NAME /
|
// / NAME /
|
||||||
// | |
|
// | |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | TYPE |
|
// | TYPE |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | CLASS |
|
// | CLASS |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | TTL |
|
// | TTL |
|
||||||
// | |
|
// | |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | RDLENGTH |
|
// | RDLENGTH |
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||||
// / RDATA /
|
// / RDATA /
|
||||||
// / /
|
// / /
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
type RR struct {
|
type RR struct {
|
||||||
NAME string
|
NAME string
|
||||||
TYPE uint16
|
TYPE uint16
|
||||||
@ -357,7 +353,7 @@ type RR struct {
|
|||||||
RDLENGTH uint16
|
RDLENGTH uint16
|
||||||
RDATA []byte
|
RDATA []byte
|
||||||
|
|
||||||
IP string
|
IP netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRR returns a new dns rr.
|
// NewRR returns a new dns rr.
|
||||||
@ -372,20 +368,17 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = binary.Write(w, binary.BigEndian, rr.TYPE)
|
if err = binary.Write(w, binary.BigEndian, rr.TYPE); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n += 2
|
n += 2
|
||||||
|
|
||||||
err = binary.Write(w, binary.BigEndian, rr.CLASS)
|
if err = binary.Write(w, binary.BigEndian, rr.CLASS); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n += 2
|
n += 2
|
||||||
|
|
||||||
err = binary.Write(w, binary.BigEndian, rr.TTL)
|
if err = binary.Write(w, binary.BigEndian, rr.TTL); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n += 4
|
n += 4
|
||||||
@ -396,8 +389,7 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
|
|||||||
}
|
}
|
||||||
n += 2
|
n += 2
|
||||||
|
|
||||||
_, err = w.Write(rr.RDATA)
|
if _, err = w.Write(rr.RDATA); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n += len(rr.RDATA)
|
n += len(rr.RDATA)
|
||||||
@ -438,9 +430,9 @@ func (m *Message) UnmarshalRR(start int, rr *RR) (n int, err error) {
|
|||||||
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
|
rr.RDATA = p[n+10 : n+10+int(rr.RDLENGTH)]
|
||||||
|
|
||||||
if rr.TYPE == QTypeA {
|
if rr.TYPE == QTypeA {
|
||||||
rr.IP = net.IP(rr.RDATA[:net.IPv4len]).String()
|
rr.IP = netip.AddrFrom4(*(*[4]byte)(rr.RDATA[:4]))
|
||||||
} else if rr.TYPE == QTypeAAAA {
|
} else if rr.TYPE == QTypeAAAA {
|
||||||
rr.IP = net.IP(rr.RDATA[:net.IPv6len]).String()
|
rr.IP = netip.AddrFrom16(*(*[16]byte)(rr.RDATA[:16]))
|
||||||
}
|
}
|
||||||
|
|
||||||
n = n + 10 + int(rr.RDLENGTH)
|
n = n + 10 + int(rr.RDLENGTH)
|
||||||
@ -479,7 +471,7 @@ func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error)
|
|||||||
var idx, size int
|
var idx, size int
|
||||||
|
|
||||||
for len(b[idx:]) != 0 {
|
for len(b[idx:]) != 0 {
|
||||||
// https://tools.ietf.org/html/rfc1035#section-4.1.4
|
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.4
|
||||||
// "Message compression",
|
// "Message compression",
|
||||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
// | 1 1| OFFSET |
|
// | 1 1| OFFSET |
|
||||||
@ -490,8 +482,7 @@ func (m *Message) UnmarshalDomainTo(sb *strings.Builder, b []byte) (int, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
offset := binary.BigEndian.Uint16(b[idx : idx+2])
|
offset := binary.BigEndian.Uint16(b[idx : idx+2])
|
||||||
err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF))
|
if err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,13 +80,13 @@ func (s *Server) ServePacket(pc net.PacketConn, caddr net.Addr, reqBytes []byte)
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[dns] error in exchange: %s", err)
|
log.F("[dns] error in exchange for %s: %s", caddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pc.WriteTo(respBytes, caddr)
|
_, err = pc.WriteTo(respBytes, caddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[dns] error in local write: %s", err)
|
log.F("[dns] error in local write to %s: %s", caddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import "sync/atomic"
|
import (
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
// UPStream is a dns upstream.
|
// UPStream is a dns upstream.
|
||||||
type UPStream struct {
|
type UPStream struct {
|
||||||
@ -10,6 +13,12 @@ type UPStream struct {
|
|||||||
|
|
||||||
// NewUPStream returns a new UpStream.
|
// NewUPStream returns a new UpStream.
|
||||||
func NewUPStream(servers []string) *UPStream {
|
func NewUPStream(servers []string) *UPStream {
|
||||||
|
// default port for dns upstream servers
|
||||||
|
for i, server := range servers {
|
||||||
|
if _, port, _ := net.SplitHostPort(server); port == "" {
|
||||||
|
servers[i] = net.JoinHostPort(server, "53")
|
||||||
|
}
|
||||||
|
}
|
||||||
return &UPStream{servers: servers}
|
return &UPStream{servers: servers}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ import (
|
|||||||
_ "github.com/nadoo/glider/proxy/kcp"
|
_ "github.com/nadoo/glider/proxy/kcp"
|
||||||
_ "github.com/nadoo/glider/proxy/mixed"
|
_ "github.com/nadoo/glider/proxy/mixed"
|
||||||
_ "github.com/nadoo/glider/proxy/obfs"
|
_ "github.com/nadoo/glider/proxy/obfs"
|
||||||
|
_ "github.com/nadoo/glider/proxy/pxyproto"
|
||||||
_ "github.com/nadoo/glider/proxy/reject"
|
_ "github.com/nadoo/glider/proxy/reject"
|
||||||
|
_ "github.com/nadoo/glider/proxy/smux"
|
||||||
_ "github.com/nadoo/glider/proxy/socks4"
|
_ "github.com/nadoo/glider/proxy/socks4"
|
||||||
_ "github.com/nadoo/glider/proxy/socks5"
|
_ "github.com/nadoo/glider/proxy/socks5"
|
||||||
_ "github.com/nadoo/glider/proxy/ss"
|
_ "github.com/nadoo/glider/proxy/ss"
|
||||||
|
@ -6,5 +6,7 @@ import (
|
|||||||
|
|
||||||
// comment out the protocols you don't need to make the compiled binary smaller.
|
// comment out the protocols you don't need to make the compiled binary smaller.
|
||||||
_ "github.com/nadoo/glider/proxy/redir"
|
_ "github.com/nadoo/glider/proxy/redir"
|
||||||
|
_ "github.com/nadoo/glider/proxy/tproxy"
|
||||||
_ "github.com/nadoo/glider/proxy/unix"
|
_ "github.com/nadoo/glider/proxy/unix"
|
||||||
|
_ "github.com/nadoo/glider/proxy/vsock"
|
||||||
)
|
)
|
||||||
|
38
go.mod
38
go.mod
@ -1,29 +1,29 @@
|
|||||||
module github.com/nadoo/glider
|
module github.com/nadoo/glider
|
||||||
|
|
||||||
go 1.15
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
|
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
|
||||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
|
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
|
||||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
|
github.com/nadoo/conflag v0.3.1
|
||||||
github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12 // indirect
|
github.com/nadoo/ipset v0.5.0
|
||||||
github.com/nadoo/conflag v0.2.3
|
github.com/xtaci/kcp-go/v5 v5.6.18
|
||||||
github.com/nadoo/ipset v0.3.0
|
golang.org/x/crypto v0.35.0
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
golang.org/x/sys v0.30.0
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1
|
|
||||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c
|
|
||||||
golang.org/x/mod v0.4.0 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435 // indirect
|
|
||||||
golang.org/x/tools v0.0.0-20201204062850-545788942d5f // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Replace dependency modules with local developing copy
|
require (
|
||||||
// use `go list -m all` to confirm the final module used
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
||||||
// replace (
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
// github.com/nadoo/conflag => ../conflag
|
github.com/klauspost/reedsolomon v1.12.4 // indirect
|
||||||
// )
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
|
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
)
|
||||||
|
215
go.sum
215
go.sum
@ -1,5 +1,10 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
||||||
@ -10,151 +15,115 @@ github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W
|
|||||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw=
|
||||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
github.com/nadoo/conflag v0.3.1/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
|
||||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
|
||||||
github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54=
|
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
||||||
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
|
|
||||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
|
||||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
|
||||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
|
||||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
|
||||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
|
||||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
|
|
||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
|
||||||
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLUjSSJMU+fW+RDMstRdmnDWj9Q+AsA=
|
|
||||||
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
|
|
||||||
github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12 h1:JJvkIBIdkzz71+2UD6CHfjDC2O3fCZJ98KUaB70gr00=
|
|
||||||
github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI=
|
|
||||||
github.com/nadoo/conflag v0.2.3 h1:/+rTaN0bHTIiQbPl1WZK78JRoqjlNqJ9Zf05ep0o5jI=
|
|
||||||
github.com/nadoo/conflag v0.2.3/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
|
|
||||||
github.com/nadoo/ipset v0.3.0 h1:TgULgp4s2PI3ItoCykDzMp8R49fRhMUNoUUEahERr5o=
|
|
||||||
github.com/nadoo/ipset v0.3.0/go.mod h1:ugJe3mH5N1UNQbXayGJnLEMALeiwCJYo49Wg4MnZTHU=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||||
github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo=
|
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
||||||
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
|
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
|
||||||
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
|
github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
|
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
|
||||||
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k=
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk=
|
|
||||||
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c=
|
|
||||||
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201204062850-545788942d5f h1:xZehGf1UH366KQnttgsBQf+bkEpQSVfG4AJX7EIxXAY=
|
|
||||||
golang.org/x/tools v0.0.0-20201204062850-545788942d5f/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package ipset
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -21,32 +21,30 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create ipset, avoid redundant.
|
|
||||||
sets := make(map[string]struct{})
|
|
||||||
for _, r := range rules {
|
|
||||||
if r.IPSet != "" {
|
|
||||||
sets[r.IPSet] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for set := range sets {
|
|
||||||
ipset.Create(set)
|
|
||||||
ipset.Flush(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// init ipset
|
|
||||||
m := &Manager{}
|
m := &Manager{}
|
||||||
|
sets := make(map[string]struct{})
|
||||||
|
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
if r.IPSet != "" {
|
if r.IPSet == "" {
|
||||||
for _, domain := range r.Domain {
|
continue
|
||||||
m.domainSet.Store(domain, r.IPSet)
|
}
|
||||||
}
|
|
||||||
for _, ip := range r.IP {
|
if _, ok := sets[r.IPSet]; !ok {
|
||||||
ipset.Add(r.IPSet, ip)
|
sets[r.IPSet] = struct{}{}
|
||||||
}
|
ipset.Create(r.IPSet)
|
||||||
for _, cidr := range r.CIDR {
|
ipset.Flush(r.IPSet)
|
||||||
ipset.Add(r.IPSet, cidr)
|
ipset.Create(r.IPSet+"6", ipset.OptIPv6())
|
||||||
}
|
ipset.Flush(r.IPSet + "6")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range r.Domain {
|
||||||
|
m.domainSet.Store(domain, r.IPSet)
|
||||||
|
}
|
||||||
|
for _, ip := range r.IP {
|
||||||
|
addToSet(r.IPSet, ip)
|
||||||
|
}
|
||||||
|
for _, cidr := range r.CIDR {
|
||||||
|
addToSet(r.IPSet, cidr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,18 +52,27 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule.
|
// AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule.
|
||||||
func (m *Manager) AddDomainIP(domain, ip string) error {
|
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
|
||||||
if domain == "" || ip == "" {
|
|
||||||
return errors.New("please specify the domain and ip address")
|
|
||||||
}
|
|
||||||
|
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
for i := len(domain); i != -1; {
|
for i := len(domain); i != -1; {
|
||||||
i = strings.LastIndexByte(domain[:i], '.')
|
i = strings.LastIndexByte(domain[:i], '.')
|
||||||
if setName, ok := m.domainSet.Load(domain[i+1:]); ok {
|
if setName, ok := m.domainSet.Load(domain[i+1:]); ok {
|
||||||
ipset.Add(setName.(string), ip)
|
addAddrToSet(setName.(string), ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToSet(s, item string) error {
|
||||||
|
if strings.IndexByte(item, '.') == -1 {
|
||||||
|
return ipset.Add(s+"6", item)
|
||||||
|
}
|
||||||
|
return ipset.Add(s, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAddrToSet(s string, ip netip.Addr) error {
|
||||||
|
if ip.Is4() {
|
||||||
|
return ipset.AddAddr(s, ip)
|
||||||
|
}
|
||||||
|
return ipset.AddAddr(s+"6", ip)
|
||||||
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// +build !linux
|
//go:build !linux
|
||||||
|
|
||||||
package ipset
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/nadoo/glider/rule"
|
"github.com/nadoo/glider/rule"
|
||||||
)
|
)
|
||||||
@ -17,6 +18,6 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddDomainIP implements the DNSAnswerHandler function
|
// AddDomainIP implements the DNSAnswerHandler function
|
||||||
func (m *Manager) AddDomainIP(domain, ip string) error {
|
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
|
||||||
return errors.New("ipset not supported on this os")
|
return errors.New("ipset not supported on this os")
|
||||||
}
|
}
|
||||||
|
35
log/log.go
35
log/log.go
@ -1,35 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
stdlog "log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// F is the main log function.
|
|
||||||
var F = func(string, ...interface{}) {}
|
|
||||||
|
|
||||||
// Debugf prints debug log.
|
|
||||||
func Debugf(format string, v ...interface{}) {
|
|
||||||
stdlog.SetFlags(stdlog.LstdFlags | stdlog.Lshortfile)
|
|
||||||
stdlog.Output(2, fmt.Sprintf(format, v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print prints log.
|
|
||||||
func Print(v ...interface{}) {
|
|
||||||
stdlog.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf prints log.
|
|
||||||
func Printf(format string, v ...interface{}) {
|
|
||||||
stdlog.Printf(format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal log and exit.
|
|
||||||
func Fatal(v ...interface{}) {
|
|
||||||
stdlog.Fatal(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf log and exit.
|
|
||||||
func Fatalf(f string, v ...interface{}) {
|
|
||||||
stdlog.Fatalf(f, v...)
|
|
||||||
}
|
|
21
main.go
21
main.go
@ -5,20 +5,19 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/dns"
|
"github.com/nadoo/glider/dns"
|
||||||
"github.com/nadoo/glider/ipset"
|
"github.com/nadoo/glider/ipset"
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/rule"
|
"github.com/nadoo/glider/rule"
|
||||||
"github.com/nadoo/glider/service"
|
"github.com/nadoo/glider/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "0.13.0"
|
version = "0.17.0"
|
||||||
config = parseConfig()
|
config = parseConfig()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,8 +37,8 @@ func main() {
|
|||||||
|
|
||||||
// rules
|
// rules
|
||||||
for _, r := range config.rules {
|
for _, r := range config.rules {
|
||||||
for _, domain := range r.Domain {
|
if len(r.DNSServers) > 0 {
|
||||||
if len(r.DNSServers) > 0 {
|
for _, domain := range r.Domain {
|
||||||
d.SetServers(domain, r.DNSServers)
|
d.SetServers(domain, r.DNSServers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,6 +62,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, r := range config.rules {
|
||||||
|
r.IP, r.CIDR, r.Domain = nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// enable checkers
|
// enable checkers
|
||||||
pxy.Check()
|
pxy.Check()
|
||||||
|
|
||||||
@ -72,14 +75,16 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go local.ListenAndServe()
|
go local.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
// run services
|
// run services
|
||||||
for _, s := range config.Services {
|
for _, s := range config.Services {
|
||||||
args := strings.Split(s, ",")
|
service, err := service.New(s)
|
||||||
go service.Run(args[0], args[1:]...)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
go service.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
41
pkg/log/log.go
Normal file
41
pkg/log/log.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdlog "log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var enable = false
|
||||||
|
|
||||||
|
// Set sets the logger's verbose mode and output flags.
|
||||||
|
func Set(verbose bool, flag int) {
|
||||||
|
enable = verbose
|
||||||
|
stdlog.SetFlags(flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// F prints debug log.
|
||||||
|
func F(f string, v ...any) {
|
||||||
|
if enable {
|
||||||
|
stdlog.Output(2, fmt.Sprintf(f, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print prints log.
|
||||||
|
func Print(v ...any) {
|
||||||
|
stdlog.Print(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf prints log.
|
||||||
|
func Printf(f string, v ...any) {
|
||||||
|
stdlog.Printf(f, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal log and exit.
|
||||||
|
func Fatal(v ...any) {
|
||||||
|
stdlog.Fatal(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf log and exit.
|
||||||
|
func Fatalf(f string, v ...any) {
|
||||||
|
stdlog.Fatalf(f, v...)
|
||||||
|
}
|
@ -3,6 +3,7 @@ package pool
|
|||||||
import (
|
import (
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"sync"
|
"sync"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -17,11 +18,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for i := 0; i < num; i++ {
|
for i := range num {
|
||||||
size := 1 << i
|
size := 1 << i
|
||||||
sizes[i] = size
|
sizes[i] = size
|
||||||
pools[i].New = func() interface{} {
|
pools[i].New = func() any {
|
||||||
return make([]byte, size)
|
buf := make([]byte, size)
|
||||||
|
return unsafe.SliceData(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,11 +32,10 @@ func init() {
|
|||||||
// otherwise, this function will call make([]byte, size) directly.
|
// otherwise, this function will call make([]byte, size) directly.
|
||||||
func GetBuffer(size int) []byte {
|
func GetBuffer(size int) []byte {
|
||||||
if size >= 1 && size <= maxsize {
|
if size >= 1 && size <= maxsize {
|
||||||
i := bits.Len32(uint32(size)) - 1
|
i := bits.Len32(uint32(size - 1))
|
||||||
if sizes[i] < size {
|
if p := pools[i].Get().(*byte); p != nil {
|
||||||
i += 1
|
return unsafe.Slice(p, 1<<i)[:size]
|
||||||
}
|
}
|
||||||
return pools[i].Get().([]byte)[:size]
|
|
||||||
}
|
}
|
||||||
return make([]byte, size)
|
return make([]byte, size)
|
||||||
}
|
}
|
||||||
@ -42,9 +43,9 @@ func GetBuffer(size int) []byte {
|
|||||||
// PutBuffer puts a buffer into pool.
|
// PutBuffer puts a buffer into pool.
|
||||||
func PutBuffer(buf []byte) {
|
func PutBuffer(buf []byte) {
|
||||||
if size := cap(buf); size >= 1 && size <= maxsize {
|
if size := cap(buf); size >= 1 && size <= maxsize {
|
||||||
i := bits.Len32(uint32(size)) - 1
|
i := bits.Len32(uint32(size - 1))
|
||||||
if sizes[i] == size {
|
if sizes[i] == size {
|
||||||
pools[i].Put(buf)
|
pools[i].Put(unsafe.SliceData(buf))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var bytesBufPool = sync.Pool{
|
var bytesBufPool = sync.Pool{
|
||||||
New: func() interface{} { return &bytes.Buffer{} },
|
New: func() any { return &bytes.Buffer{} },
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBytesBuffer returns a bytes.buffer from pool.
|
// GetBytesBuffer returns a bytes.buffer from pool.
|
||||||
@ -16,10 +16,8 @@ func GetBytesBuffer() *bytes.Buffer {
|
|||||||
|
|
||||||
// PutBytesBuffer puts a bytes.buffer into pool.
|
// PutBytesBuffer puts a bytes.buffer into pool.
|
||||||
func PutBytesBuffer(buf *bytes.Buffer) {
|
func PutBytesBuffer(buf *bytes.Buffer) {
|
||||||
if buf.Cap() > 64<<10 {
|
if buf.Cap() <= 64<<10 {
|
||||||
return
|
buf.Reset()
|
||||||
|
bytesBufPool.Put(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
|
||||||
bytesBufPool.Put(buf)
|
|
||||||
}
|
}
|
106
pkg/smux/frame.go
Normal file
106
pkg/smux/frame.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 xtaci
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
package smux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ( // cmds
|
||||||
|
// protocol version 1:
|
||||||
|
cmdSYN byte = iota // stream open
|
||||||
|
cmdFIN // stream close, a.k.a EOF mark
|
||||||
|
cmdPSH // data push
|
||||||
|
cmdNOP // no operation
|
||||||
|
|
||||||
|
// protocol version 2 extra commands
|
||||||
|
// notify bytes consumed by remote peer-end
|
||||||
|
cmdUPD
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// data size of cmdUPD, format:
|
||||||
|
// |4B data consumed(ACK)| 4B window size(WINDOW) |
|
||||||
|
szCmdUPD = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// initial peer window guess, a slow-start
|
||||||
|
initialPeerWindow = 262144
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sizeOfVer = 1
|
||||||
|
sizeOfCmd = 1
|
||||||
|
sizeOfLength = 2
|
||||||
|
sizeOfSid = 4
|
||||||
|
headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength
|
||||||
|
)
|
||||||
|
|
||||||
|
// Frame defines a packet from or to be multiplexed into a single connection
|
||||||
|
type Frame struct {
|
||||||
|
ver byte // version
|
||||||
|
cmd byte // command
|
||||||
|
sid uint32 // stream id
|
||||||
|
data []byte // payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFrame creates a new frame with given version, command and stream id
|
||||||
|
func newFrame(version byte, cmd byte, sid uint32) Frame {
|
||||||
|
return Frame{ver: version, cmd: cmd, sid: sid}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawHeader is a byte array representation of Frame header
|
||||||
|
type rawHeader [headerSize]byte
|
||||||
|
|
||||||
|
func (h rawHeader) Version() byte {
|
||||||
|
return h[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h rawHeader) Cmd() byte {
|
||||||
|
return h[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h rawHeader) Length() uint16 {
|
||||||
|
return binary.LittleEndian.Uint16(h[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h rawHeader) StreamID() uint32 {
|
||||||
|
return binary.LittleEndian.Uint32(h[4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h rawHeader) String() string {
|
||||||
|
return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d",
|
||||||
|
h.Version(), h.Cmd(), h.StreamID(), h.Length())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updHeader is a byte array representation of cmdUPD
|
||||||
|
type updHeader [szCmdUPD]byte
|
||||||
|
|
||||||
|
func (h updHeader) Consumed() uint32 {
|
||||||
|
return binary.LittleEndian.Uint32(h[:])
|
||||||
|
}
|
||||||
|
func (h updHeader) Window() uint32 {
|
||||||
|
return binary.LittleEndian.Uint32(h[4:])
|
||||||
|
}
|
128
pkg/smux/mux.go
Normal file
128
pkg/smux/mux.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 xtaci
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
package smux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is used to tune the Smux session
|
||||||
|
type Config struct {
|
||||||
|
// SMUX Protocol version, support 1,2
|
||||||
|
Version int
|
||||||
|
|
||||||
|
// Disabled keepalive
|
||||||
|
KeepAliveDisabled bool
|
||||||
|
|
||||||
|
// KeepAliveInterval is how often to send a NOP command to the remote
|
||||||
|
KeepAliveInterval time.Duration
|
||||||
|
|
||||||
|
// KeepAliveTimeout is how long the session
|
||||||
|
// will be closed if no data has arrived
|
||||||
|
KeepAliveTimeout time.Duration
|
||||||
|
|
||||||
|
// MaxFrameSize is used to control the maximum
|
||||||
|
// frame size to sent to the remote
|
||||||
|
MaxFrameSize int
|
||||||
|
|
||||||
|
// MaxReceiveBuffer is used to control the maximum
|
||||||
|
// number of data in the buffer pool
|
||||||
|
MaxReceiveBuffer int
|
||||||
|
|
||||||
|
// MaxStreamBuffer is used to control the maximum
|
||||||
|
// number of data per stream
|
||||||
|
MaxStreamBuffer int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig is used to return a default configuration
|
||||||
|
func DefaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
Version: 1,
|
||||||
|
KeepAliveInterval: 10 * time.Second,
|
||||||
|
KeepAliveTimeout: 30 * time.Second,
|
||||||
|
MaxFrameSize: 32768,
|
||||||
|
MaxReceiveBuffer: 4194304,
|
||||||
|
MaxStreamBuffer: 65536,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyConfig is used to verify the sanity of configuration
|
||||||
|
func VerifyConfig(config *Config) error {
|
||||||
|
if !(config.Version == 1 || config.Version == 2) {
|
||||||
|
return errors.New("unsupported protocol version")
|
||||||
|
}
|
||||||
|
if !config.KeepAliveDisabled {
|
||||||
|
if config.KeepAliveInterval == 0 {
|
||||||
|
return errors.New("keep-alive interval must be positive")
|
||||||
|
}
|
||||||
|
if config.KeepAliveTimeout < config.KeepAliveInterval {
|
||||||
|
return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.MaxFrameSize <= 0 {
|
||||||
|
return errors.New("max frame size must be positive")
|
||||||
|
}
|
||||||
|
if config.MaxFrameSize > 65535 {
|
||||||
|
return errors.New("max frame size must not be larger than 65535")
|
||||||
|
}
|
||||||
|
if config.MaxReceiveBuffer <= 0 {
|
||||||
|
return errors.New("max receive buffer must be positive")
|
||||||
|
}
|
||||||
|
if config.MaxStreamBuffer <= 0 {
|
||||||
|
return errors.New("max stream buffer must be positive")
|
||||||
|
}
|
||||||
|
if config.MaxStreamBuffer > config.MaxReceiveBuffer {
|
||||||
|
return errors.New("max stream buffer must not be larger than max receive buffer")
|
||||||
|
}
|
||||||
|
if config.MaxStreamBuffer > math.MaxInt32 {
|
||||||
|
return errors.New("max stream buffer cannot be larger than 2147483647")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is used to initialize a new server-side connection.
|
||||||
|
func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
if err := VerifyConfig(config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newSession(config, conn, false), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is used to initialize a new client-side connection.
|
||||||
|
func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := VerifyConfig(config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newSession(config, conn, true), nil
|
||||||
|
}
|
621
pkg/smux/session.go
Normal file
621
pkg/smux/session.go
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 xtaci
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
package smux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/heap"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAcceptBacklog = 1024
|
||||||
|
maxShaperSize = 1024
|
||||||
|
openCloseTimeout = 30 * time.Second // Timeout for opening/closing streams
|
||||||
|
)
|
||||||
|
|
||||||
|
// CLASSID represents the class of a frame
|
||||||
|
type CLASSID int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CLSCTRL CLASSID = iota // prioritized control signal
|
||||||
|
CLSDATA
|
||||||
|
)
|
||||||
|
|
||||||
|
// timeoutError representing timeouts for operations such as accept, read and write
|
||||||
|
//
|
||||||
|
// To better cooperate with the standard library, timeoutError should implement the standard library's `net.Error`.
|
||||||
|
//
|
||||||
|
// For example, using smux to implement net.Listener and work with http.Server, the keep-alive connection (*smux.Stream) will be unexpectedly closed.
|
||||||
|
// For more details, see https://github.com/xtaci/smux/pull/99.
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (timeoutError) Error() string { return "timeout" }
|
||||||
|
func (timeoutError) Temporary() bool { return true }
|
||||||
|
func (timeoutError) Timeout() bool { return true }
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidProtocol = errors.New("invalid protocol")
|
||||||
|
ErrConsumed = errors.New("peer consumed more than sent")
|
||||||
|
ErrGoAway = errors.New("stream id overflows, should start a new connection")
|
||||||
|
ErrTimeout net.Error = &timeoutError{}
|
||||||
|
ErrWouldBlock = errors.New("operation would block on IO")
|
||||||
|
)
|
||||||
|
|
||||||
|
// writeRequest represents a request to write a frame
|
||||||
|
type writeRequest struct {
|
||||||
|
class CLASSID
|
||||||
|
frame Frame
|
||||||
|
seq uint32
|
||||||
|
result chan writeResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeResult represents the result of a write request
|
||||||
|
type writeResult struct {
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session defines a multiplexed connection for streams
|
||||||
|
type Session struct {
|
||||||
|
conn io.ReadWriteCloser
|
||||||
|
|
||||||
|
config *Config
|
||||||
|
nextStreamID uint32 // next stream identifier
|
||||||
|
nextStreamIDLock sync.Mutex
|
||||||
|
|
||||||
|
bucket int32 // token bucket
|
||||||
|
bucketNotify chan struct{} // used for waiting for tokens
|
||||||
|
|
||||||
|
streams map[uint32]*stream // all streams in this session
|
||||||
|
streamLock sync.Mutex // locks streams
|
||||||
|
|
||||||
|
die chan struct{} // flag session has died
|
||||||
|
dieOnce sync.Once
|
||||||
|
|
||||||
|
// socket error handling
|
||||||
|
socketReadError atomic.Value
|
||||||
|
socketWriteError atomic.Value
|
||||||
|
chSocketReadError chan struct{}
|
||||||
|
chSocketWriteError chan struct{}
|
||||||
|
socketReadErrorOnce sync.Once
|
||||||
|
socketWriteErrorOnce sync.Once
|
||||||
|
|
||||||
|
// smux protocol errors
|
||||||
|
protoError atomic.Value
|
||||||
|
chProtoError chan struct{}
|
||||||
|
protoErrorOnce sync.Once
|
||||||
|
|
||||||
|
chAccepts chan *stream
|
||||||
|
|
||||||
|
dataReady int32 // flag data has arrived
|
||||||
|
|
||||||
|
goAway int32 // flag id exhausted
|
||||||
|
|
||||||
|
deadline atomic.Value
|
||||||
|
|
||||||
|
requestID uint32 // Monotonic increasing write request ID
|
||||||
|
shaper chan writeRequest // a shaper for writing
|
||||||
|
writes chan writeRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
|
||||||
|
s := new(Session)
|
||||||
|
s.die = make(chan struct{})
|
||||||
|
s.conn = conn
|
||||||
|
s.config = config
|
||||||
|
s.streams = make(map[uint32]*stream)
|
||||||
|
s.chAccepts = make(chan *stream, defaultAcceptBacklog)
|
||||||
|
s.bucket = int32(config.MaxReceiveBuffer)
|
||||||
|
s.bucketNotify = make(chan struct{}, 1)
|
||||||
|
s.shaper = make(chan writeRequest)
|
||||||
|
s.writes = make(chan writeRequest)
|
||||||
|
s.chSocketReadError = make(chan struct{})
|
||||||
|
s.chSocketWriteError = make(chan struct{})
|
||||||
|
s.chProtoError = make(chan struct{})
|
||||||
|
|
||||||
|
if client {
|
||||||
|
s.nextStreamID = 1
|
||||||
|
} else {
|
||||||
|
s.nextStreamID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.shaperLoop()
|
||||||
|
go s.recvLoop()
|
||||||
|
go s.sendLoop()
|
||||||
|
if !config.KeepAliveDisabled {
|
||||||
|
go s.keepalive()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenStream is used to create a new stream
|
||||||
|
func (s *Session) OpenStream() (*Stream, error) {
|
||||||
|
if s.IsClosed() {
|
||||||
|
return nil, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate stream id
|
||||||
|
s.nextStreamIDLock.Lock()
|
||||||
|
if s.goAway > 0 {
|
||||||
|
s.nextStreamIDLock.Unlock()
|
||||||
|
return nil, ErrGoAway
|
||||||
|
}
|
||||||
|
|
||||||
|
s.nextStreamID += 2
|
||||||
|
sid := s.nextStreamID
|
||||||
|
if sid == sid%2 { // stream-id overflows
|
||||||
|
s.goAway = 1
|
||||||
|
s.nextStreamIDLock.Unlock()
|
||||||
|
return nil, ErrGoAway
|
||||||
|
}
|
||||||
|
s.nextStreamIDLock.Unlock()
|
||||||
|
|
||||||
|
stream := newStream(sid, s.config.MaxFrameSize, s)
|
||||||
|
|
||||||
|
if _, err := s.writeControlFrame(newFrame(byte(s.config.Version), cmdSYN, sid)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.streamLock.Lock()
|
||||||
|
defer s.streamLock.Unlock()
|
||||||
|
select {
|
||||||
|
case <-s.chSocketReadError:
|
||||||
|
return nil, s.socketReadError.Load().(error)
|
||||||
|
case <-s.chSocketWriteError:
|
||||||
|
return nil, s.socketWriteError.Load().(error)
|
||||||
|
case <-s.die:
|
||||||
|
return nil, io.ErrClosedPipe
|
||||||
|
default:
|
||||||
|
s.streams[sid] = stream
|
||||||
|
wrapper := &Stream{stream: stream}
|
||||||
|
// NOTE(x): disabled finalizer for issue #997
|
||||||
|
/*
|
||||||
|
runtime.SetFinalizer(wrapper, func(s *Stream) {
|
||||||
|
s.Close()
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a generic ReadWriteCloser
|
||||||
|
func (s *Session) Open() (io.ReadWriteCloser, error) {
|
||||||
|
return s.OpenStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptStream is used to block until the next available stream
|
||||||
|
// is ready to be accepted.
|
||||||
|
func (s *Session) AcceptStream() (*Stream, error) {
|
||||||
|
var deadline <-chan time.Time
|
||||||
|
if d, ok := s.deadline.Load().(time.Time); ok && !d.IsZero() {
|
||||||
|
timer := time.NewTimer(time.Until(d))
|
||||||
|
defer timer.Stop()
|
||||||
|
deadline = timer.C
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case stream := <-s.chAccepts:
|
||||||
|
wrapper := &Stream{stream: stream}
|
||||||
|
runtime.SetFinalizer(wrapper, func(s *Stream) {
|
||||||
|
s.Close()
|
||||||
|
})
|
||||||
|
return wrapper, nil
|
||||||
|
case <-deadline:
|
||||||
|
return nil, ErrTimeout
|
||||||
|
case <-s.chSocketReadError:
|
||||||
|
return nil, s.socketReadError.Load().(error)
|
||||||
|
case <-s.chProtoError:
|
||||||
|
return nil, s.protoError.Load().(error)
|
||||||
|
case <-s.die:
|
||||||
|
return nil, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept Returns a generic ReadWriteCloser instead of smux.Stream
|
||||||
|
func (s *Session) Accept() (io.ReadWriteCloser, error) {
|
||||||
|
return s.AcceptStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is used to close the session and all streams.
|
||||||
|
func (s *Session) Close() error {
|
||||||
|
var once bool
|
||||||
|
s.dieOnce.Do(func() {
|
||||||
|
close(s.die)
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
|
||||||
|
if once {
|
||||||
|
s.streamLock.Lock()
|
||||||
|
for k := range s.streams {
|
||||||
|
s.streams[k].sessionClose()
|
||||||
|
}
|
||||||
|
s.streamLock.Unlock()
|
||||||
|
return s.conn.Close()
|
||||||
|
} else {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseChan can be used by someone who wants to be notified immediately when this
|
||||||
|
// session is closed
|
||||||
|
func (s *Session) CloseChan() <-chan struct{} {
|
||||||
|
return s.die
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifyBucket notifies recvLoop that bucket is available
|
||||||
|
func (s *Session) notifyBucket() {
|
||||||
|
select {
|
||||||
|
case s.bucketNotify <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) notifyReadError(err error) {
|
||||||
|
s.socketReadErrorOnce.Do(func() {
|
||||||
|
s.socketReadError.Store(err)
|
||||||
|
close(s.chSocketReadError)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) notifyWriteError(err error) {
|
||||||
|
s.socketWriteErrorOnce.Do(func() {
|
||||||
|
s.socketWriteError.Store(err)
|
||||||
|
close(s.chSocketWriteError)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) notifyProtoError(err error) {
|
||||||
|
s.protoErrorOnce.Do(func() {
|
||||||
|
s.protoError.Store(err)
|
||||||
|
close(s.chProtoError)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed does a safe check to see if we have shutdown
|
||||||
|
func (s *Session) IsClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-s.die:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumStreams returns the number of currently open streams
|
||||||
|
func (s *Session) NumStreams() int {
|
||||||
|
if s.IsClosed() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
s.streamLock.Lock()
|
||||||
|
defer s.streamLock.Unlock()
|
||||||
|
return len(s.streams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline sets a deadline used by Accept* calls.
|
||||||
|
// A zero time value disables the deadline.
|
||||||
|
func (s *Session) SetDeadline(t time.Time) error {
|
||||||
|
s.deadline.Store(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr satisfies net.Conn interface
|
||||||
|
func (s *Session) LocalAddr() net.Addr {
|
||||||
|
if ts, ok := s.conn.(interface {
|
||||||
|
LocalAddr() net.Addr
|
||||||
|
}); ok {
|
||||||
|
return ts.LocalAddr()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr satisfies net.Conn interface
|
||||||
|
func (s *Session) RemoteAddr() net.Addr {
|
||||||
|
if ts, ok := s.conn.(interface {
|
||||||
|
RemoteAddr() net.Addr
|
||||||
|
}); ok {
|
||||||
|
return ts.RemoteAddr()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify the session that a stream has closed
|
||||||
|
func (s *Session) streamClosed(sid uint32) {
|
||||||
|
s.streamLock.Lock()
|
||||||
|
if stream, ok := s.streams[sid]; ok {
|
||||||
|
n := stream.recycleTokens()
|
||||||
|
if n > 0 { // return remaining tokens to the bucket
|
||||||
|
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
|
||||||
|
s.notifyBucket()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(s.streams, sid)
|
||||||
|
}
|
||||||
|
s.streamLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// returnTokens is called by stream to return token after read
|
||||||
|
func (s *Session) returnTokens(n int) {
|
||||||
|
if atomic.AddInt32(&s.bucket, int32(n)) > 0 {
|
||||||
|
s.notifyBucket()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recvLoop keeps on reading from underlying connection if tokens are available
|
||||||
|
func (s *Session) recvLoop() {
|
||||||
|
var hdr rawHeader
|
||||||
|
var updHdr updHeader
|
||||||
|
|
||||||
|
for {
|
||||||
|
for atomic.LoadInt32(&s.bucket) <= 0 && !s.IsClosed() {
|
||||||
|
select {
|
||||||
|
case <-s.bucketNotify:
|
||||||
|
case <-s.die:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read header first
|
||||||
|
if _, err := io.ReadFull(s.conn, hdr[:]); err == nil {
|
||||||
|
atomic.StoreInt32(&s.dataReady, 1)
|
||||||
|
if hdr.Version() != byte(s.config.Version) {
|
||||||
|
s.notifyProtoError(ErrInvalidProtocol)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sid := hdr.StreamID()
|
||||||
|
switch hdr.Cmd() {
|
||||||
|
case cmdNOP:
|
||||||
|
case cmdSYN: // stream opening
|
||||||
|
s.streamLock.Lock()
|
||||||
|
if _, ok := s.streams[sid]; !ok {
|
||||||
|
stream := newStream(sid, s.config.MaxFrameSize, s)
|
||||||
|
s.streams[sid] = stream
|
||||||
|
select {
|
||||||
|
case s.chAccepts <- stream:
|
||||||
|
case <-s.die:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.streamLock.Unlock()
|
||||||
|
case cmdFIN: // stream closing
|
||||||
|
s.streamLock.Lock()
|
||||||
|
if stream, ok := s.streams[sid]; ok {
|
||||||
|
stream.fin()
|
||||||
|
stream.notifyReadEvent()
|
||||||
|
}
|
||||||
|
s.streamLock.Unlock()
|
||||||
|
case cmdPSH: // data frame
|
||||||
|
if hdr.Length() > 0 {
|
||||||
|
newbuf := pool.GetBuffer(int(hdr.Length()))
|
||||||
|
if written, err := io.ReadFull(s.conn, newbuf); err == nil {
|
||||||
|
s.streamLock.Lock()
|
||||||
|
if stream, ok := s.streams[sid]; ok {
|
||||||
|
stream.pushBytes(newbuf)
|
||||||
|
// a stream used some token
|
||||||
|
atomic.AddInt32(&s.bucket, -int32(written))
|
||||||
|
stream.notifyReadEvent()
|
||||||
|
} else {
|
||||||
|
// data directed to a missing/closed stream, recycle the buffer immediately.
|
||||||
|
pool.PutBuffer(newbuf)
|
||||||
|
}
|
||||||
|
s.streamLock.Unlock()
|
||||||
|
} else {
|
||||||
|
s.notifyReadError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case cmdUPD: // a window update signal
|
||||||
|
if _, err := io.ReadFull(s.conn, updHdr[:]); err == nil {
|
||||||
|
s.streamLock.Lock()
|
||||||
|
if stream, ok := s.streams[sid]; ok {
|
||||||
|
stream.update(updHdr.Consumed(), updHdr.Window())
|
||||||
|
}
|
||||||
|
s.streamLock.Unlock()
|
||||||
|
} else {
|
||||||
|
s.notifyReadError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s.notifyProtoError(ErrInvalidProtocol)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.notifyReadError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keepalive sends NOP frame to peer to keep the connection alive, and detect dead peers
|
||||||
|
func (s *Session) keepalive() {
|
||||||
|
tickerPing := time.NewTicker(s.config.KeepAliveInterval)
|
||||||
|
tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
|
||||||
|
defer tickerPing.Stop()
|
||||||
|
defer tickerTimeout.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tickerPing.C:
|
||||||
|
s.writeFrameInternal(newFrame(byte(s.config.Version), cmdNOP, 0), tickerPing.C, CLSCTRL)
|
||||||
|
s.notifyBucket() // force a signal to the recvLoop
|
||||||
|
case <-tickerTimeout.C:
|
||||||
|
if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
|
||||||
|
// recvLoop may block while bucket is 0, in this case,
|
||||||
|
// session should not be closed.
|
||||||
|
if atomic.LoadInt32(&s.bucket) > 0 {
|
||||||
|
s.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-s.die:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shaperLoop implements a priority queue for write requests,
|
||||||
|
// some control messages are prioritized over data messages
|
||||||
|
func (s *Session) shaperLoop() {
|
||||||
|
var reqs shaperHeap
|
||||||
|
var next writeRequest
|
||||||
|
var chWrite chan writeRequest
|
||||||
|
var chShaper chan writeRequest
|
||||||
|
|
||||||
|
for {
|
||||||
|
// chWrite is not available until it has packet to send
|
||||||
|
if len(reqs) > 0 {
|
||||||
|
chWrite = s.writes
|
||||||
|
next = heap.Pop(&reqs).(writeRequest)
|
||||||
|
} else {
|
||||||
|
chWrite = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// control heap size, chShaper is not available until packets are less than maximum allowed
|
||||||
|
if len(reqs) >= maxShaperSize {
|
||||||
|
chShaper = nil
|
||||||
|
} else {
|
||||||
|
chShaper = s.shaper
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertion on non nil
|
||||||
|
if chShaper == nil && chWrite == nil {
|
||||||
|
panic("both channel are nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.die:
|
||||||
|
return
|
||||||
|
case r := <-chShaper:
|
||||||
|
if chWrite != nil { // next is valid, reshape
|
||||||
|
heap.Push(&reqs, next)
|
||||||
|
}
|
||||||
|
heap.Push(&reqs, r)
|
||||||
|
case chWrite <- next:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendLoop sends frames to the underlying connection
|
||||||
|
func (s *Session) sendLoop() {
|
||||||
|
var buf []byte
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
var vec [][]byte // vector for writeBuffers
|
||||||
|
|
||||||
|
bw, ok := s.conn.(interface {
|
||||||
|
WriteBuffers(v [][]byte) (n int, err error)
|
||||||
|
})
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
buf = make([]byte, headerSize)
|
||||||
|
vec = make([][]byte, 2)
|
||||||
|
} else {
|
||||||
|
buf = make([]byte, (1<<16)+headerSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.die:
|
||||||
|
return
|
||||||
|
case request := <-s.writes:
|
||||||
|
buf[0] = request.frame.ver
|
||||||
|
buf[1] = request.frame.cmd
|
||||||
|
binary.LittleEndian.PutUint16(buf[2:], uint16(len(request.frame.data)))
|
||||||
|
binary.LittleEndian.PutUint32(buf[4:], request.frame.sid)
|
||||||
|
|
||||||
|
// support for scatter-gather I/O
|
||||||
|
if len(vec) > 0 {
|
||||||
|
vec[0] = buf[:headerSize]
|
||||||
|
vec[1] = request.frame.data
|
||||||
|
n, err = bw.WriteBuffers(vec)
|
||||||
|
} else {
|
||||||
|
copy(buf[headerSize:], request.frame.data)
|
||||||
|
n, err = s.conn.Write(buf[:headerSize+len(request.frame.data)])
|
||||||
|
}
|
||||||
|
|
||||||
|
n -= headerSize
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
result := writeResult{
|
||||||
|
n: n,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
request.result <- result
|
||||||
|
close(request.result)
|
||||||
|
|
||||||
|
// store conn error
|
||||||
|
if err != nil {
|
||||||
|
s.notifyWriteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeControlFrame writes the control frame to the underlying connection
|
||||||
|
// and returns the number of bytes written if successful
|
||||||
|
func (s *Session) writeControlFrame(f Frame) (n int, err error) {
|
||||||
|
timer := time.NewTimer(openCloseTimeout)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
return s.writeFrameInternal(f, timer.C, CLSCTRL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal writeFrame version to support deadline used in keepalive
|
||||||
|
func (s *Session) writeFrameInternal(f Frame, deadline <-chan time.Time, class CLASSID) (int, error) {
|
||||||
|
req := writeRequest{
|
||||||
|
class: class,
|
||||||
|
frame: f,
|
||||||
|
seq: atomic.AddUint32(&s.requestID, 1),
|
||||||
|
result: make(chan writeResult, 1),
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case s.shaper <- req:
|
||||||
|
case <-s.die:
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
case <-s.chSocketWriteError:
|
||||||
|
return 0, s.socketWriteError.Load().(error)
|
||||||
|
case <-deadline:
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-req.result:
|
||||||
|
return result.n, result.err
|
||||||
|
case <-s.die:
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
case <-s.chSocketWriteError:
|
||||||
|
return 0, s.socketWriteError.Load().(error)
|
||||||
|
case <-deadline:
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
56
pkg/smux/shaper.go
Normal file
56
pkg/smux/shaper.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 xtaci
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
package smux
|
||||||
|
|
||||||
|
// _itimediff returns the time difference between two uint32 values.
|
||||||
|
// The result is a signed 32-bit integer representing the difference between 'later' and 'earlier'.
|
||||||
|
func _itimediff(later, earlier uint32) int32 {
|
||||||
|
return (int32)(later - earlier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shaperHeap is a min-heap of writeRequest.
|
||||||
|
// It orders writeRequests by class first, then by sequence number within the same class.
|
||||||
|
type shaperHeap []writeRequest
|
||||||
|
|
||||||
|
func (h shaperHeap) Len() int { return len(h) }
|
||||||
|
|
||||||
|
// Less determines the ordering of elements in the heap.
|
||||||
|
// Requests are ordered by their class first. If two requests have the same class,
|
||||||
|
// they are ordered by their sequence numbers.
|
||||||
|
func (h shaperHeap) Less(i, j int) bool {
|
||||||
|
if h[i].class != h[j].class {
|
||||||
|
return h[i].class < h[j].class
|
||||||
|
}
|
||||||
|
return _itimediff(h[j].seq, h[i].seq) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h shaperHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
|
func (h *shaperHeap) Push(x interface{}) { *h = append(*h, x.(writeRequest)) }
|
||||||
|
|
||||||
|
func (h *shaperHeap) Pop() interface{} {
|
||||||
|
old := *h
|
||||||
|
n := len(old)
|
||||||
|
x := old[n-1]
|
||||||
|
*h = old[0 : n-1]
|
||||||
|
return x
|
||||||
|
}
|
615
pkg/smux/stream.go
Normal file
615
pkg/smux/stream.go
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 xtaci
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
package smux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// wrapper for GC
|
||||||
|
type Stream struct {
|
||||||
|
*stream
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream implements net.Conn
|
||||||
|
type stream struct {
|
||||||
|
id uint32 // Stream identifier
|
||||||
|
sess *Session
|
||||||
|
|
||||||
|
buffers [][]byte // the sequential buffers of stream
|
||||||
|
heads [][]byte // slice heads of the buffers above, kept for recycle
|
||||||
|
|
||||||
|
bufferLock sync.Mutex // Mutex to protect access to buffers
|
||||||
|
frameSize int // Maximum frame size for the stream
|
||||||
|
|
||||||
|
// notify a read event
|
||||||
|
chReadEvent chan struct{}
|
||||||
|
|
||||||
|
// flag the stream has closed
|
||||||
|
die chan struct{}
|
||||||
|
dieOnce sync.Once // Ensures die channel is closed only once
|
||||||
|
|
||||||
|
// FIN command
|
||||||
|
chFinEvent chan struct{}
|
||||||
|
finEventOnce sync.Once // Ensures chFinEvent is closed only once
|
||||||
|
|
||||||
|
// deadlines
|
||||||
|
readDeadline atomic.Value
|
||||||
|
writeDeadline atomic.Value
|
||||||
|
|
||||||
|
// per stream sliding window control
|
||||||
|
numRead uint32 // count num of bytes read
|
||||||
|
numWritten uint32 // count num of bytes written
|
||||||
|
incr uint32 // bytes sent since last window update
|
||||||
|
|
||||||
|
// UPD command
|
||||||
|
peerConsumed uint32 // num of bytes the peer has consumed
|
||||||
|
peerWindow uint32 // peer window, initialized to 256KB, updated by peer
|
||||||
|
chUpdate chan struct{} // notify of remote data consuming and window update
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStream initializes and returns a new Stream.
|
||||||
|
func newStream(id uint32, frameSize int, sess *Session) *stream {
|
||||||
|
s := new(stream)
|
||||||
|
s.id = id
|
||||||
|
s.chReadEvent = make(chan struct{}, 1)
|
||||||
|
s.chUpdate = make(chan struct{}, 1)
|
||||||
|
s.frameSize = frameSize
|
||||||
|
s.sess = sess
|
||||||
|
s.die = make(chan struct{})
|
||||||
|
s.chFinEvent = make(chan struct{})
|
||||||
|
s.peerWindow = initialPeerWindow // set to initial window size
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the stream's unique identifier.
|
||||||
|
func (s *stream) ID() uint32 {
|
||||||
|
return s.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads data from the stream into the provided buffer.
|
||||||
|
func (s *stream) Read(b []byte) (n int, err error) {
|
||||||
|
for {
|
||||||
|
n, err = s.tryRead(b)
|
||||||
|
if err == ErrWouldBlock {
|
||||||
|
if ew := s.waitRead(); ew != nil {
|
||||||
|
return 0, ew
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryRead attempts to read data from the stream without blocking.
|
||||||
|
func (s *stream) tryRead(b []byte) (n int, err error) {
|
||||||
|
if s.sess.config.Version == 2 {
|
||||||
|
return s.tryReadv2(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A critical section to copy data from buffers to
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
if len(s.buffers) > 0 {
|
||||||
|
n = copy(b, s.buffers[0])
|
||||||
|
s.buffers[0] = s.buffers[0][n:]
|
||||||
|
if len(s.buffers[0]) == 0 {
|
||||||
|
s.buffers[0] = nil
|
||||||
|
s.buffers = s.buffers[1:]
|
||||||
|
// full recycle
|
||||||
|
pool.PutBuffer(s.heads[0])
|
||||||
|
s.heads = s.heads[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.bufferLock.Unlock()
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
s.sess.returnTokens(n)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.die:
|
||||||
|
return 0, io.EOF
|
||||||
|
default:
|
||||||
|
return 0, ErrWouldBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryReadv2 is the non-blocking version of Read for version 2 streams.
|
||||||
|
func (s *stream) tryReadv2(b []byte) (n int, err error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var notifyConsumed uint32
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
if len(s.buffers) > 0 {
|
||||||
|
n = copy(b, s.buffers[0])
|
||||||
|
s.buffers[0] = s.buffers[0][n:]
|
||||||
|
if len(s.buffers[0]) == 0 {
|
||||||
|
s.buffers[0] = nil
|
||||||
|
s.buffers = s.buffers[1:]
|
||||||
|
// full recycle
|
||||||
|
pool.PutBuffer(s.heads[0])
|
||||||
|
s.heads = s.heads[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in an ideal environment:
|
||||||
|
// if more than half of buffer has consumed, send read ack to peer
|
||||||
|
// based on round-trip time of ACK, continous flowing data
|
||||||
|
// won't slow down due to waiting for ACK, as long as the
|
||||||
|
// consumer keeps on reading data.
|
||||||
|
//
|
||||||
|
// s.numRead == n implies that it's the initial reading
|
||||||
|
s.numRead += uint32(n)
|
||||||
|
s.incr += uint32(n)
|
||||||
|
|
||||||
|
// for initial reading, send window update
|
||||||
|
if s.incr >= uint32(s.sess.config.MaxStreamBuffer/2) || s.numRead == uint32(n) {
|
||||||
|
notifyConsumed = s.numRead
|
||||||
|
s.incr = 0 // reset couting for next window update
|
||||||
|
}
|
||||||
|
s.bufferLock.Unlock()
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
s.sess.returnTokens(n)
|
||||||
|
|
||||||
|
// send window update if necessary
|
||||||
|
if notifyConsumed > 0 {
|
||||||
|
err := s.sendWindowUpdate(notifyConsumed)
|
||||||
|
return n, err
|
||||||
|
} else {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.die:
|
||||||
|
return 0, io.EOF
|
||||||
|
default:
|
||||||
|
return 0, ErrWouldBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo implements io.WriteTo
|
||||||
|
// WriteTo writes data to w until there's no more data to write or when an error occurs.
|
||||||
|
// The return value n is the number of bytes written. Any error encountered during the write is also returned.
|
||||||
|
// WriteTo calls Write in a loop until there is no more data to write or when an error occurs.
|
||||||
|
// If the underlying stream is a v2 stream, it will send window update to peer when necessary.
|
||||||
|
// If the underlying stream is a v1 stream, it will not send window update to peer.
|
||||||
|
func (s *stream) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if s.sess.config.Version == 2 {
|
||||||
|
return s.writeTov2(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
var buf []byte
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
if len(s.buffers) > 0 {
|
||||||
|
buf = s.buffers[0]
|
||||||
|
s.buffers = s.buffers[1:]
|
||||||
|
s.heads = s.heads[1:]
|
||||||
|
}
|
||||||
|
s.bufferLock.Unlock()
|
||||||
|
|
||||||
|
if buf != nil {
|
||||||
|
nw, ew := w.Write(buf)
|
||||||
|
// NOTE: WriteTo is a reader, so we need to return tokens here
|
||||||
|
s.sess.returnTokens(len(buf))
|
||||||
|
pool.PutBuffer(buf)
|
||||||
|
if nw > 0 {
|
||||||
|
n += int64(nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ew != nil {
|
||||||
|
return n, ew
|
||||||
|
}
|
||||||
|
} else if ew := s.waitRead(); ew != nil {
|
||||||
|
return n, ew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check comments in WriteTo
|
||||||
|
func (s *stream) writeTov2(w io.Writer) (n int64, err error) {
|
||||||
|
for {
|
||||||
|
var notifyConsumed uint32
|
||||||
|
var buf []byte
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
if len(s.buffers) > 0 {
|
||||||
|
buf = s.buffers[0]
|
||||||
|
s.buffers = s.buffers[1:]
|
||||||
|
s.heads = s.heads[1:]
|
||||||
|
}
|
||||||
|
s.numRead += uint32(len(buf))
|
||||||
|
s.incr += uint32(len(buf))
|
||||||
|
if s.incr >= uint32(s.sess.config.MaxStreamBuffer/2) || s.numRead == uint32(len(buf)) {
|
||||||
|
notifyConsumed = s.numRead
|
||||||
|
s.incr = 0
|
||||||
|
}
|
||||||
|
s.bufferLock.Unlock()
|
||||||
|
|
||||||
|
if buf != nil {
|
||||||
|
nw, ew := w.Write(buf)
|
||||||
|
// NOTE: WriteTo is a reader, so we need to return tokens here
|
||||||
|
s.sess.returnTokens(len(buf))
|
||||||
|
pool.PutBuffer(buf)
|
||||||
|
if nw > 0 {
|
||||||
|
n += int64(nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ew != nil {
|
||||||
|
return n, ew
|
||||||
|
}
|
||||||
|
|
||||||
|
if notifyConsumed > 0 {
|
||||||
|
if err := s.sendWindowUpdate(notifyConsumed); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ew := s.waitRead(); ew != nil {
|
||||||
|
return n, ew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendWindowUpdate sends a window update frame to the peer.
|
||||||
|
func (s *stream) sendWindowUpdate(consumed uint32) error {
|
||||||
|
var timer *time.Timer
|
||||||
|
var deadline <-chan time.Time
|
||||||
|
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
|
||||||
|
timer = time.NewTimer(time.Until(d))
|
||||||
|
defer timer.Stop()
|
||||||
|
deadline = timer.C
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := newFrame(byte(s.sess.config.Version), cmdUPD, s.id)
|
||||||
|
var hdr updHeader
|
||||||
|
binary.LittleEndian.PutUint32(hdr[:], consumed)
|
||||||
|
binary.LittleEndian.PutUint32(hdr[4:], uint32(s.sess.config.MaxStreamBuffer))
|
||||||
|
frame.data = hdr[:]
|
||||||
|
_, err := s.sess.writeFrameInternal(frame, deadline, CLSCTRL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitRead blocks until a read event occurs or a deadline is reached.
|
||||||
|
func (s *stream) waitRead() error {
|
||||||
|
var timer *time.Timer
|
||||||
|
var deadline <-chan time.Time
|
||||||
|
if d, ok := s.readDeadline.Load().(time.Time); ok && !d.IsZero() {
|
||||||
|
timer = time.NewTimer(time.Until(d))
|
||||||
|
defer timer.Stop()
|
||||||
|
deadline = timer.C
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.chReadEvent: // notify some data has arrived, or closed
|
||||||
|
return nil
|
||||||
|
case <-s.chFinEvent:
|
||||||
|
// BUGFIX(xtaci): Fix for https://github.com/xtaci/smux/issues/82
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
defer s.bufferLock.Unlock()
|
||||||
|
if len(s.buffers) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
case <-s.sess.chSocketReadError:
|
||||||
|
return s.sess.socketReadError.Load().(error)
|
||||||
|
case <-s.sess.chProtoError:
|
||||||
|
return s.sess.protoError.Load().(error)
|
||||||
|
case <-deadline:
|
||||||
|
return ErrTimeout
|
||||||
|
case <-s.die:
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements net.Conn
|
||||||
|
//
|
||||||
|
// Note that the behavior when multiple goroutines write concurrently is not deterministic,
|
||||||
|
// frames may interleave in random way.
|
||||||
|
func (s *stream) Write(b []byte) (n int, err error) {
|
||||||
|
if s.sess.config.Version == 2 {
|
||||||
|
return s.writeV2(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadline <-chan time.Time
|
||||||
|
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
|
||||||
|
timer := time.NewTimer(time.Until(d))
|
||||||
|
defer timer.Stop()
|
||||||
|
deadline = timer.C
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if stream has closed
|
||||||
|
select {
|
||||||
|
case <-s.chFinEvent: // passive closing
|
||||||
|
return 0, io.EOF
|
||||||
|
case <-s.die:
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// frame split and transmit
|
||||||
|
sent := 0
|
||||||
|
frame := newFrame(byte(s.sess.config.Version), cmdPSH, s.id)
|
||||||
|
bts := b
|
||||||
|
for len(bts) > 0 {
|
||||||
|
sz := len(bts)
|
||||||
|
if sz > s.frameSize {
|
||||||
|
sz = s.frameSize
|
||||||
|
}
|
||||||
|
frame.data = bts[:sz]
|
||||||
|
bts = bts[sz:]
|
||||||
|
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
|
||||||
|
s.numWritten++
|
||||||
|
sent += n
|
||||||
|
if err != nil {
|
||||||
|
return sent, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeV2 writes data to the stream for version 2 streams.
|
||||||
|
func (s *stream) writeV2(b []byte) (n int, err error) {
|
||||||
|
// check empty input
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if stream has closed
|
||||||
|
select {
|
||||||
|
case <-s.chFinEvent:
|
||||||
|
return 0, io.EOF
|
||||||
|
case <-s.die:
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// create write deadline timer
|
||||||
|
var deadline <-chan time.Time
|
||||||
|
if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() {
|
||||||
|
timer := time.NewTimer(time.Until(d))
|
||||||
|
defer timer.Stop()
|
||||||
|
deadline = timer.C
|
||||||
|
}
|
||||||
|
|
||||||
|
// frame split and transmit process
|
||||||
|
sent := 0
|
||||||
|
frame := newFrame(byte(s.sess.config.Version), cmdPSH, s.id)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// per stream sliding window control
|
||||||
|
// [.... [consumed... numWritten] ... win... ]
|
||||||
|
// [.... [consumed...................+rmtwnd]]
|
||||||
|
var bts []byte
|
||||||
|
// note:
|
||||||
|
// even if uint32 overflow, this math still works:
|
||||||
|
// eg1: uint32(0) - uint32(math.MaxUint32) = 1
|
||||||
|
// eg2: int32(uint32(0) - uint32(1)) = -1
|
||||||
|
//
|
||||||
|
// basicially, you can take it as a MODULAR ARITHMETIC
|
||||||
|
inflight := int32(atomic.LoadUint32(&s.numWritten) - atomic.LoadUint32(&s.peerConsumed))
|
||||||
|
if inflight < 0 { // security check for malformed data
|
||||||
|
return 0, ErrConsumed
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure you understand 'win' is calculated in modular arithmetic(2^32(4GB))
|
||||||
|
win := int32(atomic.LoadUint32(&s.peerWindow)) - inflight
|
||||||
|
|
||||||
|
if win > 0 {
|
||||||
|
// determine how many bytes to send
|
||||||
|
if win > int32(len(b)) {
|
||||||
|
bts = b
|
||||||
|
b = nil
|
||||||
|
} else {
|
||||||
|
bts = b[:win]
|
||||||
|
b = b[win:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// frame split and transmit
|
||||||
|
for len(bts) > 0 {
|
||||||
|
// splitting frame
|
||||||
|
sz := len(bts)
|
||||||
|
if sz > s.frameSize {
|
||||||
|
sz = s.frameSize
|
||||||
|
}
|
||||||
|
frame.data = bts[:sz]
|
||||||
|
bts = bts[sz:]
|
||||||
|
|
||||||
|
// transmit of frame
|
||||||
|
n, err := s.sess.writeFrameInternal(frame, deadline, CLSDATA)
|
||||||
|
atomic.AddUint32(&s.numWritten, uint32(sz))
|
||||||
|
sent += n
|
||||||
|
if err != nil {
|
||||||
|
return sent, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is any data left to be sent,
|
||||||
|
// wait until stream closes, window changes or deadline reached
|
||||||
|
// this blocking behavior will back propagate flow control to upper layer.
|
||||||
|
if len(b) > 0 {
|
||||||
|
select {
|
||||||
|
case <-s.chFinEvent:
|
||||||
|
return 0, io.EOF
|
||||||
|
case <-s.die:
|
||||||
|
return sent, io.ErrClosedPipe
|
||||||
|
case <-deadline:
|
||||||
|
return sent, ErrTimeout
|
||||||
|
case <-s.sess.chSocketWriteError:
|
||||||
|
return sent, s.sess.socketWriteError.Load().(error)
|
||||||
|
case <-s.chUpdate: // notify of remote data consuming and window update
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return sent, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements net.Conn
|
||||||
|
func (s *stream) Close() error {
|
||||||
|
var once bool
|
||||||
|
var err error
|
||||||
|
s.dieOnce.Do(func() {
|
||||||
|
close(s.die)
|
||||||
|
once = true
|
||||||
|
})
|
||||||
|
|
||||||
|
if once {
|
||||||
|
// send FIN in order
|
||||||
|
f := newFrame(byte(s.sess.config.Version), cmdFIN, s.id)
|
||||||
|
|
||||||
|
timer := time.NewTimer(openCloseTimeout)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
_, err = s.sess.writeFrameInternal(f, timer.C, CLSDATA)
|
||||||
|
s.sess.streamClosed(s.id)
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDieCh returns a readonly chan which can be readable
|
||||||
|
// when the stream is to be closed.
|
||||||
|
func (s *stream) GetDieCh() <-chan struct{} {
|
||||||
|
return s.die
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the read deadline as defined by
|
||||||
|
// net.Conn.SetReadDeadline.
|
||||||
|
// A zero time value disables the deadline.
|
||||||
|
func (s *stream) SetReadDeadline(t time.Time) error {
|
||||||
|
s.readDeadline.Store(t)
|
||||||
|
s.notifyReadEvent()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the write deadline as defined by
|
||||||
|
// net.Conn.SetWriteDeadline.
|
||||||
|
// A zero time value disables the deadline.
|
||||||
|
func (s *stream) SetWriteDeadline(t time.Time) error {
|
||||||
|
s.writeDeadline.Store(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline sets both read and write deadlines as defined by
|
||||||
|
// net.Conn.SetDeadline.
|
||||||
|
// A zero time value disables the deadlines.
|
||||||
|
func (s *stream) SetDeadline(t time.Time) error {
|
||||||
|
if err := s.SetReadDeadline(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.SetWriteDeadline(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// session closes
|
||||||
|
func (s *stream) sessionClose() { s.dieOnce.Do(func() { close(s.die) }) }
|
||||||
|
|
||||||
|
// LocalAddr satisfies net.Conn interface
|
||||||
|
func (s *stream) LocalAddr() net.Addr {
|
||||||
|
if ts, ok := s.sess.conn.(interface {
|
||||||
|
LocalAddr() net.Addr
|
||||||
|
}); ok {
|
||||||
|
return ts.LocalAddr()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr satisfies net.Conn interface
|
||||||
|
func (s *stream) RemoteAddr() net.Addr {
|
||||||
|
if ts, ok := s.sess.conn.(interface {
|
||||||
|
RemoteAddr() net.Addr
|
||||||
|
}); ok {
|
||||||
|
return ts.RemoteAddr()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushBytes append buf to buffers
|
||||||
|
func (s *stream) pushBytes(buf []byte) (written int, err error) {
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
s.buffers = append(s.buffers, buf)
|
||||||
|
s.heads = append(s.heads, buf)
|
||||||
|
s.bufferLock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// recycleTokens transform remaining bytes to tokens(will truncate buffer)
|
||||||
|
func (s *stream) recycleTokens() (n int) {
|
||||||
|
s.bufferLock.Lock()
|
||||||
|
for k := range s.buffers {
|
||||||
|
n += len(s.buffers[k])
|
||||||
|
pool.PutBuffer(s.heads[k])
|
||||||
|
}
|
||||||
|
s.buffers = nil
|
||||||
|
s.heads = nil
|
||||||
|
s.bufferLock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify read event
|
||||||
|
func (s *stream) notifyReadEvent() {
|
||||||
|
select {
|
||||||
|
case s.chReadEvent <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update command
|
||||||
|
func (s *stream) update(consumed uint32, window uint32) {
|
||||||
|
atomic.StoreUint32(&s.peerConsumed, consumed)
|
||||||
|
atomic.StoreUint32(&s.peerWindow, window)
|
||||||
|
select {
|
||||||
|
case s.chUpdate <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark this stream has been closed in protocol
|
||||||
|
func (s *stream) fin() {
|
||||||
|
s.finEventOnce.Do(func() {
|
||||||
|
close(s.chFinEvent)
|
||||||
|
})
|
||||||
|
}
|
31
pkg/sockopt/sockopt.go
Normal file
31
pkg/sockopt/sockopt.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package sockopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options is the options struct.
|
||||||
|
type Options struct {
|
||||||
|
bindIface *net.Interface
|
||||||
|
reuseAddr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option is the function paramater.
|
||||||
|
type Option func(opts *Options)
|
||||||
|
|
||||||
|
// Bind sets the bind interface option.
|
||||||
|
func Bind(intf *net.Interface) Option { return func(opts *Options) { opts.bindIface = intf } }
|
||||||
|
|
||||||
|
// ReuseAddr sets the reuse addr option.
|
||||||
|
func ReuseAddr() Option { return func(opts *Options) { opts.reuseAddr = true } }
|
||||||
|
|
||||||
|
// Control returns a control function for the net.Dialer and net.ListenConfig.
|
||||||
|
func Control(opts ...Option) func(network, address string, c syscall.RawConn) error {
|
||||||
|
option := &Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
return control(option)
|
||||||
|
}
|
28
pkg/sockopt/sockopt_darwin.go
Normal file
28
pkg/sockopt/sockopt_darwin.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package sockopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func control(opt *Options) func(network, address string, c syscall.RawConn) error {
|
||||||
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
|
||||||
|
if opt.bindIface != nil {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, opt.bindIface.Index)
|
||||||
|
case "tcp6", "udp6":
|
||||||
|
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, opt.bindIface.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.reuseAddr {
|
||||||
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||||
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
23
pkg/sockopt/sockopt_linux.go
Normal file
23
pkg/sockopt/sockopt_linux.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package sockopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func control(opt *Options) func(network, address string, c syscall.RawConn) error {
|
||||||
|
return func(network, address string, c syscall.RawConn) (err error) {
|
||||||
|
return c.Control(func(fd uintptr) {
|
||||||
|
|
||||||
|
if opt.bindIface != nil {
|
||||||
|
unix.BindToDevice(int(fd), opt.bindIface.Name)
|
||||||
|
}
|
||||||
|
if opt.reuseAddr {
|
||||||
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||||
|
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
9
pkg/sockopt/sockopt_others.go
Normal file
9
pkg/sockopt/sockopt_others.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//go:build !linux && !darwin
|
||||||
|
|
||||||
|
package sockopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func control(opt *Options) func(string, string, syscall.RawConn) error { return nil }
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,11 +68,12 @@ func (a Addr) String() string {
|
|||||||
return net.JoinHostPort(host, port)
|
return net.JoinHostPort(host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAddrBuf reads just enough bytes from r to get a valid Addr.
|
// Network returns network name. Implements net.Addr interface.
|
||||||
func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
|
func (a Addr) Network() string { return "socks" }
|
||||||
if len(b) < MaxAddrLen {
|
|
||||||
return nil, io.ErrShortBuffer
|
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
||||||
}
|
func ReadAddr(r io.Reader) (Addr, error) {
|
||||||
|
b := make([]byte, MaxAddrLen)
|
||||||
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
_, err := io.ReadFull(r, b[:1]) // read 1st byte for address type
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -96,11 +98,6 @@ func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
|
|||||||
return nil, Errors[8]
|
return nil, Errors[8]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAddr reads just enough bytes from r to get a valid Addr.
|
|
||||||
func ReadAddr(r io.Reader) (Addr, error) {
|
|
||||||
return ReadAddrBuf(r, make([]byte, MaxAddrLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed.
|
||||||
func SplitAddr(b []byte) Addr {
|
func SplitAddr(b []byte) Addr {
|
||||||
addrLen := 1
|
addrLen := 1
|
||||||
@ -136,16 +133,16 @@ func ParseAddr(s string) Addr {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip, err := netip.ParseAddr(host); err == nil {
|
||||||
|
if ip.Is4() {
|
||||||
addr = make([]byte, 1+net.IPv4len+2)
|
addr = make([]byte, 1+net.IPv4len+2)
|
||||||
addr[0] = ATypIP4
|
addr[0] = ATypIP4
|
||||||
copy(addr[1:], ip4)
|
|
||||||
} else {
|
} else {
|
||||||
addr = make([]byte, 1+net.IPv6len+2)
|
addr = make([]byte, 1+net.IPv6len+2)
|
||||||
addr[0] = ATypIP6
|
addr[0] = ATypIP6
|
||||||
copy(addr[1:], ip)
|
|
||||||
}
|
}
|
||||||
|
copy(addr[1:], ip.AsSlice())
|
||||||
} else {
|
} else {
|
||||||
if len(host) > 255 {
|
if len(host) > 255 {
|
||||||
return nil
|
return nil
|
@ -10,15 +10,15 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
// TCPBufSize is the size of tcp buffer.
|
// TCPBufSize is the size of tcp buffer.
|
||||||
TCPBufSize = 32 << 10
|
TCPBufSize = 32 << 10
|
||||||
|
|
||||||
// UDPBufSize is the size of udp buffer.
|
// UDPBufSize is the size of udp buffer.
|
||||||
UDPBufSize = 64 << 10
|
UDPBufSize = 2 << 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// Conn is a connection with buffered reader.
|
// Conn is a connection with buffered reader.
|
||||||
@ -45,6 +45,12 @@ func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) }
|
|||||||
// WriteTo implements io.WriterTo.
|
// WriteTo implements io.WriterTo.
|
||||||
func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) }
|
func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) }
|
||||||
|
|
||||||
|
// Close closes the Conn.
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
pool.PutBufReader(c.r)
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Relay relays between left and right.
|
// Relay relays between left and right.
|
||||||
func Relay(left, right net.Conn) error {
|
func Relay(left, right net.Conn) error {
|
||||||
var err, err1 error
|
var err, err1 error
|
||||||
@ -73,12 +79,6 @@ func Relay(left, right net.Conn) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the Conn.
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
pool.PutBufReader(c.r)
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy copies from src to dst.
|
// Copy copies from src to dst.
|
||||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||||
dst = underlyingWriter(dst)
|
dst = underlyingWriter(dst)
|
||||||
@ -175,19 +175,30 @@ func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
|||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RelayUDP copys from src to dst at target with read timeout.
|
// CopyUDP copys from src to dst at target with read timeout.
|
||||||
func RelayUDP(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration) error {
|
// if step sets to non-zero value,
|
||||||
b := pool.GetBuffer(UDPBufSize)
|
// the read timeout will be increased from 0 to timeout by step in every read operation.
|
||||||
defer pool.PutBuffer(b)
|
func CopyUDP(dst net.PacketConn, writeTo net.Addr, src net.PacketConn, timeout time.Duration, step time.Duration) error {
|
||||||
|
buf := pool.GetBuffer(UDPBufSize)
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
|
var t time.Duration
|
||||||
for {
|
for {
|
||||||
src.SetReadDeadline(time.Now().Add(timeout))
|
if t += step; t == 0 || t > timeout {
|
||||||
n, _, err := src.ReadFrom(b)
|
t = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
src.SetReadDeadline(time.Now().Add(t))
|
||||||
|
n, addr, err := src.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = dst.WriteTo(b[:n], target)
|
if writeTo != nil {
|
||||||
|
addr = writeTo
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dst.WriteTo(buf[:n], addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// ErrNotSupported indicates that the operation is not supported
|
||||||
ErrNotSupported = errors.New("not supported")
|
ErrNotSupported = errors.New("not supported")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ type UDPDialer interface {
|
|||||||
Addr() string
|
Addr() string
|
||||||
|
|
||||||
// DialUDP connects to the given address
|
// DialUDP connects to the given address
|
||||||
DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error)
|
DialUDP(network, addr string) (pc net.PacketConn, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialerCreator is a function to create dialers.
|
// DialerCreator is a function to create dialers.
|
||||||
@ -53,6 +55,10 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
|
|||||||
return nil, errors.New("DialerFromURL: dialer cannot be nil")
|
return nil, errors.New("DialerFromURL: dialer cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(s, "://") {
|
||||||
|
s = s + "://"
|
||||||
|
}
|
||||||
|
|
||||||
scheme := s[:strings.Index(s, ":")]
|
scheme := s[:strings.Index(s, ":")]
|
||||||
c, ok := dialerCreators[strings.ToLower(scheme)]
|
c, ok := dialerCreators[strings.ToLower(scheme)]
|
||||||
if ok {
|
if ok {
|
||||||
@ -61,3 +67,13 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
|
|||||||
|
|
||||||
return nil, errors.New("unknown scheme '" + scheme + "'")
|
return nil, errors.New("unknown scheme '" + scheme + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialerSchemes returns the registered dialer schemes.
|
||||||
|
func DialerSchemes() string {
|
||||||
|
s := make([]string, 0, len(dialerCreators))
|
||||||
|
for name := range dialerCreators {
|
||||||
|
s = append(s, name)
|
||||||
|
}
|
||||||
|
sort.Strings(s)
|
||||||
|
return strings.Join(s, " ")
|
||||||
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/sockopt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Direct proxy.
|
// Direct proxy.
|
||||||
@ -16,16 +18,17 @@ type Direct struct {
|
|||||||
relayTimeout time.Duration
|
relayTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default dialer.
|
func init() {
|
||||||
var Default = &Direct{dialTimeout: time.Second * 3}
|
RegisterDialer("direct", NewDirectDialer)
|
||||||
|
}
|
||||||
|
|
||||||
// NewDirect returns a Direct dialer.
|
// NewDirect returns a Direct dialer.
|
||||||
func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) {
|
func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) {
|
||||||
d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout}
|
d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout}
|
||||||
|
|
||||||
if intface != "" {
|
if intface != "" {
|
||||||
if ip := net.ParseIP(intface); ip != nil {
|
if addr, err := netip.ParseAddr(intface); err == nil {
|
||||||
d.ip = net.ParseIP(intface)
|
d.ip = addr.AsSlice()
|
||||||
} else {
|
} else {
|
||||||
iface, err := net.InterfaceByName(intface)
|
iface, err := net.InterfaceByName(intface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -38,6 +41,14 @@ func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDirectDialer returns a direct dialer.
|
||||||
|
func NewDirectDialer(s string, d Dialer) (Dialer, error) {
|
||||||
|
if d == nil {
|
||||||
|
return NewDirect("", time.Duration(3)*time.Second, time.Duration(3)*time.Second)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Addr returns forwarder's address.
|
// Addr returns forwarder's address.
|
||||||
func (d *Direct) Addr() string { return "DIRECT" }
|
func (d *Direct) Addr() string { return "DIRECT" }
|
||||||
|
|
||||||
@ -76,6 +87,10 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout}
|
dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout}
|
||||||
|
if d.iface != nil {
|
||||||
|
dialer.Control = sockopt.Control(sockopt.Bind(d.iface))
|
||||||
|
}
|
||||||
|
|
||||||
c, err := dialer.Dial(network, addr)
|
c, err := dialer.Dial(network, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -93,33 +108,44 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address.
|
// DialUDP connects to the given address.
|
||||||
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
// TODO: support specifying local interface
|
|
||||||
var la string
|
var la string
|
||||||
if d.ip != nil {
|
if d.ip != nil {
|
||||||
la = net.JoinHostPort(d.ip.String(), "0")
|
la = net.JoinHostPort(d.ip.String(), "0")
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := net.ListenPacket(network, la)
|
lc := &net.ListenConfig{}
|
||||||
if err != nil {
|
if d.iface != nil {
|
||||||
log.F("ListenPacket error: %s", err)
|
lc.Control = sockopt.Control(sockopt.Bind(d.iface))
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uAddr, err := net.ResolveUDPAddr("udp", addr)
|
return lc.ListenPacket(context.Background(), network, la)
|
||||||
return pc, uAddr, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IFaceIPs returns ip addresses according to the specified interface.
|
// IFaceIPs returns ip addresses according to the specified interface.
|
||||||
func (d *Direct) IFaceIPs() (ips []net.IP) {
|
func (d *Direct) IFaceIPs() (ips []net.IP) {
|
||||||
ipnets, err := d.iface.Addrs()
|
ipNets, err := d.iface.Addrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for _, ipNet := range ipNets {
|
||||||
for _, ipnet := range ipnets {
|
ips = append(ips, ipNet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
||||||
ips = append(ips, ipnet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AddUsage("direct", `
|
||||||
|
Direct scheme:
|
||||||
|
direct://
|
||||||
|
|
||||||
|
Only needed when you want to specify the outgoing interface:
|
||||||
|
glider -verbose -listen :8443 -forward direct://#interface=eth0
|
||||||
|
|
||||||
|
Or load balance multiple interfaces directly:
|
||||||
|
glider -verbose -listen :8443 -forward direct://#interface=eth0 -forward direct://#interface=eth1 -strategy rr
|
||||||
|
|
||||||
|
Or you can use the high availability mode:
|
||||||
|
glider -verbose -listen :8443 -forward direct://#interface=eth0&priority=100 -forward direct://#interface=eth1&priority=200 -strategy ha
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +33,8 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
buf := pool.GetBytesBuffer()
|
||||||
|
defer pool.PutBytesBuffer(buf)
|
||||||
|
|
||||||
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
||||||
buf.WriteString("Host: " + addr + "\r\n")
|
buf.WriteString("Host: " + addr + "\r\n")
|
||||||
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
buf.WriteString("Proxy-Connection: Keep-Alive\r\n")
|
||||||
@ -45,7 +47,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
// header ended
|
// header ended
|
||||||
buf.WriteString("\r\n")
|
buf.WriteString("\r\n")
|
||||||
_, err = rc.Write(buf.Bytes())
|
_, err = rc.Write(buf.Bytes())
|
||||||
pool.PutBytesBuffer(buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -76,6 +77,6 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||||
return nil, nil, proxy.ErrNotSupported
|
return nil, proxy.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -112,3 +112,10 @@ func extractUserPass(auth string) (username, password string, ok bool) {
|
|||||||
|
|
||||||
return s[:idx], s[idx+1:], true
|
return s[:idx], s[idx+1:], true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("http", `
|
||||||
|
Http scheme:
|
||||||
|
http://[user:pass@]host:port
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
@ -3,16 +3,16 @@ package http
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"fmt"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Methods are http methods from rfc.
|
// Methods are http methods from rfc.
|
||||||
// https://www.ietf.org/rfc/rfc2616.txt, http methods must be uppercase
|
// https://www.rfc-editor.org/rfc/rfc2616, http methods must be uppercase
|
||||||
var Methods = [...][]byte{
|
var Methods = [...][]byte{
|
||||||
[]byte("GET"),
|
[]byte("GET"),
|
||||||
[]byte("POST"),
|
[]byte("POST"),
|
||||||
@ -46,7 +46,7 @@ func parseRequest(r *bufio.Reader) (*request, error) {
|
|||||||
|
|
||||||
method, uri, proto, ok := parseStartLine(line)
|
method, uri, proto, ok := parseStartLine(line)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("error in parseStartLine")
|
return nil, fmt.Errorf("error in parseStartLine: %s", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
header, err := tpr.ReadMIMEHeader()
|
header, err := tpr.ReadMIMEHeader()
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|||||||
func (s *HTTP) ListenAndServe() {
|
func (s *HTTP) ListenAndServe() {
|
||||||
l, err := net.Listen("tcp", s.addr)
|
l, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[http] failed to listen on %s: %v", s.addr, err)
|
log.Fatalf("[http] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
@ -42,22 +42,22 @@ func (s *HTTP) ListenAndServe() {
|
|||||||
|
|
||||||
// Serve serves a connection.
|
// Serve serves a connection.
|
||||||
func (s *HTTP) Serve(cc net.Conn) {
|
func (s *HTTP) Serve(cc net.Conn) {
|
||||||
defer cc.Close()
|
|
||||||
|
|
||||||
if c, ok := cc.(*net.TCPConn); ok {
|
if c, ok := cc.(*net.TCPConn); ok {
|
||||||
c.SetKeepAlive(true)
|
c.SetKeepAlive(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := proxy.NewConn(cc)
|
c := proxy.NewConn(cc)
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
req, err := parseRequest(c.Reader())
|
req, err := parseRequest(c.Reader())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[http] can not parse request from %s", c.RemoteAddr())
|
log.F("[http] can not parse request from %s, error: %v", c.RemoteAddr(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.pretend {
|
if s.pretend {
|
||||||
fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", req.proto)
|
fmt.Fprintf(c, "%s 404 Not Found\r\nServer: nginx\r\n\r\n404 Not Found\r\n", req.proto)
|
||||||
log.F("[http] %s <-> %s,pretend as web server", c.RemoteAddr().String(), s.Addr())
|
log.F("[http] %s <-> %s, pretend as web server", c.RemoteAddr().String(), s.Addr())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
kcp "github.com/xtaci/kcp-go/v5"
|
kcp "github.com/xtaci/kcp-go/v5"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -143,15 +143,14 @@ func NewKCPDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
|||||||
|
|
||||||
// NewKCPServer returns a kcp proxy server.
|
// NewKCPServer returns a kcp proxy server.
|
||||||
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
transport := strings.Split(s, ",")
|
schemes := strings.SplitN(s, ",", 2)
|
||||||
|
k, err := NewKCP(schemes[0], nil, p)
|
||||||
k, err := NewKCP(transport[0], nil, p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(transport) > 1 {
|
if len(schemes) > 1 {
|
||||||
k.server, err = proxy.ServerFromURL(transport[1], p)
|
k.server, err = proxy.ServerFromURL(schemes[1], p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -164,7 +163,7 @@ func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
|||||||
func (s *KCP) ListenAndServe() {
|
func (s *KCP) ListenAndServe() {
|
||||||
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[kcp] failed to listen on %s: %v", s.addr, err)
|
log.Fatalf("[kcp] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
@ -240,8 +239,8 @@ func (s *KCP) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
return nil, nil, proxy.ErrNotSupported
|
return nil, proxy.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *KCP) setParams(c *kcp.UDPSession) {
|
func (s *KCP) setParams(c *kcp.UDPSession) {
|
||||||
@ -267,3 +266,16 @@ func (s *KCP) setParams(c *kcp.UDPSession) {
|
|||||||
c.SetMtu(1350)
|
c.SetMtu(1350)
|
||||||
c.SetACKNoDelay(true)
|
c.SetACKNoDelay(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("kcp", `
|
||||||
|
KCP scheme:
|
||||||
|
kcp://CRYPT:KEY@host:port[?dataShards=NUM&parityShards=NUM&mode=MODE]
|
||||||
|
|
||||||
|
Available crypt types for KCP:
|
||||||
|
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
||||||
|
|
||||||
|
Available modes for KCP:
|
||||||
|
fast, fast2, fast3, normal, default: fast
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package mixed
|
package mixed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/http"
|
"github.com/nadoo/glider/proxy/http"
|
||||||
"github.com/nadoo/glider/proxy/socks5"
|
"github.com/nadoo/glider/proxy/socks5"
|
||||||
@ -61,11 +60,11 @@ func (m *Mixed) ListenAndServe() {
|
|||||||
|
|
||||||
l, err := net.Listen("tcp", m.addr)
|
l, err := net.Listen("tcp", m.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[mixed] failed to listen on %s: %v", m.addr, err)
|
log.Fatalf("[mixed] failed to listen on %s: %v", m.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.F("[mixed] listening TCP on %s", m.addr)
|
log.F("[mixed] http & socks5 server listening TCP on %s", m.addr)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
@ -80,37 +79,12 @@ func (m *Mixed) ListenAndServe() {
|
|||||||
|
|
||||||
// Serve serves connections.
|
// Serve serves connections.
|
||||||
func (m *Mixed) Serve(c net.Conn) {
|
func (m *Mixed) Serve(c net.Conn) {
|
||||||
defer c.Close()
|
conn := proxy.NewConn(c)
|
||||||
|
if head, err := conn.Peek(1); err == nil {
|
||||||
if c, ok := c.(*net.TCPConn); ok {
|
if head[0] == socks5.Version {
|
||||||
c.SetKeepAlive(true)
|
m.socks5Server.Serve(conn)
|
||||||
}
|
|
||||||
|
|
||||||
cc := proxy.NewConn(c)
|
|
||||||
head, err := cc.Peek(1)
|
|
||||||
if err != nil {
|
|
||||||
// log.F("[mixed] socks5 peek error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check socks5, client send socksversion: 5 as the first byte
|
|
||||||
if head[0] == socks5.Version {
|
|
||||||
m.socks5Server.Serve(cc)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
head, err = cc.Peek(8)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[mixed] http peek error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range http.Methods {
|
|
||||||
if bytes.HasPrefix(head, method) {
|
|
||||||
m.httpServer.Serve(cc)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.httpServer.Serve(conn)
|
||||||
log.F("[mixed] unknown request from %s, ignored", c.RemoteAddr())
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPObfs struct
|
// HTTPObfs struct
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,6 +106,16 @@ func (s *Obfs) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
return nil, nil, proxy.ErrNotSupported
|
return nil, proxy.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("simple-obfs", `
|
||||||
|
Simple-Obfs scheme:
|
||||||
|
simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]
|
||||||
|
|
||||||
|
Available types for simple-obfs:
|
||||||
|
http, tls
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// https://www.ietf.org/rfc/rfc5246.txt
|
// https://www.rfc-editor.org/rfc/rfc5246
|
||||||
// https://golang.org/src/crypto/tls/handshake_messages.go
|
// https://golang.org/src/crypto/tls/handshake_messages.go
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
@ -17,7 +17,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -64,10 +64,7 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
|
|||||||
n := len(b)
|
n := len(b)
|
||||||
for i := 0; i < n; i += chunkSize {
|
for i := 0; i < n; i += chunkSize {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
end := i + chunkSize
|
end := min(i+chunkSize, n)
|
||||||
if end > n {
|
|
||||||
end = n
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||||
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Proxy is a dialer manager.
|
// Proxy is a dialer manager.
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
@ -8,7 +11,7 @@ type Proxy interface {
|
|||||||
Dial(network, addr string) (c net.Conn, dialer Dialer, err error)
|
Dial(network, addr string) (c net.Conn, dialer Dialer, err error)
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, writeTo net.Addr, err error)
|
DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, err error)
|
||||||
|
|
||||||
// Get the dialer by dstAddr.
|
// Get the dialer by dstAddr.
|
||||||
NextDialer(dstAddr string) Dialer
|
NextDialer(dstAddr string) Dialer
|
||||||
@ -16,3 +19,28 @@ type Proxy interface {
|
|||||||
// Record records result while using the dialer from proxy.
|
// Record records result while using the dialer from proxy.
|
||||||
Record(dialer Dialer, success bool)
|
Record(dialer Dialer, success bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
msg strings.Builder
|
||||||
|
usages = make(map[string]string)
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddUsage adds help message for the named proxy.
|
||||||
|
func AddUsage(name, usage string) {
|
||||||
|
usages[name] = usage
|
||||||
|
msg.WriteString(usage)
|
||||||
|
msg.WriteString("\n--")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage returns help message of the named proxy.
|
||||||
|
func Usage(name string) string {
|
||||||
|
if name == "all" {
|
||||||
|
return msg.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if usage, ok := usages[name]; ok {
|
||||||
|
return usage
|
||||||
|
}
|
||||||
|
|
||||||
|
return "can not find usage for: " + name
|
||||||
|
}
|
||||||
|
134
proxy/pxyproto/server.go
Normal file
134
proxy/pxyproto/server.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package pxyproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterServer("pxyproto", NewPxyProtoServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PxyProtoServer struct.
|
||||||
|
type PxyProtoServer struct {
|
||||||
|
addr string
|
||||||
|
proxy proxy.Proxy
|
||||||
|
server proxy.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPxyProtoServer returns a PxyProtoServer struct.
|
||||||
|
func NewPxyProtoServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
|
schemes := strings.SplitN(s, ",", 2)
|
||||||
|
u, err := url.Parse(schemes[0])
|
||||||
|
if err != nil {
|
||||||
|
log.F("[pxyproto] parse url err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &PxyProtoServer{proxy: p, addr: u.Host}
|
||||||
|
if len(schemes) < 2 {
|
||||||
|
return nil, errors.New("[pxyproto] you must use pxyproto with a proxy server, e.g: pxyproto://:1234,http://")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.server, err = proxy.ServerFromURL(schemes[1], p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on server's addr and serves connections.
|
||||||
|
func (s *PxyProtoServer) ListenAndServe() {
|
||||||
|
l, err := net.Listen("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("[pxyproto] failed to listen on %s: %v", s.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
log.F("[pxyproto] listening TCP on %s", s.addr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.F("[pxyproto] failed to accept: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.Serve(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve serves a connection.
|
||||||
|
func (s *PxyProtoServer) Serve(cc net.Conn) {
|
||||||
|
c, err := newServerConn(cc)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[pxyproto] parse header failed, error: %v", err)
|
||||||
|
cc.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.F("[pxyproto] %s <-> %s <-> %s <-> %s",
|
||||||
|
// c.RemoteAddr(), c.LocalAddr(), cc.RemoteAddr(), cc.LocalAddr())
|
||||||
|
|
||||||
|
if s.server != nil {
|
||||||
|
s.server.Serve(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverConn struct {
|
||||||
|
*proxy.Conn
|
||||||
|
src, dst net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServerConn(c net.Conn) (*serverConn, error) {
|
||||||
|
sc := &serverConn{
|
||||||
|
Conn: proxy.NewConn(c),
|
||||||
|
src: c.RemoteAddr(),
|
||||||
|
dst: c.LocalAddr(),
|
||||||
|
}
|
||||||
|
return sc, sc.parseHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
// "PROXY TCPx SRC_IP DST_IP SRC_PORT DST_PORT"
|
||||||
|
func (c *serverConn) parseHeader() error {
|
||||||
|
line, err := c.Conn.Reader().ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strings.ReplaceAll(line, "\r\n", "")
|
||||||
|
// log.F("[pxyproto] req header: %s", line)
|
||||||
|
|
||||||
|
header := strings.Split(line, " ")
|
||||||
|
if len(header) != 6 {
|
||||||
|
return fmt.Errorf("invalid header: %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if header[0] != "PROXY" {
|
||||||
|
return fmt.Errorf("invalid header: %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.src, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[2], header[4]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse header: %s, error: %v", line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.dst, err = net.ResolveTCPAddr("tcp", net.JoinHostPort(header[3], header[5]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse header: %s, error: %v", line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *serverConn) LocalAddr() net.Addr { return c.dst }
|
||||||
|
func (c *serverConn) RemoteAddr() net.Addr { return c.src }
|
@ -2,12 +2,13 @@ package redir
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ func NewRedir6Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
|||||||
func (s *RedirProxy) ListenAndServe() {
|
func (s *RedirProxy) ListenAndServe() {
|
||||||
l, err := net.Listen("tcp", s.addr)
|
l, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[redir] failed to listen on %s: %v", s.addr, err)
|
log.Fatalf("[redir] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,19 +84,20 @@ func (s *RedirProxy) Serve(cc net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.SetKeepAlive(true)
|
c.SetKeepAlive(true)
|
||||||
tgt, err := getOrigDst(c, s.ipv6)
|
tgtAddr, err := getOrigDst(c, s.ipv6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[redir] failed to get target address: %v", err)
|
log.F("[redir] failed to get target address: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tgt := tgtAddr.String()
|
||||||
|
|
||||||
// loop request
|
// loop request
|
||||||
if c.LocalAddr().String() == tgt.String() {
|
if c.LocalAddr().String() == tgt {
|
||||||
log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt)
|
log.F("[redir] %s <-> %s, unallowed request to redir port", c.RemoteAddr(), tgt)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, dialer, err := s.proxy.Dial("tcp", tgt.String())
|
rc, dialer, err := s.proxy.Dial("tcp", tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||||
return
|
return
|
||||||
@ -114,12 +116,12 @@ func (s *RedirProxy) Serve(cc net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the original destination of a TCP connection.
|
// Get the original destination of a TCP connection.
|
||||||
func getOrigDst(c *net.TCPConn, ipv6 bool) (*net.TCPAddr, error) {
|
func getOrigDst(c *net.TCPConn, ipv6 bool) (netip.AddrPort, error) {
|
||||||
rc, err := c.SyscallConn()
|
rc, err := c.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return netip.AddrPort{}, err
|
||||||
}
|
}
|
||||||
var addr *net.TCPAddr
|
var addr netip.AddrPort
|
||||||
rc.Control(func(fd uintptr) {
|
rc.Control(func(fd uintptr) {
|
||||||
if ipv6 {
|
if ipv6 {
|
||||||
addr, err = getorigdstIPv6(fd)
|
addr, err = getorigdstIPv6(fd)
|
||||||
@ -131,32 +133,29 @@ func getOrigDst(c *net.TCPConn, ipv6 bool) (*net.TCPAddr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||||
func getorigdst(fd uintptr) (*net.TCPAddr, error) {
|
func getorigdst(fd uintptr) (netip.AddrPort, error) {
|
||||||
const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
|
const _SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h
|
||||||
var raw syscall.RawSockaddrInet4
|
var raw syscall.RawSockaddrInet4
|
||||||
siz := unsafe.Sizeof(raw)
|
siz := unsafe.Sizeof(raw)
|
||||||
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, _SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||||
return nil, err
|
return netip.AddrPort{}, err
|
||||||
}
|
}
|
||||||
var addr net.TCPAddr
|
// NOTE: raw.Port is big-endian, just change it to little-endian
|
||||||
addr.IP = raw.Addr[:]
|
// TODO: improve here when we add big-endian $GOARCH support
|
||||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian
|
port := raw.Port<<8 | raw.Port>>8
|
||||||
addr.Port = int(port[0])<<8 | int(port[1])
|
return netip.AddrPortFrom(netip.AddrFrom4(raw.Addr), port), nil
|
||||||
return &addr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
|
// Call ipv6_getorigdst() from linux/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
|
||||||
// NOTE: I haven't tried yet but it should work since Linux 3.8.
|
func getorigdstIPv6(fd uintptr) (netip.AddrPort, error) {
|
||||||
func getorigdstIPv6(fd uintptr) (*net.TCPAddr, error) {
|
|
||||||
const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
||||||
var raw syscall.RawSockaddrInet6
|
var raw syscall.RawSockaddrInet6
|
||||||
siz := unsafe.Sizeof(raw)
|
siz := unsafe.Sizeof(raw)
|
||||||
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IPV6, _IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil {
|
||||||
return nil, err
|
return netip.AddrPort{}, err
|
||||||
}
|
}
|
||||||
var addr net.TCPAddr
|
// NOTE: raw.Port is big-endian, just change it to little-endian
|
||||||
addr.IP = raw.Addr[:]
|
// TODO: improve here when we add big-endian $GOARCH support
|
||||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian
|
port := raw.Port<<8 | raw.Port>>8
|
||||||
addr.Port = int(port[0])<<8 | int(port[1])
|
return netip.AddrPortFrom(netip.AddrFrom16(raw.Addr), port), nil
|
||||||
return &addr, nil
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// +build linux,!386
|
//go:build linux && !386
|
||||||
|
|
||||||
package redir
|
package redir
|
||||||
|
|
||||||
|
@ -34,6 +34,13 @@ func (s *Reject) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
return nil, nil, errors.New("REJECT")
|
return nil, errors.New("REJECT")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("reject", `
|
||||||
|
Reject scheme:
|
||||||
|
reject://
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +16,11 @@ type Server interface {
|
|||||||
Serve(c net.Conn)
|
Serve(c net.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PacketServer interface.
|
||||||
|
type PacketServer interface {
|
||||||
|
ServePacket(pc net.PacketConn)
|
||||||
|
}
|
||||||
|
|
||||||
// ServerCreator is a function to create proxy servers.
|
// ServerCreator is a function to create proxy servers.
|
||||||
type ServerCreator func(s string, proxy Proxy) (Server, error)
|
type ServerCreator func(s string, proxy Proxy) (Server, error)
|
||||||
|
|
||||||
@ -28,9 +34,9 @@ func RegisterServer(name string, c ServerCreator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServerFromURL calls the registered creator to create proxy servers.
|
// ServerFromURL calls the registered creator to create proxy servers.
|
||||||
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function.
|
// proxy can not be nil.
|
||||||
func ServerFromURL(s string, p Proxy) (Server, error) {
|
func ServerFromURL(s string, proxy Proxy) (Server, error) {
|
||||||
if p == nil {
|
if proxy == nil {
|
||||||
return nil, errors.New("ServerFromURL: dialer cannot be nil")
|
return nil, errors.New("ServerFromURL: dialer cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +47,18 @@ func ServerFromURL(s string, p Proxy) (Server, error) {
|
|||||||
scheme := s[:strings.Index(s, ":")]
|
scheme := s[:strings.Index(s, ":")]
|
||||||
c, ok := serverCreators[strings.ToLower(scheme)]
|
c, ok := serverCreators[strings.ToLower(scheme)]
|
||||||
if ok {
|
if ok {
|
||||||
return c(s, p)
|
return c(s, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("unknown scheme '" + scheme + "'")
|
return nil, errors.New("unknown scheme '" + scheme + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerSchemes returns the registered server schemes.
|
||||||
|
func ServerSchemes() string {
|
||||||
|
s := make([]string, 0, len(serverCreators))
|
||||||
|
for name := range serverCreators {
|
||||||
|
s = append(s, name)
|
||||||
|
}
|
||||||
|
sort.Strings(s)
|
||||||
|
return strings.Join(s, " ")
|
||||||
|
}
|
||||||
|
79
proxy/smux/client.go
Normal file
79
proxy/smux/client.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package smux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/pkg/smux"
|
||||||
|
"github.com/nadoo/glider/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SmuxClient struct.
|
||||||
|
type SmuxClient struct {
|
||||||
|
dialer proxy.Dialer
|
||||||
|
addr string
|
||||||
|
mu sync.Mutex
|
||||||
|
session *smux.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterDialer("smux", NewSmuxDialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSmuxDialer returns a smux dialer.
|
||||||
|
func NewSmuxDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[smux] parse url err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &SmuxClient{
|
||||||
|
dialer: d,
|
||||||
|
addr: u.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns forwarder's address.
|
||||||
|
func (s *SmuxClient) Addr() string {
|
||||||
|
if s.addr == "" {
|
||||||
|
return s.dialer.Addr()
|
||||||
|
}
|
||||||
|
return s.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the address addr on the network net via the proxy.
|
||||||
|
func (s *SmuxClient) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.session != nil {
|
||||||
|
if c, err := s.session.OpenStream(); err == nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
s.session.Close()
|
||||||
|
}
|
||||||
|
if err := s.initConn(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.session.OpenStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUDP connects to the given address via the proxy.
|
||||||
|
func (s *SmuxClient) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
|
return nil, proxy.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmuxClient) initConn() error {
|
||||||
|
conn, err := s.dialer.Dial("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[smux] dial to %s error: %s", s.addr, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.session, err = smux.Client(conn, nil)
|
||||||
|
return err
|
||||||
|
}
|
119
proxy/smux/server.go
Normal file
119
proxy/smux/server.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package smux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/pkg/smux"
|
||||||
|
"github.com/nadoo/glider/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SmuxServer struct.
|
||||||
|
type SmuxServer struct {
|
||||||
|
proxy proxy.Proxy
|
||||||
|
addr string
|
||||||
|
server proxy.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterServer("smux", NewSmuxServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSmuxServer returns a smux transport layer before the real server.
|
||||||
|
func NewSmuxServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
|
schemes := strings.SplitN(s, ",", 2)
|
||||||
|
u, err := url.Parse(schemes[0])
|
||||||
|
if err != nil {
|
||||||
|
log.F("[smux] parse url err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &SmuxServer{
|
||||||
|
proxy: p,
|
||||||
|
addr: u.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(schemes) > 1 {
|
||||||
|
m.server, err = proxy.ServerFromURL(schemes[1], p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe listens on server's addr and serves connections.
|
||||||
|
func (s *SmuxServer) ListenAndServe() {
|
||||||
|
l, err := net.Listen("tcp", s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("[smux] failed to listen on %s: %v", s.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
log.F("[smux] listening mux on %s", s.addr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.F("[smux] failed to accept: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.Serve(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve serves a connection.
|
||||||
|
func (s *SmuxServer) Serve(c net.Conn) {
|
||||||
|
// we know the internal server will close the connection after serve
|
||||||
|
// defer c.Close()
|
||||||
|
|
||||||
|
session, err := smux.Server(c, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[smux] failed to create session: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Accept a stream
|
||||||
|
stream, err := session.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go s.ServeStream(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeStream serves a smux stream.
|
||||||
|
func (s *SmuxServer) ServeStream(c *smux.Stream) {
|
||||||
|
if s.server != nil {
|
||||||
|
s.server.Serve(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
rc, dialer, err := s.proxy.Dial("tcp", "")
|
||||||
|
if err != nil {
|
||||||
|
log.F("[smux] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err)
|
||||||
|
s.proxy.Record(dialer, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
log.F("[smux] %s <-> %s", c.RemoteAddr(), dialer.Addr())
|
||||||
|
|
||||||
|
if err = proxy.Relay(c, rc); err != nil {
|
||||||
|
log.F("[smux] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err)
|
||||||
|
// record remote conn failure only
|
||||||
|
if !strings.Contains(err.Error(), s.addr) {
|
||||||
|
s.proxy.Record(dialer, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
proxy/smux/smux.go
Normal file
10
proxy/smux/smux.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package smux
|
||||||
|
|
||||||
|
import "github.com/nadoo/glider/proxy"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("smux", `
|
||||||
|
Smux scheme:
|
||||||
|
smux://host:port
|
||||||
|
`)
|
||||||
|
}
|
@ -11,8 +11,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,12 +25,14 @@ const (
|
|||||||
|
|
||||||
// SOCKS4 is a base socks4 struct.
|
// SOCKS4 is a base socks4 struct.
|
||||||
type SOCKS4 struct {
|
type SOCKS4 struct {
|
||||||
dialer proxy.Dialer
|
dialer proxy.Dialer
|
||||||
addr string
|
addr string
|
||||||
|
socks4a bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proxy.RegisterDialer("socks4", NewSocks4Dialer)
|
proxy.RegisterDialer("socks4", NewSocks4Dialer)
|
||||||
|
proxy.RegisterDialer("socks4a", NewSocks4Dialer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSOCKS4 returns a socks4 proxy.
|
// NewSOCKS4 returns a socks4 proxy.
|
||||||
@ -42,8 +44,9 @@ func NewSOCKS4(s string, dialer proxy.Dialer) (*SOCKS4, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h := &SOCKS4{
|
h := &SOCKS4{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
addr: u.Host,
|
addr: u.Host,
|
||||||
|
socks4a: u.Scheme == "socks4a",
|
||||||
}
|
}
|
||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
@ -85,8 +88,8 @@ func (s *SOCKS4) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||||
return nil, nil, proxy.ErrNotSupported
|
return nil, proxy.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SOCKS4) lookupIP(host string) (ip net.IP, err error) {
|
func (s *SOCKS4) lookupIP(host string) (ip net.IP, err error) {
|
||||||
@ -115,27 +118,46 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("[socks4] failed to parse port number: " + portStr)
|
return errors.New("[socks4] failed to parse port number: " + portStr)
|
||||||
}
|
}
|
||||||
if port < 1 || port > 0xffff {
|
|
||||||
return errors.New("[socks4] port number out of range: " + portStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
ip, err := s.lookupIP(host)
|
const baseBufSize = 8 + 1 // 1 is the len(userid)
|
||||||
if err != nil {
|
bufSize := baseBufSize
|
||||||
return err
|
var ip net.IP
|
||||||
|
if ip = net.ParseIP(host); ip == nil {
|
||||||
|
if s.socks4a {
|
||||||
|
// The client should set the first three bytes of DSTIP to NULL
|
||||||
|
// and the last byte to a non-zero value.
|
||||||
|
ip = []byte{0, 0, 0, 1}
|
||||||
|
bufSize += len(host) + 1
|
||||||
|
} else {
|
||||||
|
ip, err = s.lookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ip = ip.To4()
|
||||||
|
if ip == nil {
|
||||||
|
return errors.New("[socks4] IPv6 is not supported by socks4")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// taken from https://github.com/h12w/socks/blob/master/socks.go and https://en.wikipedia.org/wiki/SOCKS
|
||||||
// taken from https://github.com/h12w/socks/blob/master/socks.go
|
buf := pool.GetBuffer(bufSize)
|
||||||
buf := []byte{
|
defer pool.PutBuffer(buf)
|
||||||
|
copy(buf, []byte{
|
||||||
Version,
|
Version,
|
||||||
ConnectCommand,
|
ConnectCommand,
|
||||||
byte(port >> 8), // higher byte of destination port
|
byte(port >> 8), // higher byte of destination port
|
||||||
byte(port), // lower byte of destination port (big endian)
|
byte(port), // lower byte of destination port (big endian)
|
||||||
ip[0], ip[1], ip[2], ip[3],
|
ip[0], ip[1], ip[2], ip[3],
|
||||||
0, // user id
|
0, // user id
|
||||||
|
})
|
||||||
|
if s.socks4a {
|
||||||
|
copy(buf[baseBufSize:], host)
|
||||||
|
buf[len(buf)-1] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := pool.GetBuffer(8)
|
resp := pool.GetBuffer(8)
|
||||||
@ -164,3 +186,10 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("socks4", `
|
||||||
|
Socks4 scheme:
|
||||||
|
socks4://host:port
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
@ -4,14 +4,19 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterDialer("socks5", NewSocks5Dialer)
|
||||||
|
}
|
||||||
|
|
||||||
// NewSocks5Dialer returns a socks5 proxy dialer.
|
// NewSocks5Dialer returns a socks5 proxy dialer.
|
||||||
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||||
return NewSocks5(s, d, nil)
|
return NewSocks5(s, d, nil)
|
||||||
@ -27,6 +32,21 @@ func (s *Socks5) Addr() string {
|
|||||||
|
|
||||||
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
// Dial connects to the address addr on the network net via the SOCKS5 proxy.
|
||||||
func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
c, err := s.dial(network, s.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5]: dial to %s error: %s", s.addr, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.connect(c, addr, socks.CmdConnect); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Socks5) dial(network, addr string) (net.Conn, error) {
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp", "tcp6", "tcp4":
|
case "tcp", "tcp6", "tcp4":
|
||||||
default:
|
default:
|
||||||
@ -39,84 +59,59 @@ func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.connect(c, addr); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||||
c, err := s.dialer.Dial("tcp", s.addr)
|
c, err := s.dial("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
|
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// send VER, NMETHODS, METHODS
|
var uAddr socks.Addr
|
||||||
c.Write([]byte{Version, 1, 0})
|
if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
buf := pool.GetBuffer(socks.MaxAddrLen)
|
buf := pool.GetBuffer(socks.MaxAddrLen)
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
// read VER METHOD
|
uAddress := uAddr.String()
|
||||||
if _, err := io.ReadFull(c, buf[:2]); err != nil {
|
h, p, _ := net.SplitHostPort(uAddress)
|
||||||
return nil, nil, err
|
// if returned bind ip is unspecified
|
||||||
|
if ip, err := netip.ParseAddr(h); err == nil && ip.IsUnspecified() {
|
||||||
|
// indicate using conventional addr
|
||||||
|
h, _, _ = net.SplitHostPort(s.addr)
|
||||||
|
uAddress = net.JoinHostPort(h, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
dstAddr := socks.ParseAddr(addr)
|
pc, err = s.dialer.DialUDP(network, uAddress)
|
||||||
// write VER CMD RSV ATYP DST.ADDR DST.PORT
|
|
||||||
c.Write(append([]byte{Version, socks.CmdUDPAssociate, 0}, dstAddr...))
|
|
||||||
|
|
||||||
// read VER REP RSV ATYP BND.ADDR BND.PORT
|
|
||||||
if _, err := io.ReadFull(c, buf[:3]); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rep := buf[1]
|
|
||||||
if rep != 0 {
|
|
||||||
log.F("[socks5] server reply: %d, not succeeded", rep)
|
|
||||||
return nil, nil, errors.New("server connect failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
uAddr, err := socks.ReadAddrBuf(c, buf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
log.F("[socks5] dialudp to %s error: %s", uAddress, err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, nextHop, err := s.dialer.DialUDP(network, uAddr.String())
|
writeTo, err := net.ResolveUDPAddr("udp", uAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[socks5] dialudp to %s error: %s", uAddr.String(), err)
|
log.F("[socks5] resolve addr error: %s", err)
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pkc := NewPktConn(pc, nextHop, dstAddr, true, c)
|
return NewPktConn(pc, writeTo, socks.ParseAddr(addr), c), err
|
||||||
return pkc, nextHop, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect takes an existing connection to a socks5 proxy server,
|
// connect takes an existing connection to a socks5 proxy server,
|
||||||
// and commands the server to extend that connection to target,
|
// and commands the server to extend that connection to target,
|
||||||
// which must be a canonical address with a host and port.
|
// which must be a canonical address with a host and port.
|
||||||
func (s *Socks5) connect(conn net.Conn, target string) error {
|
func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Addr, err error) {
|
||||||
host, portStr, err := net.SplitHostPort(target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
|
||||||
}
|
|
||||||
if port < 1 || port > 0xffff {
|
|
||||||
return errors.New("proxy: port number out of range: " + portStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the size here is just an estimate
|
// the size here is just an estimate
|
||||||
buf := make([]byte, 0, 6+len(host))
|
buf := pool.GetBuffer(socks.MaxAddrLen)
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
buf = append(buf, Version)
|
buf = append(buf[:0], Version)
|
||||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||||
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
|
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
|
||||||
} else {
|
} else {
|
||||||
@ -124,17 +119,17 @@ func (s *Socks5) connect(conn net.Conn, target string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := conn.Write(buf); err != nil {
|
if _, err := conn.Write(buf); err != nil {
|
||||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
return addr, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
return addr, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
}
|
}
|
||||||
if buf[0] != Version {
|
if buf[0] != Version {
|
||||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||||
}
|
}
|
||||||
if buf[1] == 0xff {
|
if buf[1] == 0xff {
|
||||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf[1] == socks.AuthPassword {
|
if buf[1] == socks.AuthPassword {
|
||||||
@ -146,45 +141,29 @@ func (s *Socks5) connect(conn net.Conn, target string) error {
|
|||||||
buf = append(buf, s.password...)
|
buf = append(buf, s.password...)
|
||||||
|
|
||||||
if _, err := conn.Write(buf); err != nil {
|
if _, err := conn.Write(buf); err != nil {
|
||||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
return addr, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
return addr, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf[1] != 0 {
|
if buf[1] != 0 {
|
||||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
buf = append(buf, Version, socks.CmdConnect, 0 /* reserved */)
|
buf = append(buf, Version, cmd, 0 /* reserved */)
|
||||||
|
buf = append(buf, socks.ParseAddr(target)...)
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
buf = append(buf, socks.ATypIP4)
|
|
||||||
ip = ip4
|
|
||||||
} else {
|
|
||||||
buf = append(buf, socks.ATypIP6)
|
|
||||||
}
|
|
||||||
buf = append(buf, ip...)
|
|
||||||
} else {
|
|
||||||
if len(host) > 255 {
|
|
||||||
return errors.New("proxy: destination hostname too long: " + host)
|
|
||||||
}
|
|
||||||
buf = append(buf, socks.ATypDomain)
|
|
||||||
buf = append(buf, byte(len(host)))
|
|
||||||
buf = append(buf, host...)
|
|
||||||
}
|
|
||||||
buf = append(buf, byte(port>>8), byte(port))
|
|
||||||
|
|
||||||
if _, err := conn.Write(buf); err != nil {
|
if _, err := conn.Write(buf); err != nil {
|
||||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
return addr, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
// read VER REP RSV
|
||||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
if _, err := io.ReadFull(conn, buf[:3]); err != nil {
|
||||||
|
return addr, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
failure := "unknown error"
|
failure := "unknown error"
|
||||||
@ -193,38 +172,8 @@ func (s *Socks5) connect(conn net.Conn, target string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(failure) > 0 {
|
if len(failure) > 0 {
|
||||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
return addr, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesToDiscard := 0
|
return socks.ReadAddr(conn)
|
||||||
switch buf[3] {
|
|
||||||
case socks.ATypIP4:
|
|
||||||
bytesToDiscard = net.IPv4len
|
|
||||||
case socks.ATypIP6:
|
|
||||||
bytesToDiscard = net.IPv6len
|
|
||||||
case socks.ATypDomain:
|
|
||||||
_, err := io.ReadFull(conn, buf[:1])
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
bytesToDiscard = int(buf[0])
|
|
||||||
default:
|
|
||||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cap(buf) < bytesToDiscard {
|
|
||||||
buf = make([]byte, bytesToDiscard)
|
|
||||||
} else {
|
|
||||||
buf = buf[:bytesToDiscard]
|
|
||||||
}
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also need to discard the port number
|
|
||||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
|
||||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,24 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PktConn .
|
// PktConn .
|
||||||
type PktConn struct {
|
type PktConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
|
||||||
writeAddr net.Addr // write to and read from addr
|
|
||||||
|
|
||||||
tgtAddr socks.Addr
|
|
||||||
tgtHeader bool
|
|
||||||
|
|
||||||
ctrlConn net.Conn // tcp control conn
|
ctrlConn net.Conn // tcp control conn
|
||||||
|
writeTo net.Addr // write to and read from addr
|
||||||
|
target socks.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPktConn returns a PktConn.
|
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr.
|
||||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool, ctrlConn net.Conn) *PktConn {
|
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctrlConn net.Conn) *PktConn {
|
||||||
pc := &PktConn{
|
pc := &PktConn{
|
||||||
PacketConn: c,
|
PacketConn: c,
|
||||||
writeAddr: writeAddr,
|
writeTo: writeAddr,
|
||||||
tgtAddr: tgtAddr,
|
target: targetAddr,
|
||||||
tgtHeader: tgtHeader,
|
|
||||||
ctrlConn: ctrlConn,
|
ctrlConn: ctrlConn,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,23 +45,24 @@ func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHea
|
|||||||
|
|
||||||
// ReadFrom overrides the original function from net.PacketConn.
|
// ReadFrom overrides the original function from net.PacketConn.
|
||||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
if !pc.tgtHeader {
|
n, _, target, err := pc.readFrom(b)
|
||||||
return pc.PacketConn.ReadFrom(b)
|
return n, target, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
|
||||||
buf := pool.GetBuffer(len(b))
|
buf := pool.GetBuffer(len(b))
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, raddr, err
|
return n, raddr, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n < 3 {
|
if n < 3 {
|
||||||
return n, raddr, errors.New("not enough size to get addr")
|
return n, raddr, nil, errors.New("not enough size to get addr")
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc1928#section-7
|
// https://www.rfc-editor.org/rfc/rfc1928#section-7
|
||||||
// +----+------+------+----------+----------+----------+
|
// +----+------+------+----------+----------+----------+
|
||||||
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
// |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
||||||
// +----+------+------+----------+----------+----------+
|
// +----+------+------+----------+----------+----------+
|
||||||
@ -74,38 +70,46 @@ func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|||||||
// +----+------+------+----------+----------+----------+
|
// +----+------+------+----------+----------+----------+
|
||||||
tgtAddr := socks.SplitAddr(buf[3:n])
|
tgtAddr := socks.SplitAddr(buf[3:n])
|
||||||
if tgtAddr == nil {
|
if tgtAddr == nil {
|
||||||
return n, raddr, errors.New("can not get addr")
|
return n, raddr, nil, errors.New("can not get target addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := net.ResolveUDPAddr("udp", tgtAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return n, raddr, nil, errors.New("wrong target addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc.writeTo == nil {
|
||||||
|
pc.writeTo = raddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc.target == nil {
|
||||||
|
pc.target = make([]byte, len(tgtAddr))
|
||||||
|
copy(pc.target, tgtAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
n = copy(b, buf[3+len(tgtAddr):n])
|
n = copy(b, buf[3+len(tgtAddr):n])
|
||||||
|
return n, raddr, target, err
|
||||||
//test
|
|
||||||
if pc.writeAddr == nil {
|
|
||||||
pc.writeAddr = raddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if pc.tgtAddr == nil {
|
|
||||||
pc.tgtAddr = make([]byte, len(tgtAddr))
|
|
||||||
copy(pc.tgtAddr, tgtAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, raddr, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo overrides the original function from net.PacketConn.
|
// WriteTo overrides the original function from net.PacketConn.
|
||||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
if !pc.tgtHeader {
|
target := pc.target
|
||||||
return pc.PacketConn.WriteTo(b, addr)
|
if addr != nil {
|
||||||
|
target = socks.ParseAddr(addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if target == nil {
|
||||||
|
return 0, errors.New("invalid addr")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
buf := pool.GetBytesBuffer()
|
||||||
defer pool.PutBytesBuffer(buf)
|
defer pool.PutBytesBuffer(buf)
|
||||||
|
|
||||||
buf.Write([]byte{0, 0, 0})
|
buf.Write([]byte{0, 0, 0})
|
||||||
tgtLen, _ := buf.Write(pc.tgtAddr)
|
tgtLen, _ := buf.Write(target)
|
||||||
buf.Write(b)
|
buf.Write(b)
|
||||||
|
|
||||||
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeAddr)
|
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo)
|
||||||
if n > tgtLen+3 {
|
if n > tgtLen+3 {
|
||||||
return n - tgtLen - 3, err
|
return n - tgtLen - 3, err
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,18 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var nm sync.Map
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterServer("socks5", NewSocks5Server)
|
||||||
|
}
|
||||||
|
|
||||||
// NewSocks5Server returns a socks5 proxy server.
|
// NewSocks5Server returns a socks5 proxy server.
|
||||||
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
return NewSocks5(s, nil, p)
|
return NewSocks5(s, nil, p)
|
||||||
@ -29,7 +35,7 @@ func (s *Socks5) ListenAndServe() {
|
|||||||
func (s *Socks5) ListenAndServeTCP() {
|
func (s *Socks5) ListenAndServeTCP() {
|
||||||
l, err := net.Listen("tcp", s.addr)
|
l, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[socks5] failed to listen on %s: %v", s.addr, err)
|
log.Fatalf("[socks5] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,87 +103,119 @@ func (s *Socks5) Serve(c net.Conn) {
|
|||||||
func (s *Socks5) ListenAndServeUDP() {
|
func (s *Socks5) ListenAndServeUDP() {
|
||||||
lc, err := net.ListenPacket("udp", s.addr)
|
lc, err := net.ListenPacket("udp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[socks5] failed to listen on UDP %s: %v", s.addr, err)
|
log.Fatalf("[socks5] failed to listen on UDP %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer lc.Close()
|
defer lc.Close()
|
||||||
|
|
||||||
log.F("[socks5] listening UDP on %s", s.addr)
|
log.F("[socks5] listening UDP on %s", s.addr)
|
||||||
|
|
||||||
var nm sync.Map
|
s.ServePacket(lc)
|
||||||
buf := make([]byte, proxy.UDPBufSize)
|
}
|
||||||
|
|
||||||
|
// ServePacket implements proxy.PacketServer.
|
||||||
|
func (s *Socks5) ServePacket(pc net.PacketConn) {
|
||||||
for {
|
for {
|
||||||
c := NewPktConn(lc, nil, nil, true, nil)
|
c := NewPktConn(pc, nil, nil, nil)
|
||||||
|
buf := pool.GetBuffer(proxy.UDPBufSize)
|
||||||
|
|
||||||
n, raddr, err := c.ReadFrom(buf)
|
n, srcAddr, dstAddr, err := c.readFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[socks5u] remote read error: %v", err)
|
log.F("[socks5u] remote read error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var pc *PktConn
|
var session *Session
|
||||||
v, ok := nm.Load(raddr.String())
|
sessionKey := srcAddr.String()
|
||||||
if !ok && v == nil {
|
|
||||||
if c.tgtAddr == nil {
|
|
||||||
log.F("[socks5u] can not get target address, not a valid request")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lpc, dialer, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5u] remote dial error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pc = NewPktConn(lpc, nextHop, nil, false, nil)
|
|
||||||
nm.Store(raddr.String(), pc)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
proxy.RelayUDP(c, raddr, pc, 2*time.Minute)
|
|
||||||
pc.Close()
|
|
||||||
nm.Delete(raddr.String())
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.F("[socks5u] %s <-> %s via %s", raddr, c.tgtAddr, dialer.Addr())
|
|
||||||
|
|
||||||
|
v, ok := nm.Load(sessionKey)
|
||||||
|
if !ok || v == nil {
|
||||||
|
session = newSession(sessionKey, srcAddr, dstAddr, c)
|
||||||
|
nm.Store(sessionKey, session)
|
||||||
|
go s.serveSession(session)
|
||||||
} else {
|
} else {
|
||||||
pc = v.(*PktConn)
|
session = v.(*Session)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
session.msgCh <- message{dstAddr, buf[:n]}
|
||||||
if err != nil {
|
|
||||||
log.F("[socks5u] remote write error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.F("[socks5u] %s <-> %s", raddr, c.tgtAddr)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Socks5) serveSession(session *Session) {
|
||||||
|
dstPC, dialer, err := s.proxy.DialUDP("udp", session.srcPC.target.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5u] remote dial error: %v", err)
|
||||||
|
nm.Delete(session.key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dstPC.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
proxy.CopyUDP(session.srcPC, nil, dstPC, 2*time.Minute, 5*time.Second)
|
||||||
|
nm.Delete(session.key)
|
||||||
|
close(session.finCh)
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.F("[socks5u] %s <-> %s via %s", session.src, session.srcPC.target, dialer.Addr())
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-session.msgCh:
|
||||||
|
_, err = dstPC.WriteTo(msg.msg, msg.dst)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[socks5u] writeTo %s error: %v", msg.dst, err)
|
||||||
|
}
|
||||||
|
pool.PutBuffer(msg.msg)
|
||||||
|
msg.msg = nil
|
||||||
|
case <-session.finCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
dst net.Addr
|
||||||
|
msg []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session is a udp session
|
||||||
|
type Session struct {
|
||||||
|
key string
|
||||||
|
src net.Addr
|
||||||
|
dst net.Addr
|
||||||
|
srcPC *PktConn
|
||||||
|
msgCh chan message
|
||||||
|
finCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
|
||||||
|
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
// Handshake fast-tracks SOCKS initialization to get target address to connect.
|
||||||
func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
func (s *Socks5) handshake(c net.Conn) (socks.Addr, error) {
|
||||||
// Read RFC 1928 for request and reply structure and sizes
|
// Read RFC 1928 for request and reply structure and sizes
|
||||||
buf := make([]byte, socks.MaxAddrLen)
|
buf := pool.GetBuffer(socks.MaxAddrLen)
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
// read VER, NMETHODS, METHODS
|
// read VER, NMETHODS, METHODS
|
||||||
if _, err := io.ReadFull(rw, buf[:2]); err != nil {
|
if _, err := io.ReadFull(c, buf[:2]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nmethods := buf[1]
|
nmethods := buf[1]
|
||||||
if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil {
|
if _, err := io.ReadFull(c, buf[:nmethods]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// write VER METHOD
|
// write VER METHOD
|
||||||
if s.user != "" && s.password != "" {
|
if s.user != "" && s.password != "" {
|
||||||
_, err := rw.Write([]byte{Version, socks.AuthPassword})
|
_, err := c.Write([]byte{Version, socks.AuthPassword})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.ReadFull(rw, buf[:2])
|
_, err = io.ReadFull(c, buf[:2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -185,28 +223,28 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
|||||||
// Get username
|
// Get username
|
||||||
userLen := int(buf[1])
|
userLen := int(buf[1])
|
||||||
if userLen <= 0 {
|
if userLen <= 0 {
|
||||||
rw.Write([]byte{1, 1})
|
c.Write([]byte{1, 1})
|
||||||
return nil, errors.New("auth failed: wrong username length")
|
return nil, errors.New("auth failed: wrong username length")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.ReadFull(rw, buf[:userLen]); err != nil {
|
if _, err := io.ReadFull(c, buf[:userLen]); err != nil {
|
||||||
return nil, errors.New("auth failed: cannot get username")
|
return nil, errors.New("auth failed: cannot get username")
|
||||||
}
|
}
|
||||||
user := string(buf[:userLen])
|
user := string(buf[:userLen])
|
||||||
|
|
||||||
// Get password
|
// Get password
|
||||||
_, err = rw.Read(buf[:1])
|
_, err = c.Read(buf[:1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("auth failed: cannot get password len")
|
return nil, errors.New("auth failed: cannot get password len")
|
||||||
}
|
}
|
||||||
|
|
||||||
passLen := int(buf[0])
|
passLen := int(buf[0])
|
||||||
if passLen <= 0 {
|
if passLen <= 0 {
|
||||||
rw.Write([]byte{1, 1})
|
c.Write([]byte{1, 1})
|
||||||
return nil, errors.New("auth failed: wrong password length")
|
return nil, errors.New("auth failed: wrong password length")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.ReadFull(rw, buf[:passLen])
|
_, err = io.ReadFull(c, buf[:passLen])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("auth failed: cannot get password")
|
return nil, errors.New("auth failed: cannot get password")
|
||||||
}
|
}
|
||||||
@ -214,7 +252,7 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
|||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
if user != s.user || pass != s.password {
|
if user != s.user || pass != s.password {
|
||||||
_, err = rw.Write([]byte{1, 1})
|
_, err = c.Write([]byte{1, 1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -222,30 +260,33 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Response auth state
|
// Response auth state
|
||||||
_, err = rw.Write([]byte{1, 0})
|
_, err = c.Write([]byte{1, 0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if _, err := rw.Write([]byte{Version, socks.AuthNone}); err != nil {
|
} else if _, err := c.Write([]byte{Version, socks.AuthNone}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
// read VER CMD RSV ATYP DST.ADDR DST.PORT
|
||||||
if _, err := io.ReadFull(rw, buf[:3]); err != nil {
|
if _, err := io.ReadFull(c, buf[:3]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cmd := buf[1]
|
cmd := buf[1]
|
||||||
addr, err := socks.ReadAddrBuf(rw, buf)
|
addr, err := socks.ReadAddr(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case socks.CmdConnect:
|
case socks.CmdConnect:
|
||||||
_, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded
|
_, err = c.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded
|
||||||
case socks.CmdUDPAssociate:
|
case socks.CmdUDPAssociate:
|
||||||
listenAddr := socks.ParseAddr(rw.(net.Conn).LocalAddr().String())
|
listenAddr := socks.ParseAddr(c.LocalAddr().String())
|
||||||
_, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
|
if listenAddr == nil { // maybe it's unix socket
|
||||||
|
listenAddr = socks.ParseAddr("127.0.0.1:0")
|
||||||
|
}
|
||||||
|
_, err = c.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, socks.Errors[7]
|
return nil, socks.Errors[7]
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// https://tools.ietf.org/html/rfc1928
|
// https://www.rfc-editor.org/rfc/rfc1928
|
||||||
|
|
||||||
// socks5 client:
|
// socks5 client:
|
||||||
// https://github.com/golang/net/tree/master/proxy
|
// https://github.com/golang/net/tree/master/proxy
|
||||||
@ -12,7 +12,7 @@ package socks5
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,11 +28,6 @@ type Socks5 struct {
|
|||||||
password string
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
proxy.RegisterDialer("socks5", NewSocks5Dialer)
|
|
||||||
proxy.RegisterServer("socks5", NewSocks5Server)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address.
|
// NewSocks5 returns a Proxy that makes SOCKS v5 connections to the given address.
|
||||||
// with an optional username and password. (RFC 1928)
|
// with an optional username and password. (RFC 1928)
|
||||||
func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
||||||
@ -56,3 +51,10 @@ func NewSocks5(s string, d proxy.Dialer, p proxy.Proxy) (*Socks5, error) {
|
|||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("socks5", `
|
||||||
|
Socks5 scheme:
|
||||||
|
socks5://[user:pass@]host:port
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ss/cipher/internal/shadowaead"
|
"github.com/nadoo/glider/proxy/ss/cipher/shadowaead"
|
||||||
"github.com/nadoo/glider/proxy/ss/cipher/internal/shadowstream"
|
"github.com/nadoo/glider/proxy/ss/cipher/shadowstream"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cipher interface.
|
// Cipher interface.
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -25,7 +25,6 @@ type writer struct {
|
|||||||
io.Writer
|
io.Writer
|
||||||
cipher.AEAD
|
cipher.AEAD
|
||||||
nonce [32]byte
|
nonce [32]byte
|
||||||
buf []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWriter wraps an io.Writer with AEAD encryption.
|
// NewWriter wraps an io.Writer with AEAD encryption.
|
@ -4,7 +4,7 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bufSize = 32 * 1024
|
const bufSize = 32 * 1024
|
@ -4,9 +4,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSSDialer returns a ss proxy dialer.
|
// NewSSDialer returns a ss proxy dialer.
|
||||||
@ -45,13 +45,19 @@ func (s *SS) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *SS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
func (s *SS) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
pc, nextHop, err := s.dialer.DialUDP(network, s.addr)
|
pc, err := s.dialer.DialUDP(network, s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ss] dialudp to %s error: %s", s.addr, err)
|
log.F("[ss] dialudp to %s error: %s", s.addr, err)
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pkc := NewPktConn(s.PacketConn(pc), nextHop, socks.ParseAddr(addr), true)
|
writeTo, err := net.ResolveUDPAddr("udp", s.addr)
|
||||||
return pkc, nextHop, err
|
if err != nil {
|
||||||
|
log.F("[ss] resolve addr error: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkc := NewPktConn(s.PacketConn(pc), writeTo, socks.ParseAddr(addr))
|
||||||
|
return pkc, nil
|
||||||
}
|
}
|
||||||
|
@ -4,77 +4,78 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PktConn .
|
// PktConn .
|
||||||
type PktConn struct {
|
type PktConn struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
|
writeTo net.Addr
|
||||||
writeAddr net.Addr // write to and read from addr
|
target socks.Addr // if target is not nil, it may be a tunnel
|
||||||
|
|
||||||
tgtAddr socks.Addr
|
|
||||||
tgtHeader bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPktConn returns a PktConn
|
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr.
|
||||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool) *PktConn {
|
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr) *PktConn {
|
||||||
pc := &PktConn{
|
return &PktConn{PacketConn: c, writeTo: writeAddr, target: targetAddr}
|
||||||
PacketConn: c,
|
|
||||||
writeAddr: writeAddr,
|
|
||||||
tgtAddr: tgtAddr,
|
|
||||||
tgtHeader: tgtHeader}
|
|
||||||
return pc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom overrides the original function from net.PacketConn
|
// ReadFrom overrides the original function from net.PacketConn.
|
||||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
if !pc.tgtHeader {
|
n, _, target, err := pc.readFrom(b)
|
||||||
return pc.PacketConn.ReadFrom(b)
|
return n, target, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
|
||||||
buf := pool.GetBuffer(len(b))
|
buf := pool.GetBuffer(len(b))
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, raddr, err
|
return n, raddr, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tgtAddr := socks.SplitAddr(buf[:n])
|
tgtAddr := socks.SplitAddr(buf[:n])
|
||||||
if tgtAddr == nil {
|
if tgtAddr == nil {
|
||||||
return n, raddr, errors.New("can not get addr")
|
return n, raddr, nil, errors.New("can not get target addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := net.ResolveUDPAddr("udp", tgtAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return n, raddr, nil, errors.New("wrong target addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc.writeTo == nil {
|
||||||
|
pc.writeTo = raddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc.target == nil {
|
||||||
|
pc.target = make([]byte, len(tgtAddr))
|
||||||
|
copy(pc.target, tgtAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
n = copy(b, buf[len(tgtAddr):n])
|
n = copy(b, buf[len(tgtAddr):n])
|
||||||
|
return n, raddr, target, err
|
||||||
//test
|
|
||||||
if pc.writeAddr == nil {
|
|
||||||
pc.writeAddr = raddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if pc.tgtAddr == nil {
|
|
||||||
pc.tgtAddr = make([]byte, len(tgtAddr))
|
|
||||||
copy(pc.tgtAddr, tgtAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, raddr, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo overrides the original function from net.PacketConn
|
// WriteTo overrides the original function from net.PacketConn
|
||||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
if !pc.tgtHeader {
|
target := pc.target
|
||||||
return pc.PacketConn.WriteTo(b, addr)
|
if addr != nil {
|
||||||
|
target = socks.ParseAddr(addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if target == nil {
|
||||||
|
return 0, errors.New("invalid addr")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := pool.GetBytesBuffer()
|
buf := pool.GetBytesBuffer()
|
||||||
defer pool.PutBytesBuffer(buf)
|
defer pool.PutBytesBuffer(buf)
|
||||||
|
|
||||||
tgtLen, _ := buf.Write(pc.tgtAddr)
|
tgtLen, _ := buf.Write(target)
|
||||||
buf.Write(b)
|
buf.Write(b)
|
||||||
|
|
||||||
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeAddr)
|
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo)
|
||||||
if n > tgtLen {
|
if n > tgtLen {
|
||||||
return n - tgtLen, err
|
return n - tgtLen, err
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
package ss
|
package ss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var nm sync.Map
|
||||||
|
|
||||||
// NewSSServer returns a ss proxy server.
|
// NewSSServer returns a ss proxy server.
|
||||||
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||||
return NewSS(s, nil, p)
|
return NewSS(s, nil, p)
|
||||||
@ -26,7 +30,7 @@ func (s *SS) ListenAndServe() {
|
|||||||
func (s *SS) ListenAndServeTCP() {
|
func (s *SS) ListenAndServeTCP() {
|
||||||
l, err := net.Listen("tcp", s.addr)
|
l, err := net.Listen("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ss] failed to listen on %s: %v", s.addr, err)
|
log.Fatalf("[ss] failed to listen on %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,18 +55,17 @@ func (s *SS) Serve(c net.Conn) {
|
|||||||
c.SetKeepAlive(true)
|
c.SetKeepAlive(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
c = s.StreamConn(c)
|
sc := s.StreamConn(c)
|
||||||
|
|
||||||
tgt, err := socks.ReadAddr(c)
|
tgt, err := socks.ReadAddr(sc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ss] failed to get target address: %v", err)
|
log.F("[ss] %s <-> target error: %v", c.RemoteAddr(), err)
|
||||||
|
proxy.Copy(io.Discard, c) // https://github.com/nadoo/glider/issues/180
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
network := "tcp"
|
|
||||||
dialer := s.proxy.NextDialer(tgt.String())
|
dialer := s.proxy.NextDialer(tgt.String())
|
||||||
|
rc, err := dialer.Dial("tcp", tgt.String())
|
||||||
rc, err := dialer.Dial(network, tgt.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||||
return
|
return
|
||||||
@ -71,7 +74,7 @@ func (s *SS) Serve(c net.Conn) {
|
|||||||
|
|
||||||
log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
log.F("[ss] %s <-> %s via %s", c.RemoteAddr(), tgt, dialer.Addr())
|
||||||
|
|
||||||
if err = proxy.Relay(c, rc); err != nil {
|
if err = proxy.Relay(sc, rc); err != nil {
|
||||||
log.F("[ss] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
log.F("[ss] %s <-> %s via %s, relay error: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||||
// record remote conn failure only
|
// record remote conn failure only
|
||||||
if !strings.Contains(err.Error(), s.addr) {
|
if !strings.Contains(err.Error(), s.addr) {
|
||||||
@ -80,61 +83,96 @@ func (s *SS) Serve(c net.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServeUDP serves udp ss requests.
|
// ListenAndServeUDP serves udp requests.
|
||||||
func (s *SS) ListenAndServeUDP() {
|
func (s *SS) ListenAndServeUDP() {
|
||||||
lc, err := net.ListenPacket("udp", s.addr)
|
lc, err := net.ListenPacket("udp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ss] failed to listen on UDP %s: %v", s.addr, err)
|
log.Fatalf("[ss] failed to listen on UDP %s: %v", s.addr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer lc.Close()
|
defer lc.Close()
|
||||||
|
|
||||||
lc = s.PacketConn(lc)
|
|
||||||
|
|
||||||
log.F("[ss] listening UDP on %s", s.addr)
|
log.F("[ss] listening UDP on %s", s.addr)
|
||||||
|
|
||||||
var nm sync.Map
|
s.ServePacket(lc)
|
||||||
buf := make([]byte, proxy.UDPBufSize)
|
}
|
||||||
|
|
||||||
|
// ServePacket implements proxy.PacketServer.
|
||||||
|
func (s *SS) ServePacket(pc net.PacketConn) {
|
||||||
|
lc := s.PacketConn(pc)
|
||||||
for {
|
for {
|
||||||
c := NewPktConn(lc, nil, nil, true)
|
c := NewPktConn(lc, nil, nil)
|
||||||
|
buf := pool.GetBuffer(proxy.UDPBufSize)
|
||||||
|
|
||||||
n, raddr, err := c.ReadFrom(buf)
|
n, srcAddr, dstAddr, err := c.readFrom(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ssu] remote read error: %v", err)
|
log.F("[ssu] remote read error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var pc *PktConn
|
var session *Session
|
||||||
v, ok := nm.Load(raddr.String())
|
sessionKey := srcAddr.String()
|
||||||
if !ok && v == nil {
|
|
||||||
lpc, dialer, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
|
||||||
if err != nil {
|
|
||||||
log.F("[ssu] remote dial error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pc = NewPktConn(lpc, nextHop, nil, false)
|
|
||||||
nm.Store(raddr.String(), pc)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
proxy.RelayUDP(c, raddr, pc, 2*time.Minute)
|
|
||||||
pc.Close()
|
|
||||||
nm.Delete(raddr.String())
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.F("[ssu] %s <-> %s via %s", raddr, c.tgtAddr, dialer.Addr())
|
|
||||||
|
|
||||||
|
v, ok := nm.Load(sessionKey)
|
||||||
|
if !ok || v == nil {
|
||||||
|
session = newSession(sessionKey, srcAddr, dstAddr, c)
|
||||||
|
nm.Store(sessionKey, session)
|
||||||
|
go s.serveSession(session)
|
||||||
} else {
|
} else {
|
||||||
pc = v.(*PktConn)
|
session = v.(*Session)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
session.msgCh <- message{dstAddr, buf[:n]}
|
||||||
if err != nil {
|
|
||||||
log.F("[ssu] remote write error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.F("[ssu] %s <-> %s", raddr, c.tgtAddr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SS) serveSession(session *Session) {
|
||||||
|
dstPC, dialer, err := s.proxy.DialUDP("udp", session.dst.String())
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ssu] remote dial error: %v", err)
|
||||||
|
nm.Delete(session.key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dstPC.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
proxy.CopyUDP(session.srcPC, nil, dstPC, 2*time.Minute, 5*time.Second)
|
||||||
|
nm.Delete(session.key)
|
||||||
|
close(session.finCh)
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.F("[ssu] %s <-> %s via %s", session.src, session.dst, dialer.Addr())
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-session.msgCh:
|
||||||
|
_, err = dstPC.WriteTo(msg.msg, msg.dst)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ssu] writeTo %s error: %v", msg.dst, err)
|
||||||
|
}
|
||||||
|
pool.PutBuffer(msg.msg)
|
||||||
|
msg.msg = nil
|
||||||
|
case <-session.finCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
dst net.Addr
|
||||||
|
msg []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session is a udp session
|
||||||
|
type Session struct {
|
||||||
|
key string
|
||||||
|
src net.Addr
|
||||||
|
dst net.Addr
|
||||||
|
srcPC *PktConn
|
||||||
|
msgCh chan message
|
||||||
|
finCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSession(key string, src, dst net.Addr, srcPC *PktConn) *Session {
|
||||||
|
return &Session{key, src, dst, srcPC, make(chan message, 32), make(chan struct{})}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ package ss
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/ss/cipher"
|
"github.com/nadoo/glider/proxy/ss/cipher"
|
||||||
)
|
)
|
||||||
@ -48,3 +48,19 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
|||||||
|
|
||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("ss", `
|
||||||
|
SS scheme:
|
||||||
|
ss://method:pass@host:port
|
||||||
|
|
||||||
|
Available methods for ss:
|
||||||
|
AEAD Ciphers:
|
||||||
|
AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AEAD_XCHACHA20_POLY1305
|
||||||
|
Stream Ciphers:
|
||||||
|
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
|
||||||
|
Alias:
|
||||||
|
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
|
||||||
|
Plain: NONE
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
123
proxy/ssh/ssh.go
123
proxy/ssh/ssh.go
@ -1,14 +1,17 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,7 +20,13 @@ type SSH struct {
|
|||||||
dialer proxy.Dialer
|
dialer proxy.Dialer
|
||||||
proxy proxy.Proxy
|
proxy proxy.Proxy
|
||||||
addr string
|
addr string
|
||||||
|
|
||||||
|
conn net.Conn
|
||||||
|
client *ssh.Client
|
||||||
config *ssh.ClientConfig
|
config *ssh.ClientConfig
|
||||||
|
|
||||||
|
once sync.Once
|
||||||
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -28,7 +37,7 @@ func init() {
|
|||||||
func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("parse err: %s", err)
|
log.F("[ssh] parse err: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,18 +47,16 @@ func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := &ssh.ClientConfig{
|
config := &ssh.ClientConfig{
|
||||||
User: user,
|
User: user,
|
||||||
Timeout: time.Second * 3,
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pass, _ := u.User.Password(); pass != "" {
|
if pass, _ := u.User.Password(); pass != "" {
|
||||||
config.Auth = []ssh.AuthMethod{ssh.Password(pass)}
|
config.Auth = []ssh.AuthMethod{ssh.Password(pass)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if key := u.Query().Get("key"); key != "" {
|
query := u.Query()
|
||||||
|
if key := query.Get("key"); key != "" {
|
||||||
keyAuth, err := privateKeyAuth(key)
|
keyAuth, err := privateKeyAuth(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ssh] read key file error: %s", err)
|
log.F("[ssh] read key file error: %s", err)
|
||||||
@ -58,14 +65,30 @@ func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
|||||||
config.Auth = append(config.Auth, keyAuth)
|
config.Auth = append(config.Auth, keyAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
ssh := &SSH{
|
// timeout of ssh handshake and channel operation
|
||||||
|
qtimeout := query.Get("timeout")
|
||||||
|
if qtimeout == "" {
|
||||||
|
qtimeout = "5" // default timeout
|
||||||
|
}
|
||||||
|
timeout, err := strconv.ParseUint(qtimeout, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[ssh] parse timeout err: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Timeout = time.Second * time.Duration(timeout)
|
||||||
|
|
||||||
|
t := &SSH{
|
||||||
dialer: d,
|
dialer: d,
|
||||||
proxy: p,
|
proxy: p,
|
||||||
addr: u.Host,
|
addr: u.Host,
|
||||||
config: config,
|
config: config,
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssh, nil
|
if _, port, _ := net.SplitHostPort(t.addr); port == "" {
|
||||||
|
t.addr = net.JoinHostPort(t.addr, "22")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSSHDialer returns a ssh proxy dialer.
|
// NewSSHDialer returns a ssh proxy dialer.
|
||||||
@ -83,28 +106,78 @@ func (s *SSH) Addr() string {
|
|||||||
|
|
||||||
// Dial connects to the address addr on the network net via the proxy.
|
// Dial connects to the address addr on the network net via the proxy.
|
||||||
func (s *SSH) Dial(network, addr string) (net.Conn, error) {
|
func (s *SSH) Dial(network, addr string) (net.Conn, error) {
|
||||||
c, err := s.dialer.Dial(network, s.addr)
|
s.once.Do(func() { go s.keepConn(s.initConn() == nil) })
|
||||||
|
|
||||||
|
s.mutex.RLock()
|
||||||
|
defer s.mutex.RUnlock()
|
||||||
|
|
||||||
|
if s.client == nil {
|
||||||
|
return nil, errors.New("ssh client is nil")
|
||||||
|
}
|
||||||
|
return s.dial(network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSH) dial(network, addr string) (net.Conn, error) {
|
||||||
|
s.conn.SetDeadline(time.Now().Add(s.config.Timeout))
|
||||||
|
c, err := s.client.Dial(network, addr)
|
||||||
|
s.conn.SetDeadline(time.Time{})
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSH) initConn() error {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
log.F("[ssh] connecting to %s", s.addr)
|
||||||
|
c, err := s.dialer.Dial("tcp", s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ssh]: dial to %s error: %s", s.addr, err)
|
log.F("[ssh] dial connection to %s error: %s", s.addr, err)
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sshc, ch, req, err := ssh.NewClientConn(c, s.addr, s.config)
|
c.SetDeadline(time.Now().Add(s.config.Timeout))
|
||||||
|
conn, ch, req, err := ssh.NewClientConn(c, s.addr, s.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[ssh]: initial connection to %s error: %s", s.addr, err)
|
log.F("[ssh] initial connection to %s error: %s", s.addr, err)
|
||||||
return nil, err
|
c.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
s.conn = c
|
||||||
|
s.client = ssh.NewClient(conn, ch, req)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSH) keepConn(connected bool) {
|
||||||
|
if connected {
|
||||||
|
s.client.Conn.Wait()
|
||||||
|
s.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssh.NewClient(sshc, ch, req).Dial(network, addr)
|
sleep := time.Second
|
||||||
|
for {
|
||||||
|
if err := s.initConn(); err != nil {
|
||||||
|
sleep *= 2
|
||||||
|
if sleep > time.Second*60 {
|
||||||
|
sleep = time.Second * 60
|
||||||
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sleep = time.Second
|
||||||
|
s.client.Conn.Wait()
|
||||||
|
s.conn.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||||
return nil, nil, proxy.ErrNotSupported
|
return nil, proxy.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
||||||
buffer, err := ioutil.ReadFile(file)
|
buffer, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -116,3 +189,11 @@ func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
|||||||
|
|
||||||
return ssh.PublicKeys(key), nil
|
return ssh.PublicKeys(key), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("ssh", `
|
||||||
|
SSH scheme:
|
||||||
|
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
||||||
|
timeout: timeout of ssh handshake and channel operation, default: 5
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
@ -5,10 +5,10 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/des"
|
"crypto/des"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/rc4"
|
"crypto/rc4"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
"github.com/aead/chacha20"
|
"github.com/aead/chacha20"
|
||||||
"github.com/dgryski/go-camellia"
|
"github.com/dgryski/go-camellia"
|
||||||
@ -18,7 +18,7 @@ import (
|
|||||||
"golang.org/x/crypto/cast5"
|
"golang.org/x/crypto/cast5"
|
||||||
"golang.org/x/crypto/salsa20/salsa"
|
"golang.org/x/crypto/salsa20/salsa"
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,22 +8,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/pool"
|
"github.com/nadoo/glider/pkg/pool"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bufSize = proxy.TCPBufSize
|
var bufSize = proxy.TCPBufSize
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSTCPConn the struct that override the net.Conn methods
|
// SSTCPConn the struct that override the net.Conn methods
|
||||||
type SSTCPConn struct {
|
type SSTCPConn struct {
|
||||||
|
@ -17,8 +17,8 @@ type IObfs interface {
|
|||||||
GetServerInfo() (s *ssr.ServerInfo)
|
GetServerInfo() (s *ssr.ServerInfo)
|
||||||
Encode(data []byte) (encodedData []byte, err error)
|
Encode(data []byte) (encodedData []byte, err error)
|
||||||
Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
|
Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
|
||||||
SetData(data interface{})
|
SetData(data any)
|
||||||
GetData() interface{}
|
GetData() any
|
||||||
GetOverhead() int
|
GetOverhead() int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package obfs
|
package obfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -13,7 +13,7 @@ func newHttpPost() IObfs {
|
|||||||
// newHttpSimple create a http_simple object
|
// newHttpSimple create a http_simple object
|
||||||
|
|
||||||
t := &httpSimplePost{
|
t := &httpSimplePost{
|
||||||
userAgentIndex: rand.Intn(len(requestUserAgent)),
|
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||||
methodGet: false,
|
methodGet: false,
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
@ -55,7 +55,7 @@ func newHttpSimple() IObfs {
|
|||||||
t := &httpSimplePost{
|
t := &httpSimplePost{
|
||||||
rawTransSent: false,
|
rawTransSent: false,
|
||||||
rawTransReceived: false,
|
rawTransReceived: false,
|
||||||
userAgentIndex: rand.Intn(len(requestUserAgent)),
|
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||||
methodGet: true,
|
methodGet: true,
|
||||||
}
|
}
|
||||||
return t
|
return t
|
||||||
@ -69,25 +69,25 @@ func (t *httpSimplePost) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &t.ServerInfo
|
return &t.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *httpSimplePost) SetData(data interface{}) {
|
func (t *httpSimplePost) SetData(data any) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *httpSimplePost) GetData() interface{} {
|
func (t *httpSimplePost) GetData() any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *httpSimplePost) boundary() (ret string) {
|
func (t *httpSimplePost) boundary() (ret string) {
|
||||||
|
|
||||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
for i := 0; i < 32; i++ {
|
for range 32 {
|
||||||
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
|
ret = fmt.Sprintf("%s%c", ret, set[rand.IntN(len(set))])
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) {
|
func (t *httpSimplePost) data2URLEncode(data []byte) (ret string) {
|
||||||
for i := 0; i < len(data); i++ {
|
for i := range data {
|
||||||
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -101,12 +101,12 @@ func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
dataLength := len(data)
|
dataLength := len(data)
|
||||||
var headData []byte
|
var headData []byte
|
||||||
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 {
|
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 {
|
||||||
headData = make([]byte, headSize+rand.Intn(64))
|
headData = make([]byte, headSize+rand.IntN(64))
|
||||||
} else {
|
} else {
|
||||||
headData = make([]byte, dataLength)
|
headData = make([]byte, dataLength)
|
||||||
}
|
}
|
||||||
copy(headData, data[0:len(headData)])
|
copy(headData, data[0:len(headData)])
|
||||||
requestPathIndex := rand.Intn(len(requestPath)/2) * 2
|
requestPathIndex := rand.IntN(len(requestPath)/2) * 2
|
||||||
host := t.Host
|
host := t.Host
|
||||||
var customHead string
|
var customHead string
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
}
|
}
|
||||||
hosts := strings.Split(param, ",")
|
hosts := strings.Split(param, ",")
|
||||||
if len(hosts) > 0 {
|
if len(hosts) > 0 {
|
||||||
host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
|
host = strings.TrimSpace(hosts[rand.IntN(len(hosts))])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
method := "GET /"
|
method := "GET /"
|
||||||
|
@ -33,11 +33,11 @@ func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err
|
|||||||
return data, false, nil
|
return data, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *plain) SetData(data interface{}) {
|
func (p *plain) SetData(data any) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *plain) GetData() interface{} {
|
func (p *plain) GetData() any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package obfs
|
package obfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
crand "crypto/rand"
|
||||||
|
"math/rand/v2"
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
)
|
)
|
||||||
@ -31,11 +32,11 @@ func (r *randomHead) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &r.ServerInfo
|
return &r.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *randomHead) SetData(data interface{}) {
|
func (r *randomHead) SetData(data any) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *randomHead) GetData() interface{} {
|
func (r *randomHead) GetData() any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,9 +58,9 @@ func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
r.rawTransSent = true
|
r.rawTransSent = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size := rand.Intn(96) + 8
|
size := rand.IntN(96) + 8
|
||||||
encodedData = make([]byte, size)
|
encodedData = make([]byte, size)
|
||||||
rand.Read(encodedData)
|
crand.Read(encodedData)
|
||||||
ssr.SetCRC32(encodedData, size)
|
ssr.SetCRC32(encodedData, size)
|
||||||
|
|
||||||
d := make([]byte, dataLength)
|
d := make([]byte, dataLength)
|
||||||
|
@ -3,10 +3,11 @@ package obfs
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
crand "crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -53,18 +54,18 @@ func (t *tls12TicketAuth) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &t.ServerInfo
|
return &t.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tls12TicketAuth) SetData(data interface{}) {
|
func (t *tls12TicketAuth) SetData(data any) {
|
||||||
if auth, ok := data.(*tlsAuthData); ok {
|
if auth, ok := data.(*tlsAuthData); ok {
|
||||||
t.data = auth
|
t.data = auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tls12TicketAuth) GetData() interface{} {
|
func (t *tls12TicketAuth) GetData() any {
|
||||||
if t.data == nil {
|
if t.data == nil {
|
||||||
t.data = &tlsAuthData{}
|
t.data = &tlsAuthData{}
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
|
|
||||||
rand.Read(b)
|
crand.Read(b)
|
||||||
copy(t.data.localClientID[:], b)
|
copy(t.data.localClientID[:], b)
|
||||||
}
|
}
|
||||||
return t.data
|
return t.data
|
||||||
@ -76,7 +77,7 @@ func (t *tls12TicketAuth) getHost() string {
|
|||||||
hosts := strings.Split(t.Param, ",")
|
hosts := strings.Split(t.Param, ",")
|
||||||
if len(hosts) > 0 {
|
if len(hosts) > 0 {
|
||||||
|
|
||||||
host = hosts[rand.Intn(len(hosts))]
|
host = hosts[rand.IntN(len(hosts))]
|
||||||
host = strings.TrimSpace(host)
|
host = strings.TrimSpace(host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +97,6 @@ func packData(prefixData []byte, suffixData []byte) (outData []byte) {
|
|||||||
|
|
||||||
func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
||||||
encodedData = make([]byte, 0)
|
encodedData = make([]byte, 0)
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
switch t.handshakeStatus {
|
switch t.handshakeStatus {
|
||||||
case 8:
|
case 8:
|
||||||
if len(data) < 1024 {
|
if len(data) < 1024 {
|
||||||
@ -108,7 +108,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
start := 0
|
start := 0
|
||||||
var l int
|
var l int
|
||||||
for len(data)-start > 2048 {
|
for len(data)-start > 2048 {
|
||||||
l = rand.Intn(4096) + 100
|
l = rand.IntN(4096) + 100
|
||||||
if l > len(data)-start {
|
if l > len(data)-start {
|
||||||
l = len(data) - start
|
l = len(data) - start
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
start := 0
|
start := 0
|
||||||
var l int
|
var l int
|
||||||
for len(data)-start > 2048 {
|
for len(data)-start > 2048 {
|
||||||
l = rand.Intn(4096) + 100
|
l = rand.IntN(4096) + 100
|
||||||
if l > len(data)-start {
|
if l > len(data)-start {
|
||||||
l = len(data) - start
|
l = len(data) - start
|
||||||
}
|
}
|
||||||
@ -148,7 +148,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
hmacData := make([]byte, 43)
|
hmacData := make([]byte, 43)
|
||||||
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
||||||
copy(hmacData, handshakeFinish)
|
copy(hmacData, handshakeFinish)
|
||||||
rand.Read(hmacData[11:33])
|
crand.Read(hmacData[11:33])
|
||||||
h := t.hmacSHA1(hmacData[:33])
|
h := t.hmacSHA1(hmacData[:33])
|
||||||
copy(hmacData[33:], h)
|
copy(hmacData[33:], h)
|
||||||
encodedData = append(hmacData, t.sendSaver...)
|
encodedData = append(hmacData, t.sendSaver...)
|
||||||
@ -169,11 +169,11 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
|||||||
tlsDataLen += len(sni)
|
tlsDataLen += len(sni)
|
||||||
copy(tlsData[tlsDataLen:], tlsData2)
|
copy(tlsData[tlsDataLen:], tlsData2)
|
||||||
tlsDataLen += len(tlsData2)
|
tlsDataLen += len(tlsData2)
|
||||||
ticketLen := rand.Intn(164)*2 + 64
|
ticketLen := rand.IntN(164)*2 + 64
|
||||||
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
||||||
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
||||||
//ticketLen := 208
|
//ticketLen := 208
|
||||||
rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
crand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
||||||
tlsDataLen += ticketLen
|
tlsDataLen += ticketLen
|
||||||
copy(tlsData[tlsDataLen:], tlsData3)
|
copy(tlsData[tlsDataLen:], tlsData3)
|
||||||
tlsDataLen += len(tlsData3)
|
tlsDataLen += len(tlsData3)
|
||||||
@ -278,7 +278,7 @@ func (t *tls12TicketAuth) packAuthData() (outData []byte) {
|
|||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
binary.BigEndian.PutUint32(outData[0:4], uint32(now))
|
binary.BigEndian.PutUint32(outData[0:4], uint32(now))
|
||||||
|
|
||||||
rand.Read(outData[4 : 4+18])
|
crand.Read(outData[4 : 4+18])
|
||||||
|
|
||||||
hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len])
|
hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len])
|
||||||
copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash)
|
copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash)
|
||||||
|
@ -4,9 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
crand "crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -58,13 +59,13 @@ func (a *authAES128) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &a.ServerInfo
|
return &a.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authAES128) SetData(data interface{}) {
|
func (a *authAES128) SetData(data any) {
|
||||||
if auth, ok := data.(*AuthData); ok {
|
if auth, ok := data.(*AuthData); ok {
|
||||||
a.data = auth
|
a.data = auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authAES128) GetData() interface{} {
|
func (a *authAES128) GetData() any {
|
||||||
if a.data == nil {
|
if a.data == nil {
|
||||||
a.data = &AuthData{}
|
a.data = &AuthData{}
|
||||||
}
|
}
|
||||||
@ -74,15 +75,14 @@ func (a *authAES128) GetData() interface{} {
|
|||||||
func (a *authAES128) packData(data []byte) (outData []byte) {
|
func (a *authAES128) packData(data []byte) (outData []byte) {
|
||||||
dataLength := len(data)
|
dataLength := len(data)
|
||||||
randLength := 1
|
randLength := 1
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
if dataLength <= 1200 {
|
if dataLength <= 1200 {
|
||||||
if a.packID > 4 {
|
if a.packID > 4 {
|
||||||
randLength += rand.Intn(32)
|
randLength += rand.IntN(32)
|
||||||
} else {
|
} else {
|
||||||
if dataLength > 900 {
|
if dataLength > 900 {
|
||||||
randLength += rand.Intn(128)
|
randLength += rand.IntN(128)
|
||||||
} else {
|
} else {
|
||||||
randLength += rand.Intn(512)
|
randLength += rand.IntN(512)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ func (a *authAES128) packData(data []byte) (outData []byte) {
|
|||||||
h := a.hmac(key, outData[0:2])
|
h := a.hmac(key, outData[0:2])
|
||||||
copy(outData[2:4], h[:2])
|
copy(outData[2:4], h[:2])
|
||||||
// 4~rand length+4, rand number
|
// 4~rand length+4, rand number
|
||||||
rand.Read(outData[4 : 4+randLength])
|
crand.Read(outData[4 : 4+randLength])
|
||||||
// 4, rand length
|
// 4, rand length
|
||||||
if randLength < 128 {
|
if randLength < 128 {
|
||||||
outData[4] = byte(randLength & 0xFF)
|
outData[4] = byte(randLength & 0xFF)
|
||||||
@ -121,11 +121,10 @@ func (a *authAES128) packData(data []byte) (outData []byte) {
|
|||||||
func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
||||||
dataLength := len(data)
|
dataLength := len(data)
|
||||||
var randLength int
|
var randLength int
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
if dataLength > 400 {
|
if dataLength > 400 {
|
||||||
randLength = rand.Intn(512)
|
randLength = rand.IntN(512)
|
||||||
} else {
|
} else {
|
||||||
randLength = rand.Intn(1024)
|
randLength = rand.IntN(1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataOffset := randLength + 16 + 4 + 4 + 7
|
dataOffset := randLength + 16 + 4 + 4 + 7
|
||||||
@ -136,7 +135,7 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
|||||||
copy(key, a.IV)
|
copy(key, a.IV)
|
||||||
copy(key[a.IVLen:], a.Key)
|
copy(key[a.IVLen:], a.Key)
|
||||||
|
|
||||||
rand.Read(outData[dataOffset-randLength:])
|
crand.Read(outData[dataOffset-randLength:])
|
||||||
a.data.mutex.Lock()
|
a.data.mutex.Lock()
|
||||||
a.data.connectionID++
|
a.data.connectionID++
|
||||||
if a.data.connectionID > 0xFF000000 {
|
if a.data.connectionID > 0xFF000000 {
|
||||||
@ -144,9 +143,9 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
|||||||
}
|
}
|
||||||
if len(a.data.clientID) == 0 {
|
if len(a.data.clientID) == 0 {
|
||||||
a.data.clientID = make([]byte, 8)
|
a.data.clientID = make([]byte, 8)
|
||||||
rand.Read(a.data.clientID)
|
crand.Read(a.data.clientID)
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
rand.Read(b)
|
crand.Read(b)
|
||||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||||
}
|
}
|
||||||
copy(encrypt[4:], a.data.clientID)
|
copy(encrypt[4:], a.data.clientID)
|
||||||
@ -163,13 +162,13 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
|||||||
uid := make([]byte, 4)
|
uid := make([]byte, 4)
|
||||||
if len(params) >= 2 {
|
if len(params) >= 2 {
|
||||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil {
|
if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil {
|
||||||
rand.Read(uid)
|
crand.Read(uid)
|
||||||
} else {
|
} else {
|
||||||
binary.LittleEndian.PutUint32(uid, uint32(userID))
|
binary.LittleEndian.PutUint32(uid, uint32(userID))
|
||||||
a.userKey = a.hashDigest([]byte(params[1]))
|
a.userKey = a.hashDigest([]byte(params[1]))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rand.Read(uid)
|
crand.Read(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.userKey == nil {
|
if a.userKey == nil {
|
||||||
@ -196,7 +195,7 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
|||||||
h := a.hmac(key, encrypt[0:20])
|
h := a.hmac(key, encrypt[0:20])
|
||||||
copy(encrypt[20:], h[:4])
|
copy(encrypt[20:], h[:4])
|
||||||
|
|
||||||
rand.Read(outData[0:1])
|
crand.Read(outData[0:1])
|
||||||
h = a.hmac(key, outData[0:1])
|
h = a.hmac(key, outData[0:1])
|
||||||
copy(outData[1:], h[0:7-1])
|
copy(outData[1:], h[0:7-1])
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
stdCipher "crypto/cipher"
|
stdCipher "crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -68,13 +68,13 @@ func (a *authChainA) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &a.ServerInfo
|
return &a.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChainA) SetData(data interface{}) {
|
func (a *authChainA) SetData(data any) {
|
||||||
if auth, ok := data.(*AuthData); ok {
|
if auth, ok := data.(*AuthData); ok {
|
||||||
a.data = auth
|
a.data = auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authChainA) GetData() interface{} {
|
func (a *authChainA) GetData() any {
|
||||||
if a.data == nil {
|
if a.data == nil {
|
||||||
a.data = &AuthData{}
|
a.data = &AuthData{}
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ func authChainAGetRandLen(dataLength int, random *tools.Shift128plusContext, las
|
|||||||
|
|
||||||
func getRandStartPos(random *tools.Shift128plusContext, randLength int) int {
|
func getRandStartPos(random *tools.Shift128plusContext, randLength int) int {
|
||||||
if randLength > 0 {
|
if randLength > 0 {
|
||||||
return int(random.Next() % 8589934609 % uint64(randLength))
|
return int(int64(random.Next()%8589934609) % int64(randLength))
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ func (a *authChainA) packAuthData(data []byte) (outData []byte) {
|
|||||||
copy(a.userKey, a.Key)
|
copy(a.userKey, a.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < 4; i++ {
|
for i := range 4 {
|
||||||
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
||||||
}
|
}
|
||||||
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
||||||
|
@ -34,14 +34,14 @@ func (a *authChainA) authChainBInitDataSize() {
|
|||||||
random.InitFromBin(a.Key)
|
random.InitFromBin(a.Key)
|
||||||
length := random.Next()%8 + 4
|
length := random.Next()%8 + 4
|
||||||
a.dataSizeList = make([]int, length)
|
a.dataSizeList = make([]int, length)
|
||||||
for i := 0; i < int(length); i++ {
|
for i := range int(length) {
|
||||||
a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
a.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||||
}
|
}
|
||||||
sort.Ints(a.dataSizeList)
|
sort.Ints(a.dataSizeList)
|
||||||
|
|
||||||
length = random.Next()%16 + 8
|
length = random.Next()%16 + 8
|
||||||
a.dataSizeList2 = make([]int, length)
|
a.dataSizeList2 = make([]int, length)
|
||||||
for i := 0; i < int(length); i++ {
|
for i := range int(length) {
|
||||||
a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
a.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440)
|
||||||
}
|
}
|
||||||
sort.Ints(a.dataSizeList2)
|
sort.Ints(a.dataSizeList2)
|
||||||
|
@ -2,8 +2,9 @@ package protocol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||||
@ -34,13 +35,13 @@ func (a *authSHA1v4) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &a.ServerInfo
|
return &a.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authSHA1v4) SetData(data interface{}) {
|
func (a *authSHA1v4) SetData(data any) {
|
||||||
if auth, ok := data.(*AuthData); ok {
|
if auth, ok := data.(*AuthData); ok {
|
||||||
a.data = auth
|
a.data = auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authSHA1v4) GetData() interface{} {
|
func (a *authSHA1v4) GetData() any {
|
||||||
if a.data == nil {
|
if a.data == nil {
|
||||||
a.data = &AuthData{}
|
a.data = &AuthData{}
|
||||||
}
|
}
|
||||||
@ -53,9 +54,9 @@ func (a *authSHA1v4) packData(data []byte) (outData []byte) {
|
|||||||
|
|
||||||
if dataLength <= 1300 {
|
if dataLength <= 1300 {
|
||||||
if dataLength > 400 {
|
if dataLength > 400 {
|
||||||
randLength += rand.Intn(128)
|
randLength += rand.IntN(128)
|
||||||
} else {
|
} else {
|
||||||
randLength += rand.Intn(1024)
|
randLength += rand.IntN(1024)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,9 +91,9 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
|||||||
randLength := 1
|
randLength := 1
|
||||||
if dataLength <= 1300 {
|
if dataLength <= 1300 {
|
||||||
if dataLength > 400 {
|
if dataLength > 400 {
|
||||||
randLength += rand.Intn(128)
|
randLength += rand.IntN(128)
|
||||||
} else {
|
} else {
|
||||||
randLength += rand.Intn(1024)
|
randLength += rand.IntN(1024)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataOffset := randLength + 4 + 2
|
dataOffset := randLength + 4 + 2
|
||||||
@ -104,9 +105,9 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
|||||||
}
|
}
|
||||||
if len(a.data.clientID) == 0 {
|
if len(a.data.clientID) == 0 {
|
||||||
a.data.clientID = make([]byte, 8)
|
a.data.clientID = make([]byte, 8)
|
||||||
rand.Read(a.data.clientID)
|
crand.Read(a.data.clientID)
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
rand.Read(b)
|
crand.Read(b)
|
||||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||||
}
|
}
|
||||||
// 0-1, out length
|
// 0-1, out length
|
||||||
@ -122,7 +123,7 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
|||||||
// 2~6, crc of out length+salt+key
|
// 2~6, crc of out length+salt+key
|
||||||
binary.LittleEndian.PutUint32(outData[2:], crc32)
|
binary.LittleEndian.PutUint32(outData[2:], crc32)
|
||||||
// 6~rand length+6, rand numbers
|
// 6~rand length+6, rand numbers
|
||||||
rand.Read(outData[dataOffset-randLength : dataOffset])
|
crand.Read(outData[dataOffset-randLength : dataOffset])
|
||||||
// 6, rand length
|
// 6, rand length
|
||||||
if randLength < 128 {
|
if randLength < 128 {
|
||||||
outData[6] = byte(randLength & 0xFF)
|
outData[6] = byte(randLength & 0xFF)
|
||||||
|
@ -23,8 +23,8 @@ type IProtocol interface {
|
|||||||
GetServerInfo() *ssr.ServerInfo
|
GetServerInfo() *ssr.ServerInfo
|
||||||
PreEncrypt(data []byte) ([]byte, error)
|
PreEncrypt(data []byte) ([]byte, error)
|
||||||
PostDecrypt(data []byte) ([]byte, int, error)
|
PostDecrypt(data []byte) ([]byte, int, error)
|
||||||
SetData(data interface{})
|
SetData(data any)
|
||||||
GetData() interface{}
|
GetData() any
|
||||||
GetOverhead() int
|
GetOverhead() int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +33,11 @@ func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) {
|
|||||||
return data, len(data), nil
|
return data, len(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *origin) SetData(data interface{}) {
|
func (o *origin) SetData(data any) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *origin) GetData() interface{} {
|
func (o *origin) GetData() any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +63,11 @@ func (v *verifySHA1) GetServerInfo() (s *ssr.ServerInfo) {
|
|||||||
return &v.ServerInfo
|
return &v.ServerInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *verifySHA1) SetData(data interface{}) {
|
func (v *verifySHA1) SetData(data any) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *verifySHA1) GetData() interface{} {
|
func (v *verifySHA1) GetData() any {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createCRC32Table() {
|
func createCRC32Table() {
|
||||||
for i := 0; i < 256; i++ {
|
for i := range 256 {
|
||||||
crc := uint32(i)
|
crc := uint32(i)
|
||||||
for j := 8; j > 0; j-- {
|
for j := 8; j > 0; j-- {
|
||||||
if crc&1 == 1 {
|
if crc&1 == 1 {
|
||||||
|
@ -37,7 +37,7 @@ func (ctx *Shift128plusContext) InitFromBinDatalen(bin []byte, datalen int) {
|
|||||||
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8])
|
||||||
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
for range 4 {
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/glider/log"
|
"github.com/nadoo/glider/pkg/log"
|
||||||
|
"github.com/nadoo/glider/pkg/socks"
|
||||||
"github.com/nadoo/glider/proxy"
|
"github.com/nadoo/glider/proxy"
|
||||||
"github.com/nadoo/glider/proxy/socks"
|
|
||||||
|
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal"
|
"github.com/nadoo/glider/proxy/ssr/internal"
|
||||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||||
@ -31,10 +31,10 @@ type SSR struct {
|
|||||||
EncryptPassword string
|
EncryptPassword string
|
||||||
Obfs string
|
Obfs string
|
||||||
ObfsParam string
|
ObfsParam string
|
||||||
ObfsData interface{}
|
ObfsData any
|
||||||
Protocol string
|
Protocol string
|
||||||
ProtocolParam string
|
ProtocolParam string
|
||||||
ProtocolData interface{}
|
ProtocolData any
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query
|
// NewSSR returns a shadowsocksr proxy, ssr://method:pass@host:port/query
|
||||||
@ -152,6 +152,13 @@ func (s *SSR) Dial(network, addr string) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||||
return nil, nil, proxy.ErrNotSupported
|
return nil, proxy.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.AddUsage("ssr", `
|
||||||
|
SSR scheme:
|
||||||
|
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user