mirror of
https://github.com/nadoo/glider.git
synced 2025-04-29 23:49:58 +08:00
Compare commits
194 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 | ||
![]() |
42c15b9262 | ||
![]() |
03be29daba | ||
![]() |
e6631aaf84 | ||
![]() |
10b7f2d5e6 | ||
![]() |
539f604e37 | ||
![]() |
1b484cca8f | ||
![]() |
2db84bb7aa | ||
![]() |
142865535e | ||
![]() |
63112a1509 | ||
![]() |
e077cb86b4 | ||
![]() |
87f1f44912 | ||
![]() |
c9f3c20bc1 | ||
![]() |
adfb2fb9b4 | ||
![]() |
f60d248ac7 | ||
![]() |
98ce20b295 | ||
![]() |
6820644073 | ||
![]() |
1fcfeabdb1 | ||
![]() |
eaaa6d5aa9 | ||
![]() |
65d606d29c | ||
![]() |
38f84a625d | ||
![]() |
1173f533ec | ||
![]() |
e6e5c3d4b6 | ||
![]() |
8fbd69d4cc | ||
![]() |
da1075116f | ||
![]() |
678e9b9845 | ||
![]() |
453fe925b3 | ||
![]() |
4887089a4b | ||
![]() |
8a2e95b17e | ||
![]() |
417ac556f7 | ||
![]() |
c15c55fe05 | ||
![]() |
7c92aca331 | ||
![]() |
b3940e4b77 | ||
![]() |
dad45afb17 |
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 }}
|
||||
|
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/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,31 +33,64 @@ builds:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
|
||||
# https://goreleaser.com/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/snapshots/
|
||||
snapshot:
|
||||
name_template: "dev@{{.ShortCommit}}"
|
||||
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
||||
|
||||
# https://goreleaser.com/checksum/
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||
|
||||
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 -o glider
|
||||
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"]
|
||||
|
492
README.md
492
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,68 +39,92 @@ 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
|
||||
|tls |√| |√| |transport client & server
|
||||
|kcp | |√|√| |transport client & server
|
||||
|unix |√| |√| |transport client & server
|
||||
|websocket |√| |√| |transport client & server
|
||||
|simple-obfs | | |√| |transport client only
|
||||
|tcptun |√| | | |transport server only
|
||||
|udptun | |√| | |transport server only
|
||||
|redir |√| | | |linux only
|
||||
|redir6 |√| | | |linux only(ipv6)
|
||||
|reject | | |√|√|reject all requests
|
||||
|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)
|
||||
|
||||
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.12.1 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=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
|
||||
-checkinterval int
|
||||
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)
|
||||
-checktolerance int
|
||||
fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode
|
||||
-checkwebsite string
|
||||
fowarder check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80 (default "www.apple.com")
|
||||
-config string
|
||||
config file path
|
||||
-dialtimeout int
|
||||
@ -107,26 +133,34 @@ glider 0.12.1 usage:
|
||||
local dns server listen address
|
||||
-dnsalwaystcp
|
||||
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)
|
||||
-dnsminttl int
|
||||
minimum TTL value for entries in the CACHE(seconds)
|
||||
-dnsnoaaaa
|
||||
disable AAAA query
|
||||
-dnsrecord value
|
||||
custom dns record, format: domain/ip
|
||||
-dnsserver value
|
||||
remote dns server address
|
||||
-dnstimeout int
|
||||
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
|
||||
-interface string
|
||||
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)
|
||||
-relaytimeout int
|
||||
@ -135,20 +169,122 @@ glider 0.12.1 usage:
|
||||
rule file path
|
||||
-rules-dir string
|
||||
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]
|
||||
-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
|
||||
|
||||
Available schemes:
|
||||
listen: mixed ss socks5 http vless trojan trojanc redir redir6 tcptun udptun tls ws unix kcp
|
||||
forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc tls ws unix kcp simple-obfs
|
||||
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>
|
||||
|
||||
#### Schemes
|
||||
|
||||
<details>
|
||||
<summary><code>glider -scheme all</code></summary>
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
@ -159,33 +295,20 @@ Available methods for ss:
|
||||
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: DUMMY
|
||||
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://
|
||||
@ -194,7 +317,7 @@ Proxy over tls client:
|
||||
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://
|
||||
@ -202,17 +325,44 @@ Proxy over tls server:
|
||||
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]
|
||||
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://
|
||||
@ -220,119 +370,62 @@ TLS and Websocket with a specified proxy protocol:
|
||||
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]
|
||||
|
||||
Available crypt types for KCP:
|
||||
none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20
|
||||
|
||||
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
|
||||
|
||||
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...
|
||||
|
||||
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 ss://AEAD_CHACHA20_POLY1305:pass@:8443 -verbose
|
||||
-listen on 0.0.0.0:8443 as a ss server.
|
||||
|
||||
./glider -listen socks5://user1:pass1@:1080 -verbose
|
||||
-listen on :1080 as a socks5 proxy server, enable authentication.
|
||||
|
||||
./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 redir://:1081 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ss server.
|
||||
|
||||
./glider -listen redir://:1081 -forward "ssr://method:pass@1.1.1.1:8444?protocol=a&protocol_param=b&obfs=c&obfs_param=d"
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ssr server.
|
||||
|
||||
./glider -listen redir://:1081 -forward "tls://abc.com:443,vmess://security:uuid@?alterID=10"
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote tls+vmess server.
|
||||
|
||||
./glider -listen redir://:1081 -forward "ws://1.1.1.1:80,vmess://security:uuid@?alterID=10"
|
||||
-listen on :1081 as a transparent redirect server, forward all requests via remote ws+vmess server.
|
||||
|
||||
./glider -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443
|
||||
-listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.
|
||||
|
||||
./glider -listen udptun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443
|
||||
-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 -listen redir://:1081 -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2
|
||||
-listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.
|
||||
|
||||
./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 -verbose -dns=:53 -dnsserver=8.8.8.8:53 -dnsrecord=www.example.com/1.2.3.4
|
||||
-listen on :53 as dns server, forward dns requests to 8.8.8.8:53, return 1.2.3.4 when resolving www.example.com.
|
||||
|
||||
Services:
|
||||
dhcpd: service=dhcpd,INTERFACE,START_IP,END_IP
|
||||
e.g.,service=dhcpd,eth1,192.168.50.100,192.168.50.199
|
||||
if you want to listen on any address, just set CID to 4294967295.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
run:
|
||||
#### Examples
|
||||
|
||||
<details>
|
||||
<summary><code>glider -example</code></summary>
|
||||
|
||||
```bash
|
||||
glider -verbose -listen :8443 -forward SCHEME://HOST:PORT
|
||||
```
|
||||
```bash
|
||||
glider -config CONFIGPATH
|
||||
```
|
||||
```bash
|
||||
glider -config CONFIGPATH -listen :8080 -verbose
|
||||
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.
|
||||
```
|
||||
|
||||
</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)
|
||||
@ -342,13 +435,40 @@ glider -config CONFIGPATH -listen :8080 -verbose
|
||||
|
||||
## 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
|
||||
|
||||
@ -357,7 +477,7 @@ glider -config CONFIGPATH -listen :8080 -verbose
|
||||
|
||||
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:
|
||||
|
||||
@ -366,9 +486,9 @@ glider -config CONFIGPATH -listen :8080 -verbose
|
||||
// _ "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>
|
||||
@ -406,11 +526,17 @@ glider -config CONFIGPATH -listen :8080 -verbose
|
||||
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`
|
||||
|
385
config.go
385
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"
|
||||
)
|
||||
|
||||
@ -17,11 +18,14 @@ var flag = conflag.New()
|
||||
// Config is global config struct.
|
||||
type Config struct {
|
||||
Verbose bool
|
||||
LogFlags int
|
||||
TCPBufSize int
|
||||
UDPBufSize int
|
||||
|
||||
Listens []string
|
||||
|
||||
Forwards []string
|
||||
StrategyConfig rule.StrategyConfig
|
||||
Strategy rule.Strategy
|
||||
|
||||
RuleFiles []string
|
||||
RulesDir string
|
||||
@ -39,20 +43,35 @@ 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.StrategyConfig.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||
flag.StringVar(&conf.StrategyConfig.CheckWebSite, "checkwebsite", "www.apple.com", "fowarder check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80")
|
||||
flag.IntVar(&conf.StrategyConfig.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
||||
flag.IntVar(&conf.StrategyConfig.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
||||
flag.IntVar(&conf.StrategyConfig.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
||||
flag.BoolVar(&conf.StrategyConfig.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
||||
flag.IntVar(&conf.StrategyConfig.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
||||
flag.IntVar(&conf.StrategyConfig.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
||||
flag.IntVar(&conf.StrategyConfig.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
||||
flag.StringVar(&conf.StrategyConfig.IntFace, "interface", "", "source ip or source interface")
|
||||
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)")
|
||||
flag.IntVar(&conf.Strategy.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
||||
flag.StringVar(&conf.Strategy.IntFace, "interface", "", "source ip or source interface")
|
||||
|
||||
flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path")
|
||||
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
||||
@ -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,221 +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 tcptun udptun tls ws unix kcp\n")
|
||||
fmt.Fprintf(w, " forward: reject ss socks4 socks5 http ssr ssh vless vmess trojan trojanc 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: DUMMY\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]\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]\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, "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, "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 socks5://user1:pass1@:1080 -verbose\n")
|
||||
fmt.Fprintf(w, " -listen on :1080 as a socks5 proxy server, enable authentication.\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 redir://:1081 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(w, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, " "+app+" -listen redir://:1081 -forward \"ssr://method:pass@1.1.1.1:8444?protocol=a&protocol_param=b&obfs=c&obfs_param=d\"\n")
|
||||
fmt.Fprintf(w, " -listen on :1081 as a transparent redirect server, forward all requests via remote ssr server.\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, " "+app+" -listen redir://:1081 -forward \"tls://abc.com:443,vmess://security:uuid@?alterID=10\"\n")
|
||||
fmt.Fprintf(w, " -listen on :1081 as a transparent redirect server, forward all requests via remote tls+vmess server.\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, " "+app+" -listen redir://:1081 -forward \"ws://1.1.1.1:80,vmess://security:uuid@?alterID=10\"\n")
|
||||
fmt.Fprintf(w, " -listen on :1081 as a transparent redirect server, forward all requests via remote ws+vmess server.\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, " "+app+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\n")
|
||||
fmt.Fprintf(w, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, " "+app+" -listen udptun://:53=8.8.8.8:53 -forward ss://method:pass@1.1.1.1:8443\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+" -listen redir://:1081 -dns=:53 -dnsserver=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2\n")
|
||||
fmt.Fprintf(w, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\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+" -verbose -dns=:53 -dnsserver=8.8.8.8:53 -dnsrecord=www.example.com/1.2.3.4\n")
|
||||
fmt.Fprintf(w, " -listen on :53 as dns server, forward dns requests to 8.8.8.8:53, return 1.2.3.4 when resolving www.example.com.\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(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.
|
||||
`
|
||||
|
@ -4,7 +4,15 @@ Command:
|
||||
```bash
|
||||
glider -config glider.conf
|
||||
```
|
||||
Config file, **just use the command line flag name as the key name**:
|
||||
Config file, **just use the command line flag name as key name**:
|
||||
```bash
|
||||
# COMMENT LINE
|
||||
KEY=VALUE
|
||||
KEY=VALUE
|
||||
# KEY equals to command line flag name: listen forward strategy...
|
||||
```
|
||||
|
||||
Example:
|
||||
```bash
|
||||
### glider config file
|
||||
|
||||
@ -23,13 +31,11 @@ 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
|
||||
|
||||
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||
# will be set to disabled.
|
||||
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
|
||||
# check interval
|
||||
checkinterval=30
|
||||
@ -50,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**:
|
||||
@ -61,7 +67,7 @@ forward=socks5://192.168.1.10:1080
|
||||
forward=ss://method:pass@1.1.1.1:8443
|
||||
forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
# DNS SERVER for domains in this rule file
|
||||
|
@ -12,10 +12,8 @@ forward=http://1.1.1.1:8080
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||
# will be set to disabled.
|
||||
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
|
||||
# check interval(seconds)
|
||||
checkinterval=30
|
||||
|
@ -8,7 +8,8 @@ forward=http://1.1.1.1:8080
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
|
||||
|
@ -13,7 +13,8 @@ forward=http://1.1.1.1:8080
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
|
||||
|
||||
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
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
|
||||
|
@ -10,7 +10,8 @@ forward=http://forwarder2:8080,socks5://forwarder3:1080
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
|
||||
|
@ -11,7 +11,6 @@ forward=http://1.1.1.1:8080
|
||||
dns=:5353
|
||||
dnsserver=8.8.8.8:53
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
```
|
||||
|
||||
|
@ -12,5 +12,6 @@ dnsserver=8.8.8.8:53
|
||||
|
||||
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
@ -38,8 +38,7 @@ rules-dir=rules.d
|
||||
forward=http://forwarder1:8080,socks5://forwarder2:1080
|
||||
forward=http://1.1.1.1:8080
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
checkinterval=30
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
|
||||
# specify a different dns server(if need)
|
||||
dnsserver=208.67.222.222:53
|
||||
@ -83,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:
|
||||
@ -92,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,16 +1,17 @@
|
||||
|
||||
|
||||
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
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
# as a ipset manager
|
||||
|
@ -9,7 +9,8 @@ forward=http://forwarder2:8080,socks5://forwarder3:1080
|
||||
# Round Robin mode: rr
|
||||
# High Availability mode: ha
|
||||
strategy=rr
|
||||
checkwebsite=www.apple.com
|
||||
# forwarder health check
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
# specify a different dns server(if need)
|
||||
|
@ -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,11 +52,8 @@ listen=socks5://:1080
|
||||
# listen on 1081 as a linux transparent proxy server.
|
||||
# listen=redir://:1081
|
||||
|
||||
# listen on 1082 as a tcp tunnel, all requests to :1082 will be forward to 1.1.1.1:80
|
||||
# listen=tcptun://:1082=1.1.1.1:80
|
||||
|
||||
# listen on 1083 as a udp tunnel, all requests to :1083 will be forward to 1.1.1.1:53
|
||||
# listen=udptun://:1083=1.1.1.1:53
|
||||
# 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://
|
||||
@ -64,10 +62,13 @@ 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,socks5://
|
||||
# listen=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,socks5://
|
||||
|
||||
# vless server
|
||||
# listen=vless://UUID@:1234
|
||||
@ -79,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
|
||||
# ----------
|
||||
@ -112,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
|
||||
@ -128,33 +130,33 @@ 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@
|
||||
|
||||
# ss over kcp
|
||||
# forward=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||
# forward=kcp://aes:key@127.0.0.1:8444?dataShards=10&parityShards=3&mode=fast,ss://AEAD_CHACHA20_POLY1305:pass@
|
||||
|
||||
# ss with simple-obfs
|
||||
# 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
|
||||
# ---------------
|
||||
@ -192,10 +194,14 @@ maxfailures=3
|
||||
# ----------------
|
||||
# We can check whether a forwarder is available.
|
||||
|
||||
# Used to connect via forwarders, if the host is unreachable, the forwarder
|
||||
# will be set to disabled.
|
||||
# MUST be a HTTP website server address, format: HOST[:PORT]. HTTPS NOT SUPPORTED.
|
||||
checkwebsite=www.apple.com
|
||||
# Forwarder health check:
|
||||
# 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]
|
||||
# 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
|
||||
|
||||
# check interval(seconds)
|
||||
checkinterval=30
|
||||
@ -206,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
|
||||
|
||||
@ -214,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
|
||||
@ -237,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
|
||||
|
@ -14,7 +14,7 @@ forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080
|
||||
strategy=rr
|
||||
|
||||
# FORWARDER CHECK SETTINGS
|
||||
checkwebsite=www.apple.com
|
||||
check=http://www.msftconnecttest.com/connecttest.txt#expect=200
|
||||
checkinterval=30
|
||||
|
||||
# DNS SERVER for domains in this rule file
|
||||
@ -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
|
||||
|
@ -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)
|
||||
|
||||
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 {
|
||||
if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
|
||||
for _, h := range c.handlers {
|
||||
h(resp.Question.QNAME, answer.IP)
|
||||
}
|
||||
if answer.IP != "" {
|
||||
ips = append(ips, answer.IP)
|
||||
ips = append(ips, answer.IP.String())
|
||||
}
|
||||
if answer.TTL != 0 {
|
||||
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,15 +286,18 @@ 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
|
||||
}
|
||||
|
||||
wb := pool.GetWriteBuffer()
|
||||
defer pool.PutWriteBuffer(wb)
|
||||
wb := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(wb)
|
||||
|
||||
_, err = m.MarshalTo(wb)
|
||||
if err != nil {
|
||||
@ -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
|
||||
|
@ -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,7 +36,7 @@ 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:
|
||||
@ -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,7 +163,7 @@ 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
|
||||
@ -183,7 +181,6 @@ func UnmarshalMessage(b []byte) (*Message, error) {
|
||||
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
// | 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,7 +242,7 @@ 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:
|
||||
@ -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,8 +318,8 @@ 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.
|
||||
@ -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,16 +9,18 @@ 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"
|
||||
_ "github.com/nadoo/glider/proxy/ssh"
|
||||
_ "github.com/nadoo/glider/proxy/ssr"
|
||||
_ "github.com/nadoo/glider/proxy/tcptun"
|
||||
_ "github.com/nadoo/glider/proxy/tcp"
|
||||
_ "github.com/nadoo/glider/proxy/tls"
|
||||
_ "github.com/nadoo/glider/proxy/trojan"
|
||||
_ "github.com/nadoo/glider/proxy/udptun"
|
||||
_ "github.com/nadoo/glider/proxy/udp"
|
||||
_ "github.com/nadoo/glider/proxy/vless"
|
||||
_ "github.com/nadoo/glider/proxy/vmess"
|
||||
_ "github.com/nadoo/glider/proxy/ws"
|
||||
|
@ -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"
|
||||
)
|
||||
|
42
go.mod
42
go.mod
@ -1,25 +1,29 @@
|
||||
module github.com/nadoo/glider
|
||||
|
||||
go 1.15
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/insomniacslk/dhcp v0.0.0-20200922210017-67c425063dca
|
||||
github.com/mzz2017/shadowsocksR v1.0.0
|
||||
github.com/nadoo/conflag v0.2.3
|
||||
github.com/nadoo/go-shadowsocks2 v0.1.2
|
||||
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-20201016220609-9e8e0b390897
|
||||
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 // indirect
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd // indirect
|
||||
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
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/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
|
||||
// github.com/nadoo/go-shadowsocks2 => ../go-shadowsocks2
|
||||
// )
|
||||
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
|
||||
)
|
||||
|
254
go.sum
254
go.sum
@ -1,8 +1,12 @@
|
||||
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-20140412174459-3be6b3054dd1/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4=
|
||||
@ -11,187 +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/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-20200922210017-67c425063dca h1:zhwTlFGM8ZkD5J/c43IWkxSJQWzhm20QWou8zajbCck=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20200922210017-67c425063dca/go.mod h1:R91D4vf3FqLtuRO/oJhs/SMUEqc33OSVsUEI88JuZCc=
|
||||
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-20200726165900-d699427278d3/go.mod h1:HrWYfaMfyH5ODyA6hVxnedRaY7Jr4ctlyZf1xJt8gsw=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.2/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.4 h1:EBfaK0SWSwk+fgk6efYFWdzl8MwRWoOO1gkmiaTXPW4=
|
||||
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.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY=
|
||||
github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/klauspost/reedsolomon v1.9.6/go.mod h1:+8WD025Xpby8/kG5h/HDPIFhiiuGEtZOKw+5Y4drAD8=
|
||||
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/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/mzz2017/shadowsocksR v0.0.0-20200126130347-721f53a7b15a/go.mod h1:1SJEvxD2Y+N7SK2NpCC4wSatvfGGTUo2rhPdthUFsCU=
|
||||
github.com/mzz2017/shadowsocksR v1.0.0 h1:F/CdugIPUJYasqsRK4qWTo+8/mJgZHGXkwXhf67zJx0=
|
||||
github.com/mzz2017/shadowsocksR v1.0.0/go.mod h1:5A4hA1y7oP4SoAqcc7gZvjFF63KpKpI5aCUIMrXV1UI=
|
||||
github.com/nadoo/conflag v0.2.2 h1:xywuyaevdBnA3+4g9S11ng+Nby725WN1LXargWnAXpM=
|
||||
github.com/nadoo/conflag v0.2.2/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
|
||||
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/glider v0.9.2/go.mod h1:S/94KRJFNtgoNlyEm4+33f/DrEsj/uxvismOW4FlIa0=
|
||||
github.com/nadoo/glider v0.10.0/go.mod h1:q5d4Q5yoGk3nLAhshDJalnl0NXJ8Xh4ODEgp+qbdAAg=
|
||||
github.com/nadoo/go-shadowsocks2 v0.1.2 h1:+tCSt65YAAMf24wj3tqv6a9oVBcqSGFYVsifBZwT9w8=
|
||||
github.com/nadoo/go-shadowsocks2 v0.1.2/go.mod h1:/E2kSkS0mqF/e79wcAA0PezoWXk4CY9HldJlzwWtbwU=
|
||||
github.com/nadoo/ipset v0.3.0 h1:TgULgp4s2PI3ItoCykDzMp8R49fRhMUNoUUEahERr5o=
|
||||
github.com/nadoo/ipset v0.3.0/go.mod h1:ugJe3mH5N1UNQbXayGJnLEMALeiwCJYo49Wg4MnZTHU=
|
||||
github.com/nadoo/shadowsocksR v0.1.0 h1:sYPxZi0l8F1nxDDcckzb0DHXxhe0LNW5iSeohqPw6Fg=
|
||||
github.com/nadoo/shadowsocksR v0.1.0/go.mod h1:nqcLRU7laARXdLLBrHP8odruT/6GIureicuRTs4R+RY=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
|
||||
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/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
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.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
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.4.11+incompatible h1:tJbtarpmOoOD74cZ41uvvF5Hyt1nvctHQCOxZ6ot5xw=
|
||||
github.com/xtaci/kcp-go v5.4.11+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||
github.com/xtaci/kcp-go/v5 v5.5.12 h1:iALGyvti/oBbl1TbVoUpHEUHCorDEb3tEKl1CPY3KXM=
|
||||
github.com/xtaci/kcp-go/v5 v5.5.12/go.mod h1:H0T/EJ+lPNytnFYsKLH0JHUtiwZjG3KXlTM6c+Q4YUo=
|
||||
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=
|
||||
gitlab.com/yawning/chacha20.git v0.0.0-20190903091407-6d1cb28dc72c h1:yrfrd1u7MWIwWIulet2TZPEkeNQhQ/GcPLdPXgiEEr0=
|
||||
gitlab.com/yawning/chacha20.git v0.0.0-20190903091407-6d1cb28dc72c/go.mod h1:3x6b94nWCP/a2XB/joOPMiGYUBvqbLfeY/BkHLeDs6s=
|
||||
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/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-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191010185427-af544f31c8ac/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/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 h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
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/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-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 h1:pZPp9+iYUqwYKLjht0SDBbRCRK/9gAXDy7pz5fRDpjo=
|
||||
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/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-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/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-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 h1:yi1hN8dcqI9l8klZfy4B8mJvFmmAxJEePIQQFNSd7Cs=
|
||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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 h1:iHhCR0b26amDCiiO+kBguKZom9aMF+NrFxh9zeKR/XU=
|
||||
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-20201023174141-c8cfbd0f21e6 h1:rbvTkL9AkFts1cgI78+gG6Yu1pwaqX6hjSJAatB78E4=
|
||||
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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 != "" {
|
||||
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 {
|
||||
ipset.Add(r.IPSet, ip)
|
||||
addToSet(r.IPSet, ip)
|
||||
}
|
||||
for _, cidr := range r.CIDR {
|
||||
ipset.Add(r.IPSet, 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,26 +5,25 @@ 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.12.1"
|
||||
version = "0.17.0"
|
||||
config = parseConfig()
|
||||
)
|
||||
|
||||
func main() {
|
||||
// global rule proxy
|
||||
pxy := rule.NewProxy(config.Forwards, &config.StrategyConfig, config.rules)
|
||||
pxy := rule.NewProxy(config.Forwards, &config.Strategy, config.rules)
|
||||
|
||||
// ipset manager
|
||||
ipsetM, _ := ipset.NewManager(config.rules)
|
||||
@ -38,8 +37,8 @@ func main() {
|
||||
|
||||
// rules
|
||||
for _, r := range config.rules {
|
||||
for _, domain := range r.Domain {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
24
pkg/pool/bufreader.go
Normal file
24
pkg/pool/bufreader.go
Normal file
@ -0,0 +1,24 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bufReaderPool sync.Pool
|
||||
|
||||
// GetBufReader returns a *bufio.Reader from pool.
|
||||
func GetBufReader(r io.Reader) *bufio.Reader {
|
||||
if v := bufReaderPool.Get(); v != nil {
|
||||
br := v.(*bufio.Reader)
|
||||
br.Reset(r)
|
||||
return br
|
||||
}
|
||||
return bufio.NewReader(r)
|
||||
}
|
||||
|
||||
// PutBufReader puts a *bufio.Reader into pool.
|
||||
func PutBufReader(br *bufio.Reader) {
|
||||
bufReaderPool.Put(br)
|
||||
}
|
23
pkg/pool/bytesbuffer.go
Normal file
23
pkg/pool/bytesbuffer.go
Normal file
@ -0,0 +1,23 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bytesBufPool = sync.Pool{
|
||||
New: func() any { return &bytes.Buffer{} },
|
||||
}
|
||||
|
||||
// GetBytesBuffer returns a bytes.buffer from pool.
|
||||
func GetBytesBuffer() *bytes.Buffer {
|
||||
return bytesBufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
// PutBytesBuffer puts a bytes.buffer into pool.
|
||||
func PutBytesBuffer(buf *bytes.Buffer) {
|
||||
if buf.Cap() <= 64<<10 {
|
||||
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
|
@ -1,25 +0,0 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var writeBufPool = sync.Pool{
|
||||
New: func() interface{} { return &bytes.Buffer{} },
|
||||
}
|
||||
|
||||
// GetWriteBuffer returns a bytes.buffer from pool.
|
||||
func GetWriteBuffer() *bytes.Buffer {
|
||||
return writeBufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
// PutWriteBuffer puts a bytes.buffer into pool.
|
||||
func PutWriteBuffer(buf *bytes.Buffer) {
|
||||
if buf.Cap() > 64<<10 {
|
||||
return
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
writeBufPool.Put(buf)
|
||||
}
|
@ -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.
|
||||
@ -32,17 +32,25 @@ func NewConn(c net.Conn) *Conn {
|
||||
if conn, ok := c.(*Conn); ok {
|
||||
return conn
|
||||
}
|
||||
return &Conn{bufio.NewReader(c), c}
|
||||
return &Conn{pool.GetBufReader(c), c}
|
||||
}
|
||||
|
||||
// Reader returns the internal bufio.Reader.
|
||||
func (c *Conn) Reader() *bufio.Reader { return c.r }
|
||||
func (c *Conn) Read(p []byte) (int, error) { return c.r.Read(p) }
|
||||
|
||||
// Peek returns the next n bytes without advancing the reader.
|
||||
func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) }
|
||||
func (c *Conn) Read(p []byte) (int, error) { return c.r.Read(p) }
|
||||
|
||||
// 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
|
||||
@ -71,6 +79,30 @@ func Relay(left, right net.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy copies from src to dst.
|
||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
dst = underlyingWriter(dst)
|
||||
switch runtime.GOOS {
|
||||
case "linux", "windows", "dragonfly", "freebsd", "solaris":
|
||||
if _, ok := dst.(*net.TCPConn); ok && worthTry(src) {
|
||||
if wt, ok := src.(io.WriterTo); ok {
|
||||
return wt.WriteTo(dst)
|
||||
}
|
||||
if rt, ok := dst.(io.ReaderFrom); ok {
|
||||
return rt.ReadFrom(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
return CopyBuffer(dst, src)
|
||||
}
|
||||
|
||||
func underlyingWriter(c io.Writer) io.Writer {
|
||||
if wrap, ok := c.(*Conn); ok {
|
||||
return wrap.Conn
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func worthTry(src io.Reader) bool {
|
||||
switch v := src.(type) {
|
||||
case *net.TCPConn, *net.UnixConn:
|
||||
@ -90,30 +122,6 @@ func worthTry(src io.Reader) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func underlyingWriter(c io.Writer) io.Writer {
|
||||
if wrap, ok := c.(*Conn); ok {
|
||||
return wrap.Conn
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Copy copies from src to dst.
|
||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
dst = underlyingWriter(dst)
|
||||
switch runtime.GOOS {
|
||||
case "linux", "windows", "dragonfly", "freebsd", "solaris":
|
||||
if _, ok := dst.(*net.TCPConn); ok && worthTry(src) {
|
||||
if wt, ok := src.(io.WriterTo); ok {
|
||||
return wt.WriteTo(dst)
|
||||
}
|
||||
if rt, ok := dst.(io.ReaderFrom); ok {
|
||||
return rt.ReadFrom(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
return CopyBuffer(dst, src)
|
||||
}
|
||||
|
||||
// CopyN copies n bytes (or until an error) from src to dst.
|
||||
func CopyN(dst io.Writer, src io.Reader, n int64) (written int64, err error) {
|
||||
written, err = Copy(dst, io.LimitReader(src, n))
|
||||
@ -167,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,9 +3,15 @@ package proxy
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotSupported indicates that the operation is not supported
|
||||
ErrNotSupported = errors.New("not supported")
|
||||
)
|
||||
|
||||
// Dialer is used to create connection.
|
||||
type Dialer interface {
|
||||
TCPDialer
|
||||
@ -27,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.
|
||||
@ -49,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 {
|
||||
@ -57,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 = d.ip.String() + ":0"
|
||||
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"
|
||||
)
|
||||
|
||||
@ -32,7 +32,9 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := pool.GetWriteBuffer()
|
||||
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.PutWriteBuffer(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, errors.New("http client does not support udp")
|
||||
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()
|
||||
|
@ -1,7 +1,6 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -9,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"
|
||||
)
|
||||
|
||||
@ -23,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()
|
||||
@ -43,16 +42,16 @@ 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
|
||||
}
|
||||
|
||||
@ -114,8 +113,8 @@ func (s *HTTP) servHTTP(req *request, c *proxy.Conn) {
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
buf := pool.GetWriteBuffer()
|
||||
defer pool.PutWriteBuffer(buf)
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
// send request to remote server
|
||||
req.WriteBuf(buf)
|
||||
@ -133,7 +132,9 @@ func (s *HTTP) servHTTP(req *request, c *proxy.Conn) {
|
||||
}
|
||||
}()
|
||||
|
||||
r := bufio.NewReader(rc)
|
||||
r := pool.GetBufReader(rc)
|
||||
defer pool.PutBufReader(r)
|
||||
|
||||
tpr := textproto.NewReader(r)
|
||||
line, err := tpr.ReadLine()
|
||||
if err != nil {
|
||||
|
107
proxy/kcp/kcp.go
107
proxy/kcp/kcp.go
@ -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"
|
||||
)
|
||||
|
||||
@ -25,6 +25,7 @@ type KCP struct {
|
||||
key string
|
||||
crypt string
|
||||
block kcp.BlockCrypt
|
||||
mode string
|
||||
|
||||
dataShards int
|
||||
parityShards int
|
||||
@ -81,6 +82,7 @@ func NewKCP(s string, d proxy.Dialer, p proxy.Proxy) (*KCP, error) {
|
||||
addr: addr,
|
||||
key: key,
|
||||
crypt: crypt,
|
||||
mode: query.Get("mode"),
|
||||
dataShards: int(dataShards),
|
||||
parityShards: int(parityShards),
|
||||
}
|
||||
@ -92,6 +94,10 @@ func NewKCP(s string, d proxy.Dialer, p proxy.Proxy) (*KCP, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if k.mode == "" {
|
||||
k.mode = "fast"
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
@ -137,23 +143,18 @@ 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, ",")
|
||||
|
||||
// prepare transport listener
|
||||
// TODO: check here
|
||||
if len(transport) < 2 {
|
||||
return nil, errors.New("[kcp] malformd listener:" + 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
@ -162,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()
|
||||
@ -176,13 +177,7 @@ func (s *KCP) ListenAndServe() {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: change them to customizable later?
|
||||
c.SetStreamMode(true)
|
||||
c.SetWriteDelay(false)
|
||||
c.SetNoDelay(0, 30, 2, 1)
|
||||
c.SetWindowSize(1024, 1024)
|
||||
c.SetMtu(1350)
|
||||
c.SetACKNoDelay(true)
|
||||
s.setParams(c)
|
||||
|
||||
go s.Serve(c)
|
||||
}
|
||||
@ -190,11 +185,30 @@ func (s *KCP) ListenAndServe() {
|
||||
|
||||
// Serve serves connections.
|
||||
func (s *KCP) Serve(c net.Conn) {
|
||||
// we know the internal server will close the connection after serve
|
||||
// defer c.Close()
|
||||
|
||||
if s.server != nil {
|
||||
s.server.Serve(c)
|
||||
return
|
||||
}
|
||||
|
||||
defer c.Close()
|
||||
|
||||
rc, dialer, err := s.proxy.Dial("tcp", "")
|
||||
if err != nil {
|
||||
log.F("[kcp] %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("[kcp] %s <-> %s", c.RemoteAddr(), dialer.Addr())
|
||||
|
||||
if err = proxy.Relay(c, rc); err != nil {
|
||||
log.F("[kcp] %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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,13 +229,7 @@ func (s *KCP) Dial(network, addr string) (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: change them to customizable later?
|
||||
c.SetStreamMode(true)
|
||||
c.SetWriteDelay(false)
|
||||
c.SetNoDelay(0, 30, 2, 1)
|
||||
c.SetWindowSize(1024, 1024)
|
||||
c.SetMtu(1350)
|
||||
c.SetACKNoDelay(true)
|
||||
s.setParams(c)
|
||||
|
||||
c.SetDSCP(0)
|
||||
c.SetReadBuffer(4194304)
|
||||
@ -231,6 +239,43 @@ 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, errors.New("kcp client does not support udp now")
|
||||
func (s *KCP) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *KCP) setParams(c *kcp.UDPSession) {
|
||||
// TODO: change them to customizable later?
|
||||
c.SetStreamMode(true)
|
||||
c.SetWriteDelay(false)
|
||||
|
||||
switch s.mode {
|
||||
case "normal":
|
||||
c.SetNoDelay(0, 40, 2, 1)
|
||||
case "fast":
|
||||
c.SetNoDelay(0, 30, 2, 1)
|
||||
case "fast2":
|
||||
c.SetNoDelay(1, 20, 2, 1)
|
||||
case "fast3":
|
||||
c.SetNoDelay(1, 10, 2, 1)
|
||||
default:
|
||||
log.F("[kcp] unkonw mode: %s, use fast mode instead", s.mode)
|
||||
c.SetNoDelay(0, 30, 2, 1)
|
||||
}
|
||||
|
||||
c.SetWindowSize(1024, 1024)
|
||||
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
|
||||
conn := proxy.NewConn(c)
|
||||
if head, err := conn.Peek(1); err == nil {
|
||||
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)
|
||||
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
|
||||
@ -43,8 +43,8 @@ func (p *HTTPObfs) NewConn(c net.Conn) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (c *HTTPObfsConn) writeHeader() (int, error) {
|
||||
buf := pool.GetWriteBuffer()
|
||||
defer pool.PutWriteBuffer(buf)
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
buf.WriteString("GET " + c.obfsURI + " HTTP/1.1\r\n")
|
||||
buf.WriteString("Host: " + c.obfsHost + "\r\n")
|
||||
|
@ -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, errors.New("obfs client does not support udp now")
|
||||
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,8 +1,7 @@
|
||||
// https://www.ietf.org/rfc/rfc5246.txt
|
||||
// https://www.rfc-editor.org/rfc/rfc5246
|
||||
// https://golang.org/src/crypto/tls/handshake_messages.go
|
||||
|
||||
// NOTE:
|
||||
// https://github.com/shadowsocks/simple-obfs/blob/master/src/obfs_tls.c
|
||||
// The official obfs-server only checks 6 static bytes of client hello packet,
|
||||
// so if we send a malformed packet, e.g: set a wrong length number of extensions,
|
||||
// obfs-server will treat it as a correct packet, but in wireshak, it's malformed.
|
||||
@ -18,7 +17,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/pool"
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,18 +42,13 @@ type TLSObfsConn struct {
|
||||
net.Conn
|
||||
reqSent bool
|
||||
reader *bufio.Reader
|
||||
buf []byte
|
||||
buf [lenSize]byte
|
||||
leftBytes int
|
||||
}
|
||||
|
||||
// NewConn returns a new obfs connection
|
||||
func (p *TLSObfs) NewConn(c net.Conn) (net.Conn, error) {
|
||||
cc := &TLSObfsConn{
|
||||
Conn: c,
|
||||
TLSObfs: p,
|
||||
buf: make([]byte, lenSize),
|
||||
}
|
||||
|
||||
cc := &TLSObfsConn{Conn: c, TLSObfs: p}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
@ -64,16 +58,13 @@ func (c *TLSObfsConn) Write(b []byte) (int, error) {
|
||||
return c.handshake(b)
|
||||
}
|
||||
|
||||
buf := pool.GetWriteBuffer()
|
||||
defer pool.PutWriteBuffer(buf)
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
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])))
|
||||
@ -129,10 +120,18 @@ func (c *TLSObfsConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
||||
buf := pool.GetWriteBuffer()
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
bufExt := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(bufExt)
|
||||
|
||||
bufHello := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(bufHello)
|
||||
|
||||
// prepare extension & clientHello content
|
||||
bufExt, bufHello := extension(b, c.obfsHost), clientHello()
|
||||
extension(b, c.obfsHost, bufExt)
|
||||
clientHello(bufHello)
|
||||
|
||||
// prepare lengths
|
||||
extLen := bufExt.Len()
|
||||
@ -167,7 +166,7 @@ func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
||||
buf.Write(bufExt.Bytes())
|
||||
|
||||
_, err := c.Conn.Write(buf.Bytes())
|
||||
pool.PutWriteBuffer(buf)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -175,9 +174,7 @@ func (c *TLSObfsConn) handshake(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func clientHello() *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
|
||||
func clientHello(buf *bytes.Buffer) {
|
||||
// Version: TLS 1.2 (0x0303)
|
||||
buf.Write([]byte{0x03, 0x03})
|
||||
|
||||
@ -188,7 +185,7 @@ func clientHello() *bytes.Buffer {
|
||||
// clients do not send current time, and server do not check it,
|
||||
// golang tls client and chrome browser send random bytes instead.
|
||||
//
|
||||
binary.Write(&buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||
binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix()))
|
||||
random := make([]byte, 28)
|
||||
// The above 2 lines of codes was added to make it compatible with some server implementation,
|
||||
// if we don't need the compatibility, just use the following code instead.
|
||||
@ -206,7 +203,7 @@ func clientHello() *bytes.Buffer {
|
||||
|
||||
// https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L57
|
||||
// Cipher Suites Length: 56
|
||||
binary.Write(&buf, binary.BigEndian, uint16(56))
|
||||
binary.Write(buf, binary.BigEndian, uint16(56))
|
||||
// Cipher Suites (28 suites)
|
||||
buf.Write([]byte{
|
||||
0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f,
|
||||
@ -219,44 +216,40 @@ func clientHello() *bytes.Buffer {
|
||||
buf.WriteByte(0x01)
|
||||
// Compression Methods (1 method)
|
||||
buf.WriteByte(0x00)
|
||||
|
||||
return &buf
|
||||
}
|
||||
|
||||
func extension(b []byte, server string) *bytes.Buffer {
|
||||
var buf bytes.Buffer
|
||||
|
||||
func extension(b []byte, server string, buf *bytes.Buffer) {
|
||||
// Extension: SessionTicket TLS
|
||||
buf.Write([]byte{0x00, 0x23}) // type
|
||||
// NOTE: send some data in sessionticket, the server will treat it as data too
|
||||
binary.Write(&buf, binary.BigEndian, uint16(len(b))) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b))) // length
|
||||
buf.Write(b)
|
||||
|
||||
// Extension: server_name
|
||||
buf.Write([]byte{0x00, 0x00}) // type
|
||||
binary.Write(&buf, binary.BigEndian, uint16(len(server)+5)) // length
|
||||
binary.Write(&buf, binary.BigEndian, uint16(len(server)+3)) // Server Name list length
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) // Server Name list length
|
||||
buf.WriteByte(0x00) // Server Name Type: host_name (0)
|
||||
binary.Write(&buf, binary.BigEndian, uint16(len(server))) // Server Name length
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(server))) // Server Name length
|
||||
buf.WriteString(server)
|
||||
|
||||
// https://github.com/shadowsocks/simple-obfs/blob/7659eeccf473aa41eb294e92c32f8f60a8747325/src/obfs_tls.c#L88
|
||||
// Extension: ec_point_formats (len=4)
|
||||
buf.Write([]byte{0x00, 0x0b}) // type
|
||||
binary.Write(&buf, binary.BigEndian, uint16(4)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(4)) // length
|
||||
buf.WriteByte(0x03) // format length
|
||||
buf.Write([]byte{0x01, 0x00, 0x02})
|
||||
|
||||
// Extension: supported_groups (len=10)
|
||||
buf.Write([]byte{0x00, 0x0a}) // type
|
||||
binary.Write(&buf, binary.BigEndian, uint16(10)) // length
|
||||
binary.Write(&buf, binary.BigEndian, uint16(8)) // Supported Groups List Length: 8
|
||||
binary.Write(buf, binary.BigEndian, uint16(10)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(8)) // Supported Groups List Length: 8
|
||||
buf.Write([]byte{0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18})
|
||||
|
||||
// Extension: signature_algorithms (len=32)
|
||||
buf.Write([]byte{0x00, 0x0d}) // type
|
||||
binary.Write(&buf, binary.BigEndian, uint16(32)) // length
|
||||
binary.Write(&buf, binary.BigEndian, uint16(30)) // Signature Hash Algorithms Length: 30
|
||||
binary.Write(buf, binary.BigEndian, uint16(32)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(30)) // Signature Hash Algorithms Length: 30
|
||||
buf.Write([]byte{
|
||||
0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02,
|
||||
0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
|
||||
@ -264,11 +257,9 @@ func extension(b []byte, server string) *bytes.Buffer {
|
||||
|
||||
// Extension: encrypt_then_mac (len=0)
|
||||
buf.Write([]byte{0x00, 0x16}) // type
|
||||
binary.Write(&buf, binary.BigEndian, uint16(0)) // length
|
||||
binary.Write(buf, binary.BigEndian, uint16(0)) // length
|
||||
|
||||
// Extension: extended_master_secret (len=0)
|
||||
buf.Write([]byte{0x00, 0x17}) // type
|
||||
binary.Write(&buf, binary.BigEndian, uint16(0)) // length
|
||||
|
||||
return &buf
|
||||
binary.Write(buf, binary.BigEndian, uint16(0)) // length
|
||||
}
|
||||
|
@ -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, 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 }
|
@ -1,16 +1,14 @@
|
||||
// getOrigDst:
|
||||
// https://github.com/shadowsocks/go-shadowsocks2/blob/master/tcp_linux.go#L30
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
@ -58,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
|
||||
}
|
||||
|
||||
@ -86,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
|
||||
@ -117,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)
|
||||
@ -134,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,10 +3,11 @@ package proxy
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server interface
|
||||
// Server interface.
|
||||
type Server interface {
|
||||
// ListenAndServe sets up a listener and serve on it
|
||||
ListenAndServe()
|
||||
@ -15,22 +16,27 @@ type Server interface {
|
||||
Serve(c net.Conn)
|
||||
}
|
||||
|
||||
// ServerCreator is a function to create proxy servers
|
||||
// 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)
|
||||
|
||||
var (
|
||||
serverCreators = make(map[string]ServerCreator)
|
||||
)
|
||||
|
||||
// RegisterServer is used to register a proxy server
|
||||
// RegisterServer is used to register a proxy server.
|
||||
func RegisterServer(name string, c ServerCreator) {
|
||||
serverCreators[strings.ToLower(name)] = c
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// ServerFromURL calls the registered creator to create proxy servers.
|
||||
// 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"
|
||||
)
|
||||
|
||||
@ -27,10 +27,12 @@ const (
|
||||
type SOCKS4 struct {
|
||||
dialer proxy.Dialer
|
||||
addr string
|
||||
socks4a bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("socks4", NewSocks4Dialer)
|
||||
proxy.RegisterDialer("socks4a", NewSocks4Dialer)
|
||||
}
|
||||
|
||||
// NewSOCKS4 returns a socks4 proxy.
|
||||
@ -44,6 +46,7 @@ func NewSOCKS4(s string, dialer proxy.Dialer) (*SOCKS4, error) {
|
||||
h := &SOCKS4{
|
||||
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, errors.New("[socks4] DialUDP are not supported by Socks4")
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// taken from https://github.com/h12w/socks/blob/master/socks.go
|
||||
buf := []byte{
|
||||
}
|
||||
} else {
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
return errors.New("[socks4] IPv6 is not supported by socks4")
|
||||
}
|
||||
}
|
||||
// taken from https://github.com/h12w/socks/blob/master/socks.go and https://en.wikipedia.org/wiki/SOCKS
|
||||
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,37 +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 = 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())
|
||||
}
|
||||
|
||||
buf := pool.GetWriteBuffer()
|
||||
defer pool.PutWriteBuffer(buf)
|
||||
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
|
||||
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 {
|
||||
session = v.(*Session)
|
||||
}
|
||||
|
||||
lpc, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
||||
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)
|
||||
continue
|
||||
nm.Delete(session.key)
|
||||
return
|
||||
}
|
||||
|
||||
pc = NewPktConn(lpc, nextHop, nil, false, nil)
|
||||
nm.Store(raddr.String(), pc)
|
||||
defer dstPC.Close()
|
||||
|
||||
go func() {
|
||||
proxy.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||
pc.Close()
|
||||
nm.Delete(raddr.String())
|
||||
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", raddr, c.tgtAddr, nextHop)
|
||||
log.F("[socks5u] %s <-> %s via %s", session.src, session.srcPC.target, dialer.Addr())
|
||||
|
||||
} else {
|
||||
pc = v.(*PktConn)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||
for {
|
||||
select {
|
||||
case msg := <-session.msgCh:
|
||||
_, err = dstPC.WriteTo(msg.msg, msg.dst)
|
||||
if err != nil {
|
||||
log.F("[socks5u] remote write error: %v", err)
|
||||
continue
|
||||
log.F("[socks5u] writeTo %s error: %v", msg.dst, err)
|
||||
}
|
||||
pool.PutBuffer(msg.msg)
|
||||
msg.msg = nil
|
||||
case <-session.finCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log.F("[socks5u] %s <-> %s", raddr, c.tgtAddr)
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
143
proxy/ss/cipher/cipher.go
Normal file
143
proxy/ss/cipher/cipher.go
Normal file
@ -0,0 +1,143 @@
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ss/cipher/shadowaead"
|
||||
"github.com/nadoo/glider/proxy/ss/cipher/shadowstream"
|
||||
)
|
||||
|
||||
// Cipher interface.
|
||||
type Cipher interface {
|
||||
StreamConnCipher
|
||||
PacketConnCipher
|
||||
}
|
||||
|
||||
// StreamConnCipher is the stream connection cipher.
|
||||
type StreamConnCipher interface {
|
||||
StreamConn(net.Conn) net.Conn
|
||||
}
|
||||
|
||||
// PacketConnCipher is the packet connection cipher.
|
||||
type PacketConnCipher interface {
|
||||
PacketConn(net.PacketConn) net.PacketConn
|
||||
}
|
||||
|
||||
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
|
||||
var ErrCipherNotSupported = errors.New("cipher not supported")
|
||||
|
||||
// List of AEAD ciphers: key size in bytes and constructor
|
||||
var aeadList = map[string]struct {
|
||||
KeySize int
|
||||
New func([]byte) (shadowaead.Cipher, error)
|
||||
}{
|
||||
"AEAD_AES_128_GCM": {16, shadowaead.AESGCM},
|
||||
"AEAD_AES_192_GCM": {24, shadowaead.AESGCM},
|
||||
"AEAD_AES_256_GCM": {32, shadowaead.AESGCM},
|
||||
"AEAD_CHACHA20_POLY1305": {32, shadowaead.Chacha20Poly1305},
|
||||
|
||||
// http://shadowsocks.org/en/spec/AEAD-Ciphers.html
|
||||
// not listed in spec
|
||||
"AEAD_XCHACHA20_POLY1305": {32, shadowaead.XChacha20Poly1305},
|
||||
}
|
||||
|
||||
// List of stream ciphers: key size in bytes and constructor
|
||||
var streamList = map[string]struct {
|
||||
KeySize int
|
||||
New func(key []byte) (shadowstream.Cipher, error)
|
||||
}{
|
||||
"AES-128-CTR": {16, shadowstream.AESCTR},
|
||||
"AES-192-CTR": {24, shadowstream.AESCTR},
|
||||
"AES-256-CTR": {32, shadowstream.AESCTR},
|
||||
"AES-128-CFB": {16, shadowstream.AESCFB},
|
||||
"AES-192-CFB": {24, shadowstream.AESCFB},
|
||||
"AES-256-CFB": {32, shadowstream.AESCFB},
|
||||
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
|
||||
"XCHACHA20": {32, shadowstream.Xchacha20},
|
||||
|
||||
// http://shadowsocks.org/en/spec/Stream-Ciphers.html
|
||||
// marked as "DO NOT USE"
|
||||
"CHACHA20": {32, shadowstream.ChaCha20},
|
||||
"RC4-MD5": {16, shadowstream.RC4MD5},
|
||||
}
|
||||
|
||||
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
|
||||
func PickCipher(name string, key []byte, password string) (Cipher, error) {
|
||||
name = strings.ToUpper(name)
|
||||
|
||||
switch name {
|
||||
case "DUMMY", "NONE":
|
||||
return &dummy{}, nil
|
||||
case "CHACHA20-IETF-POLY1305":
|
||||
name = "AEAD_CHACHA20_POLY1305"
|
||||
case "XCHACHA20-IETF-POLY1305":
|
||||
name = "AEAD_XCHACHA20_POLY1305"
|
||||
case "AES-128-GCM":
|
||||
name = "AEAD_AES_128_GCM"
|
||||
case "AES-192-GCM":
|
||||
name = "AEAD_AES_192_GCM"
|
||||
case "AES-256-GCM":
|
||||
name = "AEAD_AES_256_GCM"
|
||||
}
|
||||
|
||||
if choice, ok := aeadList[name]; ok {
|
||||
if len(key) == 0 {
|
||||
key = kdf(password, choice.KeySize)
|
||||
}
|
||||
if len(key) != choice.KeySize {
|
||||
return nil, shadowaead.KeySizeError(choice.KeySize)
|
||||
}
|
||||
aead, err := choice.New(key)
|
||||
return &aeadCipher{aead}, err
|
||||
}
|
||||
|
||||
if choice, ok := streamList[name]; ok {
|
||||
if len(key) == 0 {
|
||||
key = kdf(password, choice.KeySize)
|
||||
}
|
||||
if len(key) != choice.KeySize {
|
||||
return nil, shadowstream.KeySizeError(choice.KeySize)
|
||||
}
|
||||
ciph, err := choice.New(key)
|
||||
return &streamCipher{ciph}, err
|
||||
}
|
||||
|
||||
return nil, ErrCipherNotSupported
|
||||
}
|
||||
|
||||
type aeadCipher struct{ shadowaead.Cipher }
|
||||
|
||||
func (aead *aeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
|
||||
func (aead *aeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
return shadowaead.NewPacketConn(c, aead)
|
||||
}
|
||||
|
||||
type streamCipher struct{ shadowstream.Cipher }
|
||||
|
||||
func (ciph *streamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
|
||||
func (ciph *streamCipher) PacketConn(c net.PacketConn) net.PacketConn {
|
||||
return shadowstream.NewPacketConn(c, ciph)
|
||||
}
|
||||
|
||||
// dummy cipher does not encrypt
|
||||
type dummy struct{}
|
||||
|
||||
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
|
||||
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
|
||||
|
||||
// key-derivation function from original Shadowsocks
|
||||
func kdf(password string, keyLen int) []byte {
|
||||
var b, prev []byte
|
||||
h := md5.New()
|
||||
for len(b) < keyLen {
|
||||
h.Write(prev)
|
||||
h.Write([]byte(password))
|
||||
b = h.Sum(b)
|
||||
prev = b[len(b)-h.Size():]
|
||||
h.Reset()
|
||||
}
|
||||
return b[:keyLen]
|
||||
}
|
94
proxy/ss/cipher/shadowaead/cipher.go
Normal file
94
proxy/ss/cipher/shadowaead/cipher.go
Normal file
@ -0,0 +1,94 @@
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// Cipher generates a pair of stream ciphers for encryption and decryption.
|
||||
type Cipher interface {
|
||||
KeySize() int
|
||||
SaltSize() int
|
||||
Encrypter(salt []byte) (cipher.AEAD, error)
|
||||
Decrypter(salt []byte) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
// KeySizeError is an error about the key size.
|
||||
type KeySizeError int
|
||||
|
||||
func (e KeySizeError) Error() string {
|
||||
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
||||
}
|
||||
|
||||
func hkdfSHA1(secret, salt, info, outkey []byte) {
|
||||
r := hkdf.New(sha1.New, secret, salt, info)
|
||||
if _, err := io.ReadFull(r, outkey); err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
}
|
||||
|
||||
type metaCipher struct {
|
||||
psk []byte
|
||||
makeAEAD func(key []byte) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
func (a *metaCipher) KeySize() int { return len(a.psk) }
|
||||
func (a *metaCipher) SaltSize() int {
|
||||
if ks := a.KeySize(); ks > 16 {
|
||||
return ks
|
||||
}
|
||||
return 16
|
||||
}
|
||||
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
|
||||
subkey := make([]byte, a.KeySize())
|
||||
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
||||
return a.makeAEAD(subkey)
|
||||
}
|
||||
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
|
||||
subkey := make([]byte, a.KeySize())
|
||||
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
|
||||
return a.makeAEAD(subkey)
|
||||
}
|
||||
|
||||
func aesGCM(key []byte) (cipher.AEAD, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewGCM(blk)
|
||||
}
|
||||
|
||||
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
|
||||
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
|
||||
func AESGCM(psk []byte) (Cipher, error) {
|
||||
switch l := len(psk); l {
|
||||
case 16, 24, 32: // AES 128/196/256
|
||||
default:
|
||||
return nil, aes.KeySizeError(l)
|
||||
}
|
||||
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
|
||||
}
|
||||
|
||||
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
||||
// must be 32.
|
||||
func Chacha20Poly1305(psk []byte) (Cipher, error) {
|
||||
if len(psk) != chacha20poly1305.KeySize {
|
||||
return nil, KeySizeError(chacha20poly1305.KeySize)
|
||||
}
|
||||
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
|
||||
}
|
||||
|
||||
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
|
||||
// must be 32.
|
||||
func XChacha20Poly1305(psk []byte) (Cipher, error) {
|
||||
if len(psk) != chacha20poly1305.KeySize {
|
||||
return nil, KeySizeError(chacha20poly1305.KeySize)
|
||||
}
|
||||
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
|
||||
}
|
67
proxy/ss/cipher/shadowaead/conn.go
Normal file
67
proxy/ss/cipher/shadowaead/conn.go
Normal file
@ -0,0 +1,67 @@
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type streamConn struct {
|
||||
net.Conn
|
||||
Cipher
|
||||
r *reader
|
||||
w *writer
|
||||
}
|
||||
|
||||
// NewConn wraps a stream-oriented net.Conn with cipher.
|
||||
func NewConn(c net.Conn, ciph Cipher) net.Conn { return &streamConn{Conn: c, Cipher: ciph} }
|
||||
|
||||
func (c *streamConn) initReader() error {
|
||||
salt := make([]byte, c.SaltSize())
|
||||
if _, err := io.ReadFull(c.Conn, salt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aead, err := c.Decrypter(salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.r = newReader(c.Conn, aead)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *streamConn) Read(b []byte) (int, error) {
|
||||
if c.r == nil {
|
||||
if err := c.initReader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *streamConn) initWriter() error {
|
||||
salt := make([]byte, c.SaltSize())
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
return err
|
||||
}
|
||||
aead, err := c.Encrypter(salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.Conn.Write(salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.w = newWriter(c.Conn, aead)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *streamConn) Write(b []byte) (int, error) {
|
||||
if c.w == nil {
|
||||
if err := c.initWriter(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.w.Write(b)
|
||||
}
|
97
proxy/ss/cipher/shadowaead/packet.go
Normal file
97
proxy/ss/cipher/shadowaead/packet.go
Normal file
@ -0,0 +1,97 @@
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
|
||||
var ErrShortPacket = errors.New("short packet")
|
||||
|
||||
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
|
||||
|
||||
// Pack encrypts plaintext using Cipher with a randomly generated salt and
|
||||
// returns a slice of dst containing the encrypted packet and any error occurred.
|
||||
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
|
||||
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
|
||||
saltSize := ciph.SaltSize()
|
||||
salt := dst[:saltSize]
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aead, err := ciph.Encrypter(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
|
||||
return dst[:saltSize+len(b)], nil
|
||||
}
|
||||
|
||||
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
|
||||
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
|
||||
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
|
||||
saltSize := ciph.SaltSize()
|
||||
if len(pkt) < saltSize {
|
||||
return nil, ErrShortPacket
|
||||
}
|
||||
salt := pkt[:saltSize]
|
||||
aead, err := ciph.Decrypter(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pkt) < saltSize+aead.Overhead() {
|
||||
return nil, ErrShortPacket
|
||||
}
|
||||
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
|
||||
return b, err
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
Cipher
|
||||
sync.Mutex
|
||||
buf []byte // write lock
|
||||
}
|
||||
|
||||
// NewPacketConn wraps a net.PacketConn with cipher
|
||||
func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn {
|
||||
const maxPacketSize = 64 * 1024
|
||||
return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, maxPacketSize)}
|
||||
}
|
||||
|
||||
// WriteTo encrypts b and write to addr using the embedded PacketConn.
|
||||
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
buf, err := Pack(c.buf, b, c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
// ReadFrom reads from the embedded PacketConn and decrypts into b.
|
||||
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
copy(b, bb)
|
||||
return len(bb), addr, err
|
||||
}
|
150
proxy/ss/cipher/shadowaead/stream.go
Normal file
150
proxy/ss/cipher/shadowaead/stream.go
Normal file
@ -0,0 +1,150 @@
|
||||
// protocol:
|
||||
// format: [encrypted payload length] [Overhead] [encrypted payload] [Overhead]
|
||||
// sizes: 2 bytes, aead.Overhead() bytes, n bytes, aead.Overhead() bytes
|
||||
// max(n): 0x3FFF
|
||||
|
||||
package shadowaead
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
lenSize = 2
|
||||
// max payload size: 16383 from ss-libev aead.c: CHUNK_SIZE_MASK
|
||||
maxPayload = 0x3FFF
|
||||
// buf size, shoud enough to save lenSize + aead.Overhead() + maxPayload + aead.Overhead()
|
||||
bufSize = 17 << 10
|
||||
)
|
||||
|
||||
type writer struct {
|
||||
io.Writer
|
||||
cipher.AEAD
|
||||
nonce [32]byte
|
||||
}
|
||||
|
||||
// NewWriter wraps an io.Writer with AEAD encryption.
|
||||
func NewWriter(w io.Writer, aead cipher.AEAD) io.Writer { return newWriter(w, aead) }
|
||||
|
||||
func newWriter(w io.Writer, aead cipher.AEAD) *writer {
|
||||
return &writer{Writer: w, AEAD: aead}
|
||||
}
|
||||
|
||||
// Write encrypts p and writes to the embedded io.Writer.
|
||||
func (w *writer) Write(p []byte) (n int, err error) {
|
||||
buf := pool.GetBuffer(bufSize)
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
nonce := w.nonce[:w.NonceSize()]
|
||||
encLenSize := lenSize + w.Overhead()
|
||||
for nw := maxPayload; n < len(p); n += nw {
|
||||
if left := len(p) - n; left < maxPayload {
|
||||
nw = left
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nw))
|
||||
w.Seal(buf[:0], nonce, buf[:lenSize], nil)
|
||||
increment(nonce)
|
||||
|
||||
w.Seal(buf[:encLenSize], nonce, p[n:n+nw], nil)
|
||||
increment(nonce)
|
||||
|
||||
if _, err = w.Writer.Write(buf[:encLenSize+nw+w.Overhead()]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
io.Reader
|
||||
cipher.AEAD
|
||||
nonce [32]byte
|
||||
buf []byte
|
||||
offset int
|
||||
}
|
||||
|
||||
// NewReader wraps an io.Reader with AEAD decryption.
|
||||
func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { return newReader(r, aead) }
|
||||
|
||||
func newReader(r io.Reader, aead cipher.AEAD) *reader {
|
||||
return &reader{Reader: r, AEAD: aead}
|
||||
}
|
||||
|
||||
// NOTE: len(p) MUST >= max payload size + AEAD overhead.
|
||||
func (r *reader) read(p []byte) (int, error) {
|
||||
nonce := r.nonce[:r.NonceSize()]
|
||||
|
||||
// read encrypted lenSize + overhead
|
||||
p = p[:lenSize+r.Overhead()]
|
||||
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// decrypt lenSize
|
||||
_, err := r.Open(p[:0], nonce, p, nil)
|
||||
increment(nonce)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// get payload size
|
||||
size := int(binary.BigEndian.Uint16(p[:lenSize]))
|
||||
|
||||
// read encrypted payload + overhead
|
||||
p = p[:size+r.Overhead()]
|
||||
if _, err := io.ReadFull(r.Reader, p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// decrypt payload
|
||||
_, err = r.Open(p[:0], nonce, p, nil)
|
||||
increment(nonce)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (r *reader) Read(p []byte) (int, error) {
|
||||
if r.buf == nil {
|
||||
if len(p) >= maxPayload+r.Overhead() {
|
||||
return r.read(p)
|
||||
}
|
||||
|
||||
buf := pool.GetBuffer(bufSize)
|
||||
n, err := r.read(buf)
|
||||
if err != nil {
|
||||
pool.PutBuffer(buf)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r.buf = buf[:n]
|
||||
r.offset = 0
|
||||
}
|
||||
|
||||
n := copy(p, r.buf[r.offset:])
|
||||
r.offset += n
|
||||
if r.offset == len(r.buf) {
|
||||
pool.PutBuffer(r.buf)
|
||||
r.buf = nil
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
|
||||
func increment(b []byte) {
|
||||
for i := range b {
|
||||
b[i]++
|
||||
if b[i] != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
143
proxy/ss/cipher/shadowstream/cipher.go
Normal file
143
proxy/ss/cipher/shadowstream/cipher.go
Normal file
@ -0,0 +1,143 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rc4"
|
||||
"strconv"
|
||||
|
||||
"github.com/aead/chacha20"
|
||||
"github.com/aead/chacha20/chacha"
|
||||
)
|
||||
|
||||
// Cipher generates a pair of stream ciphers for encryption and decryption.
|
||||
type Cipher interface {
|
||||
IVSize() int
|
||||
Encrypter(iv []byte) cipher.Stream
|
||||
Decrypter(iv []byte) cipher.Stream
|
||||
}
|
||||
|
||||
// KeySizeError is an error about the key size.
|
||||
type KeySizeError int
|
||||
|
||||
func (e KeySizeError) Error() string {
|
||||
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
|
||||
}
|
||||
|
||||
// CTR mode
|
||||
type ctrStream struct{ cipher.Block }
|
||||
|
||||
func (b *ctrStream) IVSize() int { return b.BlockSize() }
|
||||
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
|
||||
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
|
||||
|
||||
// AESCTR returns an aesctr cipher.
|
||||
func AESCTR(key []byte) (Cipher, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctrStream{blk}, nil
|
||||
}
|
||||
|
||||
// CFB mode
|
||||
type cfbStream struct{ cipher.Block }
|
||||
|
||||
func (b *cfbStream) IVSize() int { return b.BlockSize() }
|
||||
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
|
||||
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
|
||||
|
||||
// AESCFB returns an aescfb cipher.
|
||||
func AESCFB(key []byte) (Cipher, error) {
|
||||
blk, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfbStream{blk}, nil
|
||||
}
|
||||
|
||||
// IETF-variant of chacha20
|
||||
type chacha20ietfkey []byte
|
||||
|
||||
func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize }
|
||||
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
|
||||
ciph, err := chacha20.NewCipher(iv, k)
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
return ciph
|
||||
}
|
||||
|
||||
// Chacha20IETF returns a Chacha20IETF cipher.
|
||||
func Chacha20IETF(key []byte) (Cipher, error) {
|
||||
if len(key) != chacha.KeySize {
|
||||
return nil, KeySizeError(chacha.KeySize)
|
||||
}
|
||||
return chacha20ietfkey(key), nil
|
||||
}
|
||||
|
||||
// xchacha20
|
||||
type xchacha20key []byte
|
||||
|
||||
func (k xchacha20key) IVSize() int { return chacha.XNonceSize }
|
||||
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
|
||||
ciph, err := chacha20.NewCipher(iv, k)
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
return ciph
|
||||
}
|
||||
|
||||
// Xchacha20 returns a Xchacha20 cipher.
|
||||
func Xchacha20(key []byte) (Cipher, error) {
|
||||
if len(key) != chacha.KeySize {
|
||||
return nil, KeySizeError(chacha.KeySize)
|
||||
}
|
||||
return xchacha20key(key), nil
|
||||
}
|
||||
|
||||
// chacah20
|
||||
type chacha20key []byte
|
||||
|
||||
func (k chacha20key) IVSize() int { return chacha.NonceSize }
|
||||
func (k chacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||
func (k chacha20key) Encrypter(iv []byte) cipher.Stream {
|
||||
ciph, err := chacha20.NewCipher(iv, k)
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
return ciph
|
||||
}
|
||||
|
||||
// ChaCha20 returns a ChaCha20 cipher.
|
||||
func ChaCha20(key []byte) (Cipher, error) {
|
||||
if len(key) != chacha.KeySize {
|
||||
return nil, KeySizeError(chacha.KeySize)
|
||||
}
|
||||
return chacha20key(key), nil
|
||||
}
|
||||
|
||||
// rc4md5
|
||||
type rc4Md5Key []byte
|
||||
|
||||
func (k rc4Md5Key) IVSize() int { return 16 }
|
||||
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
|
||||
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
|
||||
h := md5.New()
|
||||
h.Write([]byte(k))
|
||||
h.Write(iv)
|
||||
rc4key := h.Sum(nil)
|
||||
ciph, err := rc4.NewCipher(rc4key)
|
||||
if err != nil {
|
||||
panic(err) // should never happen
|
||||
}
|
||||
return ciph
|
||||
}
|
||||
|
||||
// RC4MD5 returns a RC4MD5 cipher.
|
||||
func RC4MD5(key []byte) (Cipher, error) {
|
||||
return rc4Md5Key(key), nil
|
||||
}
|
62
proxy/ss/cipher/shadowstream/conn.go
Normal file
62
proxy/ss/cipher/shadowstream/conn.go
Normal file
@ -0,0 +1,62 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
net.Conn
|
||||
Cipher
|
||||
r *reader
|
||||
w *writer
|
||||
}
|
||||
|
||||
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
|
||||
func NewConn(c net.Conn, ciph Cipher) net.Conn {
|
||||
return &conn{Conn: c, Cipher: ciph}
|
||||
}
|
||||
|
||||
func (c *conn) initReader() error {
|
||||
if c.r == nil {
|
||||
iv := make([]byte, c.IVSize())
|
||||
if _, err := io.ReadFull(c.Conn, iv); err != nil {
|
||||
return err
|
||||
}
|
||||
c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (int, error) {
|
||||
if c.r == nil {
|
||||
if err := c.initReader(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *conn) initWriter() error {
|
||||
if c.w == nil {
|
||||
iv := make([]byte, c.IVSize())
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.Conn.Write(iv); err != nil {
|
||||
return err
|
||||
}
|
||||
c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (int, error) {
|
||||
if c.w == nil {
|
||||
if err := c.initWriter(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.w.Write(b)
|
||||
}
|
80
proxy/ss/cipher/shadowstream/packet.go
Normal file
80
proxy/ss/cipher/shadowstream/packet.go
Normal file
@ -0,0 +1,80 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
|
||||
var ErrShortPacket = errors.New("short packet")
|
||||
|
||||
// Pack encrypts plaintext using stream cipher s and a random IV.
|
||||
// Returns a slice of dst containing random IV and ciphertext.
|
||||
// Ensure len(dst) >= s.IVSize() + len(plaintext).
|
||||
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
|
||||
if len(dst) < s.IVSize()+len(plaintext) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
iv := dst[:s.IVSize()]
|
||||
_, err := io.ReadFull(rand.Reader, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
|
||||
return dst[:len(iv)+len(plaintext)], nil
|
||||
}
|
||||
|
||||
// Unpack decrypts pkt using stream cipher s.
|
||||
// Returns a slice of dst containing decrypted plaintext.
|
||||
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
|
||||
if len(pkt) < s.IVSize() {
|
||||
return nil, ErrShortPacket
|
||||
}
|
||||
|
||||
if len(dst) < len(pkt)-s.IVSize() {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
iv := pkt[:s.IVSize()]
|
||||
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
|
||||
return dst[:len(pkt)-len(iv)], nil
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
Cipher
|
||||
buf []byte
|
||||
sync.Mutex // write lock
|
||||
}
|
||||
|
||||
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
|
||||
func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn {
|
||||
return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, 64*1024)}
|
||||
}
|
||||
|
||||
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
buf, err := Pack(c.buf, b, c.Cipher)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = c.PacketConn.WriteTo(buf, addr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.PacketConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
|
||||
if err != nil {
|
||||
return n, addr, err
|
||||
}
|
||||
copy(b, bb)
|
||||
return len(bb), addr, err
|
||||
}
|
55
proxy/ss/cipher/shadowstream/stream.go
Normal file
55
proxy/ss/cipher/shadowstream/stream.go
Normal file
@ -0,0 +1,55 @@
|
||||
package shadowstream
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"io"
|
||||
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
)
|
||||
|
||||
const bufSize = 32 * 1024
|
||||
|
||||
type writer struct {
|
||||
io.Writer
|
||||
cipher.Stream
|
||||
}
|
||||
|
||||
// NewWriter wraps an io.Writer with stream cipher encryption.
|
||||
func NewWriter(w io.Writer, s cipher.Stream) io.Writer {
|
||||
return &writer{Writer: w, Stream: s}
|
||||
}
|
||||
|
||||
func (w *writer) Write(p []byte) (n int, err error) {
|
||||
buf := pool.GetBuffer(bufSize)
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
for nw := 0; n < len(p) && err == nil; n += nw {
|
||||
end := n + len(buf)
|
||||
if end > len(p) {
|
||||
end = len(p)
|
||||
}
|
||||
w.XORKeyStream(buf, p[n:end])
|
||||
nw, err = w.Writer.Write(buf[:end-n])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
io.Reader
|
||||
cipher.Stream
|
||||
}
|
||||
|
||||
// NewReader wraps an io.Reader with stream cipher decryption.
|
||||
func NewReader(r io.Reader, s cipher.Stream) io.Reader {
|
||||
return &reader{Reader: r, Stream: s}
|
||||
}
|
||||
|
||||
func (r *reader) Read(p []byte) (int, error) {
|
||||
n, err := r.Reader.Read(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
p = p[:n]
|
||||
r.XORKeyStream(p, p)
|
||||
return n, nil
|
||||
}
|
@ -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.
|
||||
@ -42,17 +42,22 @@ func (s *SS) Dial(network, addr string) (net.Conn, error) {
|
||||
}
|
||||
|
||||
return c, err
|
||||
|
||||
}
|
||||
|
||||
// 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,76 +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 = 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())
|
||||
}
|
||||
|
||||
buf := pool.GetWriteBuffer()
|
||||
defer pool.PutWriteBuffer(buf)
|
||||
if target == nil {
|
||||
return 0, errors.New("invalid addr")
|
||||
}
|
||||
|
||||
tgtLen, _ := buf.Write(pc.tgtAddr)
|
||||
buf := pool.GetBytesBuffer()
|
||||
defer pool.PutBytesBuffer(buf)
|
||||
|
||||
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, nextHop, err := s.proxy.DialUDP("udp", c.tgtAddr.String())
|
||||
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 {
|
||||
session = v.(*Session)
|
||||
}
|
||||
|
||||
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)
|
||||
continue
|
||||
nm.Delete(session.key)
|
||||
return
|
||||
}
|
||||
|
||||
pc = NewPktConn(lpc, nextHop, nil, false)
|
||||
nm.Store(raddr.String(), pc)
|
||||
defer dstPC.Close()
|
||||
|
||||
go func() {
|
||||
proxy.RelayUDP(c, raddr, pc, 2*time.Minute)
|
||||
pc.Close()
|
||||
nm.Delete(raddr.String())
|
||||
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", raddr, c.tgtAddr, nextHop)
|
||||
log.F("[ssu] %s <-> %s via %s", session.src, session.dst, dialer.Addr())
|
||||
|
||||
} else {
|
||||
pc = v.(*PktConn)
|
||||
}
|
||||
|
||||
_, err = pc.WriteTo(buf[:n], pc.writeAddr)
|
||||
for {
|
||||
select {
|
||||
case msg := <-session.msgCh:
|
||||
_, err = dstPC.WriteTo(msg.msg, msg.dst)
|
||||
if err != nil {
|
||||
log.F("[ssu] remote write error: %v", err)
|
||||
continue
|
||||
log.F("[ssu] writeTo %s error: %v", msg.dst, err)
|
||||
}
|
||||
pool.PutBuffer(msg.msg)
|
||||
msg.msg = nil
|
||||
case <-session.finCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log.F("[ssu] %s <-> %s", raddr, c.tgtAddr)
|
||||
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{})}
|
||||
}
|
||||
|
@ -2,12 +2,10 @@ package ss
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/go-shadowsocks2/core"
|
||||
|
||||
"github.com/nadoo/glider/log"
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
"github.com/nadoo/glider/proxy/ss/cipher"
|
||||
)
|
||||
|
||||
// SS is a base ss struct.
|
||||
@ -16,7 +14,7 @@ type SS struct {
|
||||
proxy proxy.Proxy
|
||||
addr string
|
||||
|
||||
core.Cipher
|
||||
cipher.Cipher
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -28,7 +26,7 @@ func init() {
|
||||
func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
log.F("parse err: %s", err)
|
||||
log.F("[ss] parse err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -36,7 +34,7 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
||||
method := u.User.Username()
|
||||
pass, _ := u.User.Password()
|
||||
|
||||
ciph, err := core.PickCipher(method, nil, pass)
|
||||
ciph, err := cipher.PickCipher(method, nil, pass)
|
||||
if err != nil {
|
||||
log.Fatalf("[ss] PickCipher for '%s', error: %s", method, err)
|
||||
}
|
||||
@ -51,7 +49,18 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// ListCipher returns all the ciphers supported.
|
||||
func ListCipher() string {
|
||||
return strings.Join(core.ListCipher(), " ")
|
||||
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
|
||||
`)
|
||||
}
|
||||
|
124
proxy/ssh/ssh.go
124
proxy/ssh/ssh.go
@ -2,14 +2,16 @@ package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -18,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() {
|
||||
@ -29,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
|
||||
}
|
||||
|
||||
@ -40,17 +48,15 @@ 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
|
||||
},
|
||||
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)
|
||||
@ -59,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.
|
||||
@ -84,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)
|
||||
if err != nil {
|
||||
log.F("[ssh]: dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
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)
|
||||
}
|
||||
|
||||
sshc, 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
|
||||
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
|
||||
}
|
||||
|
||||
return ssh.NewClient(sshc, ch, req).Dial(network, addr)
|
||||
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 connection to %s error: %s", s.addr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
||||
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, errors.New("ssh client does not support udp")
|
||||
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
|
||||
}
|
||||
@ -117,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
|
||||
`)
|
||||
}
|
||||
|
342
proxy/ssr/internal/cipher/cipher.go
Normal file
342
proxy/ssr/internal/cipher/cipher.go
Normal file
@ -0,0 +1,342 @@
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rc4"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/aead/chacha20"
|
||||
"github.com/dgryski/go-camellia"
|
||||
"github.com/dgryski/go-idea"
|
||||
"github.com/dgryski/go-rc2"
|
||||
"golang.org/x/crypto/blowfish"
|
||||
"golang.org/x/crypto/cast5"
|
||||
"golang.org/x/crypto/salsa20/salsa"
|
||||
|
||||
"github.com/nadoo/glider/pkg/pool"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
var errEmptyPassword = errors.New("empty key")
|
||||
|
||||
type DecOrEnc int
|
||||
|
||||
const (
|
||||
Decrypt DecOrEnc = iota
|
||||
Encrypt
|
||||
)
|
||||
|
||||
func newCTRStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(block, iv), nil
|
||||
}
|
||||
|
||||
func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
return newCTRStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newOFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(block, iv), nil
|
||||
}
|
||||
|
||||
func newAESOFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
return newOFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newCFBStream(block cipher.Block, err error, key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if doe == Encrypt {
|
||||
return cipher.NewCFBEncrypter(block, iv), nil
|
||||
} else {
|
||||
return cipher.NewCFBDecrypter(block, iv), nil
|
||||
}
|
||||
}
|
||||
|
||||
func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := des.NewCipher(key)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := blowfish.NewCipher(key)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := cast5.NewCipher(key)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
h := md5.New()
|
||||
h.Write(key)
|
||||
h.Write(iv)
|
||||
rc4key := h.Sum(nil)
|
||||
|
||||
return rc4.NewCipher(rc4key)
|
||||
}
|
||||
|
||||
func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
return chacha20.NewCipher(iv, key)
|
||||
}
|
||||
|
||||
func newChacha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
return chacha20.NewCipher(iv, key)
|
||||
}
|
||||
|
||||
type salsaStreamCipher struct {
|
||||
nonce [8]byte
|
||||
key [32]byte
|
||||
counter int
|
||||
}
|
||||
|
||||
func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) {
|
||||
var buf []byte
|
||||
padLen := c.counter % 64
|
||||
dataSize := len(src) + padLen
|
||||
if cap(dst) >= dataSize {
|
||||
buf = dst[:dataSize]
|
||||
// nadoo: comment out codes here to use pool buffer
|
||||
// modify start -->
|
||||
// } else if leakybuf.GlobalLeakyBufSize >= dataSize {
|
||||
// buf = leakybuf.GlobalLeakyBuf.Get()
|
||||
// defer leakybuf.GlobalLeakyBuf.Put(buf)
|
||||
// buf = buf[:dataSize]
|
||||
// } else {
|
||||
// buf = make([]byte, dataSize)
|
||||
// }
|
||||
} else {
|
||||
buf = pool.GetBuffer(dataSize)
|
||||
defer pool.PutBuffer(buf)
|
||||
}
|
||||
// --> modify end
|
||||
|
||||
var subNonce [16]byte
|
||||
copy(subNonce[:], c.nonce[:])
|
||||
binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64))
|
||||
|
||||
// It's difficult to avoid data copy here. src or dst maybe slice from
|
||||
// Conn.Read/Write, which can't have padding.
|
||||
copy(buf[padLen:], src[:])
|
||||
salsa.XORKeyStream(buf, buf, &subNonce, &c.key)
|
||||
copy(dst, buf[padLen:])
|
||||
|
||||
c.counter += len(src)
|
||||
}
|
||||
|
||||
func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
|
||||
var c salsaStreamCipher
|
||||
copy(c.nonce[:], iv[:8])
|
||||
copy(c.key[:], key[:32])
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func newCamelliaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := camellia.New(key)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newIdeaStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := idea.NewCipher(key)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newRC2Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
block, err := rc2.New(key, 16)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
func newRC4Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
return rc4.NewCipher(key)
|
||||
}
|
||||
|
||||
func newSeedStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
// TODO: SEED block cipher implementation is required
|
||||
block, err := rc2.New(key, 16)
|
||||
return newCFBStream(block, err, key, iv, doe)
|
||||
}
|
||||
|
||||
type NoneStream struct {
|
||||
cipher.Stream
|
||||
}
|
||||
|
||||
func (*NoneStream) XORKeyStream(dst, src []byte) {
|
||||
copy(dst, src)
|
||||
}
|
||||
|
||||
func newNoneStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
|
||||
return new(NoneStream), nil
|
||||
}
|
||||
|
||||
type cipherInfo struct {
|
||||
keyLen int
|
||||
ivLen int
|
||||
newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error)
|
||||
}
|
||||
|
||||
var streamCipherMethod = map[string]*cipherInfo{
|
||||
"aes-128-cfb": {16, 16, newAESCFBStream},
|
||||
"aes-192-cfb": {24, 16, newAESCFBStream},
|
||||
"aes-256-cfb": {32, 16, newAESCFBStream},
|
||||
"aes-128-ctr": {16, 16, newAESCTRStream},
|
||||
"aes-192-ctr": {24, 16, newAESCTRStream},
|
||||
"aes-256-ctr": {32, 16, newAESCTRStream},
|
||||
"aes-128-ofb": {16, 16, newAESOFBStream},
|
||||
"aes-192-ofb": {24, 16, newAESOFBStream},
|
||||
"aes-256-ofb": {32, 16, newAESOFBStream},
|
||||
"des-cfb": {8, 8, newDESStream},
|
||||
"bf-cfb": {16, 8, newBlowFishStream},
|
||||
"cast5-cfb": {16, 8, newCast5Stream},
|
||||
"rc4-md5": {16, 16, newRC4MD5Stream},
|
||||
"rc4-md5-6": {16, 6, newRC4MD5Stream},
|
||||
"chacha20": {32, 8, newChaCha20Stream},
|
||||
"chacha20-ietf": {32, 12, newChacha20IETFStream},
|
||||
"salsa20": {32, 8, newSalsa20Stream},
|
||||
"camellia-128-cfb": {16, 16, newCamelliaStream},
|
||||
"camellia-192-cfb": {24, 16, newCamelliaStream},
|
||||
"camellia-256-cfb": {32, 16, newCamelliaStream},
|
||||
"idea-cfb": {16, 8, newIdeaStream},
|
||||
"rc2-cfb": {16, 8, newRC2Stream},
|
||||
"seed-cfb": {16, 8, newSeedStream},
|
||||
"rc4": {16, 0, newRC4Stream},
|
||||
"none": {16, 0, newNoneStream},
|
||||
}
|
||||
|
||||
func CheckCipherMethod(method string) error {
|
||||
if method == "" {
|
||||
method = "rc4-md5"
|
||||
}
|
||||
_, ok := streamCipherMethod[method]
|
||||
if !ok {
|
||||
return errors.New("Unsupported encryption method: " + method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StreamCipher struct {
|
||||
enc cipher.Stream
|
||||
dec cipher.Stream
|
||||
key []byte
|
||||
info *cipherInfo
|
||||
iv []byte
|
||||
}
|
||||
|
||||
// NewStreamCipher creates a cipher that can be used in Dial() etc.
|
||||
// Use cipher.Copy() to create a new cipher with the same method and password
|
||||
// to avoid the cost of repeated cipher initialization.
|
||||
func NewStreamCipher(method, password string) (c *StreamCipher, err error) {
|
||||
if password == "" {
|
||||
return nil, errEmptyPassword
|
||||
}
|
||||
if method == "" {
|
||||
method = "rc4-md5"
|
||||
}
|
||||
mi, ok := streamCipherMethod[method]
|
||||
if !ok {
|
||||
return nil, errors.New("Unsupported encryption method: " + method)
|
||||
}
|
||||
|
||||
key := tools.EVPBytesToKey(password, mi.keyLen)
|
||||
|
||||
c = &StreamCipher{key: key, info: mi}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *StreamCipher) EncryptInited() bool {
|
||||
return c.enc != nil
|
||||
}
|
||||
|
||||
func (c *StreamCipher) DecryptInited() bool {
|
||||
return c.dec != nil
|
||||
}
|
||||
|
||||
// Initializes the block cipher with CFB mode, returns IV.
|
||||
func (c *StreamCipher) InitEncrypt() (iv []byte, err error) {
|
||||
if c.iv == nil {
|
||||
iv = make([]byte, c.info.ivLen)
|
||||
rand.Read(iv)
|
||||
c.iv = iv
|
||||
} else {
|
||||
iv = c.iv
|
||||
}
|
||||
c.enc, err = c.info.newStream(c.key, iv, Encrypt)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *StreamCipher) InitDecrypt(iv []byte) (err error) {
|
||||
c.dec, err = c.info.newStream(c.key, iv, Decrypt)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *StreamCipher) Encrypt(dst, src []byte) {
|
||||
c.enc.XORKeyStream(dst, src)
|
||||
}
|
||||
|
||||
func (c *StreamCipher) Decrypt(dst, src []byte) {
|
||||
c.dec.XORKeyStream(dst, src)
|
||||
}
|
||||
|
||||
// Copy creates a new cipher at it's initial state.
|
||||
func (c *StreamCipher) Copy() *StreamCipher {
|
||||
// This optimization maybe not necessary. But without this function, we
|
||||
// need to maintain a table cache for newTableCipher and use lock to
|
||||
// protect concurrent access to that cache.
|
||||
|
||||
// AES and DES ciphers does not return specific types, so it's difficult
|
||||
// to create copy. But their initialization time is less than 4000ns on my
|
||||
// 2.26 GHz Intel Core 2 Duo processor. So no need to worry.
|
||||
|
||||
// Currently, blow-fish and cast5 initialization cost is an order of
|
||||
// magnitude slower than other ciphers. (I'm not sure whether this is
|
||||
// because the current implementation is not highly optimized, or this is
|
||||
// the nature of the algorithm.)
|
||||
|
||||
nc := *c
|
||||
nc.enc = nil
|
||||
nc.dec = nil
|
||||
return &nc
|
||||
}
|
||||
|
||||
func (c *StreamCipher) Key() []byte {
|
||||
return c.key
|
||||
}
|
||||
|
||||
func (c *StreamCipher) IV() []byte {
|
||||
return c.iv
|
||||
}
|
||||
|
||||
func (c *StreamCipher) SetIV(iv []byte) {
|
||||
c.iv = iv
|
||||
}
|
||||
|
||||
func (c *StreamCipher) SetKey(key []byte) {
|
||||
c.key = key
|
||||
}
|
||||
|
||||
func (c *StreamCipher) InfoIVLen() int {
|
||||
return c.info.ivLen
|
||||
}
|
||||
|
||||
func (c *StreamCipher) InfoKeyLen() int {
|
||||
return c.info.keyLen
|
||||
}
|
228
proxy/ssr/internal/client.go
Normal file
228
proxy/ssr/internal/client.go
Normal file
@ -0,0 +1,228 @@
|
||||
// source code from https://github.com/v2rayA/shadowsocksR
|
||||
// Just copy here to use glider's builtin buffer pool.
|
||||
// As this protocol hasn't been maintained since 2017, it doesn't deserve our research to rewrite it.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var bufSize = proxy.TCPBufSize
|
||||
|
||||
// SSTCPConn the struct that override the net.Conn methods
|
||||
type SSTCPConn struct {
|
||||
net.Conn
|
||||
*cipher.StreamCipher
|
||||
IObfs obfs.IObfs
|
||||
IProtocol protocol.IProtocol
|
||||
readBuf []byte
|
||||
underPostdecryptBuf *bytes.Buffer
|
||||
readIndex uint64
|
||||
decryptedBuf *bytes.Buffer
|
||||
writeBuf []byte
|
||||
lastReadError error
|
||||
}
|
||||
|
||||
func NewSSTCPConn(c net.Conn, cipher *cipher.StreamCipher) *SSTCPConn {
|
||||
return &SSTCPConn{
|
||||
Conn: c,
|
||||
StreamCipher: cipher,
|
||||
readBuf: pool.GetBuffer(bufSize),
|
||||
decryptedBuf: pool.GetBytesBuffer(),
|
||||
underPostdecryptBuf: pool.GetBytesBuffer(),
|
||||
writeBuf: pool.GetBuffer(bufSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) Close() error {
|
||||
pool.PutBuffer(c.readBuf)
|
||||
pool.PutBytesBuffer(c.decryptedBuf)
|
||||
pool.PutBytesBuffer(c.underPostdecryptBuf)
|
||||
pool.PutBuffer(c.writeBuf)
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) GetIv() (iv []byte) {
|
||||
iv = make([]byte, len(c.IV()))
|
||||
copy(iv, c.IV())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) GetKey() (key []byte) {
|
||||
key = make([]byte, len(c.Key()))
|
||||
copy(key, c.Key())
|
||||
return
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) initEncryptor(b []byte) (iv []byte, err error) {
|
||||
if !c.EncryptInited() {
|
||||
iv, err = c.InitEncrypt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
overhead := c.IObfs.GetOverhead() + c.IProtocol.GetOverhead()
|
||||
// should initialize obfs/protocol now, because iv is ready now
|
||||
obfsServerInfo := c.IObfs.GetServerInfo()
|
||||
obfsServerInfo.SetHeadLen(b, 30)
|
||||
obfsServerInfo.IV, obfsServerInfo.IVLen = c.IV(), c.InfoIVLen()
|
||||
obfsServerInfo.Key, obfsServerInfo.KeyLen = c.Key(), c.InfoKeyLen()
|
||||
obfsServerInfo.Overhead = overhead
|
||||
c.IObfs.SetServerInfo(obfsServerInfo)
|
||||
|
||||
protocolServerInfo := c.IProtocol.GetServerInfo()
|
||||
protocolServerInfo.SetHeadLen(b, 30)
|
||||
protocolServerInfo.IV, protocolServerInfo.IVLen = c.IV(), c.InfoIVLen()
|
||||
protocolServerInfo.Key, protocolServerInfo.KeyLen = c.Key(), c.InfoKeyLen()
|
||||
protocolServerInfo.Overhead = overhead
|
||||
c.IProtocol.SetServerInfo(protocolServerInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) Read(b []byte) (n int, err error) {
|
||||
for {
|
||||
n, err = c.doRead(b)
|
||||
if b == nil || n != 0 || err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) doRead(b []byte) (n int, err error) {
|
||||
if c.decryptedBuf.Len() > 0 {
|
||||
return c.decryptedBuf.Read(b)
|
||||
}
|
||||
|
||||
n, err = c.Conn.Read(c.readBuf)
|
||||
if n == 0 || err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
decodedData, needSendBack, err := c.IObfs.Decode(c.readBuf[:n])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//do send back
|
||||
if needSendBack {
|
||||
c.Write(nil)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
decodedDataLen := len(decodedData)
|
||||
if decodedDataLen == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if !c.DecryptInited() {
|
||||
if len(decodedData) < c.InfoIVLen() {
|
||||
return 0, errors.New(fmt.Sprintf("invalid ivLen:%v, actual length:%v", c.InfoIVLen(), len(decodedData)))
|
||||
}
|
||||
|
||||
iv := decodedData[0:c.InfoIVLen()]
|
||||
if err = c.InitDecrypt(iv); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(c.IV()) == 0 {
|
||||
c.SetIV(iv)
|
||||
}
|
||||
|
||||
decodedDataLen -= c.InfoIVLen()
|
||||
if decodedDataLen <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
decodedData = decodedData[c.InfoIVLen():]
|
||||
}
|
||||
|
||||
buf1 := pool.GetBuffer(decodedDataLen)
|
||||
defer pool.PutBuffer(buf1)
|
||||
|
||||
c.Decrypt(buf1, decodedData)
|
||||
c.underPostdecryptBuf.Write(buf1)
|
||||
buf := c.underPostdecryptBuf.Bytes()
|
||||
|
||||
postDecryptedData, length, err := c.IProtocol.PostDecrypt(buf)
|
||||
if err != nil {
|
||||
c.underPostdecryptBuf.Reset()
|
||||
return 0, err
|
||||
}
|
||||
if length == 0 {
|
||||
// not enough to postDecrypt
|
||||
return 0, nil
|
||||
} else {
|
||||
c.underPostdecryptBuf.Next(length)
|
||||
}
|
||||
|
||||
postDecryptedLength := len(postDecryptedData)
|
||||
blength := len(b)
|
||||
|
||||
if blength >= postDecryptedLength {
|
||||
copy(b, postDecryptedData)
|
||||
return postDecryptedLength, nil
|
||||
}
|
||||
|
||||
copy(b, postDecryptedData[:blength])
|
||||
c.decryptedBuf.Write(postDecryptedData[blength:])
|
||||
|
||||
return blength, nil
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) preWrite(b []byte) (outData []byte, err error) {
|
||||
if b == nil {
|
||||
b = make([]byte, 0)
|
||||
}
|
||||
|
||||
var iv []byte
|
||||
if iv, err = c.initEncryptor(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var preEncryptedData []byte
|
||||
preEncryptedData, err = c.IProtocol.PreEncrypt(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
preEncryptedDataLen := len(preEncryptedData)
|
||||
//! \attention here the expected output buffer length MUST be accurate, it is preEncryptedDataLen now!
|
||||
|
||||
cipherData := c.writeBuf
|
||||
dataSize := preEncryptedDataLen + len(iv)
|
||||
if dataSize > len(cipherData) {
|
||||
cipherData = make([]byte, dataSize)
|
||||
} else {
|
||||
cipherData = cipherData[:dataSize]
|
||||
}
|
||||
|
||||
if iv != nil {
|
||||
// Put initialization vector in buffer before be encoded
|
||||
copy(cipherData, iv)
|
||||
}
|
||||
|
||||
c.Encrypt(cipherData[len(iv):], preEncryptedData)
|
||||
return c.IObfs.Encode(cipherData)
|
||||
}
|
||||
|
||||
func (c *SSTCPConn) Write(b []byte) (n int, err error) {
|
||||
outData, err := c.preWrite(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = c.Conn.Write(outData)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
36
proxy/ssr/internal/obfs/base.go
Normal file
36
proxy/ssr/internal/obfs/base.go
Normal file
@ -0,0 +1,36 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
|
||||
type creator func() IObfs
|
||||
|
||||
var (
|
||||
creatorMap = make(map[string]creator)
|
||||
)
|
||||
|
||||
type IObfs interface {
|
||||
SetServerInfo(s *ssr.ServerInfo)
|
||||
GetServerInfo() (s *ssr.ServerInfo)
|
||||
Encode(data []byte) (encodedData []byte, err error)
|
||||
Decode(data []byte) (decodedData []byte, needSendBack bool, err error)
|
||||
SetData(data any)
|
||||
GetData() any
|
||||
GetOverhead() int
|
||||
}
|
||||
|
||||
func register(name string, c creator) {
|
||||
creatorMap[name] = c
|
||||
}
|
||||
|
||||
// NewObfs create an obfs object by name and return as an IObfs interface
|
||||
func NewObfs(name string) IObfs {
|
||||
c, ok := creatorMap[strings.ToLower(name)]
|
||||
if ok {
|
||||
return c()
|
||||
}
|
||||
return nil
|
||||
}
|
20
proxy/ssr/internal/obfs/http_post.go
Normal file
20
proxy/ssr/internal/obfs/http_post.go
Normal file
@ -0,0 +1,20 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("http_post", newHttpPost)
|
||||
}
|
||||
|
||||
// newHttpPost create a http_post object
|
||||
func newHttpPost() IObfs {
|
||||
// newHttpSimple create a http_simple object
|
||||
|
||||
t := &httpSimplePost{
|
||||
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||
methodGet: false,
|
||||
}
|
||||
return t
|
||||
}
|
185
proxy/ssr/internal/obfs/http_simple.go
Normal file
185
proxy/ssr/internal/obfs/http_simple.go
Normal file
@ -0,0 +1,185 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
|
||||
var (
|
||||
requestPath = []string{
|
||||
"", "",
|
||||
"login.php?redir=", "",
|
||||
"register.php?code=", "",
|
||||
"?keyword=", "",
|
||||
"search?src=typd&q=", "&lang=en",
|
||||
"s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=",
|
||||
"post.php?id=", "&goto=view.php",
|
||||
}
|
||||
requestUserAgent = []string{
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0",
|
||||
"Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0",
|
||||
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0",
|
||||
"Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)",
|
||||
"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
|
||||
"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
||||
}
|
||||
)
|
||||
|
||||
// HttpSimple http_simple obfs encapsulate
|
||||
type httpSimplePost struct {
|
||||
ssr.ServerInfo
|
||||
rawTransSent bool
|
||||
rawTransReceived bool
|
||||
userAgentIndex int
|
||||
methodGet bool // true for get, false for post
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("http_simple", newHttpSimple)
|
||||
}
|
||||
|
||||
// newHttpSimple create a http_simple object
|
||||
func newHttpSimple() IObfs {
|
||||
|
||||
t := &httpSimplePost{
|
||||
rawTransSent: false,
|
||||
rawTransReceived: false,
|
||||
userAgentIndex: rand.IntN(len(requestUserAgent)),
|
||||
methodGet: true,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) SetServerInfo(s *ssr.ServerInfo) {
|
||||
t.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &t.ServerInfo
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) SetData(data any) {
|
||||
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) GetData() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) boundary() (ret string) {
|
||||
|
||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
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 := range data {
|
||||
ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]}))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) Encode(data []byte) (encodedData []byte, err error) {
|
||||
if t.rawTransSent {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
dataLength := len(data)
|
||||
var headData []byte
|
||||
if headSize := t.IVLen + t.HeadLen; dataLength-headSize > 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
|
||||
host := t.Host
|
||||
var customHead string
|
||||
|
||||
if len(t.Param) > 0 {
|
||||
customHeads := strings.Split(t.Param, "#")
|
||||
if len(customHeads) > 2 {
|
||||
customHeads = customHeads[0:2]
|
||||
}
|
||||
param := t.Param
|
||||
if len(customHeads) > 1 {
|
||||
customHead = customHeads[1]
|
||||
param = customHeads[0]
|
||||
}
|
||||
hosts := strings.Split(param, ",")
|
||||
if len(hosts) > 0 {
|
||||
host = strings.TrimSpace(hosts[rand.IntN(len(hosts))])
|
||||
}
|
||||
}
|
||||
method := "GET /"
|
||||
if !t.methodGet {
|
||||
method = "POST /"
|
||||
}
|
||||
httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n",
|
||||
method,
|
||||
requestPath[requestPathIndex],
|
||||
t.data2URLEncode(headData),
|
||||
requestPath[requestPathIndex+1],
|
||||
host,
|
||||
t.Port)
|
||||
if len(customHead) > 0 {
|
||||
httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n"
|
||||
} else {
|
||||
var contentType string
|
||||
if !t.methodGet {
|
||||
contentType = "Content-Type: multipart/form-data; boundary=" + t.boundary() + "\r\n"
|
||||
}
|
||||
httpBuf = httpBuf +
|
||||
"User-Agent: " + requestUserAgent[t.userAgentIndex] + "\r\n" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
||||
"Accept-Language: en-US,en;q=0.8\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
contentType +
|
||||
"DNT: 1\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"\r\n"
|
||||
}
|
||||
|
||||
if len(headData) < dataLength {
|
||||
encodedData = make([]byte, len(httpBuf)+(dataLength-len(headData)))
|
||||
copy(encodedData, []byte(httpBuf))
|
||||
copy(encodedData[len(httpBuf):], data[len(headData):])
|
||||
} else {
|
||||
encodedData = []byte(httpBuf)
|
||||
}
|
||||
t.rawTransSent = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||
if t.rawTransReceived {
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
pos := bytes.Index(data, []byte("\r\n\r\n"))
|
||||
if pos > 0 {
|
||||
decodedData = make([]byte, len(data)-pos-4)
|
||||
copy(decodedData, data[pos+4:])
|
||||
t.rawTransReceived = true
|
||||
}
|
||||
return decodedData, false, nil
|
||||
}
|
||||
|
||||
func (t *httpSimplePost) GetOverhead() int {
|
||||
return 0
|
||||
}
|
46
proxy/ssr/internal/obfs/plain.go
Normal file
46
proxy/ssr/internal/obfs/plain.go
Normal file
@ -0,0 +1,46 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("plain", newPlainObfs)
|
||||
}
|
||||
|
||||
type plain struct {
|
||||
ssr.ServerInfo
|
||||
}
|
||||
|
||||
func newPlainObfs() IObfs {
|
||||
p := &plain{}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *plain) SetServerInfo(s *ssr.ServerInfo) {
|
||||
p.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (p *plain) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &p.ServerInfo
|
||||
}
|
||||
|
||||
func (p *plain) Encode(data []byte) (encodedData []byte, err error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (p *plain) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
func (p *plain) SetData(data any) {
|
||||
|
||||
}
|
||||
|
||||
func (p *plain) GetData() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plain) GetOverhead() int {
|
||||
return 0
|
||||
}
|
84
proxy/ssr/internal/obfs/random_head.go
Normal file
84
proxy/ssr/internal/obfs/random_head.go
Normal file
@ -0,0 +1,84 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
)
|
||||
|
||||
type randomHead struct {
|
||||
ssr.ServerInfo
|
||||
rawTransSent bool
|
||||
rawTransReceived bool
|
||||
hasSentHeader bool
|
||||
dataBuffer []byte
|
||||
}
|
||||
|
||||
func init() {
|
||||
register("random_head", newRandomHead)
|
||||
}
|
||||
|
||||
func newRandomHead() IObfs {
|
||||
p := &randomHead{}
|
||||
return p
|
||||
}
|
||||
|
||||
func (r *randomHead) SetServerInfo(s *ssr.ServerInfo) {
|
||||
r.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (r *randomHead) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &r.ServerInfo
|
||||
}
|
||||
|
||||
func (r *randomHead) SetData(data any) {
|
||||
|
||||
}
|
||||
|
||||
func (r *randomHead) GetData() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *randomHead) Encode(data []byte) (encodedData []byte, err error) {
|
||||
if r.rawTransSent {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
dataLength := len(data)
|
||||
if r.hasSentHeader {
|
||||
if dataLength > 0 {
|
||||
d := make([]byte, len(r.dataBuffer)+dataLength)
|
||||
copy(d, r.dataBuffer)
|
||||
copy(d[len(r.dataBuffer):], data)
|
||||
r.dataBuffer = d
|
||||
} else {
|
||||
encodedData = r.dataBuffer
|
||||
r.dataBuffer = nil
|
||||
r.rawTransSent = true
|
||||
}
|
||||
} else {
|
||||
size := rand.IntN(96) + 8
|
||||
encodedData = make([]byte, size)
|
||||
crand.Read(encodedData)
|
||||
ssr.SetCRC32(encodedData, size)
|
||||
|
||||
d := make([]byte, dataLength)
|
||||
copy(d, data)
|
||||
r.dataBuffer = d
|
||||
}
|
||||
r.hasSentHeader = true
|
||||
return
|
||||
}
|
||||
|
||||
func (r *randomHead) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||
if r.rawTransReceived {
|
||||
return data, false, nil
|
||||
}
|
||||
r.rawTransReceived = true
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
func (r *randomHead) GetOverhead() int {
|
||||
return 0
|
||||
}
|
313
proxy/ssr/internal/obfs/tls12_ticket_auth.go
Normal file
313
proxy/ssr/internal/obfs/tls12_ticket_auth.go
Normal file
@ -0,0 +1,313 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("tls1.2_ticket_auth", newTLS12TicketAuth)
|
||||
register("tls1.2_ticket_fastauth", newTLS12TicketFastAuth)
|
||||
}
|
||||
|
||||
type tlsAuthData struct {
|
||||
localClientID [32]byte
|
||||
}
|
||||
|
||||
// tls12TicketAuth tls1.2_ticket_auth obfs encapsulate
|
||||
type tls12TicketAuth struct {
|
||||
ssr.ServerInfo
|
||||
data *tlsAuthData
|
||||
handshakeStatus int
|
||||
sendSaver []byte
|
||||
recvBuffer bytes.Buffer
|
||||
fastAuth bool
|
||||
}
|
||||
|
||||
// newTLS12TicketAuth create a tlv1.2_ticket_auth object
|
||||
func newTLS12TicketAuth() IObfs {
|
||||
return &tls12TicketAuth{}
|
||||
}
|
||||
|
||||
// newTLS12TicketFastAuth create a tlv1.2_ticket_fastauth object
|
||||
func newTLS12TicketFastAuth() IObfs {
|
||||
return &tls12TicketAuth{
|
||||
fastAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) SetServerInfo(s *ssr.ServerInfo) {
|
||||
t.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &t.ServerInfo
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) SetData(data any) {
|
||||
if auth, ok := data.(*tlsAuthData); ok {
|
||||
t.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) GetData() any {
|
||||
if t.data == nil {
|
||||
t.data = &tlsAuthData{}
|
||||
b := make([]byte, 32)
|
||||
|
||||
crand.Read(b)
|
||||
copy(t.data.localClientID[:], b)
|
||||
}
|
||||
return t.data
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) getHost() string {
|
||||
host := t.Host
|
||||
if len(t.Param) > 0 {
|
||||
hosts := strings.Split(t.Param, ",")
|
||||
if len(hosts) > 0 {
|
||||
|
||||
host = hosts[rand.IntN(len(hosts))]
|
||||
host = strings.TrimSpace(host)
|
||||
}
|
||||
}
|
||||
if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 {
|
||||
host = ""
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func packData(prefixData []byte, suffixData []byte) (outData []byte) {
|
||||
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(suffixData)&0xFFFF))
|
||||
outData = append(prefixData, d...)
|
||||
outData = append(outData, suffixData...)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) Encode(data []byte) (encodedData []byte, err error) {
|
||||
encodedData = make([]byte, 0)
|
||||
switch t.handshakeStatus {
|
||||
case 8:
|
||||
if len(data) < 1024 {
|
||||
d := []byte{0x17, 0x3, 0x3, 0, 0}
|
||||
binary.BigEndian.PutUint16(d[3:5], uint16(len(data)&0xFFFF))
|
||||
encodedData = append(d, data...)
|
||||
return
|
||||
} else {
|
||||
start := 0
|
||||
var l int
|
||||
for len(data)-start > 2048 {
|
||||
l = rand.IntN(4096) + 100
|
||||
if l > len(data)-start {
|
||||
l = len(data) - start
|
||||
}
|
||||
encodedData = packData(encodedData, data[start:start+l])
|
||||
start += l
|
||||
}
|
||||
if len(data)-start > 0 {
|
||||
l = len(data) - start
|
||||
encodedData = packData(encodedData, data[start:start+l])
|
||||
}
|
||||
return
|
||||
}
|
||||
case 1:
|
||||
if len(data) > 0 {
|
||||
if len(data) < 1024 {
|
||||
t.sendSaver = packData(t.sendSaver, data)
|
||||
} else {
|
||||
start := 0
|
||||
var l int
|
||||
for len(data)-start > 2048 {
|
||||
l = rand.IntN(4096) + 100
|
||||
if l > len(data)-start {
|
||||
l = len(data) - start
|
||||
}
|
||||
encodedData = packData(encodedData, data[start:start+l])
|
||||
start += l
|
||||
}
|
||||
if len(data)-start > 0 {
|
||||
l = len(data) - start
|
||||
encodedData = packData(encodedData, data[start:start+l])
|
||||
}
|
||||
t.sendSaver = append(t.sendSaver, encodedData...)
|
||||
encodedData = encodedData[:0]
|
||||
}
|
||||
return []byte{}, nil
|
||||
}
|
||||
hmacData := make([]byte, 43)
|
||||
handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20")
|
||||
copy(hmacData, handshakeFinish)
|
||||
crand.Read(hmacData[11:33])
|
||||
h := t.hmacSHA1(hmacData[:33])
|
||||
copy(hmacData[33:], h)
|
||||
encodedData = append(hmacData, t.sendSaver...)
|
||||
t.sendSaver = t.sendSaver[:0]
|
||||
t.handshakeStatus = 8
|
||||
case 0:
|
||||
tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00")
|
||||
tlsData1 := []byte("\xff\x01\x00\x01\x00")
|
||||
tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0")
|
||||
tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
|
||||
var tlsData [2048]byte
|
||||
tlsDataLen := 0
|
||||
copy(tlsData[0:], tlsData1)
|
||||
tlsDataLen += len(tlsData1)
|
||||
sni := t.sni(t.getHost())
|
||||
copy(tlsData[tlsDataLen:], sni)
|
||||
tlsDataLen += len(sni)
|
||||
copy(tlsData[tlsDataLen:], tlsData2)
|
||||
tlsDataLen += len(tlsData2)
|
||||
ticketLen := rand.IntN(164)*2 + 64
|
||||
tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff)
|
||||
tlsData[tlsDataLen-2] = uint8(ticketLen >> 8)
|
||||
//ticketLen := 208
|
||||
crand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen])
|
||||
tlsDataLen += ticketLen
|
||||
copy(tlsData[tlsDataLen:], tlsData3)
|
||||
tlsDataLen += len(tlsData3)
|
||||
|
||||
length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen
|
||||
encodedData = make([]byte, length)
|
||||
pdata := length - tlsDataLen
|
||||
l := tlsDataLen
|
||||
copy(encodedData[pdata:], tlsData[:tlsDataLen])
|
||||
encodedData[pdata-1] = uint8(tlsDataLen)
|
||||
encodedData[pdata-2] = uint8(tlsDataLen >> 8)
|
||||
pdata -= 2
|
||||
l += 2
|
||||
copy(encodedData[pdata-len(tlsData0):], tlsData0)
|
||||
pdata -= len(tlsData0)
|
||||
l += len(tlsData0)
|
||||
copy(encodedData[pdata-32:], t.data.localClientID[:])
|
||||
pdata -= 32
|
||||
l += 32
|
||||
encodedData[pdata-1] = 0x20
|
||||
pdata -= 1
|
||||
l += 1
|
||||
copy(encodedData[pdata-32:], t.packAuthData())
|
||||
pdata -= 32
|
||||
l += 32
|
||||
encodedData[pdata-1] = 0x3
|
||||
encodedData[pdata-2] = 0x3 // tls version
|
||||
pdata -= 2
|
||||
l += 2
|
||||
encodedData[pdata-1] = uint8(l)
|
||||
encodedData[pdata-2] = uint8(l >> 8)
|
||||
encodedData[pdata-3] = 0
|
||||
encodedData[pdata-4] = 1
|
||||
pdata -= 4
|
||||
l += 4
|
||||
encodedData[pdata-1] = uint8(l)
|
||||
encodedData[pdata-2] = uint8(l >> 8)
|
||||
pdata -= 2
|
||||
// l += 2
|
||||
encodedData[pdata-1] = 0x1
|
||||
encodedData[pdata-2] = 0x3 // tls version
|
||||
pdata -= 2
|
||||
// l += 2
|
||||
encodedData[pdata-1] = 0x16 // tls handshake
|
||||
// pdata -= 1
|
||||
// l += 1
|
||||
|
||||
t.sendSaver = packData(t.sendSaver, data)
|
||||
t.handshakeStatus = 1
|
||||
default:
|
||||
//log.Println(fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus))
|
||||
return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) Decode(data []byte) (decodedData []byte, needSendBack bool, err error) {
|
||||
if t.handshakeStatus == -1 {
|
||||
return data, false, nil
|
||||
}
|
||||
|
||||
if t.handshakeStatus == 8 {
|
||||
t.recvBuffer.Write(data)
|
||||
for t.recvBuffer.Len() > 5 {
|
||||
var h [5]byte
|
||||
_, _ = t.recvBuffer.Read(h[:])
|
||||
if !bytes.Equal(h[0:3], []byte{0x17, 0x3, 0x3}) {
|
||||
log.Println("incorrect magic number", h[0:3], ", 0x170303 is expected")
|
||||
return nil, false, ssr.ErrTLS12TicketAuthIncorrectMagicNumber
|
||||
}
|
||||
size := int(binary.BigEndian.Uint16(h[3:5]))
|
||||
if t.recvBuffer.Len() < size {
|
||||
unread := t.recvBuffer.Bytes()
|
||||
t.recvBuffer.Reset()
|
||||
t.recvBuffer.Write(h[:])
|
||||
t.recvBuffer.Write(unread)
|
||||
break
|
||||
}
|
||||
d := make([]byte, size)
|
||||
_, _ = t.recvBuffer.Read(d)
|
||||
decodedData = append(decodedData, d...)
|
||||
}
|
||||
return decodedData, false, nil
|
||||
}
|
||||
|
||||
if len(data) < 11+32+1+32 {
|
||||
return nil, false, ssr.ErrTLS12TicketAuthTooShortData
|
||||
}
|
||||
|
||||
hash := t.hmacSHA1(data[11 : 11+22])
|
||||
|
||||
if !hmac.Equal(data[33:33+ssr.ObfsHMACSHA1Len], hash) {
|
||||
return nil, false, ssr.ErrTLS12TicketAuthHMACError
|
||||
}
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) packAuthData() (outData []byte) {
|
||||
outSize := 32
|
||||
outData = make([]byte, outSize)
|
||||
|
||||
now := time.Now().Unix()
|
||||
binary.BigEndian.PutUint32(outData[0:4], uint32(now))
|
||||
|
||||
crand.Read(outData[4 : 4+18])
|
||||
|
||||
hash := t.hmacSHA1(outData[:outSize-ssr.ObfsHMACSHA1Len])
|
||||
copy(outData[outSize-ssr.ObfsHMACSHA1Len:], hash)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) hmacSHA1(data []byte) []byte {
|
||||
key := make([]byte, t.KeyLen+32)
|
||||
copy(key, t.Key)
|
||||
copy(key[t.KeyLen:], t.data.localClientID[:])
|
||||
|
||||
sha1Data := tools.HmacSHA1(key, data)
|
||||
return sha1Data[:ssr.ObfsHMACSHA1Len]
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) sni(u string) []byte {
|
||||
bURL := []byte(u)
|
||||
length := len(bURL)
|
||||
ret := make([]byte, length+9)
|
||||
copy(ret[9:9+length], bURL)
|
||||
binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF))
|
||||
length += 3
|
||||
binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF))
|
||||
length += 2
|
||||
binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF))
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *tls12TicketAuth) GetOverhead() int {
|
||||
return 5
|
||||
}
|
279
proxy/ssr/internal/protocol/auth_aes128_md5.go
Normal file
279
proxy/ssr/internal/protocol/auth_aes128_md5.go
Normal file
@ -0,0 +1,279 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
crand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math/rand/v2"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_aes128_md5", NewAuthAES128MD5)
|
||||
}
|
||||
|
||||
func NewAuthAES128MD5() IProtocol {
|
||||
a := &authAES128{
|
||||
salt: "auth_aes128_md5",
|
||||
hmac: tools.HmacMD5,
|
||||
hashDigest: tools.MD5Sum,
|
||||
packID: 1,
|
||||
recvInfo: recvInfo{
|
||||
recvID: 1,
|
||||
buffer: bytes.NewBuffer(nil),
|
||||
},
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
type recvInfo struct {
|
||||
recvID uint32
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
type authAES128 struct {
|
||||
ssr.ServerInfo
|
||||
recvInfo
|
||||
data *AuthData
|
||||
hasSentHeader bool
|
||||
packID uint32
|
||||
userKey []byte
|
||||
salt string
|
||||
hmac hmacMethod
|
||||
hashDigest hashDigestMethod
|
||||
}
|
||||
|
||||
func (a *authAES128) SetServerInfo(s *ssr.ServerInfo) {
|
||||
a.ServerInfo = *s
|
||||
}
|
||||
|
||||
func (a *authAES128) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &a.ServerInfo
|
||||
}
|
||||
|
||||
func (a *authAES128) SetData(data any) {
|
||||
if auth, ok := data.(*AuthData); ok {
|
||||
a.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authAES128) GetData() any {
|
||||
if a.data == nil {
|
||||
a.data = &AuthData{}
|
||||
}
|
||||
return a.data
|
||||
}
|
||||
|
||||
func (a *authAES128) packData(data []byte) (outData []byte) {
|
||||
dataLength := len(data)
|
||||
randLength := 1
|
||||
if dataLength <= 1200 {
|
||||
if a.packID > 4 {
|
||||
randLength += rand.IntN(32)
|
||||
} else {
|
||||
if dataLength > 900 {
|
||||
randLength += rand.IntN(128)
|
||||
} else {
|
||||
randLength += rand.IntN(512)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outLength := randLength + dataLength + 8
|
||||
outData = make([]byte, outLength)
|
||||
// 0~1, out length
|
||||
binary.LittleEndian.PutUint16(outData[0:], uint16(outLength&0xFFFF))
|
||||
// 2~3, hmac
|
||||
key := make([]byte, len(a.userKey)+4)
|
||||
copy(key, a.userKey)
|
||||
binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID)
|
||||
h := a.hmac(key, outData[0:2])
|
||||
copy(outData[2:4], h[:2])
|
||||
// 4~rand length+4, rand number
|
||||
crand.Read(outData[4 : 4+randLength])
|
||||
// 4, rand length
|
||||
if randLength < 128 {
|
||||
outData[4] = byte(randLength & 0xFF)
|
||||
} else {
|
||||
// 4, magic number 0xFF
|
||||
outData[4] = 0xFF
|
||||
// 5~6, rand length
|
||||
binary.LittleEndian.PutUint16(outData[5:], uint16(randLength&0xFFFF))
|
||||
}
|
||||
// rand length+4~out length-4, data
|
||||
if dataLength > 0 {
|
||||
copy(outData[randLength+4:], data)
|
||||
}
|
||||
a.packID++
|
||||
h = a.hmac(key, outData[:outLength-4])
|
||||
copy(outData[outLength-4:], h[:4])
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authAES128) packAuthData(data []byte) (outData []byte) {
|
||||
dataLength := len(data)
|
||||
var randLength int
|
||||
if dataLength > 400 {
|
||||
randLength = rand.IntN(512)
|
||||
} else {
|
||||
randLength = rand.IntN(1024)
|
||||
}
|
||||
|
||||
dataOffset := randLength + 16 + 4 + 4 + 7
|
||||
outLength := dataOffset + dataLength + 4
|
||||
outData = make([]byte, outLength)
|
||||
encrypt := make([]byte, 24)
|
||||
key := make([]byte, a.IVLen+a.KeyLen)
|
||||
copy(key, a.IV)
|
||||
copy(key[a.IVLen:], a.Key)
|
||||
|
||||
crand.Read(outData[dataOffset-randLength:])
|
||||
a.data.mutex.Lock()
|
||||
a.data.connectionID++
|
||||
if a.data.connectionID > 0xFF000000 {
|
||||
a.data.clientID = nil
|
||||
}
|
||||
if len(a.data.clientID) == 0 {
|
||||
a.data.clientID = make([]byte, 8)
|
||||
crand.Read(a.data.clientID)
|
||||
b := make([]byte, 4)
|
||||
crand.Read(b)
|
||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
copy(encrypt[4:], a.data.clientID)
|
||||
binary.LittleEndian.PutUint32(encrypt[8:], a.data.connectionID)
|
||||
a.data.mutex.Unlock()
|
||||
|
||||
now := time.Now().Unix()
|
||||
binary.LittleEndian.PutUint32(encrypt[0:4], uint32(now))
|
||||
|
||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(outLength&0xFFFF))
|
||||
binary.LittleEndian.PutUint16(encrypt[14:], uint16(randLength&0xFFFF))
|
||||
|
||||
params := strings.Split(a.Param, ":")
|
||||
uid := make([]byte, 4)
|
||||
if len(params) >= 2 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err != nil {
|
||||
crand.Read(uid)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(uid, uint32(userID))
|
||||
a.userKey = a.hashDigest([]byte(params[1]))
|
||||
}
|
||||
} else {
|
||||
crand.Read(uid)
|
||||
}
|
||||
|
||||
if a.userKey == nil {
|
||||
a.userKey = make([]byte, a.KeyLen)
|
||||
copy(a.userKey, a.Key)
|
||||
}
|
||||
|
||||
encryptKey := make([]byte, len(a.userKey))
|
||||
copy(encryptKey, a.userKey)
|
||||
|
||||
aesCipherKey := tools.EVPBytesToKey(base64.StdEncoding.EncodeToString(encryptKey)+a.salt, 16)
|
||||
block, err := aes.NewCipher(aesCipherKey)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
encryptData := make([]byte, 16)
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(encryptData, encrypt[0:16])
|
||||
copy(encrypt[4:4+16], encryptData)
|
||||
copy(encrypt[0:4], uid)
|
||||
|
||||
h := a.hmac(key, encrypt[0:20])
|
||||
copy(encrypt[20:], h[:4])
|
||||
|
||||
crand.Read(outData[0:1])
|
||||
h = a.hmac(key, outData[0:1])
|
||||
copy(outData[1:], h[0:7-1])
|
||||
|
||||
copy(outData[7:], encrypt)
|
||||
copy(outData[dataOffset:], data)
|
||||
|
||||
h = a.hmac(a.userKey, outData[0:outLength-4])
|
||||
copy(outData[outLength-4:], h[:4])
|
||||
|
||||
//log.Println("clientID:", a.data.clientID, "connectionID:", a.data.connectionID)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authAES128) PreEncrypt(plainData []byte) (outData []byte, err error) {
|
||||
dataLength := len(plainData)
|
||||
offset := 0
|
||||
if dataLength > 0 && !a.hasSentHeader {
|
||||
authLength := dataLength
|
||||
if authLength > 1200 {
|
||||
authLength = 1200
|
||||
}
|
||||
packData := a.packAuthData(plainData[:authLength])
|
||||
a.hasSentHeader = true
|
||||
outData = append(outData, packData...)
|
||||
dataLength -= authLength
|
||||
offset += authLength
|
||||
}
|
||||
const blockSize = 4096
|
||||
for dataLength > blockSize {
|
||||
packData := a.packData(plainData[offset : offset+blockSize])
|
||||
outData = append(outData, packData...)
|
||||
dataLength -= blockSize
|
||||
offset += blockSize
|
||||
}
|
||||
if dataLength > 0 {
|
||||
packData := a.packData(plainData[offset:])
|
||||
outData = append(outData, packData...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authAES128) PostDecrypt(plainData []byte) ([]byte, int, error) {
|
||||
a.buffer.Reset()
|
||||
plainLength := len(plainData)
|
||||
readlenth := 0
|
||||
key := make([]byte, len(a.userKey)+4)
|
||||
copy(key, a.userKey)
|
||||
for plainLength > 4 {
|
||||
binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID)
|
||||
|
||||
h := a.hmac(key, plainData[0:2])
|
||||
if h[0] != plainData[2] || h[1] != plainData[3] {
|
||||
return nil, 0, ssr.ErrAuthAES128IncorrectHMAC
|
||||
}
|
||||
length := int(binary.LittleEndian.Uint16(plainData[0:2]))
|
||||
if length >= 8192 || length < 8 {
|
||||
return nil, 0, ssr.ErrAuthAES128DataLengthError
|
||||
}
|
||||
if length > plainLength {
|
||||
break
|
||||
}
|
||||
a.recvID++
|
||||
pos := int(plainData[4])
|
||||
if pos < 255 {
|
||||
pos += 4
|
||||
} else {
|
||||
pos = int(binary.LittleEndian.Uint16(plainData[5:7])) + 4
|
||||
}
|
||||
|
||||
a.buffer.Write(plainData[pos : length-4])
|
||||
plainData = plainData[length:]
|
||||
plainLength -= length
|
||||
readlenth += length
|
||||
}
|
||||
return a.buffer.Bytes(), readlenth, nil
|
||||
}
|
||||
|
||||
func (a *authAES128) GetOverhead() int {
|
||||
return 9
|
||||
}
|
25
proxy/ssr/internal/protocol/auth_aes128_sha1.go
Normal file
25
proxy/ssr/internal/protocol/auth_aes128_sha1.go
Normal file
@ -0,0 +1,25 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_aes128_sha1", NewAuthAES128SHA1)
|
||||
}
|
||||
|
||||
func NewAuthAES128SHA1() IProtocol {
|
||||
a := &authAES128{
|
||||
salt: "auth_aes128_sha1",
|
||||
hmac: tools.HmacSHA1,
|
||||
hashDigest: tools.SHA1Sum,
|
||||
packID: 1,
|
||||
recvInfo: recvInfo{
|
||||
recvID: 1,
|
||||
buffer: bytes.NewBuffer(nil),
|
||||
},
|
||||
}
|
||||
return a
|
||||
}
|
320
proxy/ssr/internal/protocol/auth_chain_a.go
Normal file
320
proxy/ssr/internal/protocol/auth_chain_a.go
Normal file
@ -0,0 +1,320 @@
|
||||
// https://github.com/shadowsocksr-backup/shadowsocks-rss/blob/master/doc/auth_chain_a.md
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
stdCipher "crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/cipher"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/ssr"
|
||||
"github.com/nadoo/glider/proxy/ssr/internal/tools"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("auth_chain_a", NewAuthChainA)
|
||||
}
|
||||
|
||||
type authChainA struct {
|
||||
ssr.ServerInfo
|
||||
randomClient tools.Shift128plusContext
|
||||
randomServer tools.Shift128plusContext
|
||||
recvInfo
|
||||
cipher *cipher.StreamCipher
|
||||
hasSentHeader bool
|
||||
lastClientHash []byte
|
||||
lastServerHash []byte
|
||||
userKey []byte
|
||||
uid [4]byte
|
||||
salt string
|
||||
data *AuthData
|
||||
hmac hmacMethod
|
||||
hashDigest hashDigestMethod
|
||||
rnd rndMethod
|
||||
dataSizeList []int
|
||||
dataSizeList2 []int
|
||||
chunkID uint32
|
||||
}
|
||||
|
||||
func NewAuthChainA() IProtocol {
|
||||
a := &authChainA{
|
||||
salt: "auth_chain_a",
|
||||
hmac: tools.HmacMD5,
|
||||
hashDigest: tools.SHA1Sum,
|
||||
rnd: authChainAGetRandLen,
|
||||
recvInfo: recvInfo{
|
||||
recvID: 1,
|
||||
buffer: new(bytes.Buffer),
|
||||
},
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *authChainA) SetServerInfo(s *ssr.ServerInfo) {
|
||||
a.ServerInfo = *s
|
||||
if a.salt == "auth_chain_b" {
|
||||
a.authChainBInitDataSize()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authChainA) GetServerInfo() (s *ssr.ServerInfo) {
|
||||
return &a.ServerInfo
|
||||
}
|
||||
|
||||
func (a *authChainA) SetData(data any) {
|
||||
if auth, ok := data.(*AuthData); ok {
|
||||
a.data = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *authChainA) GetData() any {
|
||||
if a.data == nil {
|
||||
a.data = &AuthData{}
|
||||
}
|
||||
return a.data
|
||||
}
|
||||
|
||||
func authChainAGetRandLen(dataLength int, random *tools.Shift128plusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int {
|
||||
if dataLength > 1440 {
|
||||
return 0
|
||||
}
|
||||
random.InitFromBinDatalen(lastHash[:16], dataLength)
|
||||
if dataLength > 1300 {
|
||||
return int(random.Next() % 31)
|
||||
}
|
||||
if dataLength > 900 {
|
||||
return int(random.Next() % 127)
|
||||
}
|
||||
if dataLength > 400 {
|
||||
return int(random.Next() % 521)
|
||||
}
|
||||
return int(random.Next() % 1021)
|
||||
}
|
||||
|
||||
func getRandStartPos(random *tools.Shift128plusContext, randLength int) int {
|
||||
if randLength > 0 {
|
||||
return int(int64(random.Next()%8589934609) % int64(randLength))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (a *authChainA) getClientRandLen(dataLength int, overhead int) int {
|
||||
return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead)
|
||||
}
|
||||
|
||||
func (a *authChainA) getServerRandLen(dataLength int, overhead int) int {
|
||||
return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead)
|
||||
}
|
||||
|
||||
func (a *authChainA) packedDataLen(data []byte) (chunkLength, randLength int) {
|
||||
dataLength := len(data)
|
||||
randLength = a.getClientRandLen(dataLength, a.Overhead)
|
||||
chunkLength = randLength + dataLength + 2 + 2
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authChainA) packData(outData []byte, data []byte, randLength int) {
|
||||
dataLength := len(data)
|
||||
outLength := randLength + dataLength + 2
|
||||
outData[0] = byte(dataLength) ^ a.lastClientHash[14]
|
||||
outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15]
|
||||
|
||||
{
|
||||
if dataLength > 0 {
|
||||
randPart1Length := getRandStartPos(&a.randomClient, randLength)
|
||||
rand.Read(outData[2 : 2+randPart1Length])
|
||||
a.cipher.Encrypt(outData[2+randPart1Length:], data)
|
||||
rand.Read(outData[2+randPart1Length+dataLength : outLength])
|
||||
} else {
|
||||
rand.Read(outData[2 : 2+randLength])
|
||||
}
|
||||
}
|
||||
|
||||
userKeyLen := uint8(len(a.userKey))
|
||||
key := make([]byte, userKeyLen+4)
|
||||
copy(key, a.userKey)
|
||||
a.chunkID++
|
||||
binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID)
|
||||
a.lastClientHash = a.hmac(key, outData[:outLength])
|
||||
copy(outData[outLength:], a.lastClientHash[:2])
|
||||
return
|
||||
}
|
||||
|
||||
const authheadLength = 4 + 8 + 4 + 16 + 4
|
||||
|
||||
func (a *authChainA) packAuthData(data []byte) (outData []byte) {
|
||||
outData = make([]byte, authheadLength, authheadLength+1500)
|
||||
a.data.connectionID++
|
||||
if a.data.connectionID > 0xFF000000 {
|
||||
rand.Read(a.data.clientID)
|
||||
b := make([]byte, 4)
|
||||
rand.Read(b)
|
||||
a.data.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF
|
||||
}
|
||||
var key = make([]byte, a.IVLen+a.KeyLen)
|
||||
copy(key, a.IV)
|
||||
copy(key[a.IVLen:], a.Key)
|
||||
|
||||
encrypt := make([]byte, 20)
|
||||
t := time.Now().Unix()
|
||||
binary.LittleEndian.PutUint32(encrypt[:4], uint32(t))
|
||||
copy(encrypt[4:8], a.data.clientID)
|
||||
binary.LittleEndian.PutUint32(encrypt[8:], a.data.connectionID)
|
||||
binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead))
|
||||
//binary.LittleEndian.PutUint16(encrypt[14:], 0)
|
||||
|
||||
// first 12 bytes
|
||||
{
|
||||
rand.Read(outData[:4])
|
||||
a.lastClientHash = a.hmac(key, outData[:4])
|
||||
copy(outData[4:], a.lastClientHash[:8])
|
||||
}
|
||||
var base64UserKey string
|
||||
// uid & 16 bytes auth data
|
||||
{
|
||||
uid := make([]byte, 4)
|
||||
if a.userKey == nil {
|
||||
params := strings.Split(a.ServerInfo.Param, ":")
|
||||
if len(params) >= 2 {
|
||||
if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil {
|
||||
binary.LittleEndian.PutUint32(a.uid[:], uint32(userID))
|
||||
a.userKey = a.hashDigest([]byte(params[1]))
|
||||
}
|
||||
}
|
||||
if a.userKey == nil {
|
||||
rand.Read(a.uid[:])
|
||||
a.userKey = make([]byte, a.KeyLen)
|
||||
copy(a.userKey, a.Key)
|
||||
}
|
||||
}
|
||||
for i := range 4 {
|
||||
uid[i] = a.uid[i] ^ a.lastClientHash[8+i]
|
||||
}
|
||||
base64UserKey = base64.StdEncoding.EncodeToString(a.userKey)
|
||||
aesCipherKey := tools.EVPBytesToKey(base64UserKey+a.salt, 16)
|
||||
block, err := aes.NewCipher(aesCipherKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encryptData := make([]byte, 16)
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := stdCipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(encryptData, encrypt[:16])
|
||||
copy(encrypt[:4], uid[:])
|
||||
copy(encrypt[4:4+16], encryptData)
|
||||
}
|
||||
// final HMAC
|
||||
{
|
||||
a.lastServerHash = a.hmac(a.userKey, encrypt[0:20])
|
||||
|
||||
copy(outData[12:], encrypt)
|
||||
copy(outData[12+20:], a.lastServerHash[:4])
|
||||
}
|
||||
|
||||
// init cipher
|
||||
password := make([]byte, len(base64UserKey)+base64.StdEncoding.EncodedLen(16))
|
||||
copy(password, base64UserKey)
|
||||
base64.StdEncoding.Encode(password[len(base64UserKey):], a.lastClientHash[:16])
|
||||
a.cipher, _ = cipher.NewStreamCipher("rc4", string(password))
|
||||
_, _ = a.cipher.InitEncrypt()
|
||||
_ = a.cipher.InitDecrypt(nil)
|
||||
|
||||
// data
|
||||
chunkLength, randLength := a.packedDataLen(data)
|
||||
if chunkLength <= 1500 {
|
||||
outData = outData[:authheadLength+chunkLength]
|
||||
} else {
|
||||
newOutData := make([]byte, authheadLength+chunkLength)
|
||||
copy(newOutData, outData[:authheadLength])
|
||||
outData = newOutData
|
||||
}
|
||||
a.packData(outData[authheadLength:], data, randLength)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *authChainA) PreEncrypt(plainData []byte) (outData []byte, err error) {
|
||||
a.buffer.Reset()
|
||||
dataLength := len(plainData)
|
||||
length := dataLength
|
||||
offset := 0
|
||||
if length > 0 && !a.hasSentHeader {
|
||||
headSize := 1200
|
||||
if headSize > dataLength {
|
||||
headSize = dataLength
|
||||
}
|
||||
a.buffer.Write(a.packAuthData(plainData[:headSize]))
|
||||
offset += headSize
|
||||
dataLength -= headSize
|
||||
a.hasSentHeader = true
|
||||
}
|
||||
var unitSize = a.TcpMss - a.Overhead
|
||||
for dataLength > unitSize {
|
||||
dataLen, randLength := a.packedDataLen(plainData[offset : offset+unitSize])
|
||||
b := make([]byte, dataLen)
|
||||
a.packData(b, plainData[offset:offset+unitSize], randLength)
|
||||
a.buffer.Write(b)
|
||||
dataLength -= unitSize
|
||||
offset += unitSize
|
||||
}
|
||||
if dataLength > 0 {
|
||||
dataLen, randLength := a.packedDataLen(plainData[offset:])
|
||||
b := make([]byte, dataLen)
|
||||
a.packData(b, plainData[offset:], randLength)
|
||||
a.buffer.Write(b)
|
||||
}
|
||||
return a.buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *authChainA) PostDecrypt(plainData []byte) (outData []byte, n int, err error) {
|
||||
a.buffer.Reset()
|
||||
key := make([]byte, len(a.userKey)+4)
|
||||
readlenth := 0
|
||||
copy(key, a.userKey)
|
||||
for len(plainData) > 4 {
|
||||
binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID)
|
||||
dataLen := (int)((uint(plainData[1]^a.lastServerHash[15]) << 8) + uint(plainData[0]^a.lastServerHash[14]))
|
||||
randLen := a.getServerRandLen(dataLen, a.Overhead)
|
||||
length := randLen + dataLen
|
||||
if length >= 4096 {
|
||||
return nil, 0, ssr.ErrAuthChainDataLengthError
|
||||
}
|
||||
length += 4
|
||||
if length > len(plainData) {
|
||||
break
|
||||
}
|
||||
|
||||
hash := a.hmac(key, plainData[:length-2])
|
||||
if !bytes.Equal(hash[:2], plainData[length-2:length]) {
|
||||
return nil, 0, ssr.ErrAuthChainIncorrectHMAC
|
||||
}
|
||||
var dataPos int
|
||||
if dataLen > 0 && randLen > 0 {
|
||||
dataPos = 2 + getRandStartPos(&a.randomServer, randLen)
|
||||
} else {
|
||||
dataPos = 2
|
||||
}
|
||||
b := make([]byte, dataLen)
|
||||
a.cipher.Decrypt(b, plainData[dataPos:dataPos+dataLen])
|
||||
a.buffer.Write(b)
|
||||
if a.recvID == 1 {
|
||||
a.TcpMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2)))
|
||||
}
|
||||
a.lastServerHash = hash
|
||||
a.recvID++
|
||||
plainData = plainData[length:]
|
||||
readlenth += length
|
||||
|
||||
}
|
||||
return a.buffer.Bytes(), readlenth, nil
|
||||
}
|
||||
|
||||
func (a *authChainA) GetOverhead() int {
|
||||
return 4
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user