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
|
||||
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:
|
||||
|
||||
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:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
go-version: 1.15.x
|
||||
- name: Go Env
|
||||
run: go env
|
||||
fetch-depth: 0
|
||||
|
||||
- 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
|
||||
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
|
||||
.idea
|
||||
.vscode
|
||||
.zed
|
||||
.DS_Store
|
||||
|
||||
# dev test only
|
||||
/dev/
|
||||
dev*.go
|
||||
|
||||
*_test.go
|
||||
|
||||
dist
|
||||
|
||||
|
@ -1,23 +1,9 @@
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
|
||||
# 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/
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
# https://goreleaser.com/customization/build/
|
||||
builds:
|
||||
- id: default
|
||||
env:
|
||||
@ -26,6 +12,7 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
- darwin
|
||||
- freebsd
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
@ -35,6 +22,10 @@ builds:
|
||||
- mipsle
|
||||
- mips64
|
||||
- mips64le
|
||||
- riscv64
|
||||
goamd64:
|
||||
- v1
|
||||
- v3
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
@ -42,37 +33,64 @@ builds:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
|
||||
# https://goreleaser.com/customization/archive/
|
||||
archives:
|
||||
- id: default
|
||||
builds:
|
||||
- default
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
format: tar.gz
|
||||
formats: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: zip
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
- config/**/*
|
||||
- systemd/*
|
||||
|
||||
# https://goreleaser.com/customization/snapshots/
|
||||
snapshot:
|
||||
name_template: "dev@{{.ShortCommit}}"
|
||||
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
||||
|
||||
# https://goreleaser.com/customization/checksum/
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||
|
||||
# https://goreleaser.com/customization/release/
|
||||
release:
|
||||
prerelease: 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
|
||||
FROM golang:alpine AS build-env
|
||||
RUN apk --no-cache add build-base git gcc
|
||||
# Build Stage
|
||||
FROM golang:1.24-alpine AS build-env
|
||||
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
|
||||
WORKDIR /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"]
|
||||
|
538
README.md
538
README.md
@ -1,8 +1,10 @@
|
||||
# [glider](https://github.com/nadoo/glider)
|
||||
|
||||
[](https://go.dev/dl/)
|
||||
[](https://goreportcard.com/report/github.com/nadoo/glider)
|
||||
[](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).
|
||||
|
||||
@ -37,266 +39,342 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
||||
- Periodical availability checking for forwarders
|
||||
- Send requests from specific local ip/interface
|
||||
- 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
|
||||
|
||||
<details>
|
||||
<summary>click to see details</summary>
|
||||
|
||||
|Protocol | Listen/TCP | Listen/UDP | Forward/TCP | Forward/UDP | Description
|
||||
|:-: |:-:|:-:|:-:|:-:|:-
|
||||
|Mixed |√|√| | |http+socks5 server
|
||||
|HTTP |√| |√| |client & server
|
||||
|SOCKS5 |√|√|√|√|client & server
|
||||
|SS |√|√|√|√|client & server
|
||||
|Trojan |√|√|√|√|client & server
|
||||
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
||||
|VLESS |√|√|√|√|client & server
|
||||
|VMess | | |√|√|client only
|
||||
|SSR | | |√| |client only
|
||||
|SSH | | |√| |client only
|
||||
|SOCKS4 | | |√| |client only
|
||||
|TCP |√| |√| |tcp tunnel client & server
|
||||
|UDP | |√| |√|udp tunnel client & server
|
||||
|TLS |√| |√| |transport client & server
|
||||
|KCP | |√|√| |transport client & server
|
||||
|Unix |√|√|√|√|transport client & server
|
||||
|Websocket |√| |√| |transport client & server
|
||||
|Simple-Obfs | | |√| |transport client only
|
||||
|Redir |√| | | |linux only
|
||||
|Redir6 |√| | | |linux only(ipv6)
|
||||
|Reject | | |√|√|reject all requests
|
||||
|Protocol | Listen/TCP | Listen/UDP | Forward/TCP | Forward/UDP | Description
|
||||
|:-: |:-:|:-:|:-:|:-:|:-
|
||||
|Mixed |√|√| | |http+socks5 server
|
||||
|HTTP |√| |√| |client & server
|
||||
|SOCKS5 |√|√|√|√|client & server
|
||||
|SS |√|√|√|√|client & server
|
||||
|Trojan |√|√|√|√|client & server
|
||||
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
||||
|VLESS |√|√|√|√|client & server
|
||||
|VMess | | |√|√|client only
|
||||
|SSR | | |√| |client only
|
||||
|SSH | | |√| |client only
|
||||
|SOCKS4 | | |√| |client only
|
||||
|SOCKS4A | | |√| |client only
|
||||
|TCP |√| |√| |tcp tunnel client & server
|
||||
|UDP | |√| |√|udp tunnel client & server
|
||||
|TLS |√| |√| |transport client & server
|
||||
|KCP | |√|√| |transport client & server
|
||||
|Unix |√|√|√|√|transport client & server
|
||||
|VSOCK |√| |√| |transport client & server
|
||||
|Smux |√| |√| |transport client & server
|
||||
|Websocket(WS) |√| |√| |transport client & server
|
||||
|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>
|
||||
|
||||
## Install
|
||||
|
||||
Download:
|
||||
- [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
||||
|
||||
Docker:
|
||||
```bash
|
||||
docker pull nadoo/glider
|
||||
#docker pull ghcr.io/nadoo/glider
|
||||
```
|
||||
|
||||
ArchLinux:
|
||||
```bash
|
||||
sudo pacman -S glider
|
||||
```
|
||||
- Binary: [https://github.com/nadoo/glider/releases](https://github.com/nadoo/glider/releases)
|
||||
- Docker: `docker pull nadoo/glider`
|
||||
- Manjaro: `pamac install glider`
|
||||
- ArchLinux: `sudo pacman -S glider`
|
||||
- Homebrew: `brew install glider`
|
||||
- MacPorts: `sudo port install glider`
|
||||
- Source: `go install github.com/nadoo/glider@latest`
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
glider -h
|
||||
```
|
||||
<details>
|
||||
<summary>click to see details</summary>
|
||||
#### Run
|
||||
|
||||
```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=tcp[://HOST:PORT]: tcp port connect check
|
||||
check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
||||
check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
||||
check=disable: disable health check (default "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 (default "http://www.msftconnecttest.com/connecttest.txt#expect=200")
|
||||
-checkdisabledonly
|
||||
check disabled fowarders only
|
||||
check disabled fowarders only
|
||||
-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
|
||||
fowarder check timeout(seconds) (default 10)
|
||||
fowarder check timeout(seconds) (default 10)
|
||||
-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 file path
|
||||
config file path
|
||||
-dialtimeout int
|
||||
dial timeout(seconds) (default 3)
|
||||
dial timeout(seconds) (default 3)
|
||||
-dns string
|
||||
local dns server listen address
|
||||
local dns server listen address
|
||||
-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
|
||||
size of CACHE (default 4096)
|
||||
max number of dns response in CACHE (default 4096)
|
||||
-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
|
||||
minimum TTL value for entries in the CACHE(seconds)
|
||||
minimum TTL value for entries in the CACHE(seconds)
|
||||
-dnsnoaaaa
|
||||
disable AAAA query
|
||||
-dnsrecord value
|
||||
custom dns record, format: domain/ip
|
||||
custom dns record, format: domain/ip
|
||||
-dnsserver value
|
||||
remote dns server address
|
||||
remote dns server address
|
||||
-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 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 file
|
||||
include file
|
||||
-interface string
|
||||
source ip or source interface
|
||||
source ip or source interface
|
||||
-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
|
||||
max failures to change forwarder status to disabled (default 3)
|
||||
max failures to change forwarder status to disabled (default 3)
|
||||
-relaytimeout int
|
||||
relay timeout(seconds)
|
||||
relay timeout(seconds)
|
||||
-rulefile value
|
||||
rule file path
|
||||
rule file path
|
||||
-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
|
||||
run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]
|
||||
run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]
|
||||
-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 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>
|
||||
|
||||
run:
|
||||
```bash
|
||||
glider -config CONFIGPATH
|
||||
```
|
||||
```bash
|
||||
glider -verbose -listen :8443 -forward SCHEME://HOST:PORT
|
||||
```
|
||||
|
||||
#### Schemes
|
||||
|
||||
<details>
|
||||
<summary>click to see details</summary>
|
||||
<summary><code>glider -scheme all</code></summary>
|
||||
|
||||
```bash
|
||||
Available schemes:
|
||||
listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcp udp tls ws unix kcp
|
||||
forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tcp udp tls ws unix kcp simple-obfs
|
||||
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
|
||||
|
||||
--
|
||||
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:
|
||||
socks://[user:pass@]host:port
|
||||
socks5://[user:pass@]host:port
|
||||
|
||||
--
|
||||
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
|
||||
|
||||
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
|
||||
--
|
||||
SSH scheme:
|
||||
ssh://user[:pass]@host:port[?key=keypath&timeout=SECONDS]
|
||||
timeout: timeout of ssh handshake and channel operation, default: 5
|
||||
|
||||
--
|
||||
SSR scheme:
|
||||
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://host:port[?serverName=SERVERNAME][&skipVerify=true]
|
||||
|
||||
tls://host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&alpn=proto1][&alpn=proto2]
|
||||
|
||||
Proxy over tls client:
|
||||
tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme://
|
||||
tls://host:port[?skipVerify=true],http://[user:pass@]
|
||||
tls://host:port[?skipVerify=true],socks5://[user:pass@]
|
||||
tls://host:port[?skipVerify=true],vmess://[security:]uuid@?alterID=num
|
||||
|
||||
|
||||
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:
|
||||
tls://host:port?cert=PATH&key=PATH,scheme://
|
||||
tls://host:port?cert=PATH&key=PATH,http://
|
||||
tls://host:port?cert=PATH&key=PATH,socks5://
|
||||
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:
|
||||
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:
|
||||
ws://:port[/path][?host=HOST]
|
||||
|
||||
wss://:port[/path]?cert=PATH&key=PATH[?host=HOST]
|
||||
|
||||
Websocket with a specified proxy protocol:
|
||||
ws://host:port[/path][?host=HOST],scheme://
|
||||
ws://host:port[/path][?host=HOST],http://[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://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]],socks5://[user:pass@]
|
||||
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:
|
||||
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...
|
||||
if you want to listen on any address, just set CID to 4294967295.
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -304,47 +382,50 @@ Config file format(see `./glider.conf.example` as an example):
|
||||
#### Examples
|
||||
|
||||
<details>
|
||||
<summary>click to see details</summary>
|
||||
<summary><code>glider -example</code></summary>
|
||||
|
||||
```bash
|
||||
./glider -config glider.conf
|
||||
Examples:
|
||||
glider -config glider.conf
|
||||
-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.
|
||||
|
||||
./glider -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose
|
||||
-listen on 0.0.0.0:8443 as a ss server.
|
||||
|
||||
./glider -listen tls://:443?cert=crtFilePath&key=keyFilePath,http:// -verbose
|
||||
-listen on :443 as a https(http over tls) proxy server.
|
||||
|
||||
./glider -listen http://:8080 -forward socks5://127.0.0.1:1080
|
||||
-listen on :8080 as a http proxy server, forward all requests via socks5 server.
|
||||
|
||||
./glider -listen socks5://:1080 -forward "tls://abc.com:443,vmess://security:uuid@?alterID=10"
|
||||
-listen on :1080 as a socks5 server, forward all requests via remote tls+vmess server.
|
||||
|
||||
./glider -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr
|
||||
-listen on :1080 as socks5 server, forward requests via server1 and server2 in round robin mode.
|
||||
|
||||
./glider -listen tcp://:80 -forward tcp://2.2.2.2:80
|
||||
-tcp tunnel: listen on :80 and forward all requests to 2.2.2.2:80.
|
||||
|
||||
./glider -listen udp://:53 -forward ss://method:pass@1.1.1.1:8443,udp://8.8.8.8:53
|
||||
-listen on :53 and forward all udp requests to 8.8.8.8:53 via remote ss server.
|
||||
|
||||
./glider -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss 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.
|
||||
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.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Config
|
||||
|
||||
```bash
|
||||
glider -config CONFIG_PATH
|
||||
```
|
||||
|
||||
- [ConfigFile](config)
|
||||
- [glider.conf.example](config/glider.conf.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
|
||||
|
||||
- dhcpd:
|
||||
- service=dhcpd,INTERFACE,START_IP,END_IP
|
||||
- e.g., service=dhcpd,eth1,192.168.50.100,192.168.50.199
|
||||
- dhcpd / dhcpd-failover:
|
||||
- service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...]
|
||||
- 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
|
||||
|
||||
@ -369,7 +477,7 @@ Config file format(see `./glider.conf.example` as an example):
|
||||
|
||||
1. Clone the source code:
|
||||
```bash
|
||||
git clone https://github.com/nadoo/glider
|
||||
git clone https://github.com/nadoo/glider && cd glider
|
||||
```
|
||||
2. Customize features:
|
||||
|
||||
@ -378,12 +486,12 @@ Config file format(see `./glider.conf.example` as an example):
|
||||
// _ "github.com/nadoo/glider/proxy/kcp"
|
||||
```
|
||||
|
||||
3. Build it(requires **Go 1.15+** )
|
||||
3. Build it:
|
||||
```bash
|
||||
cd glider && go build -v -i -ldflags "-s -w"
|
||||
go build -v -ldflags "-s -w"
|
||||
```
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
## Proxy & Protocol Chains
|
||||
<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://
|
||||
```
|
||||
|
||||
- Chain protocols in listener: http over smux over websocket proxy server
|
||||
|
||||
``` bash
|
||||
listen=ws://:10000,smux://,http://
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Links
|
||||
|
||||
- [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.
|
||||
- [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`
|
||||
|
358
config.go
358
config.go
@ -8,7 +8,8 @@ import (
|
||||
"github.com/nadoo/conflag"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -16,7 +17,10 @@ var flag = conflag.New()
|
||||
|
||||
// Config is global config struct.
|
||||
type Config struct {
|
||||
Verbose bool
|
||||
Verbose bool
|
||||
LogFlags int
|
||||
TCPBufSize int
|
||||
UDPBufSize int
|
||||
|
||||
Listens []string
|
||||
|
||||
@ -39,15 +43,30 @@ func parseConfig() *Config {
|
||||
|
||||
flag.SetOutput(os.Stdout)
|
||||
|
||||
flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
|
||||
scheme := flag.String("scheme", "", "show help message of proxy scheme, use 'all' to see all schemes")
|
||||
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.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
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.BoolVar(&conf.Verbose, "verbose", false, "verbose mode")
|
||||
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.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.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.CheckLatencySamples, "checklatencysamples", 10, "use the average latency of the latest N checks")
|
||||
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.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.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.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")
|
||||
|
||||
// service configs
|
||||
flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]")
|
||||
|
||||
flag.Usage = usage
|
||||
err := flag.Parse()
|
||||
if err != nil {
|
||||
if err := flag.Parse(); err != nil {
|
||||
// flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// setup a log func
|
||||
if conf.Verbose {
|
||||
log.F = log.Debugf
|
||||
if *scheme != "" {
|
||||
fmt.Fprint(flag.Output(), proxy.Usage(*scheme))
|
||||
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 {
|
||||
// flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n")
|
||||
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
|
||||
for _, ruleFile := range conf.RuleFiles {
|
||||
if !path.IsAbs(ruleFile) {
|
||||
@ -117,210 +160,95 @@ func parseConfig() *Config {
|
||||
conf.rules = append(conf.rules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func usage() {
|
||||
app := os.Args[0]
|
||||
w := flag.Output()
|
||||
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, "%s %s usage:\n", app, version)
|
||||
fmt.Fprint(flag.Output(), usage1)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(w, "\n")
|
||||
|
||||
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")
|
||||
fmt.Fprintf(flag.Output(), usage2, proxy.ServerSchemes(), proxy.DialerSchemes(), version)
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
# forwarder health check
|
||||
@ -56,8 +56,8 @@ rules-dir=rules.d
|
||||
#include=more.inc.conf
|
||||
```
|
||||
See:
|
||||
- [glider.conf.example](config/glider.conf.example)
|
||||
- [examples](config/examples)
|
||||
- [glider.conf.example](glider.conf.example)
|
||||
- [examples](examples)
|
||||
|
||||
## Rule File
|
||||
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
|
||||
forward=http://forwarder5:8080,socks6://forwarder3:1080
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
|
||||
|
||||
# Round Robin mode: rr
|
||||
|
@ -82,7 +82,8 @@ Set server's nameserver to glider:
|
||||
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.
|
||||
|
||||
#### 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)
|
||||
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.
|
||||
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:
|
||||
1. client sends http request to the resolved ip of example1.com.
|
||||
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.
|
||||
|
@ -1,9 +1,9 @@
|
||||
|
||||
|
||||
forward=http://forwarder4:8080
|
||||
forward=http://forwarder1:8080
|
||||
|
||||
# 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
|
||||
|
@ -32,16 +32,17 @@ verbose=True
|
||||
# different protocols.
|
||||
|
||||
# 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=ss://AEAD_CHACHA20_POLY1305:pass@:8448
|
||||
|
||||
# listen on 8080 as a http proxy server.
|
||||
listen=http://:8080
|
||||
# listen=http://:8080
|
||||
|
||||
# listen on 1080 as a socks5 proxy server.
|
||||
listen=socks5://:1080
|
||||
# listen=socks5://:1080
|
||||
|
||||
# listen on 1234 as vless proxy server.
|
||||
# listen=vless://uuid@:1234
|
||||
@ -51,6 +52,9 @@ listen=socks5://:1080
|
||||
# listen on 1081 as a linux transparent proxy server.
|
||||
# listen=redir://:1081
|
||||
|
||||
# listen on 1082 as a linux transparent proxy server(tproxy).
|
||||
# listen=tproxy://:1082
|
||||
|
||||
# http over tls (HTTPS proxy)
|
||||
# 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@
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
# listen=trojanc://PASSWORD:1234?fallback=127.0.0.1
|
||||
# listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1
|
||||
|
||||
# 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
|
||||
|
||||
# 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@host:port?key=/path/to/keyfile
|
||||
# forward=ssh://root@host:port?key=/path/to/keyfile&timeout=5
|
||||
|
||||
# http proxy as forwarder
|
||||
# forward=http://1.1.1.1:8080
|
||||
@ -122,21 +130,21 @@ listen=socks5://:1080
|
||||
# vless forwarder
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
# forward=tls://server.com:443,ws://,vmess://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@?alterID=2
|
||||
# forward=tls://server.com:443,ws://@/path,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
|
||||
|
||||
# ss over tls
|
||||
# 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@
|
||||
|
||||
# socks5 over unix domain socket
|
||||
# forward=unix:///tmp/glider.socket,socks5://
|
||||
# forward=unix:///dev/shm/socket,socks5://
|
||||
|
||||
# FORWARDER CHAIN
|
||||
# ---------------
|
||||
@ -188,8 +196,10 @@ maxfailures=3
|
||||
|
||||
# Forwarder health check:
|
||||
# check=tcp[://HOST:PORT]: tcp port connect check
|
||||
# check=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]
|
||||
# check=file://SCRIPT_PATH: run a check script, healthy when exitcode=0, environment variables: FORWARDER_ADDR
|
||||
# check=http://HOST[:PORT][/URI][#expect=REGEX_MATCH_IN_RESP_LINE]
|
||||
# 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=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
|
||||
checktolerance=100
|
||||
|
||||
# use the average latency of the latest N checks
|
||||
checklatencysamples=10
|
||||
|
||||
# check disabled fowarders only
|
||||
checkdisabledonly=false
|
||||
|
||||
@ -210,7 +223,7 @@ checkdisabledonly=false
|
||||
# we can specify different upstream dns server in rule file for different destinations.
|
||||
|
||||
# Setup a dns forwarding server
|
||||
dns=:53
|
||||
# dns=:53
|
||||
|
||||
# global remote dns server (you can specify different dns server in rule file)
|
||||
dnsserver=8.8.8.8:53
|
||||
@ -233,35 +246,52 @@ dnsminttl=0
|
||||
# size of CACHE
|
||||
dnscachesize=4096
|
||||
|
||||
# show query log of dns cache
|
||||
dnscachelog=True
|
||||
|
||||
# disable AAAA queries
|
||||
# dnsnoaaaa=True
|
||||
|
||||
# custom records
|
||||
dnsrecord=www.example.com/1.2.3.4
|
||||
dnsrecord=www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
||||
|
||||
# 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.:
|
||||
# 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
|
||||
# ------------------
|
||||
# Specify the outbound ip/interface.
|
||||
# Specify global outbound ip/interface.
|
||||
#
|
||||
# interface=""
|
||||
# interface="192.168.1.100"
|
||||
# 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
|
||||
# ----------
|
||||
# Specify additional forward rules.
|
||||
|
||||
#
|
||||
# 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
|
||||
#rulefile=office.rule
|
||||
#rulefile=home.rule
|
||||
|
||||
|
||||
# INCLUDE MORE CONFIG FILES
|
||||
# INCLUDE CONFIG FILES
|
||||
# ----------
|
||||
#include=dnsrecord.inc.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 resolved ips for domains in rule files by dns forwarding server
|
||||
# Usually used in transparent proxy mode on linux
|
||||
# Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6
|
||||
ipset=glider
|
||||
|
||||
# DESTINATIONS
|
||||
|
101
dns/client.go
101
dns/client.go
@ -5,17 +5,18 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Config struct {
|
||||
@ -26,6 +27,8 @@ type Config struct {
|
||||
Records []string
|
||||
AlwaysTCP bool
|
||||
CacheSize int
|
||||
CacheLog bool
|
||||
NoAAAA bool
|
||||
}
|
||||
|
||||
// Client is a dns client struct.
|
||||
@ -50,7 +53,9 @@ func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
|
||||
|
||||
// custom 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
|
||||
@ -64,13 +69,21 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([
|
||||
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 v, expired := c.cache.Get(qKey(req.Question)); len(v) > 2 {
|
||||
v = valCopy(v)
|
||||
binary.BigEndian.PutUint16(v[:2], req.ID)
|
||||
|
||||
log.F("[dns] %s <-> cache, type: %d, %s",
|
||||
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
||||
if c.config.CacheLog {
|
||||
log.F("[dns] %s <-> cache, type: %d, %s",
|
||||
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
||||
}
|
||||
|
||||
if expired { // update cache
|
||||
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)
|
||||
if len(ips) != 0 && ttl > 0 {
|
||||
c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl)
|
||||
if ttl > c.config.MaxTTL {
|
||||
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",
|
||||
clientAddr, dnsServer, network, dialerAddr, resp.Question.QTYPE, resp.Question.QNAME, strings.Join(ips, ","))
|
||||
if ttl <= 0 { // we got a null result
|
||||
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
|
||||
}
|
||||
@ -121,11 +141,11 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
|
||||
ttl := c.config.MinTTL
|
||||
for _, answer := range resp.Answers {
|
||||
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
||||
for _, h := range c.handlers {
|
||||
h(resp.Question.QNAME, answer.IP)
|
||||
}
|
||||
if answer.IP != "" {
|
||||
ips = append(ips, answer.IP)
|
||||
if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
|
||||
for _, h := range c.handlers {
|
||||
h(resp.Question.QNAME, answer.IP)
|
||||
}
|
||||
ips = append(ips, answer.IP.String())
|
||||
}
|
||||
if answer.TTL != 0 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -148,12 +162,13 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
||||
|
||||
// use tcp to connect upstream server default
|
||||
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
|
||||
if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
|
||||
dialer = proxy.Default
|
||||
if dialer.Addr() == "REJECT" {
|
||||
dialer = c.proxy.NextDialer("direct:0")
|
||||
}
|
||||
|
||||
// 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)
|
||||
server = ups.Server()
|
||||
for i := 0; i < ups.Len(); i++ {
|
||||
for range ups.Len() {
|
||||
var rc net.Conn
|
||||
rc, err = dialer.Dial(network, server)
|
||||
if err != nil {
|
||||
@ -271,10 +286,13 @@ func (c *Client) AddHandler(h AnswerHandler) {
|
||||
// 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
|
||||
func (c *Client) AddRecord(record string) error {
|
||||
r := strings.Split(record, "/")
|
||||
domain, ip := r[0], r[1]
|
||||
m, err := c.MakeResponse(domain, ip)
|
||||
domain, ip, found := strings.Cut(record, "/")
|
||||
if !found {
|
||||
return errors.New("wrong record format, must contain '/'")
|
||||
}
|
||||
m, err := MakeResponse(domain, ip, uint32(c.config.MaxTTL))
|
||||
if err != nil {
|
||||
log.F("[dns] add custom record error: %s", 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.
|
||||
func (c *Client) MakeResponse(domain string, ip string) (*Message, error) {
|
||||
ipb := net.ParseIP(ip)
|
||||
if ipb == nil {
|
||||
return nil, errors.New("GenResponse: invalid ip format")
|
||||
// Note: you should make sure ttl > 0.
|
||||
func MakeResponse(domain, ip string, ttl uint32) (*Message, error) {
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rdata []byte
|
||||
var qtype, rdlen uint16
|
||||
if rdata = ipb.To4(); rdata != nil {
|
||||
qtype = QTypeA
|
||||
rdlen = net.IPv4len
|
||||
} else {
|
||||
qtype = QTypeAAAA
|
||||
rdlen = net.IPv6len
|
||||
rdata = ipb
|
||||
var qtype, rdlen uint16 = QTypeA, net.IPv4len
|
||||
if addr.Is6() {
|
||||
qtype, rdlen = QTypeAAAA, net.IPv6len
|
||||
}
|
||||
|
||||
m := NewMessage(0, Response)
|
||||
m := NewMessage(0, ResponseMsg)
|
||||
m.SetQuestion(NewQuestion(qtype, domain))
|
||||
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)
|
||||
|
||||
return m, nil
|
||||
|
191
dns/message.go
191
dns/message.go
@ -5,25 +5,25 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"math/rand/v2"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UDPMaxLen is the max size of udp dns request.
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.2.1
|
||||
// Messages carried by UDP are restricted to 512 bytes (not counting the IP
|
||||
// or UDP headers). Longer messages are truncated and the TC bit is set in
|
||||
// the header.
|
||||
const UDPMaxLen = 512
|
||||
// https://www.dnsflagday.net/2020/
|
||||
const UDPMaxLen = 1232
|
||||
|
||||
// HeaderLen is the length of dns msg header.
|
||||
const HeaderLen = 12
|
||||
|
||||
// MsgType is the dns Message type.
|
||||
type MsgType byte
|
||||
|
||||
// Message types.
|
||||
const (
|
||||
Query = 0
|
||||
Response = 1
|
||||
QueryMsg MsgType = 0
|
||||
ResponseMsg MsgType = 1
|
||||
)
|
||||
|
||||
// Query types.
|
||||
@ -36,21 +36,21 @@ const (
|
||||
const ClassINET uint16 = 1
|
||||
|
||||
// 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
|
||||
// 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:
|
||||
//
|
||||
// +---------------------+
|
||||
// | Header |
|
||||
// +---------------------+
|
||||
// | Question | the question for the name server
|
||||
// +---------------------+
|
||||
// | Answer | RRs answering the question
|
||||
// +---------------------+
|
||||
// | Authority | RRs pointing toward an authority
|
||||
// +---------------------+
|
||||
// | Additional | RRs holding additional information
|
||||
// +---------------------+
|
||||
// | Header |
|
||||
// +---------------------+
|
||||
// | Question | the question for the name server
|
||||
// +---------------------+
|
||||
// | Answer | RRs answering the question
|
||||
// +---------------------+
|
||||
// | Authority | RRs pointing toward an authority
|
||||
// +---------------------+
|
||||
// | Additional | RRs holding additional information
|
||||
type Message struct {
|
||||
Header
|
||||
// most dns implementation only support 1 question
|
||||
@ -64,7 +64,7 @@ type Message struct {
|
||||
}
|
||||
|
||||
// NewMessage returns a new message.
|
||||
func NewMessage(id uint16, msgType int) *Message {
|
||||
func NewMessage(id uint16, msgType MsgType) *Message {
|
||||
if id == 0 {
|
||||
id = uint16(rand.Uint32())
|
||||
}
|
||||
@ -91,8 +91,7 @@ func (m *Message) AddAnswer(rr *RR) error {
|
||||
// Marshal marshals message struct to []byte.
|
||||
func (m *Message) Marshal() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := m.MarshalTo(buf)
|
||||
if err != nil {
|
||||
if _, err := m.MarshalTo(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
@ -134,8 +133,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
||||
}
|
||||
|
||||
m := &Message{unMarshaled: b}
|
||||
err := UnmarshalHeader(b[:HeaderLen], &m.Header)
|
||||
if err != nil {
|
||||
if err := UnmarshalHeader(b[:HeaderLen], &m.Header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -148,7 +146,7 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
||||
|
||||
// resp answers
|
||||
rrIdx := HeaderLen + qLen
|
||||
for i := 0; i < int(m.Header.ANCOUNT); i++ {
|
||||
for range int(m.Header.ANCOUNT) {
|
||||
rr := &RR{}
|
||||
rrLen, err := m.UnmarshalRR(rrIdx, rr)
|
||||
if err != nil {
|
||||
@ -165,25 +163,24 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
||||
}
|
||||
|
||||
// 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:
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ID |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QDCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ANCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | NSCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ARCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ID |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QDCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ANCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | NSCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | ARCOUNT |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
type Header struct {
|
||||
ID uint16
|
||||
Bits uint16
|
||||
@ -194,7 +191,7 @@ type Header struct {
|
||||
}
|
||||
|
||||
// SetMsgType sets the message type.
|
||||
func (h *Header) SetMsgType(qr int) {
|
||||
func (h *Header) SetMsgType(qr MsgType) {
|
||||
h.Bits |= uint16(qr) << 15
|
||||
}
|
||||
|
||||
@ -213,10 +210,11 @@ func (h *Header) SetAncount(ancount int) {
|
||||
h.ANCOUNT = uint16(ancount)
|
||||
}
|
||||
|
||||
func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
||||
TC uint16, RD uint16, RA uint16, RCODE uint16) {
|
||||
h.Bits = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + RCODE
|
||||
}
|
||||
// Not used now, but keep it for future use.
|
||||
// func (h *Header) setFlag(QR uint16, Opcode uint16, AA uint16,
|
||||
// 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.
|
||||
func (h *Header) MarshalTo(w io.Writer) (int, error) {
|
||||
@ -244,22 +242,22 @@ func UnmarshalHeader(b []byte, h *Header) error {
|
||||
}
|
||||
|
||||
// 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,
|
||||
// i.e., the parameters that define what is being asked. The section
|
||||
// contains QDCOUNT (usually 1) entries, each of the following format:
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | |
|
||||
// / QNAME /
|
||||
// / /
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QTYPE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QCLASS |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | |
|
||||
// / QNAME /
|
||||
// / /
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QTYPE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | QCLASS |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
type Question struct {
|
||||
QNAME string
|
||||
QTYPE uint16
|
||||
@ -282,14 +280,12 @@ func (q *Question) MarshalTo(w io.Writer) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = binary.Write(w, binary.BigEndian, q.QTYPE)
|
||||
if err != nil {
|
||||
if err = binary.Write(w, binary.BigEndian, q.QTYPE); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
|
||||
err = binary.Write(w, binary.BigEndian, q.QCLASS)
|
||||
if err != nil {
|
||||
if err = binary.Write(w, binary.BigEndian, q.QCLASS); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
@ -322,33 +318,33 @@ func (m *Message) UnmarshalQuestion(b []byte, q *Question) (n int, err error) {
|
||||
}
|
||||
|
||||
// RR format:
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.2.1
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
// https://www.rfc-editor.org/rfc/rfc1035#section-3.2.1
|
||||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.3
|
||||
// The answer, authority, and additional sections all share the same
|
||||
// format: a variable number of resource records, where the number of
|
||||
// records is specified in the corresponding count field in the header.
|
||||
// Each resource record has the following format:
|
||||
//
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | |
|
||||
// / /
|
||||
// / NAME /
|
||||
// | |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | TYPE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | CLASS |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | TTL |
|
||||
// | |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | RDLENGTH |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||
// / RDATA /
|
||||
// / /
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// 1 1 1 1 1 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | |
|
||||
// / /
|
||||
// / NAME /
|
||||
// | |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | TYPE |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | CLASS |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | TTL |
|
||||
// | |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | RDLENGTH |
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||
// / RDATA /
|
||||
// / /
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
type RR struct {
|
||||
NAME string
|
||||
TYPE uint16
|
||||
@ -357,7 +353,7 @@ type RR struct {
|
||||
RDLENGTH uint16
|
||||
RDATA []byte
|
||||
|
||||
IP string
|
||||
IP netip.Addr
|
||||
}
|
||||
|
||||
// NewRR returns a new dns rr.
|
||||
@ -372,20 +368,17 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = binary.Write(w, binary.BigEndian, rr.TYPE)
|
||||
if err != nil {
|
||||
if err = binary.Write(w, binary.BigEndian, rr.TYPE); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
|
||||
err = binary.Write(w, binary.BigEndian, rr.CLASS)
|
||||
if err != nil {
|
||||
if err = binary.Write(w, binary.BigEndian, rr.CLASS); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
|
||||
err = binary.Write(w, binary.BigEndian, rr.TTL)
|
||||
if err != nil {
|
||||
if err = binary.Write(w, binary.BigEndian, rr.TTL); err != nil {
|
||||
return
|
||||
}
|
||||
n += 4
|
||||
@ -396,8 +389,7 @@ func (rr *RR) MarshalTo(w io.Writer) (n int, err error) {
|
||||
}
|
||||
n += 2
|
||||
|
||||
_, err = w.Write(rr.RDATA)
|
||||
if err != nil {
|
||||
if _, err = w.Write(rr.RDATA); err != nil {
|
||||
return
|
||||
}
|
||||
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)]
|
||||
|
||||
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 {
|
||||
rr.IP = net.IP(rr.RDATA[:net.IPv6len]).String()
|
||||
rr.IP = netip.AddrFrom16(*(*[16]byte)(rr.RDATA[:16]))
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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",
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | 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])
|
||||
err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF))
|
||||
if err != nil {
|
||||
if err := m.UnmarshalDomainPointTo(sb, int(offset&0x3FFF)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -80,13 +80,13 @@ func (s *Server) ServePacket(pc net.PacketConn, caddr net.Addr, reqBytes []byte)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
log.F("[dns] error in exchange: %s", err)
|
||||
log.F("[dns] error in exchange for %s: %s", caddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(respBytes, caddr)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package dns
|
||||
|
||||
import "sync/atomic"
|
||||
import (
|
||||
"net"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// UPStream is a dns upstream.
|
||||
type UPStream struct {
|
||||
@ -10,6 +13,12 @@ type UPStream struct {
|
||||
|
||||
// NewUPStream returns a new 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}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,9 @@ import (
|
||||
_ "github.com/nadoo/glider/proxy/kcp"
|
||||
_ "github.com/nadoo/glider/proxy/mixed"
|
||||
_ "github.com/nadoo/glider/proxy/obfs"
|
||||
_ "github.com/nadoo/glider/proxy/pxyproto"
|
||||
_ "github.com/nadoo/glider/proxy/reject"
|
||||
_ "github.com/nadoo/glider/proxy/smux"
|
||||
_ "github.com/nadoo/glider/proxy/socks4"
|
||||
_ "github.com/nadoo/glider/proxy/socks5"
|
||||
_ "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.
|
||||
_ "github.com/nadoo/glider/proxy/redir"
|
||||
_ "github.com/nadoo/glider/proxy/tproxy"
|
||||
_ "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
|
||||
|
||||
go 1.15
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb
|
||||
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-20201112113307-4de412bc85d8
|
||||
github.com/mmcloughlin/avo v0.0.0-20201130012700-45c8ae10fd12 // indirect
|
||||
github.com/nadoo/conflag v0.2.3
|
||||
github.com/nadoo/ipset v0.3.0
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
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
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||
github.com/nadoo/conflag v0.3.1
|
||||
github.com/nadoo/ipset v0.5.0
|
||||
github.com/xtaci/kcp-go/v5 v5.6.18
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
)
|
||||
|
||||
// Replace dependency modules with local developing copy
|
||||
// use `go list -m all` to confirm the final module used
|
||||
// replace (
|
||||
// github.com/nadoo/conflag => ../conflag
|
||||
// )
|
||||
require (
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/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/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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
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/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54=
|
||||
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
|
||||
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
|
||||
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
|
||||
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw=
|
||||
github.com/nadoo/conflag v0.3.1/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
|
||||
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
|
||||
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
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/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo=
|
||||
github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
|
||||
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
|
||||
github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
||||
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.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
|
||||
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
||||
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.18 h1:7oV4mc272pcnn39/13BB11Bx7hJM4ogMIEokJYVWn4g=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.18/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
||||
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/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-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-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
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-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-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-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
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/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
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/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -21,32 +21,30 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
|
||||
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{}
|
||||
sets := make(map[string]struct{})
|
||||
|
||||
for _, r := range rules {
|
||||
if r.IPSet != "" {
|
||||
for _, domain := range r.Domain {
|
||||
m.domainSet.Store(domain, r.IPSet)
|
||||
}
|
||||
for _, ip := range r.IP {
|
||||
ipset.Add(r.IPSet, ip)
|
||||
}
|
||||
for _, cidr := range r.CIDR {
|
||||
ipset.Add(r.IPSet, cidr)
|
||||
}
|
||||
if r.IPSet == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := sets[r.IPSet]; !ok {
|
||||
sets[r.IPSet] = struct{}{}
|
||||
ipset.Create(r.IPSet)
|
||||
ipset.Flush(r.IPSet)
|
||||
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.
|
||||
func (m *Manager) AddDomainIP(domain, ip string) error {
|
||||
if domain == "" || ip == "" {
|
||||
return errors.New("please specify the domain and ip address")
|
||||
}
|
||||
|
||||
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
|
||||
domain = strings.ToLower(domain)
|
||||
for i := len(domain); i != -1; {
|
||||
i = strings.LastIndexByte(domain[:i], '.')
|
||||
if setName, ok := m.domainSet.Load(domain[i+1:]); ok {
|
||||
ipset.Add(setName.(string), ip)
|
||||
addAddrToSet(setName.(string), ip)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
"github.com/nadoo/glider/rule"
|
||||
)
|
||||
@ -17,6 +18,6 @@ func NewManager(rules []*rule.Config) (*Manager, error) {
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
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"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/dns"
|
||||
"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/rule"
|
||||
"github.com/nadoo/glider/service"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "0.13.0"
|
||||
version = "0.17.0"
|
||||
config = parseConfig()
|
||||
)
|
||||
|
||||
@ -38,8 +37,8 @@ func main() {
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@ -63,6 +62,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range config.rules {
|
||||
r.IP, r.CIDR, r.Domain = nil, nil, nil
|
||||
}
|
||||
|
||||
// enable checkers
|
||||
pxy.Check()
|
||||
|
||||
@ -72,14 +75,16 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go local.ListenAndServe()
|
||||
}
|
||||
|
||||
// run services
|
||||
for _, s := range config.Services {
|
||||
args := strings.Split(s, ",")
|
||||
go service.Run(args[0], args[1:]...)
|
||||
service, err := service.New(s)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go service.Run()
|
||||
}
|
||||
|
||||
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 (
|
||||
"math/bits"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -17,11 +18,12 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := 0; i < num; i++ {
|
||||
for i := range num {
|
||||
size := 1 << i
|
||||
sizes[i] = size
|
||||
pools[i].New = func() interface{} {
|
||||
return make([]byte, size)
|
||||
pools[i].New = func() any {
|
||||
buf := make([]byte, size)
|
||||
return unsafe.SliceData(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,11 +32,10 @@ func init() {
|
||||
// otherwise, this function will call make([]byte, size) directly.
|
||||
func GetBuffer(size int) []byte {
|
||||
if size >= 1 && size <= maxsize {
|
||||
i := bits.Len32(uint32(size)) - 1
|
||||
if sizes[i] < size {
|
||||
i += 1
|
||||
i := bits.Len32(uint32(size - 1))
|
||||
if p := pools[i].Get().(*byte); p != nil {
|
||||
return unsafe.Slice(p, 1<<i)[:size]
|
||||
}
|
||||
return pools[i].Get().([]byte)[:size]
|
||||
}
|
||||
return make([]byte, size)
|
||||
}
|
||||
@ -42,9 +43,9 @@ func GetBuffer(size int) []byte {
|
||||
// PutBuffer puts a buffer into pool.
|
||||
func PutBuffer(buf []byte) {
|
||||
if size := cap(buf); size >= 1 && size <= maxsize {
|
||||
i := bits.Len32(uint32(size)) - 1
|
||||
i := bits.Len32(uint32(size - 1))
|
||||
if sizes[i] == size {
|
||||
pools[i].Put(buf)
|
||||
pools[i].Put(unsafe.SliceData(buf))
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
var bytesBufPool = sync.Pool{
|
||||
New: func() interface{} { return &bytes.Buffer{} },
|
||||
New: func() any { return &bytes.Buffer{} },
|
||||
}
|
||||
|
||||
// GetBytesBuffer returns a bytes.buffer from pool.
|
||||
@ -16,10 +16,8 @@ func GetBytesBuffer() *bytes.Buffer {
|
||||
|
||||
// PutBytesBuffer puts a bytes.buffer into pool.
|
||||
func PutBytesBuffer(buf *bytes.Buffer) {
|
||||
if buf.Cap() > 64<<10 {
|
||||
return
|
||||
if buf.Cap() <= 64<<10 {
|
||||
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"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -67,11 +68,12 @@ func (a Addr) String() string {
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
// ReadAddrBuf reads just enough bytes from r to get a valid Addr.
|
||||
func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
|
||||
if len(b) < MaxAddrLen {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
// Network returns network name. Implements net.Addr interface.
|
||||
func (a Addr) Network() string { return "socks" }
|
||||
|
||||
// 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
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -96,11 +98,6 @@ func ReadAddrBuf(r io.Reader, b []byte) (Addr, error) {
|
||||
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.
|
||||
func SplitAddr(b []byte) Addr {
|
||||
addrLen := 1
|
||||
@ -136,16 +133,16 @@ func ParseAddr(s string) Addr {
|
||||
if err != 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[0] = ATypIP4
|
||||
copy(addr[1:], ip4)
|
||||
} else {
|
||||
addr = make([]byte, 1+net.IPv6len+2)
|
||||
addr[0] = ATypIP6
|
||||
copy(addr[1:], ip)
|
||||
}
|
||||
copy(addr[1:], ip.AsSlice())
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return nil
|
@ -10,15 +10,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
// TCPBufSize is the size of tcp buffer.
|
||||
TCPBufSize = 32 << 10
|
||||
|
||||
// UDPBufSize is the size of udp buffer.
|
||||
UDPBufSize = 64 << 10
|
||||
UDPBufSize = 2 << 10
|
||||
)
|
||||
|
||||
// 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.
|
||||
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.
|
||||
func Relay(left, right net.Conn) error {
|
||||
var err, err1 error
|
||||
@ -73,12 +79,6 @@ func Relay(left, right net.Conn) error {
|
||||
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.
|
||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
dst = underlyingWriter(dst)
|
||||
@ -175,19 +175,30 @@ func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
return written, err
|
||||
}
|
||||
|
||||
// RelayUDP 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 {
|
||||
b := pool.GetBuffer(UDPBufSize)
|
||||
defer pool.PutBuffer(b)
|
||||
// CopyUDP copys from src to dst at target with read timeout.
|
||||
// if step sets to non-zero value,
|
||||
// the read timeout will be increased from 0 to timeout by step in every read operation.
|
||||
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 {
|
||||
src.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, _, err := src.ReadFrom(b)
|
||||
if t += step; t == 0 || t > timeout {
|
||||
t = timeout
|
||||
}
|
||||
|
||||
src.SetReadDeadline(time.Now().Add(t))
|
||||
n, addr, err := src.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = dst.WriteTo(b[:n], target)
|
||||
if writeTo != nil {
|
||||
addr = writeTo
|
||||
}
|
||||
|
||||
_, err = dst.WriteTo(buf[:n], addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package proxy
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotSupported indicates that the operation is not supported
|
||||
ErrNotSupported = errors.New("not supported")
|
||||
)
|
||||
|
||||
@ -31,7 +33,7 @@ type UDPDialer interface {
|
||||
Addr() string
|
||||
|
||||
// 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.
|
||||
@ -53,6 +55,10 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
|
||||
return nil, errors.New("DialerFromURL: dialer cannot be nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(s, "://") {
|
||||
s = s + "://"
|
||||
}
|
||||
|
||||
scheme := s[:strings.Index(s, ":")]
|
||||
c, ok := dialerCreators[strings.ToLower(scheme)]
|
||||
if ok {
|
||||
@ -61,3 +67,13 @@ func DialerFromURL(s string, dialer Dialer) (Dialer, error) {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/sockopt"
|
||||
)
|
||||
|
||||
// Direct proxy.
|
||||
@ -16,16 +18,17 @@ type Direct struct {
|
||||
relayTimeout time.Duration
|
||||
}
|
||||
|
||||
// Default dialer.
|
||||
var Default = &Direct{dialTimeout: time.Second * 3}
|
||||
func init() {
|
||||
RegisterDialer("direct", NewDirectDialer)
|
||||
}
|
||||
|
||||
// NewDirect returns a Direct dialer.
|
||||
func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct, error) {
|
||||
d := &Direct{dialTimeout: dialTimeout, relayTimeout: relayTimeout}
|
||||
|
||||
if intface != "" {
|
||||
if ip := net.ParseIP(intface); ip != nil {
|
||||
d.ip = net.ParseIP(intface)
|
||||
if addr, err := netip.ParseAddr(intface); err == nil {
|
||||
d.ip = addr.AsSlice()
|
||||
} else {
|
||||
iface, err := net.InterfaceByName(intface)
|
||||
if err != nil {
|
||||
@ -38,6 +41,14 @@ func NewDirect(intface string, dialTimeout, relayTimeout time.Duration) (*Direct
|
||||
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.
|
||||
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}
|
||||
if d.iface != nil {
|
||||
dialer.Control = sockopt.Control(sockopt.Bind(d.iface))
|
||||
}
|
||||
|
||||
c, err := dialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
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.
|
||||
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
// TODO: support specifying local interface
|
||||
func (d *Direct) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
var la string
|
||||
if d.ip != nil {
|
||||
la = net.JoinHostPort(d.ip.String(), "0")
|
||||
}
|
||||
|
||||
pc, err := net.ListenPacket(network, la)
|
||||
if err != nil {
|
||||
log.F("ListenPacket error: %s", err)
|
||||
return nil, nil, err
|
||||
lc := &net.ListenConfig{}
|
||||
if d.iface != nil {
|
||||
lc.Control = sockopt.Control(sockopt.Bind(d.iface))
|
||||
}
|
||||
|
||||
uAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
return pc, uAddr, err
|
||||
return lc.ListenPacket(context.Background(), network, la)
|
||||
}
|
||||
|
||||
// IFaceIPs returns ip addresses according to the specified interface.
|
||||
func (d *Direct) IFaceIPs() (ips []net.IP) {
|
||||
ipnets, err := d.iface.Addrs()
|
||||
ipNets, err := d.iface.Addrs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ipnet := range ipnets {
|
||||
ips = append(ips, ipnet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
||||
for _, ipNet := range ipNets {
|
||||
ips = append(ips, ipNet.(*net.IPNet).IP) //!ip.IsLinkLocalUnicast()
|
||||
}
|
||||
|
||||
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/textproto"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -33,6 +33,8 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
||||
}
|
||||
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n")
|
||||
buf.WriteString("Host: " + addr + "\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
|
||||
buf.WriteString("\r\n")
|
||||
_, err = rc.Write(buf.Bytes())
|
||||
pool.PutBytesBuffer(buf)
|
||||
if err != nil {
|
||||
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.
|
||||
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
return nil, nil, proxy.ErrNotSupported
|
||||
func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"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
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("http", `
|
||||
Http scheme:
|
||||
http://[user:pass@]host:port
|
||||
`)
|
||||
}
|
||||
|
@ -3,16 +3,16 @@ package http
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
)
|
||||
|
||||
// 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{
|
||||
[]byte("GET"),
|
||||
[]byte("POST"),
|
||||
@ -46,7 +46,7 @@ func parseRequest(r *bufio.Reader) (*request, error) {
|
||||
|
||||
method, uri, proto, ok := parseStartLine(line)
|
||||
if !ok {
|
||||
return nil, errors.New("error in parseStartLine")
|
||||
return nil, fmt.Errorf("error in parseStartLine: %s", line)
|
||||
}
|
||||
|
||||
header, err := tpr.ReadMIMEHeader()
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -22,7 +22,7 @@ func NewHTTPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
func (s *HTTP) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
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
|
||||
}
|
||||
defer l.Close()
|
||||
@ -42,22 +42,22 @@ func (s *HTTP) ListenAndServe() {
|
||||
|
||||
// Serve serves a connection.
|
||||
func (s *HTTP) Serve(cc net.Conn) {
|
||||
defer cc.Close()
|
||||
|
||||
if c, ok := cc.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c := proxy.NewConn(cc)
|
||||
defer c.Close()
|
||||
|
||||
req, err := parseRequest(c.Reader())
|
||||
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
|
||||
}
|
||||
|
||||
if s.pretend {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
kcp "github.com/xtaci/kcp-go/v5"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"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.
|
||||
func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
transport := strings.Split(s, ",")
|
||||
|
||||
k, err := NewKCP(transport[0], nil, p)
|
||||
schemes := strings.SplitN(s, ",", 2)
|
||||
k, err := NewKCP(schemes[0], nil, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(transport) > 1 {
|
||||
k.server, err = proxy.ServerFromURL(transport[1], p)
|
||||
if len(schemes) > 1 {
|
||||
k.server, err = proxy.ServerFromURL(schemes[1], p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -164,7 +163,7 @@ func NewKCPServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
func (s *KCP) ListenAndServe() {
|
||||
l, err := kcp.ListenWithOptions(s.addr, s.block, s.dataShards, s.parityShards)
|
||||
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
|
||||
}
|
||||
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.
|
||||
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, proxy.ErrNotSupported
|
||||
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *KCP) setParams(c *kcp.UDPSession) {
|
||||
@ -267,3 +266,16 @@ func (s *KCP) setParams(c *kcp.UDPSession) {
|
||||
c.SetMtu(1350)
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"github.com/nadoo/glider/proxy/http"
|
||||
"github.com/nadoo/glider/proxy/socks5"
|
||||
@ -61,11 +60,11 @@ func (m *Mixed) ListenAndServe() {
|
||||
|
||||
l, err := net.Listen("tcp", m.addr)
|
||||
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
|
||||
}
|
||||
|
||||
log.F("[mixed] listening TCP on %s", m.addr)
|
||||
log.F("[mixed] http & socks5 server listening TCP on %s", m.addr)
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
@ -80,37 +79,12 @@ func (m *Mixed) ListenAndServe() {
|
||||
|
||||
// Serve serves connections.
|
||||
func (m *Mixed) Serve(c net.Conn) {
|
||||
defer c.Close()
|
||||
|
||||
if c, ok := c.(*net.TCPConn); ok {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
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)
|
||||
conn := proxy.NewConn(c)
|
||||
if head, err := conn.Peek(1); err == nil {
|
||||
if head[0] == socks5.Version {
|
||||
m.socks5Server.Serve(conn)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.F("[mixed] unknown request from %s, ignored", c.RemoteAddr())
|
||||
m.httpServer.Serve(conn)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
// HTTPObfs struct
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"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.
|
||||
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, proxy.ErrNotSupported
|
||||
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
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
|
||||
|
||||
// NOTE:
|
||||
@ -17,7 +17,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -64,10 +64,7 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
|
||||
n := len(b)
|
||||
for i := 0; i < n; i += chunkSize {
|
||||
buf.Reset()
|
||||
end := i + chunkSize
|
||||
if end > n {
|
||||
end = n
|
||||
}
|
||||
end := min(i+chunkSize, n)
|
||||
|
||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b[i:end])))
|
||||
|
@ -1,6 +1,9 @@
|
||||
package proxy
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Proxy is a dialer manager.
|
||||
type Proxy interface {
|
||||
@ -8,7 +11,7 @@ type Proxy interface {
|
||||
Dial(network, addr string) (c net.Conn, dialer Dialer, err error)
|
||||
|
||||
// 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.
|
||||
NextDialer(dstAddr string) Dialer
|
||||
@ -16,3 +19,28 @@ type Proxy interface {
|
||||
// Record records result while using the dialer from proxy.
|
||||
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 (
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -55,7 +56,7 @@ func NewRedir6Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
func (s *RedirProxy) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
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
|
||||
}
|
||||
|
||||
@ -83,19 +84,20 @@ func (s *RedirProxy) Serve(cc net.Conn) {
|
||||
}
|
||||
|
||||
c.SetKeepAlive(true)
|
||||
tgt, err := getOrigDst(c, s.ipv6)
|
||||
tgtAddr, err := getOrigDst(c, s.ipv6)
|
||||
if err != nil {
|
||||
log.F("[redir] failed to get target address: %v", err)
|
||||
return
|
||||
}
|
||||
tgt := tgtAddr.String()
|
||||
|
||||
// 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)
|
||||
return
|
||||
}
|
||||
|
||||
rc, dialer, err := s.proxy.Dial("tcp", tgt.String())
|
||||
rc, dialer, err := s.proxy.Dial("tcp", tgt)
|
||||
if err != nil {
|
||||
log.F("[redir] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||
return
|
||||
@ -114,12 +116,12 @@ func (s *RedirProxy) Serve(cc net.Conn) {
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
var addr *net.TCPAddr
|
||||
var addr netip.AddrPort
|
||||
rc.Control(func(fd uintptr) {
|
||||
if ipv6 {
|
||||
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
|
||||
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
|
||||
var raw syscall.RawSockaddrInet4
|
||||
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 {
|
||||
return nil, err
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
var addr net.TCPAddr
|
||||
addr.IP = raw.Addr[:]
|
||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian
|
||||
addr.Port = int(port[0])<<8 | int(port[1])
|
||||
return &addr, nil
|
||||
// NOTE: raw.Port is big-endian, just change it to little-endian
|
||||
// TODO: improve here when we add big-endian $GOARCH support
|
||||
port := raw.Port<<8 | raw.Port>>8
|
||||
return netip.AddrPortFrom(netip.AddrFrom4(raw.Addr), port), nil
|
||||
}
|
||||
|
||||
// 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) (*net.TCPAddr, error) {
|
||||
func getorigdstIPv6(fd uintptr) (netip.AddrPort, error) {
|
||||
const _IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h
|
||||
var raw syscall.RawSockaddrInet6
|
||||
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 {
|
||||
return nil, err
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
var addr net.TCPAddr
|
||||
addr.IP = raw.Addr[:]
|
||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // raw.Port is big-endian
|
||||
addr.Port = int(port[0])<<8 | int(port[1])
|
||||
return &addr, nil
|
||||
// NOTE: raw.Port is big-endian, just change it to little-endian
|
||||
// TODO: improve here when we add big-endian $GOARCH support
|
||||
port := raw.Port<<8 | raw.Port>>8
|
||||
return netip.AddrPortFrom(netip.AddrFrom16(raw.Addr), port), nil
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// +build linux,!386
|
||||
//go:build linux && !386
|
||||
|
||||
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.
|
||||
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, errors.New("REJECT")
|
||||
func (s *Reject) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
return nil, errors.New("REJECT")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("reject", `
|
||||
Reject scheme:
|
||||
reject://
|
||||
`)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package proxy
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -15,6 +16,11 @@ type Server interface {
|
||||
Serve(c net.Conn)
|
||||
}
|
||||
|
||||
// PacketServer interface.
|
||||
type PacketServer interface {
|
||||
ServePacket(pc net.PacketConn)
|
||||
}
|
||||
|
||||
// ServerCreator is a function to create proxy servers.
|
||||
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.
|
||||
// dialer is the default upstream dialer so cannot be nil, we can use Default when calling this function.
|
||||
func ServerFromURL(s string, p Proxy) (Server, error) {
|
||||
if p == nil {
|
||||
// proxy can not be nil.
|
||||
func ServerFromURL(s string, proxy Proxy) (Server, error) {
|
||||
if proxy == 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, ":")]
|
||||
c, ok := serverCreators[strings.ToLower(scheme)]
|
||||
if ok {
|
||||
return c(s, p)
|
||||
return c(s, proxy)
|
||||
}
|
||||
|
||||
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"
|
||||
"strconv"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -25,12 +25,14 @@ const (
|
||||
|
||||
// SOCKS4 is a base socks4 struct.
|
||||
type SOCKS4 struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
socks4a bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("socks4", NewSocks4Dialer)
|
||||
proxy.RegisterDialer("socks4a", NewSocks4Dialer)
|
||||
}
|
||||
|
||||
// NewSOCKS4 returns a socks4 proxy.
|
||||
@ -42,8 +44,9 @@ func NewSOCKS4(s string, dialer proxy.Dialer) (*SOCKS4, error) {
|
||||
}
|
||||
|
||||
h := &SOCKS4{
|
||||
dialer: dialer,
|
||||
addr: u.Host,
|
||||
dialer: dialer,
|
||||
addr: u.Host,
|
||||
socks4a: u.Scheme == "socks4a",
|
||||
}
|
||||
|
||||
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.
|
||||
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
return nil, nil, proxy.ErrNotSupported
|
||||
func (s *SOCKS4) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
port, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
const baseBufSize = 8 + 1 // 1 is the len(userid)
|
||||
bufSize := baseBufSize
|
||||
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
|
||||
buf := []byte{
|
||||
// taken from https://github.com/h12w/socks/blob/master/socks.go and https://en.wikipedia.org/wiki/SOCKS
|
||||
buf := pool.GetBuffer(bufSize)
|
||||
defer pool.PutBuffer(buf)
|
||||
copy(buf, []byte{
|
||||
Version,
|
||||
ConnectCommand,
|
||||
byte(port >> 8), // higher byte of destination port
|
||||
byte(port), // lower byte of destination port (big endian)
|
||||
ip[0], ip[1], ip[2], ip[3],
|
||||
0, // user id
|
||||
})
|
||||
if s.socks4a {
|
||||
copy(buf[baseBufSize:], host)
|
||||
buf[len(buf)-1] = 0
|
||||
}
|
||||
|
||||
resp := pool.GetBuffer(8)
|
||||
@ -164,3 +186,10 @@ func (s *SOCKS4) connect(conn net.Conn, target string) error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("socks4", `
|
||||
Socks4 scheme:
|
||||
socks4://host:port
|
||||
`)
|
||||
}
|
||||
|
@ -4,14 +4,19 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"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/socks"
|
||||
)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("socks5", NewSocks5Dialer)
|
||||
}
|
||||
|
||||
// NewSocks5Dialer returns a socks5 proxy dialer.
|
||||
func NewSocks5Dialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
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.
|
||||
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 {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
@ -39,84 +59,59 @@ func (s *Socks5) Dial(network, addr string) (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.connect(c, addr); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// DialUDP connects to the given address via the proxy.
|
||||
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
c, err := s.dialer.Dial("tcp", s.addr)
|
||||
func (s *Socks5) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||
c, err := s.dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[socks5] dialudp dial tcp to %s error: %s", s.addr, err)
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// send VER, NMETHODS, METHODS
|
||||
c.Write([]byte{Version, 1, 0})
|
||||
var uAddr socks.Addr
|
||||
if uAddr, err = s.connect(c, addr, socks.CmdUDPAssociate); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := pool.GetBuffer(socks.MaxAddrLen)
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
// read VER METHOD
|
||||
if _, err := io.ReadFull(c, buf[:2]); err != nil {
|
||||
return nil, nil, err
|
||||
uAddress := uAddr.String()
|
||||
h, p, _ := net.SplitHostPort(uAddress)
|
||||
// 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)
|
||||
// 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)
|
||||
pc, err = s.dialer.DialUDP(network, uAddress)
|
||||
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 {
|
||||
log.F("[socks5] dialudp to %s error: %s", uAddr.String(), err)
|
||||
return nil, nil, err
|
||||
log.F("[socks5] resolve addr error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkc := NewPktConn(pc, nextHop, dstAddr, true, c)
|
||||
return pkc, nextHop, err
|
||||
return NewPktConn(pc, writeTo, socks.ParseAddr(addr), c), err
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *Socks5) connect(conn net.Conn, target string) 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)
|
||||
}
|
||||
|
||||
func (s *Socks5) connect(conn net.Conn, target string, cmd byte) (addr socks.Addr, err error) {
|
||||
// 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 {
|
||||
buf = append(buf, 2 /* num auth methods */, socks.AuthNone, socks.AuthPassword)
|
||||
} else {
|
||||
@ -124,17 +119,17 @@ func (s *Socks5) connect(conn net.Conn, target string) error {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
@ -146,45 +141,29 @@ func (s *Socks5) connect(conn net.Conn, target string) error {
|
||||
buf = append(buf, s.password...)
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 = append(buf, Version, socks.CmdConnect, 0 /* reserved */)
|
||||
|
||||
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))
|
||||
buf = append(buf, Version, cmd, 0 /* reserved */)
|
||||
buf = append(buf, socks.ParseAddr(target)...)
|
||||
|
||||
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 {
|
||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
// read VER REP RSV
|
||||
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"
|
||||
@ -193,38 +172,8 @@ func (s *Socks5) connect(conn net.Conn, target string) error {
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
return socks.ReadAddr(conn)
|
||||
}
|
||||
|
@ -4,29 +4,24 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/proxy/socks"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
)
|
||||
|
||||
// PktConn .
|
||||
type PktConn struct {
|
||||
net.PacketConn
|
||||
|
||||
writeAddr net.Addr // write to and read from addr
|
||||
|
||||
tgtAddr socks.Addr
|
||||
tgtHeader bool
|
||||
|
||||
ctrlConn net.Conn // tcp control conn
|
||||
writeTo net.Addr // write to and read from addr
|
||||
target socks.Addr
|
||||
}
|
||||
|
||||
// NewPktConn returns a PktConn.
|
||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool, ctrlConn net.Conn) *PktConn {
|
||||
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr.
|
||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr, ctrlConn net.Conn) *PktConn {
|
||||
pc := &PktConn{
|
||||
PacketConn: c,
|
||||
writeAddr: writeAddr,
|
||||
tgtAddr: tgtAddr,
|
||||
tgtHeader: tgtHeader,
|
||||
writeTo: writeAddr,
|
||||
target: targetAddr,
|
||||
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.
|
||||
func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.ReadFrom(b)
|
||||
}
|
||||
n, _, target, err := pc.readFrom(b)
|
||||
return n, target, err
|
||||
}
|
||||
|
||||
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
|
||||
buf := pool.GetBuffer(len(b))
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return n, raddr, err
|
||||
return n, raddr, nil, err
|
||||
}
|
||||
|
||||
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 |
|
||||
// +----+------+------+----------+----------+----------+
|
||||
@ -74,38 +70,46 @@ func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
// +----+------+------+----------+----------+----------+
|
||||
tgtAddr := socks.SplitAddr(buf[3:n])
|
||||
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])
|
||||
|
||||
//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
|
||||
return n, raddr, target, err
|
||||
}
|
||||
|
||||
// WriteTo overrides the original function from net.PacketConn.
|
||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.WriteTo(b, addr)
|
||||
target := pc.target
|
||||
if addr != nil {
|
||||
target = socks.ParseAddr(addr.String())
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return 0, errors.New("invalid addr")
|
||||
}
|
||||
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
buf.Write([]byte{0, 0, 0})
|
||||
tgtLen, _ := buf.Write(pc.tgtAddr)
|
||||
tgtLen, _ := buf.Write(target)
|
||||
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 {
|
||||
return n - tgtLen - 3, err
|
||||
}
|
||||
|
@ -8,12 +8,18 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pool"
|
||||
"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/socks"
|
||||
)
|
||||
|
||||
var nm sync.Map
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("socks5", NewSocks5Server)
|
||||
}
|
||||
|
||||
// NewSocks5Server returns a socks5 proxy server.
|
||||
func NewSocks5Server(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewSocks5(s, nil, p)
|
||||
@ -29,7 +35,7 @@ func (s *Socks5) ListenAndServe() {
|
||||
func (s *Socks5) ListenAndServeTCP() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
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
|
||||
}
|
||||
|
||||
@ -97,87 +103,119 @@ func (s *Socks5) Serve(c net.Conn) {
|
||||
func (s *Socks5) ListenAndServeUDP() {
|
||||
lc, err := net.ListenPacket("udp", s.addr)
|
||||
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
|
||||
}
|
||||
defer lc.Close()
|
||||
|
||||
log.F("[socks5] listening UDP on %s", s.addr)
|
||||
|
||||
var nm sync.Map
|
||||
buf := make([]byte, proxy.UDPBufSize)
|
||||
s.ServePacket(lc)
|
||||
}
|
||||
|
||||
// ServePacket implements proxy.PacketServer.
|
||||
func (s *Socks5) ServePacket(pc net.PacketConn) {
|
||||
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 {
|
||||
log.F("[socks5u] remote read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var pc *PktConn
|
||||
v, ok := nm.Load(raddr.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())
|
||||
var session *Session
|
||||
sessionKey := srcAddr.String()
|
||||
|
||||
v, ok := nm.Load(sessionKey)
|
||||
if !ok || v == nil {
|
||||
session = newSession(sessionKey, srcAddr, dstAddr, c)
|
||||
nm.Store(sessionKey, session)
|
||||
go s.serveSession(session)
|
||||
} else {
|
||||
pc = v.(*PktConn)
|
||||
session = v.(*Session)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||
if err != nil {
|
||||
log.F("[socks5u] remote write error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// log.F("[socks5u] %s <-> %s", raddr, c.tgtAddr)
|
||||
session.msgCh <- message{dstAddr, buf[:n]}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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
|
||||
buf := make([]byte, socks.MaxAddrLen)
|
||||
buf := pool.GetBuffer(socks.MaxAddrLen)
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
nmethods := buf[1]
|
||||
if _, err := io.ReadFull(rw, buf[:nmethods]); err != nil {
|
||||
if _, err := io.ReadFull(c, buf[:nmethods]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// write VER METHOD
|
||||
if s.user != "" && s.password != "" {
|
||||
_, err := rw.Write([]byte{Version, socks.AuthPassword})
|
||||
_, err := c.Write([]byte{Version, socks.AuthPassword})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(rw, buf[:2])
|
||||
_, err = io.ReadFull(c, buf[:2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -185,28 +223,28 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
||||
// Get username
|
||||
userLen := int(buf[1])
|
||||
if userLen <= 0 {
|
||||
rw.Write([]byte{1, 1})
|
||||
c.Write([]byte{1, 1})
|
||||
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")
|
||||
}
|
||||
user := string(buf[:userLen])
|
||||
|
||||
// Get password
|
||||
_, err = rw.Read(buf[:1])
|
||||
_, err = c.Read(buf[:1])
|
||||
if err != nil {
|
||||
return nil, errors.New("auth failed: cannot get password len")
|
||||
}
|
||||
|
||||
passLen := int(buf[0])
|
||||
if passLen <= 0 {
|
||||
rw.Write([]byte{1, 1})
|
||||
c.Write([]byte{1, 1})
|
||||
return nil, errors.New("auth failed: wrong password length")
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(rw, buf[:passLen])
|
||||
_, err = io.ReadFull(c, buf[:passLen])
|
||||
if err != nil {
|
||||
return nil, errors.New("auth failed: cannot get password")
|
||||
}
|
||||
@ -214,7 +252,7 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
||||
|
||||
// Verify
|
||||
if user != s.user || pass != s.password {
|
||||
_, err = rw.Write([]byte{1, 1})
|
||||
_, err = c.Write([]byte{1, 1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -222,30 +260,33 @@ func (s *Socks5) handshake(rw io.ReadWriter) (socks.Addr, error) {
|
||||
}
|
||||
|
||||
// Response auth state
|
||||
_, err = rw.Write([]byte{1, 0})
|
||||
_, err = c.Write([]byte{1, 0})
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
cmd := buf[1]
|
||||
addr, err := socks.ReadAddrBuf(rw, buf)
|
||||
addr, err := socks.ReadAddr(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch cmd {
|
||||
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:
|
||||
listenAddr := socks.ParseAddr(rw.(net.Conn).LocalAddr().String())
|
||||
_, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded
|
||||
listenAddr := socks.ParseAddr(c.LocalAddr().String())
|
||||
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 {
|
||||
return nil, socks.Errors[7]
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// https://tools.ietf.org/html/rfc1928
|
||||
// https://www.rfc-editor.org/rfc/rfc1928
|
||||
|
||||
// socks5 client:
|
||||
// https://github.com/golang/net/tree/master/proxy
|
||||
@ -12,7 +12,7 @@ package socks5
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -28,11 +28,6 @@ type Socks5 struct {
|
||||
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.
|
||||
// with an optional username and password. (RFC 1928)
|
||||
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
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("socks5", `
|
||||
Socks5 scheme:
|
||||
socks5://[user:pass@]host:port
|
||||
`)
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ss/cipher/internal/shadowaead"
|
||||
"github.com/nadoo/glider/proxy/ss/cipher/internal/shadowstream"
|
||||
"github.com/nadoo/glider/proxy/ss/cipher/shadowaead"
|
||||
"github.com/nadoo/glider/proxy/ss/cipher/shadowstream"
|
||||
)
|
||||
|
||||
// Cipher interface.
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,7 +25,6 @@ type writer struct {
|
||||
io.Writer
|
||||
cipher.AEAD
|
||||
nonce [32]byte
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// NewWriter wraps an io.Writer with AEAD encryption.
|
@ -4,7 +4,7 @@ import (
|
||||
"crypto/cipher"
|
||||
"io"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const bufSize = 32 * 1024
|
@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"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/socks"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func (s *SS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
pc, nextHop, err := s.dialer.DialUDP(network, s.addr)
|
||||
func (s *SS) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
pc, err := s.dialer.DialUDP(network, s.addr)
|
||||
if err != nil {
|
||||
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)
|
||||
return pkc, nextHop, err
|
||||
writeTo, err := net.ResolveUDPAddr("udp", s.addr)
|
||||
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"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/proxy/socks"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
)
|
||||
|
||||
// PktConn .
|
||||
type PktConn struct {
|
||||
net.PacketConn
|
||||
|
||||
writeAddr net.Addr // write to and read from addr
|
||||
|
||||
tgtAddr socks.Addr
|
||||
tgtHeader bool
|
||||
writeTo net.Addr
|
||||
target socks.Addr // if target is not nil, it may be a tunnel
|
||||
}
|
||||
|
||||
// NewPktConn returns a PktConn
|
||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHeader bool) *PktConn {
|
||||
pc := &PktConn{
|
||||
PacketConn: c,
|
||||
writeAddr: writeAddr,
|
||||
tgtAddr: tgtAddr,
|
||||
tgtHeader: tgtHeader}
|
||||
return pc
|
||||
// NewPktConn returns a PktConn, the writeAddr must be *net.UDPAddr or *net.UnixAddr.
|
||||
func NewPktConn(c net.PacketConn, writeAddr net.Addr, targetAddr socks.Addr) *PktConn {
|
||||
return &PktConn{PacketConn: c, writeTo: writeAddr, target: targetAddr}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.ReadFrom(b)
|
||||
}
|
||||
n, _, target, err := pc.readFrom(b)
|
||||
return n, target, err
|
||||
}
|
||||
|
||||
func (pc *PktConn) readFrom(b []byte) (int, net.Addr, net.Addr, error) {
|
||||
buf := pool.GetBuffer(len(b))
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
n, raddr, err := pc.PacketConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return n, raddr, err
|
||||
return n, raddr, nil, err
|
||||
}
|
||||
|
||||
tgtAddr := socks.SplitAddr(buf[:n])
|
||||
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])
|
||||
|
||||
//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
|
||||
return n, raddr, target, err
|
||||
}
|
||||
|
||||
// WriteTo overrides the original function from net.PacketConn
|
||||
func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
if !pc.tgtHeader {
|
||||
return pc.PacketConn.WriteTo(b, addr)
|
||||
target := pc.target
|
||||
if addr != nil {
|
||||
target = socks.ParseAddr(addr.String())
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return 0, errors.New("invalid addr")
|
||||
}
|
||||
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
tgtLen, _ := buf.Write(pc.tgtAddr)
|
||||
tgtLen, _ := buf.Write(target)
|
||||
buf.Write(b)
|
||||
|
||||
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeAddr)
|
||||
n, err := pc.PacketConn.WriteTo(buf.Bytes(), pc.writeTo)
|
||||
if n > tgtLen {
|
||||
return n - tgtLen, err
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"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/socks"
|
||||
)
|
||||
|
||||
var nm sync.Map
|
||||
|
||||
// NewSSServer returns a ss proxy server.
|
||||
func NewSSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
return NewSS(s, nil, p)
|
||||
@ -26,7 +30,7 @@ func (s *SS) ListenAndServe() {
|
||||
func (s *SS) ListenAndServeTCP() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
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
|
||||
}
|
||||
|
||||
@ -51,18 +55,17 @@ func (s *SS) Serve(c net.Conn) {
|
||||
c.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c = s.StreamConn(c)
|
||||
sc := s.StreamConn(c)
|
||||
|
||||
tgt, err := socks.ReadAddr(c)
|
||||
tgt, err := socks.ReadAddr(sc)
|
||||
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
|
||||
}
|
||||
|
||||
network := "tcp"
|
||||
dialer := s.proxy.NextDialer(tgt.String())
|
||||
|
||||
rc, err := dialer.Dial(network, tgt.String())
|
||||
rc, err := dialer.Dial("tcp", tgt.String())
|
||||
if err != nil {
|
||||
log.F("[ss] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||
return
|
||||
@ -71,7 +74,7 @@ func (s *SS) Serve(c net.Conn) {
|
||||
|
||||
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)
|
||||
// record remote conn failure only
|
||||
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() {
|
||||
lc, err := net.ListenPacket("udp", s.addr)
|
||||
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
|
||||
}
|
||||
defer lc.Close()
|
||||
|
||||
lc = s.PacketConn(lc)
|
||||
|
||||
log.F("[ss] listening UDP on %s", s.addr)
|
||||
|
||||
var nm sync.Map
|
||||
buf := make([]byte, proxy.UDPBufSize)
|
||||
s.ServePacket(lc)
|
||||
}
|
||||
|
||||
// ServePacket implements proxy.PacketServer.
|
||||
func (s *SS) ServePacket(pc net.PacketConn) {
|
||||
lc := s.PacketConn(pc)
|
||||
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 {
|
||||
log.F("[ssu] remote read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var pc *PktConn
|
||||
v, ok := nm.Load(raddr.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())
|
||||
var session *Session
|
||||
sessionKey := srcAddr.String()
|
||||
|
||||
v, ok := nm.Load(sessionKey)
|
||||
if !ok || v == nil {
|
||||
session = newSession(sessionKey, srcAddr, dstAddr, c)
|
||||
nm.Store(sessionKey, session)
|
||||
go s.serveSession(session)
|
||||
} else {
|
||||
pc = v.(*PktConn)
|
||||
session = v.(*Session)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||
if err != nil {
|
||||
log.F("[ssu] remote write error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// log.F("[ssu] %s <-> %s", raddr, c.tgtAddr)
|
||||
session.msgCh <- message{dstAddr, buf[:n]}
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
"net/url"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
@ -17,7 +20,13 @@ type SSH struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
conn net.Conn
|
||||
client *ssh.Client
|
||||
config *ssh.ClientConfig
|
||||
|
||||
once sync.Once
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -28,7 +37,7 @@ func init() {
|
||||
func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
log.F("[ssh] parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -38,18 +47,16 @@ func NewSSH(s string, d proxy.Dialer, p proxy.Proxy) (*SSH, error) {
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: user,
|
||||
Timeout: time.Second * 3,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
User: user,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
if pass, _ := u.User.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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
proxy: p,
|
||||
addr: u.Host,
|
||||
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.
|
||||
@ -83,28 +106,78 @@ func (s *SSH) Addr() string {
|
||||
|
||||
// Dial connects to the address addr on the network net via the proxy.
|
||||
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 {
|
||||
log.F("[ssh]: dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
log.F("[ssh] dial connection to %s error: %s", s.addr, 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 {
|
||||
log.F("[ssh]: initial connection to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
log.F("[ssh] initial connection to %s error: %s", s.addr, 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.
|
||||
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
||||
return nil, nil, proxy.ErrNotSupported
|
||||
func (s *SSH) DialUDP(network, addr string) (pc net.PacketConn, err error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
||||
func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
||||
buffer, err := ioutil.ReadFile(file)
|
||||
buffer, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -116,3 +189,11 @@ func privateKeyAuth(file string) (ssh.AuthMethod, error) {
|
||||
|
||||
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/des"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rc4"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/aead/chacha20"
|
||||
"github.com/dgryski/go-camellia"
|
||||
@ -18,7 +18,7 @@ import (
|
||||
"golang.org/x/crypto/cast5"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -8,22 +8,16 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/obfs"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/protocol"
|
||||
)
|
||||
|
||||
const bufSize = proxy.TCPBufSize
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
var bufSize = proxy.TCPBufSize
|
||||
|
||||
// SSTCPConn the struct that override the net.Conn methods
|
||||
type SSTCPConn struct {
|
||||
|
@ -17,8 +17,8 @@ type IObfs interface {
|
||||
GetServerInfo() (s *ssr.ServerInfo)
|
||||
Encode(data []byte) (encodedData []byte, err error)
|
||||
Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
|
||||
SetData(data interface{})
|
||||
GetData() interface{}
|
||||
SetData(data any)
|
||||
GetData() any
|
||||
GetOverhead() int
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -13,7 +13,7 @@ func newHttpPost() IObfs {
|
||||
// newHttpSimple create a http_simple object
|
||||
|
||||
t := &httpSimplePost{
|
||||
userAgentIndex: rand.Intn(len(requestUserAgent)),
|
||||
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||
methodGet: false,
|
||||
}
|
||||
return t
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
@ -55,7 +55,7 @@ func newHttpSimple() IObfs {
|
||||
t := &httpSimplePost{
|
||||
rawTransSent: false,
|
||||
rawTransReceived: false,
|
||||
userAgentIndex: rand.Intn(len(requestUserAgent)),
|
||||
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||
methodGet: true,
|
||||
}
|
||||
return t
|
||||
@ -69,25 +69,25 @@ func (t *httpSimplePost) GetServerInfo() (s *ssr.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
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) boundary() (ret string) {
|
||||
|
||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
for i := 0; i < 32; i++ {
|
||||
ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))])
|
||||
for range 32 {
|
||||
ret = fmt.Sprintf("%s%c", ret, set[rand.IntN(len(set))])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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]}))
|
||||
}
|
||||
return
|
||||
@ -101,12 +101,12 @@ func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
||||
dataLength := len(data)
|
||||
var headData []byte
|
||||
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 64 {
|
||||
headData = make([]byte, headSize+rand.Intn(64))
|
||||
headData = make([]byte, headSize+rand.IntN(64))
|
||||
} else {
|
||||
headData = make([]byte, dataLength)
|
||||
}
|
||||
copy(headData, data[0:len(headData)])
|
||||
requestPathIndex := rand.Intn(len(requestPath)/2) * 2
|
||||
requestPathIndex := rand.IntN(len(requestPath)/2) * 2
|
||||
host := t.Host
|
||||
var customHead string
|
||||
|
||||
@ -122,7 +122,7 @@ func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
||||
}
|
||||
hosts := strings.Split(param, ",")
|
||||
if len(hosts) > 0 {
|
||||
host = strings.TrimSpace(hosts[rand.Intn(len(hosts))])
|
||||
host = strings.TrimSpace(hosts[rand.IntN(len(hosts))])
|
||||
}
|
||||
}
|
||||
method := "GET /"
|
||||
|
@ -33,11 +33,11 @@ func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
crand "crypto/rand"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
@ -31,11 +32,11 @@ func (r *randomHead) GetServerInfo() (s *ssr.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
|
||||
}
|
||||
|
||||
@ -57,9 +58,9 @@ func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) {
|
||||
r.rawTransSent = true
|
||||
}
|
||||
} else {
|
||||
size := rand.Intn(96) + 8
|
||||
size := rand.IntN(96) + 8
|
||||
encodedData = make([]byte, size)
|
||||
rand.Read(encodedData)
|
||||
crand.Read(encodedData)
|
||||
ssr.SetCRC32(encodedData, size)
|
||||
|
||||
d := make([]byte, dataLength)
|
||||
|
@ -3,10 +3,11 @@ package obfs
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -53,18 +54,18 @@ func (t *tls12TicketAuth) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &t.ServerInfo
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) SetData(data interface{}) {
|
||||
func (t *tls12TicketAuth) SetData(data any) {
|
||||
if auth, ok := data.(*tlsAuthData); ok {
|
||||
t.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) GetData() interface{} {
|
||||
func (t *tls12TicketAuth) GetData() any {
|
||||
if t.data == nil {
|
||||
t.data = &tlsAuthData{}
|
||||
b := make([]byte, 32)
|
||||
|
||||
rand.Read(b)
|
||||
crand.Read(b)
|
||||
copy(t.data.localClientID[:], b)
|
||||
}
|
||||
return t.data
|
||||
@ -76,7 +77,7 @@ func (t *tls12TicketAuth) getHost() string {
|
||||
hosts := strings.Split(t.Param, ",")
|
||||
if len(hosts) > 0 {
|
||||
|
||||
host = hosts[rand.Intn(len(hosts))]
|
||||
host = hosts[rand.IntN(len(hosts))]
|
||||
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) {
|
||||
encodedData = make([]byte, 0)
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
switch t.handshakeStatus {
|
||||
case 8:
|
||||
if len(data) < 1024 {
|
||||
@ -108,7 +108,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
||||
start := 0
|
||||
var l int
|
||||
for len(data)-start > 2048 {
|
||||
l = rand.Intn(4096) + 100
|
||||
l = rand.IntN(4096) + 100
|
||||
if l > len(data)-start {
|
||||
l = len(data) - start
|
||||
}
|
||||
@ -129,7 +129,7 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
||||
start := 0
|
||||
var l int
|
||||
for len(data)-start > 2048 {
|
||||
l = rand.Intn(4096) + 100
|
||||
l = rand.IntN(4096) + 100
|
||||
if 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)
|
||||
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
||||
copy(hmacData, handshakeFinish)
|
||||
rand.Read(hmacData[11:33])
|
||||
crand.Read(hmacData[11:33])
|
||||
h := t.hmacSHA1(hmacData[:33])
|
||||
copy(hmacData[33:], h)
|
||||
encodedData = append(hmacData, t.sendSaver...)
|
||||
@ -169,11 +169,11 @@ func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
||||
tlsDataLen += len(sni)
|
||||
copy(tlsData[tlsDataLen:], tlsData2)
|
||||
tlsDataLen += len(tlsData2)
|
||||
ticketLen := rand.Intn(164)*2 + 64
|
||||
ticketLen := rand.IntN(164)*2 + 64
|
||||
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
||||
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
||||
//ticketLen := 208
|
||||
rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
||||
crand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
||||
tlsDataLen += ticketLen
|
||||
copy(tlsData[tlsDataLen:], tlsData3)
|
||||
tlsDataLen += len(tlsData3)
|
||||
@ -278,7 +278,7 @@ func (t *tls12TicketAuth) packAuthData() (outData []byte) {
|
||||
now := time.Now().Unix()
|
||||
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])
|
||||
copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash)
|
||||
|
@ -4,9 +4,10 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
crand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -58,13 +59,13 @@ func (a *authAES128) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &a.ServerInfo
|
||||
}
|
||||
|
||||
func (a *authAES128) SetData(data interface{}) {
|
||||
func (a *authAES128) SetData(data any) {
|
||||
if auth, ok := data.(*AuthData); ok {
|
||||
a.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authAES128) GetData() interface{} {
|
||||
func (a *authAES128) GetData() any {
|
||||
if a.data == nil {
|
||||
a.data = &AuthData{}
|
||||
}
|
||||
@ -74,15 +75,14 @@ func (a *authAES128) GetData() interface{} {
|
||||
func (a *authAES128) packData(data []byte) (outData []byte) {
|
||||
dataLength := len(data)
|
||||
randLength := 1
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
if dataLength <= 1200 {
|
||||
if a.packID > 4 {
|
||||
randLength += rand.Intn(32)
|
||||
randLength += rand.IntN(32)
|
||||
} else {
|
||||
if dataLength > 900 {
|
||||
randLength += rand.Intn(128)
|
||||
randLength += rand.IntN(128)
|
||||
} 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])
|
||||
copy(outData[2:4], h[:2])
|
||||
// 4~rand length+4, rand number
|
||||
rand.Read(outData[4 : 4+randLength])
|
||||
crand.Read(outData[4 : 4+randLength])
|
||||
// 4, rand length
|
||||
if randLength < 128 {
|
||||
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) {
|
||||
dataLength := len(data)
|
||||
var randLength int
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
if dataLength > 400 {
|
||||
randLength = rand.Intn(512)
|
||||
randLength = rand.IntN(512)
|
||||
} else {
|
||||
randLength = rand.Intn(1024)
|
||||
randLength = rand.IntN(1024)
|
||||
}
|
||||
|
||||
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.IVLen:], a.Key)
|
||||
|
||||
rand.Read(outData[dataOffset-randLength:])
|
||||
crand.Read(outData[dataOffset-randLength:])
|
||||
a.data.mutex.Lock()
|
||||
a.data.connectionID++
|
||||
if a.data.connectionID > 0xFF000000 {
|
||||
@ -144,9 +143,9 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
||||
}
|
||||
if len(a.data.clientID) == 0 {
|
||||
a.data.clientID = make([]byte, 8)
|
||||
rand.Read(a.data.clientID)
|
||||
crand.Read(a.data.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
crand.Read(b)
|
||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
copy(encrypt[4:], a.data.clientID)
|
||||
@ -163,13 +162,13 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
||||
uid := make([]byte, 4)
|
||||
if len(params) >= 2 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil {
|
||||
rand.Read(uid)
|
||||
crand.Read(uid)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(uid, uint32(userID))
|
||||
a.userKey = a.hashDigest([]byte(params[1]))
|
||||
}
|
||||
} else {
|
||||
rand.Read(uid)
|
||||
crand.Read(uid)
|
||||
}
|
||||
|
||||
if a.userKey == nil {
|
||||
@ -196,7 +195,7 @@ func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
||||
h := a.hmac(key, encrypt[0:20])
|
||||
copy(encrypt[20:], h[:4])
|
||||
|
||||
rand.Read(outData[0:1])
|
||||
crand.Read(outData[0:1])
|
||||
h = a.hmac(key, outData[0:1])
|
||||
copy(outData[1:], h[0:7-1])
|
||||
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
stdCipher "crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -68,13 +68,13 @@ func (a *authChainA) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &a.ServerInfo
|
||||
}
|
||||
|
||||
func (a *authChainA) SetData(data interface{}) {
|
||||
func (a *authChainA) SetData(data any) {
|
||||
if auth, ok := data.(*AuthData); ok {
|
||||
a.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authChainA) GetData() interface{} {
|
||||
func (a *authChainA) GetData() any {
|
||||
if a.data == nil {
|
||||
a.data = &AuthData{}
|
||||
}
|
||||
@ -100,7 +100,7 @@ func authChainAGetRandLen(dataLength int, random *tools.Shift128plusContext, las
|
||||
|
||||
func getRandStartPos(random *tools.Shift128plusContext, randLength int) int {
|
||||
if randLength > 0 {
|
||||
return int(random.Next() % 8589934609 % uint64(randLength))
|
||||
return int(int64(random.Next()%8589934609) % int64(randLength))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@ -194,7 +194,7 @@ func (a *authChainA) packAuthData(data []byte) (outData []byte) {
|
||||
copy(a.userKey, a.Key)
|
||||
}
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
for i := range 4 {
|
||||
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
||||
}
|
||||
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
||||
|
@ -34,14 +34,14 @@ func (a *authChainA) authChainBInitDataSize() {
|
||||
random.InitFromBin(a.Key)
|
||||
length := random.Next()%8 + 4
|
||||
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)
|
||||
}
|
||||
sort.Ints(a.dataSizeList)
|
||||
|
||||
length = random.Next()%16 + 8
|
||||
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)
|
||||
}
|
||||
sort.Ints(a.dataSizeList2)
|
||||
|
@ -2,8 +2,9 @@ package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"math/rand/v2"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
@ -34,13 +35,13 @@ func (a *authSHA1v4) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &a.ServerInfo
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) SetData(data interface{}) {
|
||||
func (a *authSHA1v4) SetData(data any) {
|
||||
if auth, ok := data.(*AuthData); ok {
|
||||
a.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authSHA1v4) GetData() interface{} {
|
||||
func (a *authSHA1v4) GetData() any {
|
||||
if a.data == nil {
|
||||
a.data = &AuthData{}
|
||||
}
|
||||
@ -53,9 +54,9 @@ func (a *authSHA1v4) packData(data []byte) (outData []byte) {
|
||||
|
||||
if dataLength <= 1300 {
|
||||
if dataLength > 400 {
|
||||
randLength += rand.Intn(128)
|
||||
randLength += rand.IntN(128)
|
||||
} else {
|
||||
randLength += rand.Intn(1024)
|
||||
randLength += rand.IntN(1024)
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,9 +91,9 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
||||
randLength := 1
|
||||
if dataLength <= 1300 {
|
||||
if dataLength > 400 {
|
||||
randLength += rand.Intn(128)
|
||||
randLength += rand.IntN(128)
|
||||
} else {
|
||||
randLength += rand.Intn(1024)
|
||||
randLength += rand.IntN(1024)
|
||||
}
|
||||
}
|
||||
dataOffset := randLength + 4 + 2
|
||||
@ -104,9 +105,9 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
||||
}
|
||||
if len(a.data.clientID) == 0 {
|
||||
a.data.clientID = make([]byte, 8)
|
||||
rand.Read(a.data.clientID)
|
||||
crand.Read(a.data.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
crand.Read(b)
|
||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
// 0-1, out length
|
||||
@ -122,7 +123,7 @@ func (a *authSHA1v4) packAuthData(data []byte) (outData []byte) {
|
||||
// 2~6, crc of out length+salt+key
|
||||
binary.LittleEndian.PutUint32(outData[2:], crc32)
|
||||
// 6~rand length+6, rand numbers
|
||||
rand.Read(outData[dataOffset-randLength : dataOffset])
|
||||
crand.Read(outData[dataOffset-randLength : dataOffset])
|
||||
// 6, rand length
|
||||
if randLength < 128 {
|
||||
outData[6] = byte(randLength & 0xFF)
|
||||
|
@ -23,8 +23,8 @@ type IProtocol interface {
|
||||
GetServerInfo() *ssr.ServerInfo
|
||||
PreEncrypt(data []byte) ([]byte, error)
|
||||
PostDecrypt(data []byte) ([]byte, int, error)
|
||||
SetData(data interface{})
|
||||
GetData() interface{}
|
||||
SetData(data any)
|
||||
GetData() any
|
||||
GetOverhead() int
|
||||
}
|
||||
|
||||
|
@ -33,11 +33,11 @@ func (o *origin) PostDecrypt(data []byte) ([]byte, int, error) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -63,11 +63,11 @@ func (v *verifySHA1) GetServerInfo() (s *ssr.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
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ func init() {
|
||||
}
|
||||
|
||||
func createCRC32Table() {
|
||||
for i := 0; i < 256; i++ {
|
||||
for i := range 256 {
|
||||
crc := uint32(i)
|
||||
for j := 8; j > 0; j-- {
|
||||
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[1] = binary.LittleEndian.Uint64(fillBin[8:])
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"strconv"
|
||||
"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/socks"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||
@ -31,10 +31,10 @@ type SSR struct {
|
||||
EncryptPassword string
|
||||
Obfs string
|
||||
ObfsParam string
|
||||
ObfsData interface{}
|
||||
ObfsData any
|
||||
Protocol string
|
||||
ProtocolParam string
|
||||
ProtocolData interface{}
|
||||
ProtocolData any
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
|
||||
return nil, nil, proxy.ErrNotSupported
|
||||
func (s *SSR) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
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